[JAVA] JPA 어노테이션 및 트랜잭션(3)

About JPA annotaion and Transaction

Posted by JongMin-Lee on February 10, 2020

관련된 글

  1. [JAVA] JPA(Java Persistence API)란?(1)
  2. [JAVA] JPA 기본설정 및 객체생성(2)
  3. [JAVA] JPA 어노테이션 및 트랜잭션(3)
  4. [JAVA] JPA 연관관계(4)
  5. [JAVA] JPA 영속성 컨텍스트(5)

이전 시간에 Member와 Club객체를 생성했습니다. 이 두 객체를 이용해서 JPA에 대해 좀더 알아 보겠습니다.

실습은 TEST Code로 진행할 수 있지만 h2-console을 활용하기 위해서 main applicaion에서 진행하였습니다.
main Apllication

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
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);

        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jongmin");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        // 트랜잭션 시작
        transaction.begin();
        try{
            Member member = new Member();
            member.setName("jongmin");
            member.setAge(30);
            entityManager.persist(member); // 영구저장

            transaction.commit(); // 커밋
        }catch (Exception e){
            transaction.rollback();
        }finally {
            entityManager.close();
        }
        entityManagerFactory.close();
    }

}
  1. EntityManagerFactory를 이용하여 persistence.xml에서 지정한 설정을 가지고 옵니다. 이 전에 지정한 persistence.xml설정에서 <persistence-unit name="jongmin">에 지정한 name으로 설정을 가지고 옵니다.
  2. EntityManager를 생성하고 Transaction을 얻습니다.
  3. 트랜잭션을 시작하고 Member객체를 만들어 값을 넣은 후 persist를 통해서 값을 넣어줍니다.
  4. commit()해주어 값을 물리적으로 데이터베이스에 넣어주고 close해줍니다.(에러시 rollback)

EntityManagerFactory는 애플리케이션 전체에서 하나만 생성하여 공유해야합니다. EntityManagerFactory를 생성할 때마다 DB와의 연결이 발생함으로 성능상에 문제가 발생할 수 있기 때문입니다.
(DB와의 연결을 해주는 인터페이스라고 생각합니다.)
EntityManager는 EntityManagerFactory의 구현체입니다. EntityManager를 통해서 DB와 데이터를 주고 받을 수 있습니다. 모든 Transaction이 끝나면 close()를 사용해서 자원을 해제합니다.

주의사항

  • 모든 변화(update, delete, save)는 Transaction안에서 일어나야 합니다.
  • EntityManagerFactory는 애플리케이션 전체에 하나만 생성하여 공유해야 합니다.
  • Transaction이 끝나면 close()를 통해서 자원을 해제시켜야 합니다.
  • EntityManager는 쓰레드간에 공유해서는 안된다.(사용하고 버린다.)

    참고
    persist()를 수행했다고 데이터베이스에 값이 완전히 들어가는 것이 아닙니다!!
    commit()을 해야 물리적으로 데이터페이스에 들어가며 이부분은 뒤에서 다루겠습니다.

main 메소드를 수행한 후 결과를 확인해 보겠습니다. 먼저 console창 입니다. console 다른 특별한 작업을 하지 않았음에도 불구하고 create table을 통해서 테이블을 생성하는 것을 알 수 있습니다. 이것이 persistence.xml에 <property name="hibernate.hbm2ddl.auto" value="create" />를 지정했기 때문입니다.
여기서는 캡쳐하지 않았지만 테이블을 생성하기 전에 drop table Member if exists을 먼저 수행하는 것을 콘솔에서 확인하실 수 있을 것입니다. 그 다음 persist를 통해서 데이터베이스에 값을 넣어주어 insert쿼리가 자동으로 날라가는 것을 볼 수 있습니다.
h2-console에 접속하여 Member 객체를 확인해보면 값이 성공적으로 들어간 것을 볼 수 있습니다. 여기서 ID는 값을 따로 넣어주지 않았지만 1값이 들어가 있습니다. 저희가 Member객체를 만들때 @GeneratedValue를 지정했기 때문에 H2데이터베이스에서 자체적으로 만들어 주었기 때문입니다.

이제 추가적인 어노테이션에대해 알아보겠습니다.

@Column

가장 많이 사용하는 어노테이션입니다.

  • name : 데이터베이스의 Column이름 지정
  • insertable, updateble : 읽기 전용여부
  • nullable : null 허용 여부, DDL 생성시 사용
  • unique : 유니크 제약조건, DDL 생성시 사용
@Enumerated

열거형 매핑으로 Enum 타입과 관련하여 매핑해줍니다.

  • EnumType.STRING : String 타입으로 열거형 이름그대로 저장해줍니다.
  • EnumType.ORDINAL : 순서를 저장해줍니다.(default)
  • 실제로 사용할 때는 EnumType.STRING을 사용해 줍시다.

    Enum Type이 추가되서 순서가 변경되면 ORDINAL이 무의미 해지기 때문에 STRING을 사용을 권장.

@Lob

컨텐츠의 길이가 길경우 사용합니다.(CLOB, BLOB)

  • CLOB : String, char[], java.sql.CLOB
  • BLOB : byte[], java.sql.BLOB
@Transient

Transient어노테이션이 추가된 필드는 매핑하지 않습니다. 데이터베이스에서는 사용하지 않지만 Class에 추가하고 싶은 경우 사용합니다.

@Temporal

시간에 관련된 어노테이션 입니다.

  • Date(java.sql.Date)
  • Time(java.sql.Time)
  • TimeStamp(java.sql.TimeStamp)

    그러나 LocalDateTime이 있으므로 잘 사용하지 않게 되는 어노테이션입니다.

@Embedded

그 다음 @Embeddable, @Embedded, @AttributeOverrides, @AttributeOverride 등이 있습니다. 이 4개의 어노테이션은 한번에 살펴보겠습니다.(아래의 예시가 적절하지 않을 수도 있습니다…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Getter @Setter
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private int age;

    @Enumerated(value = EnumType.STRING)
    private RoleType roleType;

    private String friendName;

    private String friendPhone;

}

Member 객체가 위와 같이 구성되어 있다고 하겠습니다.(Getter, Setter는 Lombok을 이용해서 처리했습니다.) 아래의 friend(친구)와 관련된 컬럼이 있다고 할 때 이 컬럼을 따로 관리하고 싶습니다. 테이블에서는 분리하지 않고 객체(클래스)만을 분리해서 사용하고 싶을 때 위의 어노테이션들을 사용할 수 있습니다.
먼저 분리시킬 Friend클래스를 생성해줍니다.

1
2
3
4
5
6
7
8
@Embeddable
@Getter @Setter
public class Friend {

    private String name;
    private String phoneNumber;

}

분리시킨 Friend클래스에 @Embeddable어노테이션을 추가해줍니다. 그 다음 Member클래스를 바꿔 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@Getter @Setter
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private int age;

    @Enumerated(value = EnumType.STRING)
    private RoleType roleType;

    @Embedded
    private Friend friend;
    
}

위와 같이 @Embedded 어노테이션을 사용하여 분리시킨 Friend클래스를 불러올 수 있습니다.
이대로 사용할 수 있지만 에러가 발생합니다. 그 이유는 name컬럼이 중복되기 때문입니다. 기존에는 friendName을 사용했지만 분리시키면서 name으로 변경 시켰습니다. 이 경우 @AttributeOverrides을 사용하여 분리한 클래스의 매핑명을 지정해 줄 수 있습니다.

1
2
3
4
5
6
@Embedded
@AttributeOverrides(
    @AttributeOverride( name = "name", column = @Column(name = "friendNsame")),
    @AttributeOverride( name = "phoneNumber", column = @Column(name = "friendPhsne"))
)
private Friend friends

다음과 같이 지정하므로써 매핑할 컬럼명을 바꿔줄 수 있습니다.

여기까지 Transaction과 기타 어노테이션에 대해 알아봤습니다.
다음 시간에는 가장 중요한 연관관계에 대해 알아보겠습니다.

전체 코드는 Github에서 확인해보실수 있습니다.

참고
  • objectdb (https://www.objectdb.com/java/jpa/persistence/overview)
  • Baeldung (https://www.baeldung.com/hibernate-date-time)
  • SKplanet Academy (https://www.youtube.com/channel/UCtV98yyffjUORQRGTuLHomw)