spring-boot-starter-data-jpa, common
spring-boot-starter-data-jpa, common
JAVA - Backend/SpringBoot - ApplicationFramework
참고
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#preface
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 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
Spring-Data-Jpa로 Entity Repository 생성 샘플
public interface PostRepository extends JpaRepository { //Entity Type, PK Type }
스프링 데이터 JPA 활용
스프링 데이터 SQL & NoSQL 저장소 지원 프로젝트의 묶음 스프링 데이터 Common 여러 저장소 지원 프로젝터의 공통 기능 제공 스프링 데이터 REST 저장소의 데이터를 하이퍼미디어 기반 HTTP 리소스로(REST API로) 제공하는 프로젝트 스프링 데이터 JPA 스프링 데이터 Common이 제공하는 기능에 JPA관련 기능 추가
@DataJpaTest : 리포지토리 관련 Bean 등록
Common - 리포지토리
Repository 실질적인 기능 없음 CrudRepository 기본적인 CRUD 기능 제공 PagingAndSortingRepository 소팅, 페이징 findAll(), Page 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 extends Repository{
E save (E entity);
List findAll();
}
public interface comRepo extends entityRepo{}
Common - Null 처리
단일 값 리턴시 Optional 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 findByLikeCountGreaterThanAndPost(int likeCount, Post post, Pagable pageable);
리턴타입 List, Optional, Page, Slice, Stream, 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 findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // distinct List findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // ignoring case List findByLastnameIgnoreCase(String lastname); // ignoring case List findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // 정렬 List findByLastnameOrderByFirstnameAsc(String lastname); List findByLastnameOrderByFirstnameDesc(String lastname); // 페이지 Page findByLastname(String lastname, Pageable pageable); Slice findByLastname(String lastname, Pageable pageable); List findByLastname(String lastname, Sort sort); List findByLastname(String lastname, Pageable pageable); // 스트리밍 Stream readAllByFirstnameNotNull(); // try-with-resource 사용할 것.(Stream을 다 쓴다음에 close() 해야 함) // 비동기 쿼리 : 비추 (테스트코드 작성이 어려움) @Async Future findByFirstname(String firstname); @Async CompletableFuture findOneByFirstname(String firstname); @Async ListenableFuture 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 extends Jpa...
2. 기본 구현체를 상속 받는 커스템 구현체 만들기 public class SimMyRepository extends SimpleJpaReposotory implements MyRepository {}
생성자 작성필요, 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 : 리스너는 Bean등록 해줘야함
또는 그냥 @EventListner사용 가능(method)
또는 Bean등록시에 public ApplicationListener postListner(){ return event->{ }; } 를 등록 가능
Event 를 applicationContext로 이벤트를 publishing 하지 않고 EntityRepository에서 extends AbstractAggregationRoot 를 상속받아서 메서드 하나 만들고 this.registerEvent로 이벤트를 등록해두면
Repository.save()시점에 등록된 모든 이벤트가 퍼블리싱되고 리스너가 call됨
Common - QueryDSL 연동
http://www.querydsl.com
http://www.querydsl.com/static/querydsl/4.1.3/reference/html_single/#jpa_integration
제공 인터페이스
Optional findOne(Predicate) : Predicate = 조건
List.. findAll(Predicate)
연동 방법
https://hiddentrap.tistory.com/127?category=833353
public interface AccountRepository extends JpaRepository, QuerydslPredicateExecutor { } @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 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
@GetMapping("/posts/{id}") public String getAPost(@PathVariable Long id){ Optional 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 getPosts(Pageable){ return postRepository.findAll(pageable); }
HATEOAS
HATEOAS 의존성 추가 필요 (starter-hateoas) : 링크 정보 등 부가정보 생성
핸들러 매개변수로 PagedResourcesAssembler 사용
@GetMapping("/posts") public PagedResources> 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
@Entitiy @NamedQuery(name = "Post.findByTitle", query="SELECT p FROM post AS p WHERE p.title = ?1")
repository 인터페이스에서 List findByTitle(String title);
또는 repsitory 메서드 위에 @Query("SELECT p FROM post AS p WHERE p.title = ?1")
nativeQuery도 사용 가능
from http://hiddentrap.tistory.com/131 by ccl(A) rewrite - 2020-03-06 08:20:52
댓글
댓글 쓰기