Spring Data JDBC 공식문서 뽀개기

Jane·2021년 4월 23일
18

Spring Data JPA vs JDBC

  • Spring Data JPA
    • entity의 변화를 추적하여 lazy loading을 제공함
      • lazy loading: 필요 시점까지 리소스 로딩을 연기하다가 필요할 때 로딩하는 것
  • Spring Data JDBC
    • entity가 로드되는 시점에 SQL문이 실행되고 entity 로딩이 완료됨
    • dirty tracking(상태 변경을 추적)이나 session이 없음
      → JPA와 다르게 수정을 하고 저장하지 않으면 변경이 반영되지 않음

DDD(Domain Driven Design)

  • DDD: 소프트웨어 코드의 구조와 비즈니스 도메인의 일치를 지향하는 설계
  • aggregate: 값이 변화할 때 일관성을 유지해야 하는 데이터의 집합
    • 각 aggregate는 하나의 aggregate root만을 갖는다.
      • aggregate root = aggregate의 엔티티 중 하나를 선택
      • aggregate는 aggregate root에 있는 메서드로만 조작되어야 한다. (atomic change)
      • aggregate root 당 일반적으로 하나의 repository를 갖는다.
      • aggregate root에서 접근가능한 모든 엔티티는 한 aggregate에 속함
      • root가 아닌 entity를 저장하는 테이블에 대한 외래키는 aggregate만 가지고 있다고 가정
  • Eventual Consistency 보장
  • 2.1.7 버전 기준으로 aggregate root가 참조한 entity는 Spring Data JDBC에 의해 삭제되고 다시 생성됨

Configuration

@Configuration
@EnableJdbcRepositories                                                                
class ApplicationConfig extends AbstractJdbcConfiguration {                            

    @Bean
    public DataSource dataSource() {                                                   

        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build();
    }

    @Bean
    NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource) { 
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean
    TransactionManager transactionManager(DataSource dataSource) {                     
        return new DataSourceTransactionManager(dataSource);
    }
}
  1. @EnableJdbcRepositories : Repository 인터페이스 구현체 생성
  2. AbstractJdbcConfiguration : Spring Data JDBC가 요구하는 default bean 제공
  3. public DataSource dataSource() : DB에 연동되는 DataSource 생성
  4. 아래 두 개 메서드 : 위에서 빌드한 dataSource를 기반으로 NamedParameterJdbcOperations 생성, transaction 관리

작동 원리
빌드한 데이터 소스를 이용하여 NamedParameterJdbcOperations와 TransactionManager를 셋업하고 @EnableJdbcRepositories를 통해 스프링 데이터 JDBC 레파지토리를 활성화한다. (base package를 지정하지 않으면 configuration 클래스가 속한 패키지 사용)

But, 스프링 부트를 사용하면 spring-boot-starter-data-jdbc 의존성을 build.gradle에 추가해주고 application properties에 data source만 configure해주면 된다😆

# build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

# application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/(데이터베이스 이름)?characterEncoding=UTF-8
spring.datasource.username=(사용자 이름)
spring.datasource.password=(사용자 비밀번호)

엔티티 영속성 관리

  • CrudRepository.save(…)메서드로 aggregate root에 새로운 entity 삽입 가능
  • aggregate root가 최신화되어있지 않으면 모든 참조된 entity가 삭제되었다가 다시 삽입된다.
    • Spring Data JDBC가 이렇게 비효율적으로 작동하는 이유는 aggregate의 이전 상태에 대한 정보가 없기 때문이다.

Spring Data에서 객체를 생성하는 원리

리플렉션의 오버헤드를 줄이기 위해 Spring Data는 런타임 시 기본으로 생성되는 팩터리 클래스를 사용한다.

class Person {
  Person(String firstname, String lastname) {}
}

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

이러한 방식으로 객체를 생성하면 reflection에 비해 10% 정도 성능을 개선할 수 있는데, private, non-static inner, CGLib proxy 클래스를 사용하거나 Spring Data에 의해 사용될 생성자가 private인 경우 reflection을 이용하여 entity가 초기화된다.

생성자

  • no-argument constructor를 제일 우선으로 사용한다.
  • argument를 받는 생성자 한 개만 있다면 그 생성자를 사용한다.
  • argument를 받는 생성자가 여러 개 있다면 @PersistenceConstructor 애노테이션이 붙은 생성자를 사용한다.

권장 사항

  • 불변 객체를 사용해라
  • all-args constructor를 제공해라
  • 생성자 오버로딩보다는 팩터리 메서드를 사용해라(@PersistenceConstructor를 사용하지 않아도 됨)
  • 롬복을 사용해라

객체 간 맵핑

도메인 객체의 인스턴스를 생성하고 저장을 위한 자료구조를 맵핑하는 것

  • 참조된 entity의 테이블은 참조하는 entity의 테이블과 동일한 이름의 컬럼을 가지고 있어야 한다.
    • 이름을 바꾸고 싶다면 NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)를 구현하면 된다.
  • Map<simple type, some entity>이나 List<some entity>의 경우 foreign key로 사용할 컬럼 외에도 참조하는 테이블 이름 뒤에 _key를 붙인 컬럼이 필요하다.
    • @MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")을 통해 이름을 설정할 수 있다.
      • idColumn은 참조하는 entity의 id를, keyColumn은 리스트 또는 맵 내의 데이터의 위치를 가리킨다.

Embedded entities

자바 데이터 모델 안에 value object를 갖기 위해 사용

public class MyEntity {

    @Id
    Integer id;

    @Embedded(onEmpty = USE_NULL) 
    EmbeddedEntity embeddedEntity;
}

public class EmbeddedEntity {
    String name;
}
  • 위와 같이 임베드되어 있다면 데이터베이스에는 id와 name 컬럼을 가진 테이블이 생성된다.
    • embedded entity는 id가 없어도 된다.
  • @Embedded(onEmpty = USE_NULL)이기 때문에 name 컬럼이 null이 되면 embeddedEntity 전체 속성이 null로 설정된다.
    • 간단하게 @Embedded.Nullable로 써도 된다.
  • USE_EMPTY로 설정한다면 기본 생성자 또는 nullable parameter 값을 받는 생성자로 새로운 인스턴스가 생성된다.

Custom converter 등록

AbstractJdbcConfiguration를 상속하고 jdbcCustomConversions()를 오버라이드하면 커스텀 컨버터를 등록할 수 있다.

@Configuration
public class DataJdbcConfiguration extends AbstractJdbcConfiguration {

    @Override
    public JdbcCustomConversions jdbcCustomConversions() {

      return new JdbcCustomConversions(Collections.singletonList(TimestampTzToDateConverter.INSTANCE));

    }

    @ReadingConverter 
    enum TimestampTzToDateConverter implements Converter<TIMESTAMPTZ, Date> {

        INSTANCE;

        @Override
        public Date convert(TIMESTAMPTZ source) {
            //...
        }
    }
}

@ReadingConverter 어노테이션을 붙이면 데이터베이스로부터 데이터를 읽어올 때만 컨버터가 적용되고, @WritingConverter를 붙이면 데이터베이스에 데이터를 입력할 때 컨버터가 적용된다.


테이블, 컬럼 이름 바꾸기

@Table 애노테이션을 달아 데이터베이스에 있는 테이블과 엔티티 클래스를 맵핑해줄 수 있다.

@Table("CUSTOM_TABLE_NAME")
public class MyEntity {
    @Id
    Integer id;

    String name;
}

기본 NamingStrategy가 데이터베이스에 있는 컬럼명과 일치하지 않는 경우, @Column 애노테이션을 통해 컬럼 이름을 바꿀 수 있다.

public class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

쿼리 메서드

  • Query derivation은 join 없이 where절에서 사용할 수 있는 속성으로 제한된다.
  • Query derivation이 지원되지 않는 메서드를 구현하고 싶으면 @Query와 함께 원하는 쿼리를 작성해주면 된다.
public interface UserRepository extends CrudRepository<User, Long> {

  @Query("select firstName, lastName from User u where u.emailAddress = :email")
  User findByEmailAddress(@Param("email") String email);
}

Source

8개의 댓글

comment-user-thumbnail
2021년 4월 25일

Jane 최고!!!!👍 학습하는데 큰 도움이 되었습니다!🙇‍♀️

1개의 답글
comment-user-thumbnail
2021년 4월 27일

제인 이거 정말 찐인데요...?!?!? 감사합니다 여기 지식맛집이네요

1개의 답글
comment-user-thumbnail
2021년 4월 28일

정리 잘 되어 있네여! 😃

1개의 답글
comment-user-thumbnail
2022년 4월 12일

spring data jdbc example 치니까 제인블로그가ㅋㅋㅋㅋㅋ 다시 도움받고갑니당..bb

1개의 답글