본문 바로가기

JAVA - Backend/SpringBoot - ApplicationFramework

spring-boot-starter-data-jpa, common

참고

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#preface 

 

Spring Data JPA - Reference Documentation

Example 108. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v

docs.spring.io

JDBC

String url = "jdbc:postgresql://localhost:5432:testtable";
String username = "user";
String password = "pass";
try((
Connection connection = DriverManager.getConnection((url, username, password))
{
    System.out.println("Connection create: " + connection);
    String sql = "INSERT INTO ACCOUT VALUES(1, 'user', 'pass');";
    try (PreparedStatement statement = connection.prepareStatement(sql)) {
        statement.execute();
    }
}


Domain Model

Account accoutn = new Account('user', 'pass');
accountRepository.save(account);


ORM은 애플리케이션의 클래스와 SQL 데이터베이스의 테이블 사이의 매핑 정보를 기술한 메타데이터를 사용하여, 자바 애플리케이션의 객체를 SQL 데이터베이스의 테이블에 자동으로 영속화해주는 기술

EntityManager : JPA 핵심 클래스

@Transactional : 트랜잭션

엔티티 매핑 어노테이션 (javax.persistence)

@Entity와 @Data는 같이 쓰지 말 것 : 양방향 관계설 정시 무한 루프 가능
toString에서 서로 참조해서 생기는 문제

@Entity(name = "엔티티 이름") 테이블 매핑, 기본값은 클래스 이름과 동일
@Table(name = "테이블 이름") 기본값은 @Entity이름과 동일
@Id 엔티티의 프라이머리 키
@GeneratedValue(strategy = GenerationType.SEQUENCE) 프라이머리 키 자동생성 방법 매핑, 기본전략은 DB에 따라 다름
TABLE, SEQUENCE, IDENTITY 중 하나
@Column(nullable=false, unique=true) 컬럼 속성
unique
nullable
length
columnDefinition
...
@Temporal(TemporalType.TIMESTAMP)
private Date created = new Date();
Date, Calendar ... (TemporalType)
@Transient 컬럼 매핑 제외
@Lob  

Value타입 매핑

엔티티타입 : 식별자가 있는 독립 존재
Value 타입 : 일반 데이터형, 생명주기가 다른 엔티티에 종속적인 타입
Composit Value 타입의 매핑 : Composit Value 클래스에 @Embeddable 사용

//오버라이딩 가능
@Embedded
@AttributeOverride(name="street", column=@Column(name="home_street"))
private Address address; 

Collection Value 타입의 매핑 TBD

관계 매핑

관계는 두 엔티티가 필요 소유자(관계를 정의한 엔티티 즉, 상대의 레퍼런스 변수를 정의한 엔티티)-비소유자
테이블에서는 관계에 방향성이 없지만 엔티티에서 A->B로 접근할 경우, B->A로 접근할 필요도 있는 경우에 따라서, 단방향 관계만 사용할 것인지, 양방향 관계를 사용해야 하는지 결정해야 한다.

단방향 @ManyToOne: 소유자에 FK생성

단방향 @OneToMany: 소유자와 비소유자를 연결하는 조인 테이블 생성

양방향 : @ManyToOne쪽 (FK를 갖는 엔티티)이 소유자가 되고 @OneToMany쪽은 mappedBy로 소유자에서 참조하는 레퍼런스 이름을 기술한다.
Study {@ManuToOne private Account owner;} <- 소유자
Account {@OneToMany(mappedBy=owner) private Set <Study> studies = new HashSet <>();} 
관계의 매핑은 반드시 소유자 엔티티에 해야 한다. 이 경우 Study.setOwner();
일반적으로 ConvinientMethod 사용하여 관계 생성
addStudy(Study study){ this.getStudies(). add(study); study.setOwner(this); }
removeStudy(Study study){ this.getStudies(). remove(study); study.setOwner(null);

엔티티 상태와 Cascade : P-C 관계에서 저장과 삭제 등 상태 전이

엔티티의 상태
Transient : JPA가 모르는 상태 (Git의 add 전?)
Persistent : save()를 통해 JPA의 관리대상이 된 상태 (Git의 stage?)
Detached : JPA가 관리하지 않는 상태 (Git의 unstage) - 트랜잭션이 종료되고 리턴돼서 다른 곳에서 사용될 때
Rmoved : JPA가 관리하지만 삭제하기로 한 상태

@oneToMany 또는 @ManyToOne의 옵션 : Parent와 Child관계에 있을 경우에 사용할 수 있음
@oneToMany(cascade = CascadeType.ALL)
Account와 Study는 P-C관계가 아님, Post와 Comment가 전형적인 P-C관계

Fetch (fetch = FetchType.)

연관 관계의 엔티티를 언제 Fetch? Eager: 지금, Lazy: 나중에 필요할때
@OneToMany의 기본값은 Lazy
@ManyToOne의 기본값은 Eager

쿼리: JPQL, Criteria, Native Query가 있지만 QueryDSL쓸거라서 생략

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#criteria

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#sql

 

Hibernate ORM 5.2.18.Final User Guide

The legacy way to bootstrap a SessionFactory is via the org.hibernate.cfg.Configuration object. Configuration represents, essentially, a single point for specifying all aspects of building the SessionFactory: everything from settings, to mappings, to strat

docs.jboss.org

Spring-Data-Jpa로 Entity Repository 생성 샘플

public interface PostRepository extends JpaRepository<Post, Long> { //Entity Type, PK Type
}

스프링 데이터 JPA 활용

스프링 데이터 SQL & NoSQL 저장소 지원 프로젝트의 묶음
스프링 데이터 Common 여러 저장소 지원 프로젝터의 공통 기능 제공
스프링 데이터 REST 저장소의 데이터를 하이퍼미디어 기반 HTTP 리소스로(REST API로) 제공하는 프로젝트
스프링 데이터 JPA 스프링 데이터 Common이 제공하는 기능에 JPA관련 기능 추가

@DataJpaTest : 리포지토리 관련 Bean 등록

Common - 리포지토리

Repository 실질적인 기능 없음
CrudRepository 기본적인 CRUD 기능 제공
PagingAndSortingRepository 소팅, 페이징 findAll(), Page<Post> page = PostRepo.findAll(PageRequest.of(page, size)
JpaRepository (JPA)  

Common - 인터페이스 정의(쓸일 없음)

사용자 정의 Reposotory Interface 생성
@RepositoryDefinition(domainClass = Entity.class, idClass = idType.class)
public interface entityRepo(){}

@NoReposotiryBean
public interface entityRepo<T, Id extends Serializable> extends Repository<T, Id>{
   <E extends T> E save (E entity);
    List<T> findAll();
}
public interface comRepo extends entityRepo<Comment, Long>{}

Common - Null 처리

단일 값 리턴시 Optional<post> post = post.repo.findByName(); 등 Optional 사용
Collection은 Null 이 아닌 비어있는 Collection 리턴
스프링 5.0부터는 Null 관련 애노테이션 지원: (package)@NonAllApi, (method)@NonNull, (parameter)@Nullable

Null 애노테이션 관련 IntelliJ 설정

Common - 쿼리 만들기

미리 정의한 쿼리 사용 - USE_DELCARED_QUERY : 정의 방법은 저장소 마다 다름
EX) @Query(value = "SELECT c FROM Comment AS c", nativeQuery = true) 
or
     @NamedQuery

미리 정의한 쿼리가 없으면 만들기 - CREATE_IF_NOT_FOUND (기본값)

메소드 이름 분석을 통해 쿼리 생성 - CREATE

리턴타입 {접두어} {도입부}By{프로퍼티 표현식}(조건식)[(And|Or){프로퍼티 표현식}(조건식)]{정렬조건}(매개변수)
Page<Comment> findByLikeCountGreaterThanAndPost(int likeCount, Post post, Pagable pageable);

리턴타입 List<Post>, Optional<Post>, Page<Post>, Slice<Post>, Stream<Post>, Post
접두어 Find, Get, Query, Count, Delete ...
도입부 (옵션) Distinct, First(N), Top(N)
프로퍼티(필드) 표현식 Person.Address.ZipCode => find(Person)ByAddress_ZipCode(...)
조건식 IgnoreCase, Between, LessThan, GraterThan, Like, Contains, ...
정렬 조건 OrderBy{프로퍼티}Asc|Desc
매개변수 Pageable, Sort
// 기본예제
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// distinct
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// ignoring case
List<Person> findByLastnameIgnoreCase(String lastname);
// ignoring case
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

// 정렬
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);

// 페이지
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);

// 스트리밍
Stream<User> readAllByFirstnameNotNull(); // try-with-resource 사용할 것.(Stream을 다 쓴다음에 close() 해야 함)

// 비동기 쿼리 : 비추 (테스트코드 작성이 어려움)
@Async Future<User> findByFirstname(String firstname);
@Async CompletableFuture<User> findOneByFirstname(String firstname);
@Async ListenableFuture<User> findOneByLastname(String firstname); // 스프링 TaskExecutor에 전달

Future Non-Blocking Thread call, future.get() : Blocking call
ListenableFuture는 addCallback 으로 콜백 함수를 등록할 수 있으므로 Async는 이걸로 사용

Common - 커스텀 리포지토리

1. 커스텀 리포지토리 인터페이스 정의
2. 인터페이스 구현 클래스 만들기 (접미어 Impl 붙여야함)
3. 엔티티 리포지토리에  커스텀 리포지토리 인터페이스 추가 extends

Common - 기본 리포지토리 커스터마이징

1. JpaRepostory를 상속 받는 인터페이스 정의 @NoRepositoryBean public interface MyRepository<T, ID extends Serializable> extends Jpa...
2. 기본 구현체를 상속 받는 커스템 구현체 만들기 public class SimMyRepository<T, ID extends Serializable> extends SimpleJpaReposotory<T, ID> implements MyRepository<T, ID> {}
생성자 작성필요, EntityManager는 주입이 아니라 인자로 받아온 거를 멤버변수에 넣어서 사용
3. @EnableJpaRepositories에 설정, repositoryBaseClass=SimMyRepository.class

Common - 도메인(Entity) 이벤트

ApplicationContext extends ApplicationEventPublisher : 애플리케이션 컨텍스는 이벤트 퍼블리싱 기능을 갖고 있음
@Autowired
ApplicationContext applicationContext;
applicationContext.publishEvent(new PostPublishedEvent(post));

Event 생성: PostPublichedEvent extends ApplicationEvent

Event 리스너 생성: PostListener implements ApplicationListener<PostPublisshedEvent> : 리스너는 Bean등록 해줘야함
또는 그냥 @EventListner사용 가능(method)
또는 Bean등록시에 public ApplicationListener<PostPublishedEvent> postListner(){ return event->{ }; } 를 등록 가능

Event 를 applicationContext로 이벤트를 publishing 하지 않고 EntityRepository에서 extends AbstractAggregationRoot<E> 를 상속받아서 메서드 하나 만들고 this.registerEvent로 이벤트를 등록해두면
Repository.save()시점에 등록된 모든 이벤트가 퍼블리싱되고 리스너가 call됨

Common - QueryDSL 연동

http://www.querydsl.com

 

Querydsl - Unified Queries for Java

Unified Queries for Java. Querydsl is compact, safe and easy to learn.

http://www.querydsl.com/static/querydsl/4.1.3/reference/html_single/#jpa_integration 

 

Querydsl Reference Guide

The Java 6 APT annotation processing functionality is used in Querydsl for code generation in the JPA, JDO and Mongodb modules. This section describes various configuration options for the code generation and an alternative to APT usage. 3.3.1. Path initia

www.querydsl.com

제공 인터페이스
Optional<T> findOne(Predicate) : Predicate = 조건
List<T>.. findAll(Predicate)

연동 방법
https://hiddentrap.tistory.com/127?category=833353

 

QueryDsl

참고 https://www.4te.co.kr/890?category=624359 QueryDSL 사용하기 JPA를 사용하면서 QueryDSL을 셋팅하고 사용하는 부분에 있어서 매번 헷깔려 정리한다. QueryDSL을 사용하기 위해서 build.gradle 파일에 아래..

hiddentrap.tistory.com

public interface AccountRepository extends JpaRepository<Account, Long>, QuerydslPredicateExecutor<Account> {
}

@ExtendWith(SpringExtension.class)
@DataJpaTest
class AccountTest {

    @Autowired
    AccountRepository accountRepository;

    @Test
    void crud(){
        Predicate predicate = QAccount.account
                .firstName.containsIgnoreCase("keesun")
                .and(QAccount.account.lastName.startsWith("baik"));

        Optional<Account> one = accountRepository.findOne(predicate);
        assertThat(one).isEmpty();

    }

}

Common - 웹 지원 기능

도메인 클래스 컨버터

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/converter/Converter.html 

 

Converter (Spring Framework 5.2.4.RELEASE API)

A converter converts a source object of type S to a target of type T. Implementations of this interface are thread-safe and can be shared. Implementations may additionally implement ConditionalConverter.

docs.spring.io

@GetMapping("/posts/{id}")
public String getAPost(@PathVariable Long id){
    Optional<Post> byId = postRepository.findById(id);
    Post post = byId.get();
    return post.getTitle();
}

@GetMapping("/posts/{id}")
public String getAPost(@PathVariable("id") Post post) {
    return post.getTitle();
}

Pageable, Sort 매개변수 

파라메터
page : 0부터 시작
size: 기본값 20
sort: 필드,asc|desc
ex: sort=created, desc&sort=title

@GetMapping("/posts")
public PageM<Post> getPosts(Pageable){
    return postRepository.findAll(pageable);
}

HATEOAS

HATEOAS 의존성 추가 필요 (starter-hateoas) : 링크 정보 등 부가정보 생성
핸들러 매개변수로 PagedResourcesAssembler 사용

@GetMapping("/posts")
public PagedResources<Resource<Post>> getPosts(Pageable pageable, PagedResourcesAssembler assembler){
    return assembler.toResource(posts.findall(pageable));
}

JPA - JpaRepository 

JpaReposory 상속할때 @Repository 안붙여도 됨 SimpleJpaRepository 구현체에 이미 붙어있어서 중복임

JPA - Entity 저장

savedEntity=repository.save(entity); 이후 entitiy 사용금지, savedEntity 사용할것

JPA - 쿼리 메소드

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation 

 

Spring Data JPA - Reference Documentation

Example 108. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v

docs.spring.io

@Entitiy @NamedQuery(name = "Post.findByTitle", query="SELECT p FROM post AS p WHERE p.title = ?1")
repository 인터페이스에서 List<post> findByTitle(String title);

또는 repsitory 메서드 위에 @Query("SELECT p FROM post AS p WHERE p.title = ?1")
nativeQuery도 사용 가능