Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

@Entity 속성 2 본문

JPA

@Entity 속성 2

Seung__ 2021. 6. 15. 21:18

1. SQL이란? SQL 종류


들어가기에 앖서서 SQL(Structed Query Language, 구조적 질의언어)에 대해서 간략하게 알아보자.

SQL은 관계형 DB의 관리시스템의 DATA를 관리하기 위한 만들어진 특수 목적의 프로그래밍 언어로

DDL, DML등이 존재한다.

 

데이터 정의 언어(DDL ,Data Definition Language) : 데이터베이스 객체들을 생성, 변경, 제거 할 때 사용 (테이블 기준)

 - CREATE : DB, TABLE 생성

 - ALTER : TABLE 수정

 - DROP : DB, TABLE 삭제

 - TRUNCATE : TABLE 초기화

 

DML : Data Manipulation Language --> 데이터를 실직적으로 관리하는데 사용되는 언어 

 - SELECT : 데이터 조회

 - INSERT : 데이터 삽입

 - UPDATE : 데이터 수정

 - DELETE : 데이터 삭제


 

2. @Column


@Column : 필드의 각 컬럼의 속성을 지정하는 annotation

String name() default "" Column의 이름을 지정
필드 name을 그대로 두고 db에서 사용하는 이름을 지정 가능.
boolean unique() default false 해당 Column의 값들은 table 내에서 유일하다.
boolean nullable() default true; 일반적으로 조회 쿼리사용할 때는 사전에 걸러주는 validation역할이 아님
ddl쿼리를 자동으로 생성할 때 not null fixed(?)를 만들어 주는 속성
nullable= true기본속성
nullable=false로 변경 시 not null로 변경되는 걸 확인 가능.
boolean insertable() default true; 해당 column에 data 삽입 가능 여부
boolean updatable() default true; 해당 column에 data update가능 여부
String columnDefinition() default "" column definition을 설정하면 해당 값이  DDL 생성시 함께 반영된다.

 

1) name()

  user class에 대음과 같이 createdAt이라는 변수가 있다. 이 column의 name을 별도로 crtdat이라고 지정해보자.

@Column(name = "crtdat", updatable = false)
    private LocalDateTime createdAt;

실행 결과 table이 생성될 때 해당 column 이름이 ctrdat로 변경된 것을 확인이 가능하다.

Hibernate: 
    
    create table user (
       id bigint not null,
        crtdat timestamp,
        email varchar(255),
        name varchar(255),
        updated_at timestamp,
        primary key (id)
    )

즉 db의 column 이름과 object의 이름을 별도로 맵핑하기 위해서는 @column의 name속성 활용

 

 

2) nullable()

ddl쿼리를 자동으로 생성할 때, 즉 table을 생성할 때 해당 column이 필수 항목인지 아닌지 지정하는 것.

일반적으로 조회 쿼리사용할 때는 사전에 걸러주는 validation역할이 아님

nullable= true 디폴트 값으로,  nullable=false로 변경 시 not null로 변경되는 걸 확인 가능.

    @Column(name = "crtdat", nullable = false)
    private LocalDateTime createdAt;
    
    private LocalDateTime updatedAt;

 

nullable=false 로 변경한 결과 crtdat의 속성이 not null로 변경된 것을 볼 수 있다.

Hibernate: 
    
    create table user (
       id bigint not null,
        crtdat timestamp not null,
        email varchar(255),
        name varchar(255),
        updated_at timestamp,
        primary key (id)
    )

 

3) unique()  : unique contraints지정, 해당 column의 data는 table 내에서 유일하다.

 @Column(unique = true)
    private LocalDateTime createdAt

 

4) insertable(), updatable()

  두 속성은 다른 속성과는 다르게 DDL(DB생성, TABLE 생성)이 아니라, DML(DATA 조회,삭제..) 쿼리에도 영향 끼침

    @Column(updatable = false)
    private LocalDateTime createdAt;

    @Column(insertable = false)
    private LocalDateTime updatedAt;

 

왜냐! 두 속성은 각각 아래와 같은 의미이기 때문!

insertable : 해당 column에 data의 삽입(DML)이 가능한지

updatable : 해당 column의 data가 업데이트(DML)가 가능하지 

 

예시로 false로 지정한 경우. insert에서 updated_at 없어짐. /  update에서 created_at 없어짐

Hibernate: 
    insert 
    into
        user
        (created_at, email, name, id) 
    values
        (?, ?, ?, ?)
.
.
.
Hibernate: 
    update
        user 
    set
        email=?,
        name=?,
        updated_at=? 
    where
        id=?

 

지정하지 않은 경우 insert에서 updated_at와 update에서 created_at를 확인이 가능하다.

Hibernate: 
    insert 
    into
        user
        (created_at, email, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?)
.
.
.
Hibernate: 
    update
        user 
    set
        created_at=?,
        email=?,
        name=?,
        updated_at=? 
    where
        id=?

 

 

 

5) @Transient

@Transient : 영속성 처리에서 제외되어 db data에 반영되지 않고 해당 객체와 생명주기 같이함.

 

Entity는 데이터에 대한 객체로 DB의 레코드값을 그대로 반영한다.

 

하지만 객체로서의 역할도 하기 때문에. DB 레코드와는 다른 data를 가질 경우도 필요하다.

 

예를들어 testData는 DB에 반영하기 않고 객채로서 사용하고 싶은 object data일 수 있음.

 

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity //Entity에는 primary key가 꼭 필요함., JPA가 관리하고 있는 객체
@EqualsAndHashCode
@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User {

    @NonNull
    private String name;

    @NonNull
    private String email;

    @Column(updatable = false)
    private LocalDateTime createdAt;

    @Column
    private LocalDateTime updatedAt;

    @Id
    @GeneratedValue //자동으로 하나씩 증가함.
    private Long id;

    //이 testData는 DB에 반영하기 않고 사용하고 싶은 object data일 수 있음.
    @Transient //영속성 처리에서 제외되어 db data에 반영되지 않고 해당 객체와 생명주기 같이합.
    private String testData;

   // @OneToMany(fetch = FetchType.EAGER)
    //private List<Address> address;
}

@Transient 지정 하지 않은 경우 DDL 쿼리 생성시 포함되어 있음.

Hibernate: 
    
    create table user (
       id bigint not null,
        created_at timestamp,
        email varchar(255),
        name varchar(255),
        test_data varchar(255),
        updated_at timestamp,
        primary key (id)
    )

@Transient 지정한 경우 DDL 쿼리 생성시 포함되지 않음.

Hibernate: 
    
    create table user (
       id bigint not null,
        created_at timestamp,
        email varchar(255),
        name varchar(255),
        updated_at timestamp,
        primary key (id)
    )

업데이트에도 빠져있음.

Hibernate: 
    update
        user 
    set
        email=?,
        name=?,
        updated_at=? 
    where
        id=?

즉 DB 레코드에는 반영하지 않지만, 객체로서  따로 쓸 수 있음.

 

 

6) ColumnDefinition 

   - DDL 실행 시 해당 column에 값 입력 가능

 @CreatedDate
 @Column(columnDefinition = "datetime(6) default now(6)", nullable = false, updatable = false)
 private LocalDateTime createdAt;

  - comment 입력 가능

@CreatedDate
@Column(columnDefinition = "datetime(6) default now(6) comment '생성시간'", nullable = false, updatable = false)
private LocalDateTime createdAt;

@LastModifiedDate
@Column(columnDefinition = "datetime(6) default now(6) comment '수정시간'", nullable = false)
private LocalDateTime updatedAt;
  create table book (
  id bigint not null auto_increment,
  created_at datetime(6) default now(6) comment '생성시간' not null,
  updated_at datetime(6) default now(6) comment '수정시간' not null,

3. @enumerate


이넘!...ㅎ

Enum 이란? 자바에서 사용하는 일종의 상수 객체, ENTITY에서는 별도의 처리방법을 가지고 있음.

 

무슨말인지 모르겠으니 하나 만들어 보자.

public enum Gender {

    MALE,
    FEMALE
}

Gender라는 Enum을 만들었다. MALE, FEMALE을 입력해 주었는데, 각각 일종의 상수의 기능을 가진다.

 

테스트를 해보자!  User class에 Gender 생성

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity //Entity에는 primary key가 꼭 필요함., JPA가 관리하고 있는 객체
@EqualsAndHashCode
@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User {

    @NonNull
    private String name;

    @NonNull
    private String email;

    @Column(updatable = false)
    private LocalDateTime createdAt;

    @Column
    private LocalDateTime updatedAt;

    @Id
    @GeneratedValue //자동으로 하나씩 증가함.
    private Long id;

    //이 testData는 DB에 반영하기 않고 사용하고 싶은 object data일 수 있음.
    @Transient //영속성 처리에서 제외되어 db data에 반영되지 않고 해당 객체와 생명주기 같이합.
    private String testData;

   // @OneToMany(fetch = FetchType.EAGER)
    //private List<Address> address;

    private Gender gender;

}

 test code에서 Repository에서 user를 불러와서 Gender를 업데이트를하고 불어와보자.

 @Test
    void enumTest() {
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user.setGender(Gender.MALE);

        userRepository.save(user);

        userRepository.findAll().forEach(System.out::println);
    }

Gender가 들어갔드아

업데이트시 gender가 들어갔드아.

 

하지만, 여기서 빈번히 발생하는 문제가 있다!

 

먼저 JPA에서 enum을 어떻게 다루는지에 대한 이해가 필요하다.

 

 

 

userRespository에 다음과 같이 추가해주자. nativeQuery는 추후에 자세히 알아 볼 예정.

 

  @Query(value = "select * from user limit 1;", nativeQuery = true)
    Map<String, Object> findRawRecord();

테스트 코드에서 user객체에서 gender에 해당하는 object를 가져와 보자.

  @Test
    void enumTest() {
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user.setGender(Gender.MALE);

        userRepository.save(user);

        userRepository.findAll().forEach(System.out::println);

        System.out.println(userRepository.findRawRecord().get("gender"));
    }
}

띠용 0이 나온다.

Hibernate: 
    select
        * 
    from
        user limit 1;
0

즉, MALE이 상수로서 0에 맵핑이 되어있다는 것을 알 수 있다.

 

좀 더 자세하게 Enumerate의 EnumType을 보자

public enum EnumType {
    /** Persist enumerated type property or field as an integer. */
    ORDINAL,

    /** Persist enumerated type property or field as a string. */
    STRING
}

ORDINARY와 STRING 두가지 옵션이 있다. 

  - ORDINARY : 정수 순서대로 

  - STRING : 문자열로 

 

즉, 우리가 아까 gender 라는 enum을 만들 었을 때 male, female순서대로 입력했는데

입력한 순서대로 0, 1로 매칭이 된 것이다.

public enum Gender {

    MALE,   //0
    FEMALE  //1
}

 

문제는 여기서 생긴다. 추후에 코드를 수정하면서 새로운 값(X)이 맨 앞에 추가되었다고 하자.

만약 내가 이전 코드에서는 male이 0에 해당되었는데, 코드 수정후에는 male이 1에 해당되고 새로 추가된 값이 0이다.

public enum Gender {
    X,      //0
    MALE,   //0 -> 1
    FEMALE  //1 -> 2
}

 

따라서 위에서 사용한 테스트 코드를 다시 돌리면 아까 전에 반환된 0이 아니라 1이 반환될 것이다.

  @Test
    void enumTest() {
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user.setGender(Gender.MALE);

        userRepository.save(user);

        userRepository.findAll().forEach(System.out::println);

        System.out.println(userRepository.findRawRecord().get("gender"));
    }
}

 

이러한 문제를 방지하기 위해서는 enum사용시 옵션을 항상 String으로 고정하자.

 @Enumerated(value = EnumType.STRING)
    private Gender gender;

문자열을 기반으로 하기 때문에 위에서 말한 data추가로 인한 data가 꼬이는 일을 방지할 수 있따.

 

'JPA' 카테고리의 다른 글

Entity Listener : 2  (0) 2021.06.16
Entity Listener - 1  (0) 2021.06.15
@Entity 기본 속성 - 1  (0) 2021.06.15
Query Method : paging  (0) 2021.05.27
Query Method 3 : 정렬  (0) 2021.05.27
Comments