반응형

원인

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

+ Recent posts