Home 2. JPA 시작하기
Post
Cancel

2. JPA 시작하기

01. 예제 프로젝트

02. 메이븐 프로젝트 생성 및 이클립스 임포트

03. 데이터베이스 생성

04. 모델 클래스와 매핑 설정

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
28
29
30
31
32
@Entity //1)
@Table(name="user") //2)
public class User {

    @Id //3)
    private String email;
    private String name; //4)

    @Temporal(TemporalType.TIMESTAMP)  //5)
    @Column(name="create_date")
    private Date createDate;

    protected User() {}

    public User(String email, String name, Date createDate) {
        this.email = email;
        this.name = name;
        this.createDate = createDate;
    }

    public String getEmail() {
        return email;
    }

    public String getName() {
        return name;
    }

    public Date getCreateDate() {
        return createDate;
    }
}

1) @Entity : JPA에서 엔티티는 DB 테이블과 매핑되는 기본 단위이다. 2) @Table : 클래스가 어떤 테이블과 매핑되는지 설정 name 속성을 이용해서 테이블 이름을 지정 3) @Id : DB에서의 PK 설정, JPA에서는 식별자라 한다. 4) name 필드 : name 필드의 경우 애노테이션이 없는데 이 경우에는 필드 이름과 동일한 이름의 테이블 컬럼에 매핑한다. 5) Date 타입을 매핑할 땐 @Timestamp 애노테이션을 사용한다.

1
2
//JPA가 EntityManager를 이용해서 식별자가 madvirus@madvirus.net에 해당하는 User 객체를 찾는다.
User user = entityManager.find(User.class, "madvirus@madvirus.net");

JPA는 테이블과 매핑된 클래스의 객체를 생성할 때 인자가 없는 기본 생성자를 사용한다. 그래서 엔티티 클래스에 기본 생성자를 추가한다.

05. JPA 설정

JPA 설정 파일을 작성해야한다. src/main/resources/META-INF/persistence.xml 경로에 persistence.xml를 생성한다.

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
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="utf-8" ?>

<persistence 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_1.xsd"
    version="2.1">

    <!-- transaction-type 설정을 통해 JPA 트랜잭션 타입을 지정할 수 있다. (로컬 트랜잭션 or 글로벌 트랜잭션) -->
    <persistence-unit name="jpastart" transaction-type="RESOURCE_LOCAL">
        <class>jpastart.reserve.model.User</class>

        <!-- 1) false로 설정시 <class>태그로 지정하지 않은 클래스는 관리 대상에 포함하지 않음 -->
        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>
            <!-- 2) 데이터베이스 연결 정보 설정 -->
            <property name="javax.persistence.jdbc.driver"
                value="com.mysql.jdbc.Driver" />
            <property name="javax.persistence.jdbc.url"
                value="jdbc:mysql://localhost/jpastart?characterEncoding=utf8" />
            <property name="javax.persistence.jdbc.user" value="jpauser" />
            <property name="javax.persistence.jdbc.password"
                value="jpapass" />

            <!-- 3) 하이버네이트 설정 -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect"
                value="org.hibernate.dialect.MySQL5InnoDBDialect" />

            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="500" />
            <property name="hibernate.c3p0.idle_test_period"
                value="2000" />
        </properties>

    </persistence-unit>

</persistence>

JPA는 영속 단위(persistence unit 별로 엔티티 클래스를 관리한다. 영속 단위는 JPA가 영속성을 관리할 단위로 영속 단위별로 매핑 대상, DB 연결 설정 등을 관리한다. 보통 한 개의 애플리케이션은 한 개의 영속 단위를 설정한다.

5.1 DB 종류와 Dialect

hibernate.dialect 속성은 하이버네이트가 쿼리를 생성할 때 사용할 Dialect 종류를 지정한다. 속성값으로 사용할 DBMS에 맞는 클래스 이름을 지정한면 하이버네이트가 DB 종류에 맞는 쿼리를 생성한다.

06. 영속 컨텍스트와 영속 객체 개요

@Entity 애노테이션이 붙은 클래스를 JPA에서는 엔티티라고 부른다. 이 엔티티는 DB에 보관되는 대상으로 JPA는 엔티티들을 영속성 컨텍스트로 관리한다. 영속성 컨텍스트는 JPA가 관리하는 엔티티 객체 집합으로 영속 컨텍스트에 속한 엔티티 객체를 DB에 반영한다.

보통 영속 컨텍스트는 세션 단위로 생긴다. 즉 세션 생성 시점에 영속 컨텍스트가 생성되고 세션 종료 시점에 컨텍스트가 사라진다. 응용 프로그램은 영속 컨텍스트에 직접 접근할 수 없기 때문에 EntityManager를 통해서 영속 컨텍스트와 관련된 작업을 수행한다. EntityManager를 통해서 영속 컨텍스트에 엔티티 객체를 추가하고, EntityManager를 통해서 영속 컨텍스트로부터 엔티티 객체를 구한다.

07. 간단한 예제 실행

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
28
public class AddUserMain {
    public static void main(String[] args) {
        //persistence.xml 파일에 명시한 영속 단위 이름을 파라미터로 보내준다.
        EntityManagerFactory emf = persistence.createEntityManagerFactory("jpastart");
        //EntityManager를 사용해서 DB 연동을 처리한다.
        EntityManager entityManager = emf.createEntityManager();
        EntityTransation transaction = entityManager.getTransaction();

        try{
            //JPA는 트랜잭션 범위에서 DB를 변경을 처리하도록 제한하고 있기 때문에, 먼저 트랜잭션을 시작해야 새로운 데이터를 추가하거나 기존 데이터를 변경할 수 있다.
            transaction.begin();
            User user = new User("user@user.com", "user", new Date());
            entityManager.persist(user);    //영속 컨텍스트에 객체 추가
            //정상 실행하면 트랜잭션 커밋
            transaction.commit();       //커밋하는 순간 실제 DB에 반영된다.

        } catch(Exception ex) {
            ex.printStackTrace();
            //문제 발생하면 트랜잭션 롤백
            transaction.rollback();
        } finally {
            //필요한 작업이 끝나면 EntityManager를 종료한다.
            entityManager.close();
        }
        //애플리케이션 자체를 종료할 때는 EntityManagerFactory를 종료하여 커넥션 풀과 같은 자원을 반환받는다.
        emf.close();
    }
}

08. EntityManagerF트actory 관련 보조 클래스

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
28
29
30
31
32
33
//EntityManager를 쉽게 생성할 수 있도록 보조 클래스를 작성할 수 있다.
public class EMF {
    private static EntityManagerFactory emf;

    public static void init() {
        emf = Persistence.createEntityFactory("jpastart");
    }

    public static EntityManager createEntityManager() {
        return emf.createEntityManager();
    }

    public static void close() {
        emf.close();
    }
}

public class AddUserMain {
    public static void main(String[] args) {
        EMF.init();
        EntityManager em = EMF.createEntityManager();

        try{
            //jpa 프로그램 수행


        }finally{
            em.close();
        }

        EMF.close();
    }
}

09. 콘솔을 사용한 사용자 관리 예제

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class UserMain{
    private static JoinService = new JoinService();
    private static GetUserService getUserService = new GetUserService();
    //...

    public static void main(String[] args) {
        EMF.init();

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        while(true) {
            System.out.println("명령어를 입력하세요.");
            String line = reader.readLine();
            String[] commends = line.split(" ");

            if(commends[0].equalsIgnoreCase("exit")) {
                System.out.println("종료합니다.");
                break;
            } else if(commends[0].equalsIgnoreCase("join")){
                handleJoinCommand(commands);
            }else if(commends[0].equalsIgnoreCase("view")){
                handleViewCommand(commands);
            }else if(commends[0].equalsIgnoreCase("list")){
              //...
            }else if(commends[0].equalsIgnoreCase("changename")){
              //...
            }else if(commends[0].equalsIgnoreCase("withdraw")){
              //...
            }else {
                System.out.println("올바른 명령어를 입력하세요.");
            }
            System.out.println("-----");
        }
        EMF.close();
    }

    private static void handleJoinCommand(String[] cmd) {
      if(cmd.length != 3) {
        System.out.println("명령어가 올바르지 않습니다.");
        System.out.println("사용법: join 이메일 이름");
        return;
      }

      try{
        joinService.join(new User(cmd[1], cmd[2], new Date());
        System.out.println("가입 요청을 처리했습니다.");
      }catch(DuplcateException e) {
        System.out.println("이미 같은 이메일을 가진 사용자가 존재합니다.");
      }
    }

    private static void handleViewCommand(String[] cmd) {
      if(cmd.length != 2) {
        System.out.println("명령어가 올바르지 않습니다.");
        System.out.println("사용법: view 이메일");
        return;
      }
      Optional<User> userOpt = getUserService.getUser(cmd[1]);
      if(userOpt.isPresent()) {
        User user = userOpt.get();
        System.out.println("이름 : " + user.getName());
        System.out.printf("생성 : %tY-%<tm-%<td\n", user.getCreateDetail());
      }else {
        System.out.println("존재하지 않습니다.");
      }
    }

    //....
}

9.1 사용자 가입 기능 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JoinService {
    public void Join(User user) {
        EntityManager em = EMF.createEntityManager();
        em.getTransaction().begin();
        try{
            //em.find()는 식별자를 이용하여 엔티티 객체를 찾는다.
            User found = em.find(User.class, user.getEmail());
            if(found != null) {
                throw new DuplicateEmailException();
            }
            em.persist(user);
            em.getTransaction().commit();
        }catch(Exception ex) {
            em.getTransaction().rollback();
            throw ex;
        }finally{
            em.close();
        }
    }
}

9.2 사용자 정보 조회 기능 구현 : 단일 사용자

1
2
3
4
5
6
7
8
9
10
11
12
13
public class GetUserService{
  //getUser() 메서드는 트랜잭션 범위에서 실행하지 않는다. 수정 기능이 없고 단일 엔티티 객체를 조회하므로 트랜잭션이 필요없다.
  public Optional<User> getUser(String email) {
    EntityManager em = EMF.createEntityManager();

    try{
      User user = em.find(User.class, email);
      return Optional.ofNullable(user);
    }finally{
      em.close();
    }
  }
}

9.3 사용자 정보 변경 기능 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Table(name="user")
public class User {

    @Id
    private String email;
    private String name;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="create_date")
    private Date createDate;

    //....


    public void changeName(String newName) {
      this.name = newName;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ChangeNameService{
  public void changeName(String email, String newName) {
    EntityManager em = EMF.createEntityManager();

    try{
      em.getTransaction().begin();
      User user = em.find(User.class, email);
      if(user == null)thorw new UserNotFoundException();

      //수정한 user 객체를 DB에 반영하는 코드가 없다.
      //JPA는 트랜잭션을 종료할 때 영속 컨텍스트에 존재하는 영속 객체의 값이 변경되었는지를 검사하여 값이 바뀐 경우 변경된 값을 DB에 반영한다.
      user.changeName(newName);
      em.getTransaction().commit();
    }catch(Exception e) {
      em.getTransaction().rollback();
      throw ex;
    }finally{
      em.close();
    }
  }
}

9.4 사용자 정보 조회 기능 구현 : 목록

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class GetUserListService{
  public List<User> getAllUsers() {
    EntityManager em = EMF.createEntityManager();
    try{
      em.getTransaction().begin();
      //JPQL을 실행하기 위한 TypedQuery를 생성 아래 쿼리에서 User는 user 테이블이 아닌 User 클래스를 의미한다.
      TypedQuery<User> query = em.createQuery("select u from User u order by u.name", User.class);
      List<User> result = query.getResultList();
      em.getTransaction().commit();
      return result;
    }catch(Exception ex) {
      em.getTransaction().rollback();
      throw ex;
    }finally{
      em.close();
    }
  }
}

JPA는 SQL과 유사한 JPQL을 제공한다.JPQL은 매핑 설정을 담은 클래스를 이용해서 쿼리를 작성한다.

9.5 사용자 정보 삭제 기능 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WithdrawService{
  public void withdraw(String email) {
    EntityManager em = EMF.createEntityManager();
    em.getTransaction().begin();

    try{
      User user = em.find(User.class, email);
      if(user == null) {
        throw new UserNotFoundException();
      }
      em.remove(user);
      em.getTransaction().commit();
    }catch(Exception ex) {
      em.getTransaction().rollback();
      throw ex;
    }finally {
      em.close();
    }
  }
}

10. 정리

JPA 프로바이더가 SQL 쿼리를 생성하기 때문에 개발자는 기본적인 추가, 조회, 삭제를 위한 SQL 쿼리를 작성하지 않아도 된다. persist() 메서드를 사용하면 자바 객체를 매핑된 테이블에 삽입하는 insert 쿼리를 실행한다. find() 메서드를 사용하면 테이블에서 데이터를 조회하는 select 쿼리를 살행하고 그 결과로부터 객체를 생성한다.

주요한 특징은 엔티티에 수정이 발생하면 이를 자동으로 DB에 반영해준다는 것이다. JPA는 객체의 변경내용을 추척하여 트랜잭션 종료시 알맞게 update 쿼리를 실행해서 변경된 값을 DB에 반영한다.

주의할 점으로는 높은 성능이 요구되는 SQL 쿼리가 필요한 기능은 JPA의 쿼리 생성 기능이 문제를 유발할 수 있다. 예) 대량 데이터를 배치로 처리, 복잡한 조회 쿼리가 필요할 때 등등..

This post is licensed under CC BY 4.0 by the author.