1) JPA 직접 사용( EntityManager )
2) 스프링 JDBC Template 사용
3) MyBatis 사용
4) 데이터베이스 커넥션 직접 사용 등등...
5) Querydsl 사용
public interface MemberRepositoryCustom {
List<Member> findMemberCustom();
}
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final EntityManager em;
@Override
public List<Member> findMemberCustom() {
return em.createQuery("select m from Member m")
.getResultList();
}
}
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}
List<Member> result = memberRepository.findMemberCustom();
Tip.
1) 실무에서는 주로 QueryDSL이나 SpringJdbcTemplate을 함께 사용할 때 사용자 정의 리포지토리 기능 자주 사용한다.2) 항상 사용자 정의 리포지토리가 필요한 것은 아니다. 그냥 임의의 리포지토리를 만들어도 된다. 예를들어 MemberQueryRepository를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록해서 그냥 직접 사용해도 된다. 물론 이 경우 스프링 데이터 JPA와는 아무런 관계 없이 별도로 동작한다.
3) 활용법2 강의 정리에서 얘기 했듯이 성능 최적화를 위해 OSIV를 껐을 때 repository 영역내에서 view단에서 사용될 패키지를 분리하고 명령 쿼리 책임 분리 ( CQRS)를 통해서 조회 쿼리를 분리 하는 등 하기 때문에 다각적으로 분석하고 분리하도록 해야한다.
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom { // 최신 사용 법
// public class MemberRepositoryImpl implements MemberRepositoryCustom { 기존 사용법
private final EntityManager em;
@Override
public List<Member> findMemberCustom() {
return em.createQuery("select m from Member m")
.getResultList();
}
}
@MappedSuperclass //내 JPA 블로그 글에 무엇인지 정리 되어있음 참고**
@Getter
public class JpaBaseEntity {
@Column(updatable = false)
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@PrePersist // persist 직전에 실행
public void prePersist() {
LocalDateTime now = LocalDateTime.now();
createdDate = now;
updatedDate = now;
}
@PreUpdate // 수정 직전에 실행
public void preUpdate() {
updatedDate = LocalDateTime.now();
}
}
// 상속을 이용한다.
public class Member extends JpaBaseEntity {
}
@CreatedDate = 생성일
@LastModifiedDate = 수정일
@CreatedBy = 생성자
@LastModifiedBy = 수정자
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {
@CreatedDate
@Column(updatable = false) // 엔티티 수정시 필드를 같이 수정하지 않는다. -> 읽기 전용
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
public class BaseEntity extends BaseTimeEntity {
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of(UUID.randomUUID().toString());
}
Tip.
1) 처음 저장 할 때 수정일(=등록일)과 수정자(=등록자)도 함께 저장된다. 중복 저장 되는 것 같지만 이렇게 해두면 변경 컬럼만 확인해도 마지막에 업데이트한 유저를 확인 할 수 있으므로 유지보수 관점에서 편리 하다.2) 전체적용하려면 어노테이션이 아닌 orm.xml에 AuditingEntityListener을 등록해야한다.
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> pageDto = page.map(MemberDto::new);
return pageDto;
}
page: 현재 페이지, ** 0부터 시작한다. **
size: 한 페이지에 노출할 데이터 건수
sort: 정렬 조건을 정의한다.
예) 정렬 속성,정렬 속성...(ASC | DESC), 정렬 방향을 변경하고 싶으면 sort 파라미터 추가 ( asc 생략 가능)
spring.data.web.pageable.default-page-size=20 /# 기본 페이지 사이즈/ spring.data.web.pageable.max-page-size=2000 /# 최대 페이지 사이즈/
@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = “username”, direction = Sort.Direction.DESC) Pageable pageable) {
...
}
public String list(
@Qualifier("member") Pageable memberPageable,
@Qualifier("order") Pageable orderPageable, ..
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> ...{
@Transactional
public<S extends T> S save(S entity){
if(entityInformation.isNew(entity)){
em.persist(entity);
return entity;
}else{
return em.merge(entity);
}
}
...
}
1. 식별자가 객체 일 때는 null이면 새로운 엔티티로 인식
2. 식별자가 자바 기본타입일때는 0이면 새로운 엔티티로 인식
3. Persistable 인터페이스를 구현해서 판단 로직 변경 가능핟.
package org.springframework.data.domain;
public interface Persistable<ID> {
ID getId();
boolean isNew();
}
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
@Id
private String id;
@CreatedDate
private LocalDateTime createdDate;
public Item(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public boolean isNew() {
return createdDate == null;
}
}
여기서부터는 대충정리..
public class QueryByExampleTest {
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
@Test
public void basic() throws Exception {
//given
Team teamA = new Team("teamA");
em.persist(teamA);
em.persist(new Member("m1", 0, teamA));
em.persist(new Member("m2", 0, teamA));
em.flush();
//when
//Probe 생성
Member member = new Member("m1");
Team team = new Team("teamA"); //내부조인으로 teamA 가능
member.setTeam(team);
//ExampleMatcher 생성, age 프로퍼티는 무시
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths("age");
Example<Member> example = Example.of(member, matcher);
List<Member> result = memberRepository.findAll(example);
//then
assertThat(result.size()).isEqualTo(1);
}
}
냅다 결론