테이블을 설계하다 보면 자기 자신을 참조하는 경우가 있다. 이를 JPA로 나타내자면 아래와 같다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Category {

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

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;

    @Builder.Default
    @OneToMany(mappedBy = "parent")
    private Set<Category> children = new HashSet<>();
}

 

 

이와 다른 형태로는 아래와 같은데 이와 다르게 자식 테이블이 별도로 생성되어 이 경우 불필요한 join query가 불필요하게 발생되어 아래와 같은 방식 보단 첫번째 방식으로 계층형 테이블을 설계해야 한다

 

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Category {

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

    private String name;

    @OneToMany
    private Set<Category> children = new HashSet<>();
}

 

반응형

SQL과 객체지향언어가 갖는 큰 차이점 중 하나가 바로 상속의 유무이다.

객체 지향언어는 상속을 지원하지만, 데이터베이스는 상속을 지원하지 않는다. 

 

스프링에서는 JPA를 이용하여 ORM 기술을 사용하는데, 클래스 간 상속 관계에 대해서는 어떻게 정의를 하는지 알아보자.

 

물리 모델을 데이터 베이스 테이블로 구현하는 방법에는 크게 4가지 방법이 존재한다.

 

- MappedSupperclass

- Single Table 전략

- Joined Table 전략

- Table per Class 전략

 

MappedSuperclass 

 

MappedSuperclass 어노테이션은 부모클래스를 데이터베이스의 테이블로 매핑하지 않고 자식클래스에게 매핑 정보를 제공하고 싶을 때만 사용한다.

 

특징

- MappedSuperclass 어노테이션을 사용한 클래스는 엔티티가 아니다. 따라서 해당 클래스를 가지고 영속성 컨텍스트 또는 JPQL 등에서 사용할 수 없다.

- MappedSuperclass 어노테이션을 사용한 클래스는 자체적으로 의미가 없기에 추상 클래스로 만드는 것이 좋다.

 

Single Table 전략

 

Single Table 전략은 각 클래스 계층에 대해 하나의 테이블을 만들며 명시적으로 지정하지 않는 경우 JPA에서는 Default로 해당 전략을 사용한다. 

discriminatorType으로 식별자를 integer로 할 것 인지 혹은 다른 타입으로 지정할 것인지 선택할 수 있다. name을 지정하게 되면 해당 식별자의 이름이 지정되어 테이블로 들어간다. 

 

특징

- 싱글 테이블이기 때문에 조인 쿼리를 사용하지 않고 한 번에 조회가 가능하다.

- 싱글 테이블이기 때문에 null 값이 들어가는 자료가 생긴다.

- 단일 테이블로 유지하기 때문에 테이블의 크기가 커진다.

 

Joined Table 전략

 

조인 전략은 계층 구조를 띄는 각 클래스가 해당 테이블로 매핑된다. 이 때, 테이블마다 반복되는 열이 하나가 있는 데, 이는 조인할 때 반드시 필요한 식별자 이다. 

 

특징

- 테이블을 정규화 할 수 있다.

- 저장공간을 효율적으로 사용한다 이는 정규화를 실시했기 때문이다.

- 조회할 때 외래키를 이용하여 조인 쿼리를 사용하기 때문에 성능이 좋지 않다.

- 데이터 등록 시 쿼리가 한 번 더 발생한다. 

 

Table per Class

 

Table per Class 는 각각의 테이블을 모두 생성하는 전략이다. 결과적으로 MappedSuperclass를 사용한 것돠 유사하다. 그러나 Table per Class 전략에서는 상위 클래스에 대한 엔티티를 정의하기 때문에 상위 클래스를 연결 및 쿼리 사용이 가능하다.

 

특징

- 서브 타입과 구분해서 처리할 때 효과적이다.

- 상속 없이 각 엔티티를 매핑하는 것과 다르지 않아, Union을 사용하여 쿼리를 작성한다.

- Union 쿼리로 인해 성능이 저하될 수 있다.

반응형

Lombok 에서 'is' prefix가 붙은 컬럼의 boolean과 Boolean의 타입 차에 따라 직렬화시 값이 달라진다. 

 

@Getter
@Setter
public class Member {

    private boolean isDisabled;

    public void test(){
        this.setDisabled(true);  //Lombok - 자동생성된 setter
        this.isDisabled();       //Lombok - 자동생성된 getter
    }
}

@Getter
@Setter
public class Member {

    private Boolean isDisabled;

    public void test(){
        this.setIsDisabled(true);  //Lombok - 자동생성된 setter
        this.getIsDisabled();      //Lombok - 자동생성된 getter
    }
}

 

위와 같이 객체의 속성이 boolean 타입에 'is' prefix를 사용하게 될 경우, getter가 isXXXX로 생성되기 때문에 정상적인 동작이 이루어지지 않는다 때문에 아래처럼 @JsonProperty의 is를 제거한 상태로 넣거나 getIsXXXX() getter() 메서드를 작성하면 정상 동작한다.

@Getter
@Setter
public class Member {

	@JsonProperty("disabled")
    private boolean isDisabled;

    public void test(){
        this.setDisabled(true);  //Lombok - 자동생성된 setter
    }
}

 

다른 방법은 타입을 Warpper class인  Boolean으로 사용하여 getter, setter를 생성하면된다.

반응형

Springboot2로 운영되는 서비스를 Springboot3 로 버전 업을 하게 되었을 시에 javax 대부분의 패키지가 jakarta 패키지로 이관되어 기존 QueryDSL의 JPAQueryFactory는 javax.persistence 패키지를 참조하고 있어 호환이 불가능 하기에 아래처럼 수정하면 에러가 해결된다.

// build.gradle

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

 

 

반응형

'SpringBoot > JPA' 카테고리의 다른 글

계층형 테이블 구현  (1) 2024.10.09
Inheritance 전략  (0) 2024.10.08
값 타입과 값 타입 컬렉션에 관한 사용법  (0) 2023.04.07

루트 프로젝트 생성 후 해당 프로젝트의 우클릭을 통해 [New] - [Module] 선택 후 Spring Initializr를 통해 새로운 모듈을 생성한 후 루트 프로젝트의 settings.gradle.kts에 멀티 모듈을 include 시킨다.

// settings.gradle.kts
rootProject.name = "bmt-backend"

include(":member-admin")

그 후 부모 프로젝트를 빌드 시키면 

위와 같이 멀티 모듈을 작성할 수 있다. 

 

반응형

값 타입인 경우

@Entity
public class DeliveryJpaEntity {
    ...
    
    @Embedded	// 생략 가능
    private AddressJpaEntity address;
    
    ...
}

@Embeddable
public class AddressJpaEntity {

    private String address;

    private String ji;

    private String bun;
}

 

값 타입 컬렉션인 경우

- 값 타입을 컬렉션에 담아서 쓰는 걸 값 타입 컬렉션이라 한다.

- 값 타입 컬렉션은 영속성 전이(Casecade)와 고아 객체 제거(Orphan remove)가 Default 이다.

@Entity
public class DeliveryJpaEntity {

    ...
    
    @ElementCollection	// 기본 fetch 전략은 lazy 이다.
    @CollectionTable(name = "delivery_item", joinColumns = @JoinColumn(name = "delivery_id"))
    private List<DeliveryItemJpaEntity> deliveryItems = new ArrayList<>();
    
    ...
}

@Embeddable
public class DeliveryItemJpaEntity {

    private String name;

    private Long quantity;

    private BigDecimal amount;
}

 

값 타입 컬렉션의 한계

엔티티는 ID로 DB에서 찾을 수 있기 때문에 쉽게 CRUD가 가능하나, 값 타입은 식별자라는 개념이 없기에 원본데이터를 찾기 어렵다, 값 타입 컬렉션은 별도의 테이블에 저장되는데 이 테이블 안에 있는 값타입의 값이 변경 되면 기존 값 타입 컬렉션을 제거하고 컬렉션 전체를 새롭게 저장하는 방식으로 이뤄진다. 때문에 컬렉션의 양이 많아지면 쿼리 수가 많아지기 때문에 컬렉션의 데이터가 많으면 값 타입 컬렉션 대신 일대다 관계 엔티티로 수정하는 것을 고려해야 한다.

반응형

'SpringBoot > JPA' 카테고리의 다른 글

계층형 테이블 구현  (1) 2024.10.09
Inheritance 전략  (0) 2024.10.08
Springboo3 으로 변경 했을시에 QueryDSL 변경 점  (0) 2023.06.09

H2 DB (공식문서)

주요 특징

  • 매우 빠른 데이터베이스 엔진
  • 오픈 소스
  • 자바로 작성
  • 표준 SQL, JDBC API 지원
  • 임베디드 및 서버 모드, 클러스터링 지원
  • 강력한 보안 기능
  • PostgreSQL ODBC 드라이버를 사용.
  • 다중 버전 동시성

사용에 대한 고찰

 외부에 물리DB를 생성하기 어려울 때나, 사이드 프로젝트를 하고 싶은데 물리 DB를 생성하기 부담 스러울때 간단히 내부 DB를 사용하고자 인메모리DB인 h2 를 사용한다.


  

Spring Boot에 H2 DB 사용법

H2 DB는 두가지 모드인 Embedded Mode 와 Server Mode가 존재하며 각 특징으로는 

  • Embedded mode(내장모드) : Application 서버 실행 종료시 데이터 모두 휘발
    • H2를 Application과 동일한 JVM을 이용하여 구동, Application이 종료되면 Data가 휘발
  • Server mode(서버모드) : Application 서버 종료시에도 데이터가 휘발 되지 않는다.
    • 별도의 JVM을 이용하여 구동
    • 여러 Application이 해당 H2에 동시 접근 가능

두 가지 모드 중 어떤 걸 선택하느냐에 따라 SpringBoot의 application.yml 의 설정이 다르며 아래는 Embedded Mode에서의 사용 방법에 대해 서술하는 내용이다.

bulid.gradle에 JPA 와 h2에 관한 의존성을 추가한다. JPA 의존성을 추가하는 이유는  H2:1.4.198 버전부터 보안상의 문제로 자동 데이터베이스 생성을 막아 두어 DB 파일이 생성되지 않고 있기 때문에 더미 Entity 생성으로 DB 생성을 유도해야하기 때문이다. 또는 h2의 버전을 1.4.198 보다 낮은 버전으로 설정해야한다.

// build.gradle
dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
//    runtimeOnly 'com.h2database:h2:1.4.197'
	...
}
# application.yml
  datasource:
    hikari: 
      jdbc-url: jdbc:h2:mem:testdb
      username: sa	# 필수로 입력해야만 한다. 
      password:
      driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
  jpa:
    hibernate:
      ddl-auto: create-drop # 시작할 때 새로 생성 하고 세션을 마칠 때 스키마를 소멸 시킵니다.
    properties:
      hibernate:
        show_sql: true
        format_sql: true

application.yml에서 해당 h2.console.enabled: true 설정한 후 console창을 보게 되면

H2 console available at 이라는 문구를 확인할 수 있으며

localhost:8080/h2-console 로 접속하게 된다면 아래와 같은 화면을 볼 수 있다.

반응형

+ Recent posts