Programming-[Backend]/JPA

[JPA기본] 2. 프로젝트 생성, JPA 기본 CRUD 및 트랜잭션

컴퓨터 탐험가 찰리 2021. 9. 25. 23:35
728x90
반응형

 

1. 프로젝트 생성

 

데이터베이스 생성

H2 데이터베이스로 실습한다. 홈페이지에서 다운로드 후, console을 실행하여 아래 사진과 같이 Embedded용 Generic H2를 만든다. 이렇게 만들면 데이터 베이스를 생성하게 된 것이 된다. 이렇게 하면 "C:\Users(사용자)\사용자 계정 이름\" 위치에 JDBC URL의 맨 마지막 글자인 test.mv Data Base File이 생성된 것을 확인할 수 있다.

 

주의사항

자바가 설치되어 있어야 한다!! H2는 자바를 기반으로한 jar 형태의 executable file을 제공하기 때문에 컴퓨터에 자바가 설치되어 있지 않다면 실행이 불가하다.

 

이후 이 Data Base File을 기반으로 Generic H2 Server 방식으로 통신을 통해 접근할 수 있는 형태로 Database를 다시 만든다. 참조 2)에 따르면 이렇게 Server로 TCP 소켓을 통해 접근해야 동시성 이슈가 생기지 않는다고 한다.

 

 


 

2. 프로젝트 생성 및 설정

 

 

프로젝트 생성

스프링 강의에서 배운 것처럼 start.spring.io에 들어가서 Gradle 프로젝트를 설정해줘도 된다. 그러나 다른 방법도 알면 좋기 때문에, JPA 강의에서처럼 intelliJ에서 Maven으로 프로젝트를 직접 생성해본다.

 

 

라이브러리 dependency 설정

Maven dependency는 다음과 같이 설정한다.

 

pom.xml

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
<!--        JPA 하이버네이트-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.32.Final</version>
        </dependency>
<!--        H2 데이터베이스-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
        </dependency>
    </dependencies>
cs

 

 

각 라이브러리의 버전은 다음 사이트에 접속(spring.io -> projects -> spring boot -> learn -> 버전별 Reference Doc.-> dependency versions)하여 groupId를 검색해보면 된다. 이것은 각 라이브러리를 단독으로 사용해도 프로젝트를 진행할 수 있지만, 결국 스프링부트를 사용하여 통합할 것이기 때문에 스프링부트에서 각 라이브러리의 어떤 버전을 지원하고 있는지 알고 있어야하기 때문이다.

 

h2database의 버전은 설치 시 다운로드한 버전과 동일한 버전으로 선택해주는 것이 안전하다.

 

https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html#dependency-versions

 

 

[Ctrl + Shift + O]를 눌러서 Maven 프로젝트를 새로고침하면, 라이브러리에 연결된 다른 라이브러리들이 다운로드 되는 것을 확인할 수 있다. 이중, hibernate core, javax.persistence는 우리가 JPA의 구현체로 hibernate를 선택했기 때문에 다운로드 된 것이다.

 

JPA persistence 설정

 

다음 위치에 똑같은 폴더명과 파일명으로 persistence.xml을 생성하고 내용을 작성해준다. 우상단에 노란색 에러가 뜨면, Add to JPA Facet configuration을 클릭해주면 된다.

 

src/main/resources/META-INF/persistence.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
    <properties>
        <!-- 필수 속성 -->
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.user" value="sa"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        <!-- 옵션 -->
        <property name="hibernate.show_sql" value="true"/>
        <property name="hibernate.format_sql" value="true"/>
        <property name="hibernate.use_sql_comments" value="true"/>
        <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
    </properties>
</persistence-unit>
</persistence>
cs

 

각 속성별 의미

persistence-unit name은 보통 데이터베이스당 1개를 만든다. 영속성 컨텍스트의 이름이 된다. 필수 속성은 jpa가 데이터베이스에 접근하는 접근 정보를 의미한다. 혹시 user, password나 url 정보 등을 변경하였다면 알맞게 바꿔주도록 한다.

 

hibernate.dialect 부분은 중요하다. JPA는 특정 데이터베이스에 의존하지 않도록 되어있다. mysql이든, mssql이든 데이터베이스를 변경해도 이론적으로는 문제가 없어야 한다. 그런데, SQL 표준을 지키지 않는 데이터베이스들은 dialect라고 불리며, 표현 방식이 데이터베이스마다 조금씩 다르기 때문에 이런 정보를 JPA에 지정해준다는 의미가 된다. 그래서 혹시 데이터베이스를 변경하게 되면 이 부분을 신경써야 한다.

 

show.sql 옵션은 JPA를 통해 데이터베이스에 쿼리가 나가는 것을 콘솔창에 표시할지 결정하는 옵션이다. format.sql은 show.sql과 같이 쓰이는 옵션으로, 콘솔창에 표시되는 sql문을 보기 편하게 정렬하여 표시해준다. use_sql_comment 부분은 쿼리문 앞에 /* */ 주석문으로 쿼리가 나오는 이유를 알려준다.

 


 

3. EntityManager와 트랜잭션

 

EntityManagerFactory, EntityManager 만들기

JPA의 구동방식은 다음과 같다. 마치 스프링의 빈 처럼, 설정 정보를 조회하여 EntityManagerFactory라는 컨텍스트를 생성해놓고, 여기서 EntityManager를 생성하는 방식이다.

 

JPA에서 모든 작업은 트랜잭션 단위로 이루어지고, 트랜잭션 내부에서 진행해야 한다.

어떤 트랜잭션이 발생할 때, DB와 connection을 열었다가 닫는데, 이럴 때마다 EntityManager가 1개 생성되어 작업을 마치고 닫히는 구조가 되어야 한다. 기본 단위가 트랜잭션임을 꼭 기억하자.

 

보통 웹 애플리케이션 서비스에서는 EntityManagerFactory는 DB당 1개만 생성되고 트랜잭션별로 EntityManager가 생성되고 소멸되는 구조이다. 엔티티 매니저는 쓰레드간에 공유되면 안된다!

 

java 디렉토리 아래에 hellojpa 패키지를 만들고, JpaMain 클래스를 만들어서 main 메서드를 만든다. emf와 em을 차례대로 부르면 된다. 여기서 Persistence.createEntityManagerFactory에 들어가는 PersistenceName 값은 persistence.xml에서 설정값으로 준 persistence-unit의 name 값이다.

 

이후 필요한 코드를 작성하고, em과 emf를 순차적으로 .close() 메서드로 닫아주면 된다.

 

src/main/java/hellojpa/JpaMain.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class JpaMain {
  public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
    EntityManager em = emf.createEntityManager();
    
    //코드가 들어갈 부분
    
    em.close();
    emf.close();
  }
}
 
cs

 

 

테이블, 객체 만들기

 

h2database 콘솔에서 다음 쿼리문을 작성하고 실행하여 Member 테이블을 만든다.

 

1
2
3
4
5
create table Member (
  id bigint not null,
  name varchar(255),
  primary key(id)
);
cs

 

다음으로 테이블에 매핑할 수 있는 엔티티 객체를 작성한다. @Entity 어노테이션을 넣어줘서 JPA에게 해당 객체가 DB와 연결될 엔티티 객체임을 알려준다. @Id 어노테이션을 넣어주어 PK값이 어떤 값인지 알려준다. 그리고 @Getter, @Setter를 추가해줬는데, 스프링 강의에서 배운 것처럼 Maven dependency에 org.projectlombok을 추가하고 annotation processor을 enabled로 바꿔주면 된다.

 

src/main/java/hellojpa/Member.java

 

1
2
3
4
5
6
7
8
9
10
@Entity
@Getter
@Setter
public class Member {
  
  @Id
  private Long id;
  private String name;
 
}
cs

 


 

4. 기본 쿼리와 JPQL

 

 

기본 트랜잭션 구조 및 객체 CREATE

 

이제 테이블에 데이터를 CREATE하는 방식을 알아보면서 기본적인 구조를 알아본다. main 클래스를 다음과 같이 수정한다.

 

EntityTransaction

위에서 언급한대로, jpa의 작업은 무조건 트랜잭션 내부에서 일어나야한다. 그래서 em.getTransaction() 메서드를 통해 트랜잭션을 불러왔다. 그리고 트랜잭션 시작을 위해 tx.begin()을, 작업 내용을 마치고 테이블에 반영을 위해 tx.commit()을, 잘못된 경우 트랜잭션을 취소하기 위해 tx.rollback()을 적용한다.

 

try-catch-finally

이렇게 트랜잭션이 진행되는 과정 중에 에러가 발생해서 em.close()등이 실행되지 않으면 문제가 될 수 있으므로 반드시 아래 코드처럼 try-catch-finally 구문을 통해서 트랜잭션을 실행해주어야 한다. 나중에는 스프링에서 자동으로 이런 처리를 해주지만, 이런 원리를 적용해야 한다는 것을 기억해야 한다.

 

em.persist()

원하는 member 객체를 생성하고 em.persist()를 실행하여 객체 정보를 영속성 컨텍스트에 올린다.

 

src/main/java/hellojpa/JpaMain.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JpaMain {
  public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
    EntityManager em = emf.createEntityManager();
 
    EntityTransaction tx = em.getTransaction();
 
    tx.begin();
 
    try {
      //코드가 들어갈 부분
      Member member = new Member();
      member.setId(1L);
      member.setName("helloA");
 
      em.persist(member);
 
      tx.commit();
    } catch (Exception e) {
      tx.rollback();
    } finally {
      em.close();
    }
    emf.close();
  }
}
cs

 

결과화면

 

위에서 배운 show.sql, format.sql, use_sql_comment 옵션이 적용되어 콘솔창에 sql문이 잘 표현된 것을 볼 수 있고 실제 database에 데이터가 잘 삽입된 것을 확인할 수 있다.

 

close()

데이터베이스 커넥션이든, emf 및 em이든 close()를 통해서 닫아주어야 한다. 이것은 각 대상들을 실행할 때, 컴퓨터의 메모리 자원을 사용하게 되어있는데 사용이 끝났음을 컴퓨터에게 알려주어야 자원을 회수하기 때문이다.

 

 


 

조회, 수정, 삭제

 

조회, 삭제는 간단하다. 차례대로 em.find, em.remove 문법을 사용하면 된다. 수정은 조회한 객체의 속성값을 set만 해주면 된다. 아래 예시 코드는 한꺼번에 설명하기 위함이고, 실제로 이런 방식으로 사용하지는 않는다.

 

src/main/java/hellojpa/JpaMain.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try {
      //코드가 들어갈 부분
      Member findMember = em.find(Member.class, 1L);
      System.out.println("findMember.id = " + findMember.getId());
      System.out.println("findMember.name = " + findMember.getName());
 
      findMember.setName("hello JPA");
      //저장 필요 x
 
      em.remove(findMember);
      System.out.println("findMember.id = " + findMember.getId());
      System.out.println("findMember.name = " + findMember.getName());
 
      tx.commit();
    } catch (Exception e) {
 
 
...중략
 
 
cs

 

 

 


JPQL

 

앞서 살펴본 기본 CRUD 기능은 EntityManager의 메서드를 통해 가능하다. 그러나 다른 테이블을 JOIN하거나, Page 기능이 반영되야 하거나, 검색 조건이 들어가야 하는 등 기본 쿼리가 아닌 경우 JPQL을 사용해야 한다.

 

JPQL은 SQL을 추상화한 객체 지향 쿼리 언어이다. 다시 말해 SQL 처럼 데이터베이스의 테이블을 대상으로 쿼리를 하는 것이 아니라 엔티티 객체를 대상으로 쿼리를 한다. 아래 예제를 통해 살펴보자.

 

createQuery 메서드 부분에 원하는 SQL문을 사용하고, 두번째 파라미터로 객체 타입을 지정해주었다. 그리고 setFirstResult() 메서드 등으로 page 기능의 offset, limit 값 등을 지정해줄 수도 있다. 아래 콘솔창에 출력된 결과 화면을 살펴보면 from절과 where 절에서 Member 객체에 대고 조회하고, Member 객체에 해당하는 필드값들을 select 문으로 치환하여 SQL문을 보내는 것을 확인할 수 있다. 이와 같이 객체를 기반으로 쿼리가 나가는 것을 확인할 수 있다. 그리고  limit 등의 page 관련 SQL문도 자동으로 추가된 것을 볼 수 있다.

 

src/main/java/hellojpa/JpaMain.java

 

1
2
3
4
5
6
7
8
9
10
11
12
try {
      //코드가 들어갈 부분
      List<Member> result = em.createQuery("select m from Member m where m.id >= 2", Member.class)
          .setFirstResult(0)
          .setMaxResults(2)
          .getResultList();
      for (Member member : result) {
        System.out.println("member = " + member.getName());
      }
 
      tx.commit();
    } catch (Exception e) {
cs

 

 

JPQL도 메서드 등 문법적으로 조금 배워야할 부분이 있다. 이 강의에서 JPQL문을 계속해서 조금씩 사용할 것 같은데, 결국은 JPQL보다 더 편리한 방식인 Querydsl을 나중에 배울 것이므로 우선은 이정도만 알고 넘어가면 될 것 같다.

 


 

참조

 

 

1. 인프런_자바 ORM 표준 JPA 프로그래밍 - 기본편_김영한 님 강의

https://www.inflearn.com/course/ORM-JPA-Basic/lecture/21735?tab=curriculum

 

 

2. H2 DataBase 실행

https://atoz-develop.tistory.com/entry/H2-Database-%EC%84%A4%EC%B9%98-%EC%84%9C%EB%B2%84-%EC%8B%A4%ED%96%89-%EC%A0%91%EC%86%8D-%EB%B0%A9%EB%B2%95

728x90
반응형