๐Spring Data JPA
Spring JPA
Transaction
- ์คํ๋ง ๋ฐ์ดํฐ JPA๋
@Transaction
์์ด๋ ๋ฑ๋ก, ์์ , ์ญ์ ๋ฅผ ์ฒ๋ฆฌํ๋ค. ์๋น์ค ๊ณ์ธต(์ธ๋ถ)
์์ Transaction์ด ์์๋๋ฉด ํด๋น Transaction์ ์ ํ๋ฐ์์ ์ฌ์ฉํ๋ค.- ๊ทธ๋ ์ง ์๋ค๋ฉด
Repository(๋ด๋ถ)
์์ ์์ฒด์ ์ผ๋ก Transaction์ ์์ํ๋ค.
์ฟผ๋ฆฌ ๋ฉ์๋
๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ
- ์์ JPA
public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
return em.createQuery("select m from Member m where m.username = :username and m.age > :age")
.setParameter("username", username)
.setParameter("age", age) .getResultList();
}
- ์คํ๋ง ๋ฐ์ดํฐ JPA
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
์กฐํ
findโฆBy
readโฆBy
queryโฆBy
getโฆBy
findHelloBy
์ฒ๋ผ โฆ์ ์๋ณํ๊ธฐ ์ํ ๋ด์ฉ(์ค๋ช )์ด ๋ค์ด๊ฐ๋ ๋๋ค.
COUNT
long countโฆBy()
EXISTS
boolean existsโฆBy()
DELETE
long deleteโฆBy()
long removeโฆBy()
DISTINCT
findDistinct
findMemberDistinctBy
LIMIT
findFirst3
findFirst
findTop
findTop3
@Query๋ฅผ ํตํด Repository ๋ฉ์๋์ ์ฟผ๋ฆฌ ์ ์ํ๊ธฐ
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username= :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
}
- ์ด์ ํญ๋ชฉ์ธ ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ์ ํ๋ผ๋ฉํฐ๊ฐ ์ฆ๊ฐํ๋ฉด ์ด๋ฆ์ด ๋งค์ฐ ์ง์ ๋ถํด์ง๋ค. ์ด๋ฐ ๊ฒฝ์ฐ
@Query
๋ฅผ ์์ฃผ ์ฌ์ฉํ๊ฒ ๋๋ค. - ์ฟผ๋ฆฌ๋ฌธ์ JPQL์ ํตํด ์์ฑํ๋ค.
@Param
์ ํตํด ์ฟผ๋ฆฌ๋ฌธ์ ํ๋ผ๋ฉํฐ๋ฅผ ๋ฐ์ธ๋ฉํ๋ค.- ์๋์ ๊ฐ์ด ์ปฌ๋ ์
์ ๋ํด์๋
in์
์์ ํ๋ผ๋ฉํฐ๋ฅผ ๋ฐ์ธ๋ฉ ํ ์ ์๋ค.
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);
๊ฐ ํ์ ์กฐํํ๊ธฐ
@Query("select m.username from Member m")
List<String> findUsernameList();
DTO๋ก ์ง์ ์กฐํํ๊ธฐ
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
์กฐํ ๊ฒฐ๊ณผ
- ๋ค์ํ ํ์ ์ผ๋ก ์ง์ ์ด ๊ฐ๋ฅํ๋ค.
์ปฌ๋ ์
- ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ๋น ์ปฌ๋ ์ ์ ๋ฐํํ๋ค.
List<Member> findByUsername(String name);
๋จ๊ฑด
- Optional์ ๋ถ์ผ ์๋ ์๋ค.
- ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด null, 2๊ฑด ์ด์์ผ ๊ฒฝ์ฐ
javax.persistence.NonUniqueResultException
์ผ ๋ฐ์๋๋ค.
Member findByUsername(String name);
Optional<Member> findByUsername(String name);
๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ
- ๊ฐ๊ธ์ ์ฌ์ฉํ์ง ๋ง์.
@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);
ํ์ด์ง๊ณผ ์ ๋ ฌ
ํ๋ผ๋ฉํฐ
org.springframework.data.domain.Sort
: ์ ๋ ฌ ๊ธฐ๋ฅorg.springframework.data.domain.Pageable
: ํ์ด์ง ๊ธฐ๋ฅ (๋ด๋ถ์ Sort ํฌํจ) ํน๋ณํ ๋ฐํ ํ์
๋ฐํํ์
org.springframework.data.domain.Page
: ์ถ๊ฐ count ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํฌํจํ๋ ํ์ด์งorg.springframework.data.domain.Slice
: ์ถ๊ฐ count ์ฟผ๋ฆฌ ์์ด ๋ค์ ํ์ด์ง๋ง ํ์ธ ๊ฐ๋ฅ (๋ด๋ถ์ ์ผ๋ก limit + 1์กฐํ)List (์๋ฐ ์ปฌ๋ ์ )
: ์ถ๊ฐ count ์ฟผ๋ฆฌ ์์ด ๊ฒฐ๊ณผ๋ง ๋ฐํ
count ์ฟผ๋ฆฌ๋ ๋งค์ฐ ๋ฌด๊ฒ๋ค. ๋ณ๋๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ถ๋ฆฌํด์ ๋ง๋๋ ๊ฒ์ ์ถ์ฒํ๋ค.
์์
- Page๋ 1์ด ์๋๋ผ 0๋ถํฐ ์์ํ๋ ๊ฒ์ ์ฃผ์ํ์
Page<Member> findByUsername(String name, Pageable pageable); //count ์ฟผ๋ฆฌ ์ฌ์ฉ
Slice<Member> findByUsername(String name, Pageable pageable); //count ์ฟผ๋ฆฌ ์ฌ์ฉ ์ํจ
List<Member> findByUsername(String name, Pageable pageable); //count ์ฟผ๋ฆฌ ์ฌ์ฉ ์ํจ
List<Member> findByUsername(String name, Sort sort);
// PageRequest๋ Pagable ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ด๋ค
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username")); Page page = memberRepository.findByAge(10, pageRequest);
List<Member> content = page.getContent(); //์กฐํ๋ ๋ฐ์ดํฐ
assertThat(content.size()).isEqualTo(3); //์กฐํ๋ ๋ฐ์ดํฐ ์
assertThat(page.getTotalElements()).isEqualTo(5); //์ ์ฒด ๋ฐ์ดํฐ ์
assertThat(page.getNumber()).isEqualTo(0); //ํ์ด์ง ๋ฒํธ
assertThat(page.getTotalPages()).isEqualTo(2); //์ ์ฒด ํ์ด์ง ๋ฒํธ
assertThat(page.isFirst()).isTrue(); //์ฒซ๋ฒ์งธ ํญ๋ชฉ์ธ๊ฐ?
assertThat(page.hasNext()).isTrue(); //๋ค์ ํ์ด์ง๊ฐ ์๋๊ฐ?
DTO ๋ณํ
Page<Member> page = memberRepository.findByAge(10, pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto());
Controller์์์ ํ์ฉ
- ๊ฐ๋จํ ๊ฒฝ์ฐ์ ๋งค์ฐ ํธ๋ฆฌํ๊ฒ Paging API๋ฅผ ๋ง๋ค ์ ์๋ค.
// ๊ธฐ๋ณธ ํ์ด์ง ์ฌ์ด์ฆ: spring.data.web.pageable.default-page-size=20
// ์ต๋ ํ์ด์ง ์ฌ์ด์ฆ: spring.data.web.pageable.max-page-size=2000
// "/members?page=0&size=3&sort=id,desc&sort=username,desc"
@GetMapping("/members")
public Page list(Pageable pageable) {
Page page = memberRepository.findAll(pageable);
return page;
}
// ์ง์ ์ค์ ๊ฐ์ ์ง์ ํ๋ ๊ฒฝ์ฐ
@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = "username", direction = Sort.Direction.DESC) Pageable pageable) {
Page page = memberRepository.findAll(pageable);
return page;
}
// Dto๋ก ๋ฐํํ๋ ๊ฒฝ์ฐ
@GetMapping("/members")
public Page list(Pageable pageable) {
return memberRepository.findAll(pageable).map(MemberDto::new);
}
๋๋ฉ์ธ ํด๋์ค ์ปจ๋ฒํฐ
- HTTP ํ๋ผ๋ฉํฐ๋ก ๋์ด์จ Entity์ id๋ก ๊ฐ์ฒด๋ฅผ ์ฐพ์์ ๋ฐ์ธ๋ฉํด์ค๋ค.
- ์ฃผ์ํ ์ ์ ๋จ์ ์กฐํ์ฉ์ผ๋ก Entity๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๋ ์ ์ด๋ค. ๋ณ๊ฒฝํ๋๋ผ๋ DB์ ๋ฐ์๋์ง ์๋๋ค.
@RestController
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
}
Bulk Update ์ฟผ๋ฆฌ
- ๋ฒํฌ์ฑ ์์ ๋ฐ ์ญ์ ๋
@Modifying
์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ค. - ๋ฒํฌ ์ฐ์ฐ์ ์์์ฑ ์ปจํ ์คํธ๋ฅผ ๋ฌด์ํ๊ณ ์คํ๋๋ค. ๋ฐ๋ผ์ ๋๊ธฐํ๋ฅผ ์ํด ์์์ฑ ์ปจํ ์คํธ๋ฅผ ์ด๊ธฐํํด์ผ ํ๋ค.
- ๋ฒํฌ์ฑ ์ฟผ๋ฆฌ ํ ์์์ฑ ์ปจํ
์คํธ๋ฅผ ์ด๊ธฐํํ๋ ค๋ฉด
@Modifying(clearAutomatically = true)
๋ฅผ ์ฌ์ฉํ๋ค.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
@EntityGraph
- ์ง์ฐ๋ก๋ฉ์ ํ๋ฅญํ ๊ธฐ๋ฅ์ด์ง๋ง
N+1
๋ฌธ์ ๋ฅผ ๋ฐ์์ํค๊ธฐ๋ ํ๋ค. - ํด๊ฒฐ์ ์ํด
fetch join
์ด๋ผ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์์ง๋ง JPQL์ ์จ์ผํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์๋ค. - ๊ฐํธํ ์ฌ์ฉ์ ์ํด
@EntityGraph
๊ฐ ์ ๊ณต๋๊ณ ๋ด๋ถ์ ์ผ๋กLEFT OUTER JOIN
์ด ์ฌ์ฉ๋๋ค.
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username);
@NamedEntityGraph
- Entity๋ฅผ ์ ์ํ ๋, EntityGraph์ ์ด๋ฆ์ ๋ถ์ฌ ํ์ฉํ ์ ์๋ค.
// Entity ํ์ผ
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team")) @Entity
public class Member {
}
// Repository ํ์ผ
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
JPA Hint์ Lock
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username); // update ์ฟผ๋ฆฌ ๋ฑ์ด ์คํ๋์ง ์๋๋ค.
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);
Projections
- ์ํ๋ ํํ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
- ๋ณต์กํ ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ๋ ์ด๋ ค์ฐ๋ฏ๋ก ๊ทธ๋ฐ ๊ฒฝ์ฐ์๋
QueryDSL
์ ์ฌ์ฉํ์. - ๊ทธ๋๋ ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ณด๋ค ๋ซ๋ค! ๊ทธ๋ฐ ๊ฒฝ์ฐ ์ฌ์ฉํ์.
// Getter ํํ๋ฅผ ์ฌ์ฉ
public interface UsernameOnly {
String getUsername();
}
// SpEL ๋ฌธ๋ฒ ์ฌ์ฉ(๋ค๋ง ๋ชจ๋ ๊ฐ๋ณ๋ก ์กฐํํ ํ ์กฐํฉํ๋ค)
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age + ' ' + target.team.name}")
String getUsername();
}
public interface MemberRepository extends JpaRepository<Member, Long> {
List<UsernameOnly> findProjectionsByUsername(String username);
}