728x90

깃허브 바로가기
JPA에서 가장 중요한 것은 엔티티와 테이블 매핑을 정확히 하는 것이다.
매핑 어노테이션을 잘 숙지해야 한다.

  • 객체와 테이블 매핑 : @Entity, @Table
  • 기본 키 매핑 : @Id
  • 필드와 컬럼 매핑 : @Column
  • 연관관계 매핑 : @ManyToOne, @JoinColumn

@Entity

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.
@Entity가 붙은 클래스는 JPA가 관리하는 것으로 엔티티라고 부른다.

속성 기능 기본값
name JPA에서 사용할 엔티티 이름을 정한다. 보통 기본값인 클래스 이름을 사용한다. 만약 다른 패키지에 이름이 같은 엔티티 클래스가 있다면 이름을 지정해서 충돌하지 않도록 해야 한다. 설정하지 않으면 클래스 이름 그대로 사용

@Entity 적용시 주의 사항

  • 기본 생성자는 필수(파라미터가 없는 public or protected 생성자)
    • Lombok에서는 @NoArgsConstructor 사용
  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안된다.

생성자가 하나도 없을 때 자바는 기본 생성자를 생성하지만, 생성자 오버로딩을 했을 때는 개발자가 직접 기본 생성자를 만들어줘야 한다.

@Table

@Table은 엔티티와 매핑할 테이블을 지정. 생략시 매핑한 엔티티 이름을 테이블 이름으로 사용

속성 기능 기본값
name 매핑할 테이블 이름 엔티티 이름을 사용
catalog catalog 기능이 있는 DB에서 catalog매핑
schema schema 기능이 있는 DB에서 schema 매핑
uniqueConstraints DDL 생성시 유니크 제약조건을 만듦. 2개 이상의 복합 유니크 제약조건도 만들 수 있다. 스키마 자동생성기능을 사용해서 DDL을 만들때만 사용

다양한 매핑

  • @Enumerated : 자바의 enum을 사용할 때
  • @Temporal : 자바의 날짜타입 매핑
  • @LOB : CLOB, BLOB 타입 매핑

DB 스키마 자동생성

JPA는 DB스키마를 자동 생성하는 기능을 지원.

application.yaml

spring:
  jpa:
    hibernate:
      ddl-auto: create

어플리케이션 실행 시점에 DB테이블 자동으로 생성

옵션 설명
create 기존 테이블을 삭제하고 새로 생성, DROP + CREATE
create-drop create 속성에 추가로 어플리케이션을 종료할때 DDL 제거, DROP + CREATE + DROP
update DB 테이블과 엔티티 매핑정보를 비교해서 변경 사항만 수정
validate DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경로를 남기고 어플리케이션을 실행하지 않는다. 이것은 DDL을 수정하지 않음.
none 자동 생성 기능을 사용하지 않으려면 ddl-auto 속성을 제거하거나 유효하지 않은 옵션 값을 주면 된다.(none은 유효하지 않은 옵션값)

sql보기

spring:
  jpa:
    properties:
      hibernate:
        show_sql: true

DDL생성 기능

@Column 매핑정보의 nullable속성 값을 false로 지정하면 자동 생성되는 DDL에 not null 제약조건을 추가할 수 있다. 자동 생성되는 DDL에 문자 크기를 length옵션으로 지정할 수 있다.

@Table 에 유니크 제약조건을 만들어주는 uniqueConstraints 속성은 유니크 제약조건을 말 그대로 걸어주는 것인데 이 속성들은 DDL을 자동 생성할때만 사용되고 JPA 실행 로직에는 영향을 주지 않는다.

직접 DDL을 만들면 사용할 이유가 없는 속성들이다.
그럼에도 이 기능을 사용한다면 엔티티만 보고도 다양한 제약조건을 파악할 수 있다는 장점이 존재

기본키 매핑

@Entity
public class Member{
    @Id
    @Column(name= "ID")
    private String id;

JPA에서 제공하는 DB 기본키 생성 전략

  • 직접 할당 : 기본 키를 어플리케이션에서 직접 할당
  • 자동 생성 : 대리 키 사용 방식
    • IDENTITY : 기본 키 생성을 DB에 위임
    • SEQUENCE : DB시퀀스를 사용하여 기본 키 할당
    • TABLE : 키 생성 테이블을 사용

자동생성 전략이 다양한 이유는 DB벤더마다 지원하는 방식이달라서 이다.
SEQUENCE나 IDENTITY 전략은 사용하는 DB에 의존

기본키를 할당하려면 @Id 사용, 자동생성 전략을 이용하려면 @GeneratedValue를 사용하면 된다.

IDENTITY 전략

기본 키 생성을 DB에 위임하는 전략
MySQL, Postgre, SQL Server, DB2 에서 사용

@Id
@GeneratedValue(strategy = GenertaionType.IDENTITY)
private Long id;

IDENTITY는 데이터를 DB에 insert한 후에 기본 키 값을 조회할 수 있음.
그러므로 엔티티에 식별자 값을 할당하려면 추가로 DB를 조회해야함

SEQUENCE 전략

유일한 값을 순서대로 생성하는 전략
Oracle, Postgre, DB2, H2 에서 사용 가능
@SequenceGenerator 에 시퀀스 생성기를 name이값에 등록하고
sequenceName 속성 이름으로 DB 시퀀스를 매핑함
그러면서
@GenerateValue(strategy = Generation.Type.SEQUENCE, generator = "SequenceGenerator의 name값")
사용하면 시퀀스 생성기 할당됨

@SequenceGenerator

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName DB에 등록된 시퀀스 이름 hibernate_sequence
initialValue DDL 생성시에 사용, 시퀀스 DDL 생성할때 처음 시작하는 수 지정 1
allocationSize 시퀀스 한번 호출에 증가하는수(성능최적화에 사용) 50
catalog, schema 데이터베이스 catalog, schema이름

DDL ex - create sequence [sequenceName] start with [initialValue] increment by [allocationSize]

TABLE 전략

키생성 전용 테이블을 하나 만들고 이름과 값으로 사용할 컬럼 만든후 시퀀스를 흉내내는 전략

create table MY_SEQUENCES {
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key (sequence_name)
}

@GeneratorValue(generator = "BOARD_SEQ_GENERATOR")

@TableGenerator

속성 기능 기본값
name 식별자 생성기 이름 필수
table 키생성 테이블명 hibernate_sequences
pkColumnName 시퀀스 컬럼명 sequence_name
valueColumnName 시퀀스 값 컬럼명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀀스 한번 호출에 증가하는 수(성능 최적화) 50
catalog, schema DB catalog, schema 이름
uniqueConstraints(DDL) 유니크 제약 조건 지정

AUTO 전략

선택한 DB의 방언에 따라 Identity, Sequence, Table 전략중 하나 자동으로 설정

기본키 매핑 정리

영속성 컨텍스트는 엔티티를 식별자 값으로 구분하기 때문에 엔티티를 영속 상태로 만드려면 식별자 값은 반드시 포함해야 하는 사실. em.perist()를 호출한 직후 발생하는일을 정리하면 아래와 같다.

  • 직접 할당
    • em.persist() 를 호출하기 전에 애플리케이션에서 직접 식별자 값 할당
    • 없다면 예외발생
  • SEQUENCE
    • DB 시퀀스에서 식별자 값 획득 후 영속성 컨텍스트에 저장
  • TABLE
    • DB 시퀀스 생성용 테이블에서 식별자 값 획득 후 영속성 컨텍스트 저장
  • IDENTITY
    • DB에 엔티티를 저장한 다음 식별자 값 획득 후 영속성 컨텍스트 저장
    • 테이블에 데이터를 저장해야 식별자 값 획득이 가능

레퍼런스

JPA에서 제공하는 필드와 컬럼 매핑용 어노테이션 정리

분류 매핑 어노테이션 설명
필드와 컬럼 매핑
@Column 컬럼을 매핑
@Enumerated 자바 enum 타입 매핑
@Temporal 날짜 타입 매핑
@Lob CLOB, BLOB 타입 매핑
@Transient 특정 필드를 DB에 매핑하지 않음
기타 @Access JPA가 엔티티에 접근하는 방식 지정

@Column

속성 기능 기본값
name 필드와 매핑할 테이블 컬럼이름 객체의 필드이름
nullable(DDL) null값 허용 여부 설정. false설정 시 DDL조건에 not null제약조건 추가됨 true
unique(DDL) 한 컬럼에 간단히 유니크 제약조건 걸 때 사용, 두 컬럼 이상 사용시 @Table.uniqueConstraints사용
ColumnDefinition(DDL) DB 칼럼 정보 직접 줄 수 있음 필드의 자바 타입과 방언 정보를 사용해서 적절한 컬럼 타입 생성
length(DDL) 문자 길이 제약조건, String타입에만 사용 255
precision, scale(DDL) BigDecimal타입에서 사용 precision은 소수점 포함 전체자리수, scale은 소수 자리수, float이나 double타입에는 적용 안됨 precision = 19, scale= 2

안쓰는 것은 구글링을 해서 찾아야겠다.

@Enumerated

  • EnumType.ORDINAL은 enum정의된 순서대로 0, 1 순서로 DB에 저장
    • 장점 : DB에 저장되는 데이터 크기가 작음
    • 단점 : 이미 저장된 enum의 순서를 변경할 수 없음
  • EnumType.STRING은 enum 이름 그대로 DB에 저장
    • 장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전
    • 단점 : DB에 저장되는 데이터 크기가 ORDINAL에 비해 큼

@Temporal

TemporalType필수 지정

  • value
    • TemporalType.DATE: 날짜, DB date 타입 매핑
    • TemporalType.TIME: 시간, DB time 타입 매핑
    • TemporalType.TIMESTAMP: 날짜와 시간, DB timestamp 타입과 매핑

@Lob

지정할 수 있는 속성이 없음.
대신 매핑하는 필드타입이 문자라면 CLOB으로 매핑 나머지는 BLOB으로 매핑

@Transient

매핑하지 않는 필드, 그렇기 때문에 DB에 저장하지도 않고 조회하지도 않음.
객체에 임시로 어떤 값을 보관하고 싶을 때 사용함.

@Access

엔티티 데이터에 접근하는 방식 지정

  • 필드 접근 : AccessType.FILED로 지정.
    • 필드에 직접 접근, 접근 권한이 private여도 접근 가능
  • 프로퍼티 접근 : AccessType.PROPERTY 로 지정. 접근자(Getter) 사용.

@Access 를 설정하지 않으면 @Id 위치를 기준으로 접근방식 설정

@Id 가 필드에 있으면 @Access(AccessType.FILED) 로 설정한 것과 같음.
그래서 생략 가능

728x90

'JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계 매핑  (0) 2022.08.04
[JPA] 연관관계 매핑  (0) 2022.08.04
[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
728x90

깃허브 바로가기
이번 발표는 내가 진행하였다. 그래서 이해하기가 더욱 더 쉬웠다.
JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계부분과 매핑한 엔티티를 실제 사용하는 두가지 부분으로 나눌 수 있다.
여기서는 매핑한 엔티티를 엔티티 매니저를 통해 사용하는 것을 알아보자.

영속성 관리

엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회 등등 엔티티와 관련된 모든 일을 처리한다.
말그대로 엔티티를 관리하는 것이다.

엔티티 매니저 팩토리와 엔티티 매니저

DB를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성한다.
팩토리를 만드는 것으로 생성할 때 비용이 아주 많이 든다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("");

교재에서는 이것을 통해 META-INF/persistence.xml에 persistence-unit name에 매핑된 값을createEntityManager에 넣어준다.

필요할때마다 엔티티 매니저 팩토리에서 엔티티 매니저를 생성하면 된다.
팩토리에서 매니저를 생성하므로, 비용이 작다.

EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리는 이름 그대로 엔티티 매니저를 만드는 공장이다.
공장을 만드는 것은 상당한 비용이 든다.
그래서 하나만 만들어서 어플리케이션 전체에서 공유하도록 설계가 되어있다.
그렇기 때문에 공장에서 엔티티 매니저를 생성하는 비용은 작다고 하는 것이다.

엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드간에 공유를 해도 되지만,
엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성문제가 발생하므로 스레드 간에 절대 공유해서는 안된다.
일반적인 웹 어플리케이션

그림에서 하나의 EntityManagerFactory에서 다수의 엔티티 매니저를 생성했다. EntityManager1은 아직 DB connection을 하지 않는다. 엔티티 매니저는 DB 연결이 꼭 필요한 시점까지 connection을 얻지 않는다.
보통 트랜잭션을 시작할 때 커넥션을 획득한다.
Hibernate를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션 풀도 만드는데
xml설정은 J2SE에서 사용한 방식이다.

영속성 컨텍스트란?

JPA를 이해하는데에 있어서 가장 중요한 용어가 바로 영속성 컨텍스트 다. 해석하자면 엔티티를 영구 저장하는 환경이다.
EntityManager로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

em.persist(member);

persist()를 단순히 회원 엔티티를 저장한다고 표현했지만 사실 persist()는 엔티티 매니저를 이용하여 회원 엔티티를 영속성 컨텍스트에 저장한다는 말이다.

영속성 컨텍스트는 물리적인 것이 아니라 논리적인 개념에 가깝기 때문에 볼 수 없다. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다. 그리고 생성된 매니저를 통해 영속성 컨텍스트에 접근하고 관리할 수가 있다.

엔티티의 생명주기

  • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed) : 삭제된 상태

엔티티 생명주기

비영속

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

엔티티 객체를 생성한 상태. 이 때는, 순수한 객체 상태이며 아직 저장하지 않은 것이다.
따라서 영속성 컨텍스트나 DB와 전혀 관련이 없다. 이것을 비영속 상태라고 한다.

영속

em.persist(member);

엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한다. 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라고 한다. persist()를 사용하는 순간부터 영속상태에 들어가게 되었다.

결국 영속상태 라는 것은 영속성 컨텍스트에 의해 관리된다 라는 뜻이다.

더불어 em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태이다.

준영속

영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.
특정 엔티티를 준영속 상태로 만들려면 detach()를 호출하면 된다.
close()를 호출해서 영속성 컨텍스트를 닫거나 clear()를 호출해서 영속성 컨텍스트를 초기화해도 영속성 컨텍스트가 관리하던 영속 상태의 엔티티는 준영속 상태가 된다.

삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.
remove()를 사용한다.

영속성 컨텍스트의 특징

영속성 컨텍스트와 식별자 값

엔티티를 식별자 값(@Id로 테이블의 PK와 매핑한 값)으로 구분한다.
따라서 영속 상태는 식별자 값이 반드시 있어야 한다. 없으면 예외가 발생한다.

영속성 컨텍스트와 DB저장

JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데
이를 flush라 한다.

영속성 컨텍스트가 엔티티를 관리할때의 장점

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

엔티티 조회

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라고 한다.
영속 상태의 엔티티는 모두 1차캐시에 저장된다. 내부에 Map이 하나 있는데 키는 @Id로 매핑한 식별자고
값은 엔티티의 인스턴스이다.

// 엔티티 생성(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//엔티티를 영속
em.persist(member);

이코드를 실행하면 1차 캐시에 회원 엔티티를 저장한다. 회원 엔티티는 아직 데이터베이스에 저장되지 않았다.

1차 캐시의 키는 식별자 값이라고 설명했다. 식별자 값은 데이터베이스 기본키와 매핑이 되었다고도 언급했다.
그렇기 때문에 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본키 값이다.

Member member = em.find(Member.class, "member1");

find()메소드를 보면 첫번째 매개변수는 엔티티 클래스 타입이고, 두번째 매개변수는 엔티티의 식별자 값이다. find()를 호출하면 1차에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회한다.

데이터베이스에서 조회

find()를 호출했는데 엔티티가 1차 캐시에 없으면 DB에서 조회하여 엔티티를 생성한다.
생성한 다음 1차 캐시에 저장한 후, 영속 상태의 엔티티를 반환해준다.

영속 엔티티의 동일성 보장

객체를 두개 생성하는데 식별자가 같은 인스턴스를 조회하면 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.

영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.

  • 동일성
    • 실제 인스턴스가 같다. 참조 값을 비교하는 == 비교의 값이 같다.
  • 동등성
    • 실제 인스턴스가 다를 수 있지만, 인스턴스가 가지고 있는 값이 같다. equals() 메소드로 확인한다.

엔티티 등록

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);

//커밋하는 순간 데이터베이스에 INSERT SQL을 보냄
transaction.commit();

persist()만 계속 진행하였을 경우 커밋 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 모아둔다. 이후 트랜잭션을 커밋할 때 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.

엔티티 수정

엔티티의 데이터만 변경했는데 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경감지라고 한다.
스냅샷 : JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사하여 저장해둔다.

  1. 커밋하면 엔티티 매니저 내부에서 플러시가 먼저 호출됨
  2. 엔티티와 스냅샷을 비교하여 변경된 엔티티 탐색
  3. 변경된 엔티티가 있으면 수정쿼리를 생성하여 쓰기 지연 sql저장소에 보낸다
  4. 쓰기지연 저장소의 sql을 데이터베이스에 보낸다.
  5. 데이터베이스 트랜잭션을 커밋한다.

변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용
JPA의 기본 전략은 엔티티의 모든 필드를 업데이트한다.

  • 모든필드를 사용하면 수정 쿼리가 항상 같다.
  • 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다.

저장되는 내용이 너무 크면 수정된 데이터만 사용하는
Hibernate의 확장 기능인 @DynamicUpdate를 사용하면 된다.

삭제도 마찬가지로 remove()를 호출해서 사용하는데 쓰기지연 SQL에 모아둔 후에
트랜잭션을 커밋하여 플러시를 호출하면 삭제 쿼리를 DB에 전달한다. remove는 호출하는 순간
영속성 컨텍스트에서 제거시킨다.

플러시

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 역할을 수행

실행했을 때 일어나는 일

  • 변경 감지가 동작하여 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해 수정된 엔티티를 탐색
    • 여기서 수정된 엔티티는 수정쿼리를 쓰기 지연 SQL저장소에 등록함
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송

플러시 하는 방법

  • em.flush() 직접 호출
    • 강제로 하는 것이기 때문에 테스트나 다른 프레임워크와 JPA를 사용할때 사용
  • 트랜잭션 커밋시 자동으로 호출
    • 트랜잭션만 커밋하면 DB에 반영되지 않음. 반영해야 하기 때문에 JPA는 자동으로 호출해준다.
  • JPQL 쿼리 실행 시 플러시가 자동 호출
    • JPA와 같이 사용했을 때, 엔티티로 조회하던 객체들은 쓰기지연에 남아있으므로 결과가 조회되지 않는다.
      그래서 플러시를 하여 내용을 DB에 반영해야 한다.

플러시 모드 옵션

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할때 (기본값)
  • FlushModeType.COMMIT : 커밋할 때만 플러시

준영속

준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.

  • em.detach(entity) : 특정엔티티만 준영속 상태로 전환
  • em.clear() : 영속성 컨텍스트를 완전히 초기화
  • em.close() : 영속성 컨텍스트를 종료

detach를 호출하는 순간 1차 캐시부터 쓰기지연 SQL저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거된다.

준영속 상태의 특징

  • 거의 비영속 상태에 가까움
    • 영속성 컨텍스트가 관리하지 않으므로 1차캐시, 쓰기지연, 변경 감지, 지연 로딩 등 어떠한 기능도 동작하지 않는다.
  • 식별자 값을 가지고 있다.
    • 비영속은 식별자 값이 없을수 있지만 이미 한번 영속되었던 상태이므로 반드시 식별자 값을 가지고 있음.
  • 지연 로딩을 할 수 없다.
    • 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할때 영속성 컨텍스트를 통해 불러오는 방법이 지연로딩인데 준영속은 컨텍스트가 더는 관리하지 않기 때문에 지연로딩에 문제가 발생한다.

병합

준영속 상태의 엔티티를 다시 영속상태로 변경할 때 사용
merge()메소드는 준영속 상태의 엔티티를 받아 그 정보로 새로운 영속 상태의 엔티티를 반환 한다.

병합은 비영속 엔티티도 영속 상태로 만들 수 있다.
병합은 준영속, 비영속을 신경 쓰지 않는다. 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합, 없으면 새로 생성해서 병합. 그래서 병합은 save or update 기능 수행

정리

  • 엔티티 매니저는 엔티티 매니저 팩토리에서 생성.
    • 영속성 컨텍스트는 엔티티 매니저를 통해 접근이 가능
  • 영속성 컨텍스트는 애플리케이션과 DB사이에 객체를 보관하는 가상DB같은 역할
    • 이로 인해 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능을 사용할 수 있다.
  • 영속성 컨텍스트에 저장한 엔티티는 플러시 시점에 DB에 반영되는데 일반적으로 트랜잭션을 커밋할 때 영속성 컨텍스트가 플러시 된다.
  • 영속성 컨텍스트가 관리하는 엔티티는 영속 상태의 엔티티인데, 컨텍스트가 더이상 관리하지 못하면 그 엔티티는 준영속 상태의 엔티티가 된다. 이 엔티티는 컨텍스트의 관리를 더는 받지 못하므로 영속성 컨텍스트가 제공하는 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능 등을 사용할 수 없다.
728x90

'JPA' 카테고리의 다른 글

[JPA] 연관관계 매핑  (0) 2022.08.04
[JPA] 엔티티 매핑  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
728x90

2장 요약정리
처음 2장은 바로 설치부터 maven 의존성, 라이브러리 설정을 진행한다.
테이블도 생성하는데 이부분은 생략하도록 하겠다.

어노테이션 정리

  • @Entity
    • 이 클래스를 테이블과 매핑한다고 JPA에게 알려줌.
    • 이렇게 @Entity가 사용된 클래스를 엔티티 클래스라고 함
  • @Table
    • 엔티티 클래스에 매핑할 테이블 정보를 알려줌
    • @Table(name = "테이블명") 으로 매핑가능함
    • name속성을 생략하면 클래스 이름을 테이블 이름으로 매핑한다
  • @Id
    • 엔티티 클래스의 필드를 테이블의 기본 키에 매핑한다.
    • @Id가 사용된 필드를 식별자 필드라고 한다.
  • @Column
    • 필드를 칼럼에 매핑한다
    • 매핑 어노테이션이 없는 경우 생략하면 필드명을 사용하여 컬럼명으로 매핑
    • 대소문자를 구분하는 DB를 사용한다면 @Column(name = '칼럼명')으로 명시적 매핑을 해야함

JPA표준 속성

  • JDBC드라이버 : javax.persistence.jdbc.driver
  • 데이터베이스 접속 ID : javax.persistence.jdbc.user
  • 데이터베이스 접속 비밀번호 : javax.persistence.jdbc.password
  • 데이터베이스 접속 url : javax.persistence.jdbc.url

hibernate 속성

  • hibernate.dialect: 데이터베이스 방언 설정 (가장 중요)

이름이 javax.persistence로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속되지 않음
hibernate로 시작하는 속성은 하이버네이트 전용 속성으로 하이버네이트에서만 사용가능

데이터베이스 방언

JPA는 특정 데이터베이스에 종속적이지 않은 기술이다. 그래서 데이터베이스를 쉽게 교체할 수 있다.
그런데 DB마다 sql문법, 함수가 조금씩 다르다.

  • 데이터 타입 - Mysql : VARCHAR, Oracle : VARCHAR2
  • 다른함수 - 문자열 자르는 함수:SUBSTRING Oracle : SUBSTR()
  • 페이징처리 - Mysql : LIMIT, Oracle : ROWNUM

위와 같이 sql 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언이라고 한다
이런 종속되는 기능을 사용하면 나중에 데이터베이스 교체가 어렵다.

JPA가 제공하는 표준 문법에 맞춰 JPA를 사용하고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다.

  • H2: org.hibernate.dialect.H2Dialect
  • Oracle 10g : org.hibernate.dialect.Oracle10gDialect
  • MySQL : org.hibernate.dialect.MYSQL5InnoDBDialect

JPA의 코드는 크게 3가지 부분으로 나뉜다

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

build.gradle 설정

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

JPA의존성과 null체크 유효성검사에 대한 의존성을 추가했다.

엔티티 매니저 설정

EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");

이 엔티티 매니저 팩토리는 데이터베이스 커넥션 풀도 생성할수 있으므로 엔티티 매니저 팩토리를 생성하는 비용이 아주 크다.
엔티티 매니저 팩토리는 어플리케이션 전체에서 딱 한번만 생성하고 공유하여 사용해야 한다.

EntityManager em = emf.createEntityManager();

JPA의 기능 대부분은 이 엔티티 매니저가 제공한다. 대표적으로 이 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 CRUD가 가능하다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로
스레드 간에 공유하거나 재사용해서는 안된다.

사용이 끝난 엔티티 매니저는 반드시 종료하여야 한다.

em.close();
emf.close(); //엔티티 매니저 팩토리도 종료하여야 한다.

CRUD

  • 등록 : em.persist('등록할객체');
  • 수정 : 객체.set메소드
  • 삭제 : em.remove(객체)
  • 조회 : 객체 변수명 = em.find(객체.class, 객체에 해당하는 pk)

JPQL

기본적인 CRUD는 JPA를 사용하여 엔티티 객체 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡긴다
그래서 SQL문을 전혀 개발자 자신이 사용하지 않았다.
문제는 검색하는 쿼리이다. JPA는 객체중심으로 개발하므로 검색할 때에도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.
JPA는 JPQL(Java Persistence Query Language)라는 쿼리 언어로 문제를 해결해준다.

JPQL은 SQL을 추상화한 JPA의 객체지향 쿼리 언어이다. SQL문법과 많이 유사해서 select, from, where, group by, having, join 등을 사용할 수 있다.

  • JPQL은 엔티티 객체를 대상으로 쿼리한다. 클래스와 필드를 대상으로 쿼리
  • SQL은 DB 테이블을 대상으로 쿼리한다.

em.createQuery("query String")를 통하여 JPQL 쿼리를 날리면 된다.

여기서 Query String은 엔티티 객체를 다루는 것이지 절대 테이블이 아니다!!!!!

JPQL은 DB 테이블을 전혀 알지 못한다.

  • 순서
    • em.createQuery("QueryString")
    • 쿼리객체 생성된 후 쿼리 객체의 getResultList() 메소드를 호출

정리

JPA를 사용하기 위하여 개발 환경 설정부터 진행하였다. 지금까지의 장점은
내가 지금 진행하고있는 게시판 api 프로젝트에서 사용한 CRUD가 이 JPA로 인해 엄청 편하다는 사실
덕분에 SQL에 의존적인 개발을 피할 수 있고 좀 더 객체지향 관점에서 코드를 볼 수있게 된 것같다.

다음 3주차에서는 영속성 관리에 대해 알아볼 것이다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 엔티티 매핑  (0) 2022.08.04
[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04
728x90

1주차 학습내용

깃허브 바로가기

1주차 스터디에 대해 간략하게 정리하는 내용이다.

JPA란?

Java Persistence API 의 줄임말로써, 자바 진영의 표준 ORM 기술이다.
JPA는 다른 JDBCTemplate 나 Mybatis 등등 객체와 관계형 데이터베이스의 차이를 메우기 위해 SQL을 작성을 해서 개발을 진행해야했었는데, 반복적인 CRUD 뿐만아니라 객체 모델링과 관계형 데이터베이스의 차이를 해결해주었다.
JPA의 핵심은 바로 SQL이 아닌 객체 중심으로 개발을 진행하기 때문에 생산성과 유지보수에서 용이하다.
이렇게 되면 테스트를 작성하기에도 편리하다.

SQL을 직접 다룰 때 발생하는 문제점

지금까지 실무에서 Mybatis나 iBatis를 사용하였다. 그래서 소스코드를 보거나 내가 구현해야 할 때 상당 시간을 데이터베이스를 매핑시킨 xml에 너무 의존하여 개발했다.

  • 왜? - 오타에 의한 , 띄어쓰기에 의한 하나라도 틀리게 되면 무수한 에러를 발생시킨다.

기존의 DTO파일에 뭔가 한 칼럼을 추가해야된다고 하면 추가할 클래스들과 sql을 수정해서 작성해야 하는데,
이 때 또 다시 아까처럼 오타가 발생하면 안되기에 꼼꼼하게 개발해야 한다.

또 연관관계에 있어서 Member라는 객체가 Team을 갖고 있는다고 가정을 한다면, Team에 대한 SQL문도 직접 매핑을 시킨다음 묶어주어야 했다.

JPA와 문제 해결

  • 저장기능persist()
  • 조회기능 find()
  • 수정 : 메소드는 별도로 없지만 객체를 변경하게 되면 적절한 update sql쿼리가 실행
  • 연관된 객체 조회기능 : 연관된 객체를 사용하는 시점에 적절한 select sql 실행

상속

객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능을 지원하기는 하지만 객체의 상속과 다르다.
JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.

연관관계

객체는 참조를 사용하여 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을
조회한다.

→ 객체를 테이블에 맞춰 모델링 한다 이렇게 객체지향 모델링을 해도 무척 번거롭고 어려움
→ 이런 패러다임 불일치는 객체지향 모델링을 포기하게 만들정도로 극복하기에 어려움

반면 JPA는 이런 문제를 손쉽게 해결해준다.

객체 그래프 탐색

참조를 사용하여 연관된 객체를 탐색하는 것이다.
SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐
반면 JPA는 자유롭게 마음껏 탐색이 가능함. 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행하기 때문에 자유로운 것.

  • 지연로딩 : 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미루는 것
  • JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지 간단한 설정으로 정의할 수 있다.

비교

DB의 경우 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는것은 쉽지 않다.
반면에 JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

정리

객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 다르기 때문에 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다. 그래서 정교한 객체 모델링을 할수록 불일치 문제는 더 커진다. 이러다보니 데이터 중심의 모델로 변하는 과정이 되버린다.
그리고 이것을 해결해주는 것이 바로 JPA다. 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

📕 ORM(Object Relational Mapping)

ORM(Object Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻.
이 프레임워크는 객체와 테이블을 매핑하여 패러다임 불일치 문제를 개발자 대신 해결해준다.

ORM이란 것은 애플리케이션과 데이터베이스 사이에 가상의 데이터베이스(JPA에서 영속성 컨텍스트라고 불리는)라는 인디렉션을 만드는 것이다.

즉, OOP(Object-Oriented Programming)를 하기위한 프로그래밍 기법이다.

원래는 데이터베이스에 종속적이었던 코드를 역으로 데이터베이스를 추상화하여 코드레벨에 맞춰주며

그렇게 함으로써 데이터에 종속적인 프로그래밍이 아닌 OOP를 할 수 있게 된다.

내가 실무를 보며 느끼기에 ORM이란 것은 결국 사전적인 정의대로 OOP를 위한 기술이다.

SQL에 종속적이다, 똑같은 코드가 반복된다 등의 잡다한 이유가 있지만

이러한 문제들의 본질은 OOP를 하지 못하기 때문에 발생하는 것이다.

애플리케이션이 데이터베이스와 연동되면 모든 코드가 자연스럽게 데이터 기반으로 돌아가기 때문에

자연스럽게 OOP 5대 원칙, SOLID도 대부분 위반하게 된다.

약어 개념
SRP 단일 책임 원칙 (Single responsibility principle)
한 클래스는 하나의 책임만 가져야 한다.
OCP 개방-폐쇄 원칙 (Open/closed principle)
“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
LSP 리스코프 치환 원칙 (Liskov substitution principle)
“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라.
ISP 인터페이스 분리 원칙 (Interface segregation principle)
“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”
DIP 의존관계 역전 원칙 (Dependency inversion principle)
프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

간단한 예제를 보자면

Member 객체를 DB에 저장할것이라고 했을 때,

Member 클래스를 Java Beans 규약에 맞추어 작성하고, SQL을 작성한다.

class Member{
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}
INSERT INTO MEMBER
    (NAME, AGE)
VALUES
    (#name, #age);

이제 접근자를 이용해 nameage를 추출하여 SQL에 넣어줘야한다.

class InsertService {
    public void insert(String name, int age);
}

...
service.insert(member.getName, member.getAge);
...

이 데이터를 뽑아내고 입력하는 과정 자체가 데이터에 종속적인 과정이며, 결국 이 과정으로 인해 모든 문제가 발생한다.

만약 이 상황에서 address라는 필드가 하나 더 필요해진다면?

Memberaddress 필드를 추가하고, SQLService 클래스도 변경되어야만 한다.

실무에서는 절대 써먹을수조차 없는 매우 간단한 예제임에도 불구하고 즉시 OCP가 위배된다.

이처럼 코드와 SQL 사이에 매우 강한 결합도(=내용 결합도)가 생기기 때문에 SOLID를 지키기가 정말 어려워진다.

나는 개인적으로 불가능하다고 생각하기도 한다.

그렇다면 ORM을 도입하면 어떻게 변할까?


@Entity
class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}

SQL은 따로 작성하지 않는다.

추상화된 가상의 데이터베이스가 알아서 처리해주기 때문이다.

이 상태에서 address 필드를 추가해야 한다면 그냥 추가하면 끝이다. 더 손댈 게 없다. 즉, OCP를 위반하지 않게된다.

이렇게 간단한 예제만으로도 SOLID를 지키기가 압도적으로 수월해지며, 결과적으로 OOP의 장점을 모두 누릴 수 있게된다.

SQL에 종속적인 문제, 재사용성 증가, 유지보수성 증가, 리팩토링 용이성 등등. 결국 모두 OOP를 하게 됨으로써 얻는 이득이다.

자바진영에 다양한 ORM프레임워크가 존재하는데 그중 Hibernate 프레임워크가 가장 많이 사용된다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04
[JPA] JPA Auditing  (0) 2022.08.03
728x90

JPA 오류 💢

jpa에서 spring.jpa.hibernate.ddl-auto=create 속성으로 테이블을 만들어줄 경우에
MariaDB default로 어떤값을 주던간에 latin1속성으로 insert가 된다. 그래서 이방법 말고 안에서 테스트만 한다고 하면 h2 DB를 사용하는 것이 더 바람직하다.

아니라면 ddl-auto 속성을 주지않고 직접 MariaDB에서 어떤 Column들을 넣을지 결정하고 먼저 생성한 다음 연결하는게 좋을 것이다.
이것 때문에 애를 많이 먹었다.

[mysqld]
datadir=C:/Program Files/MariaDB 10.5/data
port=3306
innodb_buffer_pool_size=1948M
init_connect="SET collation_connection = utf8_general_ci"
init_connect="SET NAMES utf8"
character-set-server = utf8
collation-server = utf8_general_ci
[client]
port=3306
plugin-dir=C:/Program Files/MariaDB 10.5/lib/plugin
default-character-set=utf8
[mysqldump]
default-character-set = utf8
[mysql]
default-character-set = utf8

MariaDB data 경로에 있는 my.ini 파일인데 이렇게 바꿔주면 기본으로 utf8 설정을 할 수 있다.

데이터베이스 character set 확인
show create database 데이타베이스명;

데이터베이스의 character set 변경
alter database 데이타베이스명 default character set = utf8;

테이블 character set 확인
show create table 테이블명;

테이블의 character set 변경
alter table 테이블명 default character set = utf8;

각 컬럼별 character set 확인
show full columns from 테이블명;

각 컬럼별 character set 변경
alter table 테이블명 modify column 컬럼명 varchar(20) character set utf8 collate utf8_general_ci;

ddl-auto로 생성하였다면 이렇게 일일이 바꿔주어야 한다. 꼭 명심하고 체크한 뒤에 test를 진행하자.😨

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] JPA Auditing  (0) 2022.08.03
728x90

JPA Auditing이란?

보통 엔티티에는 해당 데이터의 생성시간과 수정시간을 포함한다.
언제 만들어졌고, 언제 수정되었는지 등등 나중에 유지보수에 중요한 정보이기도 하다. 그래서 매번 DB를 insert, update 할 때 날짜 데이터를 등록/수정하는 코드들이 들어간다.

아래의 내용은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 에서 읽은 내용이다.

//생성일 추가 코드 예제
public void savePosts(){
    ...
    posts.setCreateDate(new LocalDate());
    postsRepository.save(posts);
    ...
}

이런 단순한 코드가 모든 테이블, 서비스 메소드에 포함 된다고 생각하면 귀찮고 코드가 지저분해진다.
그래서 쓰는것이 JPA Auditing이다.

LocalDate 사용

Java8부터는 기존에 쓰던 Date, Calendar 클래스가 아닌 LocalDate와 LocalDateTime을 사용한다.
Date의 문제점을 고친 타입이기 때문에 Java8이상일 경우 무조건 써야 한다고 생각하면 되겠다.
Calendar 클래스는 월 값이 명시하는 static 변수이름에 해당하는 월 -1 이었다.
ex) Calendar.FEBRUARY 2월을 나타내는 이것은 값이 1이다.

BaseTimeEntity.java

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
    //모든 Entity의 상위 클래스가 되어 Entity들의 createDate, modifiedDate를 자동으로 관리하는 역할을 하는 클래스

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

BaseTimeEntity클래스는 모든 Entity의 상위 클래스가 되어 Entity들의 createDate, modifiedDate를 자동으로 관리하는 역할을 한다.

어노테이션 설명
@MappedSuperclass JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들도 칼럼으로 인식하도록 한다
@EntityListeners BaseTimeEntity클래스에 Auditing 기능을 포함시킴
@CreatedDate Entity가 생성되어 저장될때 시간이 자동 저장된다
@LastModifiedDate 조회한Entity가 값을 변경할 때 시간이 자동 저장된다

Posts.java

@Getter
@NoArgsConstructor
@Entity // 실제 DB테이블과 매칭되는 클래스
// 기본값으로 클래스의 camelCase 이름을 스네이크케이스 테이블과 매칭시켜준다.
public class Posts extends BaseTimeEntity {
    @Id // 해당 테이블의 PK임을 명시
    @GeneratedValue(strategy = GenerationType.IDENTITY) //Identity 옵션을 넣어야 auto_increment 설정 가능
    private Long id;

    @Column(length = 500, nullable = false) // 해당클래스는 변수만 선언해도 column이 되지만
    // 추가로 변경이 필요한 옵션이 있을때 사용한다. ex) varchar max_length 기본값은 255인데, 500으로 늘리고 싶을때
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    //여기처럼 타입을 TEXT로 변경하거나, null값을 못넣게 설정할 수 있다.
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author){
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content){
        this.title = title;
        this.content = content;
    }
}

마지막으로 JPA Auditing 어노테이션을 모두 활성화 하기 위해서 Application 클래스에 활성화 어노테이션을 붙여준다.

Application.java

@EnableJpaAuditing //JPA Auditing 활성화
@SpringBootApplication
public class Application {
    public static void main(String[] args){ SpringApplication.run(Application.class, args); }
}

이제 실제 코드는 완성 됐으니까 기능 동작을 위해 테스트를 수행한다.

JPA Auditing 테스트 코드

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {

@Test
    public void BaseTimeEntity_등록(){
        //given
        LocalDateTime now = LocalDateTime.of(2021, 5, 25, 0, 0, 0);
        postsRepository.save(Posts.builder()
                                  .title("title")
                                  .content("content")
                                  .author("author")
                                  .build());
        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);

        System.out.println(">>>>>>> createDate = " + posts.getCreatedDate() + ", modifiedDate = " + posts.getModifiedDate());

        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);
    }
}

이로 인해 앞으로 등록/수정일로 고민할 필요는 없어졌다. 넣고자 하는 엔티티에 BaseTimeEntity만 상속받으면 알아서 해결해준다.

2021-07-16 수정 ✔

하지만 이 형식대로 하면 date format이 동작하지 않는데
@DateTimeFormat, @JsonFormat 들도 Auditing에선 작동하지 않는다.

이럴때 찾은 방법은 아래와 같다.

@MappedSuperclass
@Getter
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    private String createdAt;

    private String modifiedAt;

    @PrePersist //해당 엔티티 저장하기 전
    void onPrePersist(){
        this.createdAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        this.modifiedAt = createdAt;
    }

    @PreUpdate //해당 엔티티 수정 하기 전
    void onPreUpdate(){
        this.modifiedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    }
}

Pre, Post 에 따라 이전 이후 구분 가능

이랬던 format 형식을


이런식으로 변경하여 DB에 저장할 수 있다.

이상으로 JPA Auditing 에 대한 포스팅을 마친다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04

+ Recent posts