P4-1] Ch 04&05. Entity 기본, Listener 활용

uuuu.jini·2022년 1월 25일
0
post-thumbnail

목차

  1. entity의 기본 속성
  2. Listener의 활용

github 링크

1. Entity 의 기본 속성

도메인 객체인 entity를 어노테이션을 사용한 설정과 속성을 배워보았다.

@Entity

해당 객체가 jpa에서 관리하고 있는 entity 객체임을 정의한다. entity 객체에는 primary key 가 반드시 존재한다. 이는 @Id로 지정한다. 이 두가지의 어노테이션 지정으로 entity의 간단한 설정은 완료된다.

@GeneratedValue

해당 속성의 값을 개발자가 설정하는 것이 아닌 자동으로 원한는 값을 지정하는 기능을 제공한다.

  • IDENTITY : mysql 디비에서 많이 사용하는 방법이다. 실제로 사용시 transaction이 종료되기 전에 insert문이 동작을 해서 id값을 가져오게 된다. ( 각 테이블 별로 독립적으로 생성된다. )
  • SEQUENCE : h2 db 에서 기본적으로 사용한다. call next value for hibernate_sequence
  • TABLE : db 종류에 상관없이 별도의 table을 만들어두고 id값을 추출하여 사용할 수 있는 기능을 제공한다.
  • AUTO : 기본값으로 각 db에 적합한 값을 자동으로 넘겨주게 된다. db의 의존성없이 사용할 수 있다는 장점이 있다. 기본값이므로 생략이 가능하다. 일반적으로 db는 고정하여 사용하므로 구체적인 값을 지정해주는 경우도 많다.

@Table

table의 이름, 카탈로그, 스키마등의 지정이 가능하다. 일반적으로는 객체클래스의 이름으로 table의 이름이 지정이 된다.

  • name 속성 : 이름을 지정한다. [ 보통 클래스의 이름과 동일하게 table이름을 지정해주는 것이 좋다 ]
  • uniqueConstraints 속성 : 제약사항
  • index 속성 : index 사항 --> 실제 db에 적용되지 않으므로 entity 에 표기하지 않는 경우가 더 많다.

@Column

필드 scope의 어노테이션이다.

  • name 속성 : 해당 필드의 이름을 지정한다. (없을 경우 해당 변수명으로 필드명이 지정된다.) db상의 필드명으로 지정하여 해당 변수명과 매치 시켜주는 기능을 제공한다.
  • nullable 속성 : 일반적인 조회 쿼리를 사용시 validation 역할을 하지 않고 생성시 null값 여부를 지정해주는 속성이다. false/true로 지정한다. (null 허용/불허용은 중요하다)
    다른 여러 속성들은 DDL 생성시 어떻게 생성을 해줄지에 대한 설정을 해주는 기능을 한다.
  • insertable()/updateable() : dml시 조작이 가능한 속성으로서 false일경우 insertable은 insert시 해당 필드가 변경되지 않게 하며, updateable이 fasle일 경우 update 시 해당 필드가 변경되지 않게 한다.

@Transient

DB 레코드에 적용하지 않고 객체에서 따로 사용하고 싶은 필드인 경우에 사용하느 어노테이션으로 해당 어노테이션이 붙어있는 필드는 영속성에서 제외되기 때문에 DB에 적용되지 않고 해당 객체와 생명주기를 같이 하는 값이 된다.

@Enumerated

enum 이라는 자바에서 사용하는 일종의 상수 객체의 처리 방법을 위한 어노테이션으로 기본 EnumType 은 ordinal으로 순서에 따라 0-index 의 값이 DB에 저장이 된다. 이럴 경우 예상치 못한 값이 저장되거나 리팩토리 과정에서 순서가 바뀌게 되면 값이 틀어져 문제가 발생할 수 있다.
@Enumerated(value = EnumType.STRING) 을 통하여 실제 DB에도 해당 String값이 저장되도록 지정해주어야 오류를 예방할 수 있다.


2. Listener 의 활용

event를 관찰하다가 발생시 특정 동작을 진행하는 것이다. entity 리스너이기 때문에 엔티티가 동작한느 몇가지 방법에 대한 이벤트를 리슨 하고 있다. 엔티티 자체에서 동작을 정의하는 것이 가장 간단하다.

Jpa에서 제공하는 이벤트 어노테이션

  • @PrePersist : insert 이전에 호출
  • @PreUpdate : update 이전에 호출
  • @PreRemove : delete 이전에 호출
  • @PostPersist : insert 이후에 호출
  • @PostUpdate : update 이후에 호출
  • @PostRemove : delete 이후에 호출
  • @PostLoad : select 이후에 호출

현업에서는 주로 @PrePersist@PreUpdate를 많이 사용한다. 예시로 createAt과 updateAt이라는 속성을 대부분의 entity가 가지고 있으며, 이 속성은 직접 개발자가 insert 나 update시 마다 작성을 해줄 경우 실수등으로 인한 오류가 발생할 수 있는 요소가 된다. 그러므로 entity의 prePersist와 preUpdate 어노테이션을 사용하여 insert나 update가 실행되기 이전에 자동으로 해당 값을 set하는 방식을 사용한다.

@PrePersist
public void prePersist(){
	this.createAt = LocalDatetime.now();
    this.updateAt = LocalDatetime.now();
 } 

위와 같이 작성하면, insert문이 실행되기 전에 해당 entity의 createAt과 updateAt 속성을 원하는 값으로 자동으로 설정해 줄 수 있다. (@PreUpdate도 동일한 경우 사용한다.)

하지만 만약 여러 entity 가 존재하고 모든 entity에서 해당 메소드를 지정하여 작성해주게 되면 동일한 코드가 여러번 반복되게 된다. 개발자에게 동일 코드 반복이란 있으면 안되는 일 ~!

이런 경우를 위하여 listener를 따로 만들어두고 여러 entity 에서 주입받아 사용한다. entity에서는 해당 listener클래스와 연결을 맺기 위하여 @EntityListeners(value=해당listener클래스이름.class) 어노테이션을 붙여주어야 한다. 여러 entity에서 동일한 listener를 참조하는 경우애 동일한 어노테이션을 붙여주기만 하면 된다. (easy~)

그러면, 해당 listener클래스는 변경하려는(수정하려는) 속성(필드)가 무엇인지 알고 있어야한다. 그렇기 때문에 interface를 생성하여 사용한다. 먼저 createAt과 updateAt의 속성의 getter와 setter를 interface로 구현한뒤 각 entity가 해당 interface를 구현한다. ( lombok을 사용하였기 때문에 interface의 메소드들인 getter와 setter는 쉽게 구현(상속?)이 되어있다. )

listener 클래스 내부에서 우리가 반복적으로 실행할 메소드를 작성하여 주면 된다. 이때 각 메소드는 어떤 entity 타입의 필드를 수정하는 것인지 알지 못하므로 Object형을 parameter로 받아야한다.

    @PrePersist
    public void prePersist(Object o){ 
        if(o instanceof Auditable){
            ((Auditable) o).setCreateAt(LocalDateTime.now());
            ((Auditable) o).setUpdateAt(LocalDateTime.now());
        }
    }

어떤 entity타입이 들어올지 모르므로 object형으로 인자를 받고 해당 인자가 지정한 interface를 구현하고 있는지 확인한 후 설정해준다.
(interface가 필요한 이유는 그 객체가 인터페이스를 구현하는 경우에는 메소드를 가져다 써야하기 때문? 해당 객체가 어쨋든 그 인터페이스를 구현하고 있는건 확실한거기 때문에 ! 해당 메소드를 가져다 쓸수 있는거지 , 구현하는지 꼭 확인 필요하다. )

이렇게 하면 어떤 entity에서든 해당 listener를 사용할 수 있으며, 코드의 반복성이 줄어들게 된다.

리스너의 활용법 2

특정값을 추가하는 케이스도 있지만, history데이터일 경우 db에 특정데이터가 수정이되면 해당값의 복사본을 다른 테이블에 저장해두는 경우가 있다. User엔티티는 중요한 엔티티이기 때문에 수정 관련 history가 필요할 것이다.

먼저 UserHistory 클래스를 만들고 entity로 설정하였다.(@Entity,롬복설정,pk 지정 등 , histroy클래스는 history를 남기기 위해 user entity와 동일한 필드를 가져야 한다. )

UserHistory entity의 조작을 위해 UserHistroyRepository(interface)를 만들고 JpaRepository를 상속받는다.

User table로 user entity가 insert되거나 update되는 이벤트가 발생할 경우 UserHistory DB에 저장해주기 위하여 entity listener를 만들어준다. 해당 listener에서는 update나 insert 실행 전에 호출되어 userHistory db에 해당 entity를 저장해주어야 하기 위해 조작을 하는 UserHistoryRepository객체의 주입이 필요하며, User class에 @EntityListeners(value={리스너이름,리스너이름..}) 으로 표시해주어야 한다.

Entity listener에서는 스프링 빈의 주입이 수행되지 못한다. 그래서 강사님의 설명에 따라 빈을 주입하도록 해주는 다른 클래스를 제작한후 해당 클래스의 메소드를 사용하여 빈을 주입한 후 사용하여 history DB 에 저장해주었다.

    @PrePersist
    @PreUpdate 
    public void prePersistandUpdate(Object o){ 
    
        UserHistroyRepository userHistroyRepository = BeanUtils.getBean(UserHistroyRepository.class); 
        
        User user  = (User)o; 
        
        UserHistory userHistory = new UserHistory();
        userHistory.setUserId(user.getId());
        userHistory.setName(user.getName());
        userHistory.setEmail(user.getEmail());

        userHistroyRepository.save(userHistory);
    }
  1. entity 만들기 : @Entity, 롬복, @Id 등
  2. repository 만들기 : interface, jpaRepo 상속
  3. listener 만들기 : 스프링 빈 주입 안됨 !

CreateAt 과 UpdateAt 필드 [Auditing 감시]

자주 사용하는 필드로서 스프링에서 기본 리스너를 제공해준다.
기본 application 클래스에 @EnableAuditing 추가 해준다.
해당 entity에 @EntityListeners의 valud값으로 AuditingEntityListener.class를 작성하여 준다. @EntityListeners(value=AuditingEntityListener.class)

이후 auditing으로 지정해야할 CreateAt 필드에 @CreatedDate 어노테이션을 UpdateAt 필드에 @LastModifiedDate를 달아준다.

위에서 listener를 만들어서 사용하였지만 그렇지 않고 스프링에서 제공하는 어노테이션을 사용하여 동일한 기능을 수행할 수 있다. 추가로 @LastModifiedBy 나 @CreatedBy 어노테이션도 존재한다.

중복 작성 방지

위의 방법을 통할 경우 각 entity의 createAt과 updateAt필드에 대해 일일히 설정해주어야 한다. 이는 번거롭기 때문에 한 클래스에 중복되는 코드를 모아서 상속받아서 사용한다.

먼저 각 entity에서 상속받을 BaseEntity클래스를 생성하고 createAt과 updateAt 의 Auditing감시를 위해 @EntityListeners(value = AuditingEntityListener.class) 를 달아준다. 해당 baseEntity에는 중복되는 필드인 createAt과 updateAt필드를 가지며 setter와 getter를 위한 @Data 롬복을 달아준다. 두 필드에는 위에서의 설명과 같이 @CreatedDate 와 @LastModifiedDate를 달아준다. 가장 중요한 어노테이션인 @MappedSuperClass 를 클래스에 달아주어야 하며, 이는
해당 클래스의 필드를 상속받는 entity의 필드로 포함시켜주겠다는 의미이며, 즉, 이클래스를 entity들이 상속받은경우 해당 baseEntity의 필드를 entity가 포함하게 된다.

@Data
@MappedSuperclass 
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
    @CreatedDate
    private LocalDateTime createAt;
    @LastModifiedDate
    private LocalDateTime updateAt;

}

위와 같이 작성한 후 각 엔티티에서 이 클래스를 상속받기만 하면 모든 설정이 완료되는 것으로 가장 간단하고 쉬운 방법이다.

이때 상속받은 필드가 포함되어 롬복으로 처리되기 위해서는 밑의 어노테이션을 엔티티 클래스에 달아주어야 한다.

@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)

일반적으로 기본 entity를 만들어서 연결한후 상속받는 방식으로 개발을 진행한다.

profile
멋쟁이 토마토

0개의 댓글