반응형

원인

Member와 Message가 연관관계를 맺고 있고 1:N 관계를 가지고 있다.
Message 엔티티에서 Member Fetch 전략을 Lazy로 설정해준 상태이다.

오류 예시

// 메시지 제목, 내용, 작성자 이름, 받는 사람 이름 등의 정보가 들어있는 MessageResponseDTO로 변환하여 리소스를 반환하는 API

@GetMapping("/message/{message_id}")
public Result readMessage(@PathVariable Long message_id){
    Message msg = messageService.findB yId(message_id);
    MessageResponseDto messageResponseDto = MessageResponseDto.covertMessageDto(msg);
    return responseService.getSingleResult(messageResponseDto);
}

오류발생

  • Message를 단건조회하면 Message와 Lazy Loding으로 연관된 Member는 바로 초기화 되지 않고 필요할 때 정보가 채워지는 프록시 객체로 채워진다.

Message = Message 필드 + Member Proxy 객체

  • Member의 값을 써서 DTO를 채워야하는데 Member의 값이 초기화 되지 않은 상태라서 DTO를 만들 수 없음.

Lazy Loding방식이니 변환하면서 데이터를 사용할 때 쿼리를 날려 Proxy 객체를 채우지 못함.
-> Service에서 트랜잭션이 일어나도록 설정을 하였음. JPA 영속성 컨텍스트는 보통 트랜잭션과 생명주기를 같이한다. 그 말은 Service -> Controller로 나오면서 영속성 상태가 끝난다는 뜻이다. 더이상 영속성 컨텍스트에서 관리하지 않고 Member에 필요한 값이 있을때 쿼리를 날려 Proxy 객체를 채우지 않는다는 뜻이다.

해결방법

  1. Message -> DTO 변환을 컨트롤러 단에서 서비스 단으로 변경
@GetMapping("/message/{message_id}")
public Result readMessage(@PathVariable Long message_id){
    MessageResponseDto messageResponseDto = messageService.findByMessageId(message_id);
    return responseService.getSingleResult(messageResponseDto);
}
  1. Message에 있는 Member를 즉시로딩(Eager)로 변경한다.
  • 추천하지 않음.
반응형

'개발 > JPA' 카테고리의 다른 글

QueryDsl이란? 왜 사용하는가?  (0) 2023.12.15
JPA-OSIV(Open Session In View)  (0) 2023.10.14
[JPA] GeneratedValue  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10

주의점

Hibernate 에서는 여러개의 데이터를 한번에 Insert, Update 하게 해주는 기능인 Batch 기능을 지원하고 있다.

jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        generate_statistics: true
        dialect: org.hibernate.dialect.H2Dialect
        show_sql: true
        format_sql: true
        order_inserts: true
        order_updates: true
        jdbc:
          batch_size: 1000

Spring Data JPA의 saveAll 함수를 사용하면 여러개의 Insert 또는 Update 할 수 있다.

하지만, @GeneratedValue 키 값 생성 전략을 Identity나 Auto로 정하는 경우 Hibernate에서 Batch Insert 기능을 비활성화 시켜놓고 Insert 작업을 수행하게 된다.

따라서 saveAll 같은 함수를 사용해도 데이터 개수만큼 Insert 또는 Update 쿼리가 나간다.

반응형

'개발 > JPA' 카테고리의 다른 글

JPA-OSIV(Open Session In View)  (0) 2023.10.14
[JPA] could not initialize proxy - no Session  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
반응형

JPA Auditing이란?

Java에서 ORM 기술인 JPA를 사용하여 도메인을 관계형 데이터베이스 테이블에 매핑할 때 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재합니다. 대표적으로 생성일자, 수정일자, 식별자 같은 필드 및 컬럼이 있습니다.
도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복된다는 말과 일맥상통합니다.
데이터베이스에서 누가, 언제하였는지 기록을 잘 남겨놓아야 합니다. 그렇기 때문에 생성일, 수정일 컬럼은 대단히 중요한 데이터입니다.
그래서 JPA에서는 Audit 이라는 기능을 제공하고 있습니다. Audit은 감시하다, 감사하다라는 뜻으로 Spring Data JPA에서 시간에 대해서 자동으로 값을 넣어주는 기능입니다. 도메인을 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, audit을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어주게 됩니다.

Auditing 활성화 하기

  • 가장 먼저 SpringBootApplication에 @EnableJpaAuditing 어노테이션을 추가해줍니다.
@EnableJpaAuditing
@SpringBootApplication
public class TestApplication{
    public static void main(String[] argAS){
        SpringApplication.run(TestApplication.class, args);
    }
}

BaseEntity 생성하기

  • Auditing이 필요한 Entity에서 상속받을 BaseEntity를 생성합니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity{
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime updatedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;
}

@MappedSuperclass (javax.persistence)

  • Entity에서 Table에 대한 공통 매핑 정보가 필요할 때 부모 클래스에 정의하고 상속받아 해당 필드를 사용하여 중복을 제거

@EntityListenrs (javax.persistence)

  • Entity를 DB에 적용하기 이전, 이후에 커스텀 콜백을 요청할 수 있는 어노테이션

Class AuditingEntityListner (org.springframework.data.jpa)

  • Entity 영속성 및 업데이트에 대한 Auditing 정보를 캡처하는 JPA Entity Listener

@CreatedDate (org.springframework.data)

  • 데이터 생성 날짜 자동 저장 어노테이션

@LastModifiedDate (org.springframework.data)

  • 데이터 수정 날짜 자동 저장 어노테이션

@CreatedBy (org.springframework.data)

  • 데이터 생성자 자동 저장 어노테이션

@LastModifiedBy (org.springframework.data)

  • 데이터 수정자 자동 저장 어노테이션

Entity에 적용하기

@Getter
@Entity
@NoArgsConstructor(access = PROTECTED)
public class class Users extends BaseEntity{
    @Id
    @GeneratedValue
    @Column(name = "user_id")
    private Long id;

    private String name;
}

@CreatedBy, @ModifiedBy 사용하기

org.springframework.data.domain.AuditorAware를 스프링 빈으로 등록해야 합니다.

public interface AuditorAware<T> {
    /**
    * Returns the current auditor of the application.
    *
    * @return the current auditor.
    */
    Optional<T> getCurrentAuditor();
}

AuditorAware 인터페이스는 Optional를 반환하는 method가 하나 있기 때문에 아래 코드처럼 람다로 AuditorAware를 구현한 객체를 반환할 수 있습니다.

@Bean
public AuditorAware<String> auditorProvider(){
 // 람다를 이용
  return () -> Optional.of(UUID.randomUUID().toString());

  // 익명 클래스를 이용
  return new AuditorAware<String>(){
      @Override
      public Optional<String> getCurrentAuditor(){
          return Optional.of(UUID.randomUUID().toString());
      }
  }
}
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] could not initialize proxy - no Session  (0) 2023.09.10
[JPA] GeneratedValue  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
반응형

@MappedSuperclass

  • 객체의 입장에서 공통 매핑 정보가 필요할 때 사용한다.
  • id, name, created_at, updated_at은 객체의 입장에서 볼 때 계속 나온다.
  • 이렇게 공통 매핑 정보가 필요할 때 부모 클래스에 선언하고 속성만 받아서 사용하고 싶을 때 @MappedSuperclass를 사용한다.테이블 구조
-- Member 테이블
create table member(
    id bigint,
    created_at date,
    updated_at date
);
-- Team 테이블
create table team(
    team_name varchar(128),
    created_at date,
    updated_at date
);

코드로 이해하기

  • 생성시간, 수정시간을 모든 엔티티에 공통으로 가져가야 하는 상황
  • BaseEntity.java
    • 매핑정보만 상속받는 Superclass라는 의미의 @MappedSuperclass 어노테이션 선언
@Getter
@Setter
@MappedSuperclass
public abstact class BaseEntity{
    private LocalDate createdAt;
    private LocalDate update_at;
}
@Entity
public class Member extends BaseEntity{
    @Id
    private Long id;
}
@Entity
public class Team extends BaseEntity{
    @Id
    private String teamName;
}

정리

  • 상속관계 매핑이 아니다.
  • @MappedSuperclass가 선언되어 있는 클래스는 엔티티가 아니다. 당연히 테이블과 매핑도 안된다.
  • 단순히 부모 클래스를 상속받는 자식 클래스에 매핑 정보만 제공한다.
  • 부모 타입으로 조회, 검색이 불가능하다.
  • 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만드는 것을 권장한다.
  • 주로 등록일, 수정일 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
  • JPA에서 @Entity 클래스는 @Entity나 @MappedSuperclass로 지정한 클래스만 상속할 수 있다.
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] GeneratedValue  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
반응형

@Query

  • JPA에 정의된 키워드를 조합하면 특정조건에 해당하는 데이터를 원하는 형태대로 가지고 올 수 있습니다. 하지만 데이터베이스에 종속적인 문법을 사용해야 할 때나 Entity 간의 명시적으로 들어나지 않는 관계간의 조인, 데이타 조회 속도 향상등의 목적으로 직접 쿼리를 작성할 수 있는 방법을 제공하고 있습니다.
  • @Query 속성중에 nativeQuery 속성을 true로 설정하지 않았따면 기본적으로 JPQL 문법으로 동작이 됩니다.
  • JPQL 문법은 JPA 에서 사용되는 언어이며 쿼리 구문과 유사하나 Table이 아닌 Entity를 기준으로 데이터를 조회한다는 것이 다릅니다.

NativeQuery란?

  • JPA는 SQL이 지원하는 대부분의 문법과 SQL 함수들을 지원하지만 특정 데이터베이스에 종속적인 기능은 잘 지원하지 않는다. 하지만 때로는 특정 데이터베이스에 종속적인 기능이 필요할 수도 있다. 다양한 이유로 JPQL을 사용할 수 없을 때, JPA는 SQL을 직접 사용할 수 있는 기능을 제공하는데 이것을 네이티브 SQL(네이티브쿼리)라 한다.
  • 즉, 사용자가 직접 데이터베이스에 날리는 쿼리를 작성하는 것이다.
  • NativeQuery는 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
[JPA] @OneToMany 단방향 매핑의 단점  (0) 2023.09.10
반응형

Cascade란?

  • cascade 옵션이란 @OneToMany나 @ManyToOne에 옵션으로 줄 수 있는 값이다.
  • Entity의 상태 변화를 전파시키는 옵션이다.
  • 만약 Entity의 상태 변화가 있으면 연관되어 있는(ex. @OneToMany, @ManyToOne) Entity에도 상태 변화를 전이시키는 옵션이다.
  • 기본적으로는 아무 것도 전이시키지 않는다.

Entity의 상태

  1. Transient
  • 객체를 생성하고 값을 주어도 JPA나 hibernate가 그 객체에 관해 아무것도 모르는 상태. 즉, 데이터베이스와 매핑된 것이 아무것도 없다.
  1. Persistent
  • 저장을 하고나서 JPA가 아는 상태(관리하는 상태)가 된다. 그러나 .save()를 했다고 해서 바로 DB에 데이터가 들어가는 것이 아니다. JPA가 persistent 상태로 관리하고 있다가 후에 데이터를 저장한다.(1차 캐시, Dirty Checking(변경사항 감지), Write Behind(최대한 늦게, 필요한 시점에 DB에 적용)등의 기능을 제공한다)
  1. Detached
  • JPA가 더이상 관리하지 않는 상태. JPA가 제공해주는 기능들을 사용하고 싶다면 다시 persistent 상태로 돌아가야한다.
  1. Removed
  • JPA가 관리하는 상태이긴 하지만 실제 commit이 일어날 때 삭제가 일어난다.

사용 예제

ex) Post(1) : Comment(N) 관계

게시글이 존재하고 해당 게시물에 달린 댓글들은 만약 이 게시글이 저장되거나 삭제되면 같이 저장되거나 삭제되어야 한다고 가정한다.

-> 이 상태로 실행하면 post만 저장해주었기 때문에 post 테이블에만 데이터가 저장된다.

여기서 Post 객체에 cascade 옵션을 주면 comment도 같이 저장이된다.

-> Post라는 인스턴스가 Transient에서 Persistent 상태로 넘어갈 때 child 객체(Comment)도 같이 Persisten 상태가 되면서 같이 저장이 되는 것이다.

cascade remove옵션도 주고나서 삭제하면 게시글을 지우면 댓글들도 같이 삭제된다.

일반적으로는 CascadeType.ALL 옵션을 줘서 사용한다.

반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
[JPA] @OneToMany 단방향 매핑의 단점  (0) 2023.09.10
JPA 무한 재귀 해결방법  (0) 2023.09.10
반응형

orphanRemoval

  • JPA 2.0 이상에서 지원하는 것으로 ORM 스팩, JPA 레벨에서의 정의입니다.
  • orphanRemoval은 @OneToMayn 연관에서 부모 엔티티의 컬렉션 등에서 자식 엔티티가 삭제될 때 참조가 끊어지므로 DB 레벨에서도 삭제되고 @OneToOne 연관에서 엔티티가 삭제될 때 연관된 엔티티 참조가 끊어지므로 DB에서 삭제된다. 즉 참조, 연결이 끊어진(Disconnected된) 엔티티를 같이 삭제하라는 의미로 Owner 객체와 참조가 끊어진 객체들을 정리할 때 유용하다.

ex)

@Entity
class Team{
    @OneToMany(orphanRemoval=true)
    private List<Member> members;
}
반응형

'개발 > JPA' 카테고리의 다른 글

[Spring] NativeQuery  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany 단방향 매핑의 단점  (0) 2023.09.10
JPA 무한 재귀 해결방법  (0) 2023.09.10
JPA 주요속성(@JoinColumn, @MaynToOne, @OneToMany)  (0) 2023.09.10
반응형

단점

  1. 엔티티가 관리하는 외래 키가 다른 테이블에 있음 -> 작업한 Entity가 아닌 다른 Entity에서 쿼리문이 나가는 경우가 있어 헷갈림
  2. 불필요한 쿼리문이 발생(update 등..)
  3. join table 문제

객체 저장시 update 쿼리문 추가 발생

  • Team(1) : Member(N) 구조를 가지는 테이블로 비교하겠습니다.
  • Team Entity에서 @OneToMany로 단방향을 가지는 구조입니다.

ex) Entity

ex) 2개의 member를 저장하는 코드

-> 실행 결과

member에 팀을 세팅해주는 2번의 update 쿼리가 추가로 발생함
만약 @ManyToOnE의 관계였다면 member 객체가 team을 가지고 있는 구조였다면 발생하지 않습니다.(외래키를 member가 직접 관리하기 때문에)

ex) team에 속한 member 삭제

members에서 member를 제거 했으니 해당 member에 team_id를 null로 만드는 update쿼리가 날라가는게 당연하고 memberRepository로 member를 제거했으니 delete 쿼리가 날라가는게 당연합니다.

@OneToMany 양방향

-> 위의 내용 수행 결과

ex) Team의 @OneToMany에 orphanRemoval과 cascade설정을 다음과 같이 해줍니다.

members에서 member를 삭제해주기만해도 memberRepository와 연동되어 자동으로 member를 삭제해줍니다.

반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
JPA 무한 재귀 해결방법  (0) 2023.09.10
JPA 주요속성(@JoinColumn, @MaynToOne, @OneToMany)  (0) 2023.09.10
JPA 지연로딩(LAZY) 즉시로딩(EAGER)  (0) 2023.09.10
반응형

@JsonManagedReference와 @JsonBackReference 어노테이션 사용

  • Jackson 2.0 버전 이전에 순환 참조를 해결하기 위해 사용했던 어노테이션입니다.
  • @JsonManagedReference
    • 양방향 관계에서 정방향 참조할 변수에 어노테이션을 추가하면 직렬화에 포함된다.
  • @JsonBackReference
    • 양방향 관계에서 역방향 참조로 어노테이션을 추가하면 직렬화에서 제외된다.

Customer 객체에서 Order 객체에 @JsonManagedReference를 추가하고 Order에서는 Customer 객체에 @JsonBackReference 어노테이션을 추가하여 직렬화에서 Customer 객체를 제외 시켰습니다.

@Setter
@Getter
@ToString
public class Customer {
    private int id;
    private String name;
    @JsonManagedReference //serialized될 때 포함됨
    private Order order;
}

@Setter
@Getter
@ToString(exclude = "customer”) //toString() 실행시에도 무한 재귀가 발생하여 제외시킨다
public class Order {
    private int orderId;
    private List<Integer> itemIds;
    @JsonBackReference //serialization에서 제외된다
    private Customer customer;
}

@Test
public void infinite_recursion_해결책_JsonManagedReference_JsonBackReference() throws JsonProcessingException {
    Order order = new Order();
    order.setOrderId(1);
    order.setItemIds(List.of(10, 30));

    Customer customer = new Customer();
    customer.setId(2);
    customer.setName("Frank");
    customer.setOrder(order);
    order.setCustomer(customer);

    log.info("customer(toString) : {}", customer);
    log.info("customer(serialized json) : {}", objectMapper.writeValueAsString(customer));
    log.info("order(serialized json) : {}", objectMapper.writeValueAsString(order)); //customer정보는 제외된다
}

실행 화면

@JsonBackReference 어노테이션 선언으로 Order 객체에서 Customer 객체에 대한 정보는 빠지게 됩니다.

@JsonIdentityInfo - 추천 방식

  • Jackson 2.0 이후부터 새롭게 추가된 어노테이션입니다. @JsonIdentityInfo 어노테이션을 추가해서 직렬화에 포함 시킬 속성 값을 'property' 속성에 지정합니다.
  • @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
    • generator = ObjectIdGenerators.PropertyGenerator.class 클래스는 순환 참조시 사용할 Id를 생성하는데 사용되는 클래스이다.
  • @JsonIdentityInfo(property="id")
    • property 속성은 해당 클래스의 속성 이름을 지정한다.
    • 예제에서는 id는 Customer#id를 가리키고 직렬화/역직렬화 할 때 Order#customer의 역참조로 사용된다.

@JsonIgnore

제일 간단하게 해결할 수 있는 방법은 직렬화 할 때 순환 참조 되는 속성에 @JsonIgnore 어노테이션을 추가하여 직렬화에서 제외시키는 방법입니다.

@JsonIgnore
private Customer customer;

해당 customer 필드는 직렬화에서 제외되어 json 형태에 표시되지 않습니다.

반응형
반응형

@JoinColumn

  • 외래 키를 매핑할 때 사용한다.
속성 설명 기본 값
name 해당 Entity에서 매핑할 외래 키 이름 필드명 +_+ 참조하는 테이블의 기본 키 컬럼명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본 키 컬럼명
foreignKey(DDL) 외래 키 제약조건을 직접 지정할 수 있다. 이 속성은 테이블을 생성할 때만 사용한다.  
unique, nullable, insertable, updatable, columnDefinition, table @Column의 속성과 같다.  

@ManyToOne

  • 다대일 관계 매핑
속성 설명 기본 값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 패치 전략을 설정한다. @ManyToOne=FetchType.EAGER, @OneToMany=FetchType.LAZY
cascade 속성 전이 기능을 사용한다.  
targetEntity 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.  

@OneToMany

  • 다대일 관계 매핑
속성 설명 기본 값
mappedBy 연관관계의 주인 필드를 선택한다.  
fetch 글로벌 패치 전략을 설정한다. @ManyToOne=FetchType.EAGER, @OneToMany=FetchType.LAZY
cascade 속성 전이 기능을 사용한다.  
targetEntity 연관된 엔티티의 타입 정보를 설정한다. 이 기능은 거의 사용하지 않는다. 컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.  
반응형

+ Recent posts