Merge branch 'master' of https://github.com/haerong22/Study
This commit is contained in:
@@ -12,6 +12,7 @@ import javax.persistence.*;
|
||||
name="Member.findByUsername",
|
||||
query="select m from Member m where m.username = :username"
|
||||
)
|
||||
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
|
||||
public class Member {
|
||||
|
||||
@Id @GeneratedValue
|
||||
|
||||
@@ -68,4 +68,10 @@ public class MemberJpaRepository {
|
||||
.setParameter("age", age)
|
||||
.getSingleResult();
|
||||
}
|
||||
|
||||
public int bulkAgePlus(int age) {
|
||||
return em.createQuery("update Member m set m.age = m.age + 1 where m.age >= :age")
|
||||
.setParameter("age", age)
|
||||
.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,14 @@ package com.example.springdatajpa.repository;
|
||||
|
||||
import com.example.springdatajpa.dto.MemberDto;
|
||||
import com.example.springdatajpa.entity.Member;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.QueryHint;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -32,4 +36,36 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
|
||||
List<Member> findListByUsername(String username); // 컬렉션
|
||||
Member findMemberByUsername(String username); // 단건
|
||||
Optional<Member> findOptionalByUsername(String username); // Optional
|
||||
|
||||
@Query(value = "select m from Member m left join m.team t",
|
||||
countQuery = "select count(m) from Member m")
|
||||
Page<Member> findPageByAge(int age, Pageable pageable);
|
||||
|
||||
Slice<Member> findSliceByAge(int age, Pageable pageable);
|
||||
|
||||
// @Modifying(clearAutomatically = true)
|
||||
@Modifying
|
||||
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
|
||||
int bulkAgePlus(@Param("age") int age);
|
||||
|
||||
@Query("select m from Member m left join fetch m.team")
|
||||
List<Member> findMemberFetchJoin();
|
||||
|
||||
@Override
|
||||
// @EntityGraph(attributePaths = {"team"})
|
||||
List<Member> findAll();
|
||||
|
||||
@EntityGraph(attributePaths = {"team"})
|
||||
@Query("select m from Member m")
|
||||
List<Member> findMemberEntityGraph();
|
||||
|
||||
// @EntityGraph(attributePaths = {"team"})
|
||||
@EntityGraph("Member.all")
|
||||
List<Member> findEntityGraphByUsername(@Param("username") String username);
|
||||
|
||||
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
|
||||
Member findReadOnlyByUsername(String username);
|
||||
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
Member findLockByUsername(String username);
|
||||
}
|
||||
|
||||
@@ -106,4 +106,20 @@ class MemberJpaRepositoryTest {
|
||||
assertEquals(3, members.size());
|
||||
assertEquals(5, totalCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkUpdate() {
|
||||
// given
|
||||
memberJpaRepository.save(new Member("member1", 10));
|
||||
memberJpaRepository.save(new Member("member2", 19));
|
||||
memberJpaRepository.save(new Member("member3", 21));
|
||||
memberJpaRepository.save(new Member("member4", 33));
|
||||
memberJpaRepository.save(new Member("member5", 40));
|
||||
|
||||
// when
|
||||
int resultCount = memberJpaRepository.bulkAgePlus(20);
|
||||
|
||||
// then
|
||||
assertEquals(3, resultCount);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,15 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -25,6 +31,8 @@ public class MemberRepositoryTest {
|
||||
MemberRepository memberRepository;
|
||||
@Autowired
|
||||
TeamRepository teamRepository;
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
@Test
|
||||
void testMember() {
|
||||
@@ -178,5 +186,125 @@ public class MemberRepositoryTest {
|
||||
assertThrows(IncorrectResultSizeDataAccessException.class,
|
||||
() -> memberRepository.findOptionalByUsername("ddd")); // 반환 타입은 단건 인데 여러 건이 조회 되었을 경우 예외 발생
|
||||
}
|
||||
|
||||
@Test
|
||||
void paging() {
|
||||
// given
|
||||
memberRepository.save(new Member("member1", 10));
|
||||
memberRepository.save(new Member("member2", 10));
|
||||
memberRepository.save(new Member("member3", 10));
|
||||
memberRepository.save(new Member("member4", 10));
|
||||
memberRepository.save(new Member("member5", 10));
|
||||
|
||||
int age = 10;
|
||||
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
|
||||
// when
|
||||
Page<Member> page = memberRepository.findPageByAge(age, pageRequest);
|
||||
Page<MemberDto> toMap = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
|
||||
|
||||
// then
|
||||
List<Member> members = page.getContent();
|
||||
|
||||
assertEquals(3, members.size());
|
||||
assertEquals(5, page.getTotalElements());
|
||||
assertEquals(0, page.getNumber());
|
||||
assertEquals(2, page.getTotalPages());
|
||||
assertTrue(page.isFirst());
|
||||
assertTrue(page.hasNext());
|
||||
|
||||
Slice<Member> slice = memberRepository.findSliceByAge(age, pageRequest);
|
||||
List<Member> content = page.getContent();
|
||||
|
||||
assertEquals(3, content.size());
|
||||
assertEquals(0, slice.getNumber());
|
||||
assertTrue(slice.isFirst());
|
||||
assertTrue(slice.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkUpdate() {
|
||||
// given
|
||||
memberRepository.save(new Member("member1", 10));
|
||||
memberRepository.save(new Member("member2", 19));
|
||||
memberRepository.save(new Member("member3", 21));
|
||||
memberRepository.save(new Member("member4", 33));
|
||||
memberRepository.save(new Member("member5", 40));
|
||||
|
||||
// when
|
||||
int resultCount = memberRepository.bulkAgePlus(20); // 벌크 연산에서는 영속성 컨텍스트에 관계없이 DB에 저장된다.
|
||||
Member member5 = memberRepository.findMemberByUsername("member5");
|
||||
assertEquals(40, member5.getAge()); // age 가 41이 아닌 40 이다. 영속성 컨텍스트에 아직 member5의 데이터가 남아있기 때문
|
||||
|
||||
em.flush(); // 커밋
|
||||
em.clear(); // 영속성 컨텍스트를 직접 삭제 하거나 @Modifying(clearAutomatically = true) 옵션을 준다.
|
||||
member5 = memberRepository.findMemberByUsername("member5");
|
||||
assertEquals(41, member5.getAge()); // 영속성 컨텍스트에 캐시가 남아있지 않으므로 DB에서 가져온다.
|
||||
|
||||
// then
|
||||
assertEquals(3, resultCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMemberLazy() {
|
||||
// given
|
||||
// member1 -> teamA
|
||||
// member2 -> teamB
|
||||
|
||||
Team teamA = new Team("teamA");
|
||||
Team teamB = new Team("teamB");
|
||||
teamRepository.save(teamA);
|
||||
teamRepository.save(teamB);
|
||||
Member member1 = new Member("member1", 10, teamA);
|
||||
Member member2 = new Member("member2", 10, teamB);
|
||||
memberRepository.save(member1);
|
||||
memberRepository.save(member2);
|
||||
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// when
|
||||
List<Member> members = memberRepository.findAll();
|
||||
members.forEach(member -> {
|
||||
System.out.println("member.getUsername() = " + member.getUsername());
|
||||
System.out.println("member.getTeam().getClass() = " + member.getTeam().getClass()); // Lazy 로딩 이므로 프록시 객체
|
||||
System.out.println("member.getTeam().getName() = " + member.getTeam().getName()); // 이때 실제로 team 데이터 조회 ( N + 1 문제 발생 )
|
||||
});
|
||||
|
||||
// fetch join 으로 해결 -> 한 번에 team 데이터도 모두 조회
|
||||
// @EntityGraph(attributePaths = {"team"}) 사용 가능
|
||||
List<Member> memberFetchJoin = memberRepository.findMemberFetchJoin();
|
||||
memberFetchJoin.forEach(member -> {
|
||||
System.out.println("member.getUsername() = " + member.getUsername());
|
||||
System.out.println("member.getTeam().getClass() = " + member.getTeam().getClass());
|
||||
System.out.println("member.getTeam().getName() = " + member.getTeam().getName()); // fetch join
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryHint() {
|
||||
// given
|
||||
Member member1 = new Member("member1", 10);
|
||||
memberRepository.save(member1);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// when
|
||||
Member findMember = memberRepository.findReadOnlyByUsername("member1");
|
||||
findMember.setUsername("member2");// read only 옵션으로 변경 감지 X
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
void lock() {
|
||||
// given
|
||||
Member member1 = new Member("member1", 10);
|
||||
memberRepository.save(member1);
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
// when
|
||||
Member findMember = memberRepository.findLockByUsername("member1");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user