Skip to content

Commit 56a9b76

Browse files
committed
learning spring data jpa
1 parent 914f06e commit 56a9b76

29 files changed

+829
-11
lines changed

book/spring-data-jpa/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ repositories {
2323
}
2424

2525
dependencies {
26+
// https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy-dep
27+
compile group: 'net.bytebuddy', name: 'byte-buddy-dep', version: '1.10.8'
28+
2629
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2730
implementation 'org.springframework.boot:spring-boot-starter-web'
2831

Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
package datajpa;
22

3+
import java.util.Optional;
4+
import java.util.UUID;
5+
36
import org.springframework.boot.SpringApplication;
47
import org.springframework.boot.autoconfigure.SpringBootApplication;
58
import org.springframework.context.ConfigurableApplicationContext;
6-
7-
import datajpa.repository.MemberRepository;
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.data.domain.AuditorAware;
11+
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
812

913
@SpringBootApplication
1014
//@EnableJpaRepositories(basePackages = { "datajpa" })
15+
@EnableJpaAuditing
1116
public class DataJpaApplication {
1217

13-
public static void main(String[] args) {
18+
public static void main(String[] args) throws Exception {
1419
ConfigurableApplicationContext ctx = SpringApplication.run(DataJpaApplication.class, args);
15-
MemberRepository bean = ctx.getBean(MemberRepository.class);
16-
System.out.println(bean.getClass().getName());
20+
}
1721

22+
@Bean
23+
public AuditorAware<String> auditorProvider() {
24+
return () -> Optional.of(UUID.randomUUID().toString());
1825
}
19-
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package datajpa.controller;
2+
3+
import java.util.stream.IntStream;
4+
5+
import javax.annotation.PostConstruct;
6+
7+
import org.springframework.data.domain.Page;
8+
import org.springframework.data.domain.Pageable;
9+
import org.springframework.data.web.PageableDefault;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
import datajpa.dto.MemberDto;
15+
import datajpa.entity.Member;
16+
import datajpa.repository.MemberRepository;
17+
import lombok.RequiredArgsConstructor;
18+
import lombok.extern.slf4j.Slf4j;
19+
20+
@RestController
21+
@Slf4j
22+
@RequiredArgsConstructor
23+
public class MemberController {
24+
25+
private final MemberRepository memberRepository;
26+
27+
@GetMapping("/members/{id}")
28+
public String findMembers(@PathVariable("id") Long id) {
29+
Member member = memberRepository.findById(id).get();
30+
return member.getUsername();
31+
}
32+
33+
@GetMapping("/members2/{id}")
34+
public String findMember2(@PathVariable("id") Member member) {
35+
return member.getUsername();
36+
}
37+
38+
@GetMapping("/members")
39+
public Page<MemberDto> getMembers(@PageableDefault(size = 5) Pageable pageable) {
40+
return memberRepository.findAll(pageable)
41+
.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
42+
}
43+
44+
@PostConstruct
45+
private void init() {
46+
IntStream.range(1, 100).forEach(i -> {
47+
memberRepository.save(new Member("user" + i, i));
48+
});
49+
}
50+
}

book/spring-data-jpa/src/main/java/datajpa/dto/MemberDto.java

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package datajpa.dto;
22

3+
import datajpa.entity.Member;
34
import lombok.AllArgsConstructor;
45
import lombok.Data;
56

@@ -10,4 +11,10 @@ public class MemberDto {
1011
private Long id;
1112
private String username;
1213
private String teamName;
14+
15+
public MemberDto(Member member) {
16+
this.id = member.getId();
17+
this.username = member.getUsername();
18+
this.teamName = member.getTeam() != null ? member.getTeam().getName() : null;
19+
}
1320
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package datajpa.entity;
2+
3+
import java.time.LocalDateTime;
4+
5+
import javax.persistence.Column;
6+
import javax.persistence.EntityListeners;
7+
import javax.persistence.MappedSuperclass;
8+
9+
import org.springframework.data.annotation.CreatedBy;
10+
import org.springframework.data.annotation.CreatedDate;
11+
import org.springframework.data.annotation.LastModifiedBy;
12+
import org.springframework.data.annotation.LastModifiedDate;
13+
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
14+
15+
import lombok.Getter;
16+
17+
/**
18+
* Spring data jpa audit
19+
*/
20+
@EntityListeners(AuditingEntityListener.class)
21+
@MappedSuperclass
22+
@Getter
23+
public class BaseEntity {
24+
25+
@CreatedDate
26+
@Column(updatable = false)
27+
private LocalDateTime createdDate;
28+
29+
@LastModifiedDate
30+
private LocalDateTime lastModifiedDate;
31+
32+
@CreatedBy
33+
@Column(updatable = false)
34+
private String createdBy;
35+
36+
@LastModifiedBy
37+
private String lastModifiedBy;
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datajpa.entity;
2+
3+
import java.time.LocalDateTime;
4+
5+
import javax.persistence.Column;
6+
import javax.persistence.MappedSuperclass;
7+
import javax.persistence.PrePersist;
8+
import javax.persistence.PreUpdate;
9+
10+
import lombok.Getter;
11+
12+
/**
13+
* JPA 기반 Audit
14+
*/
15+
@MappedSuperclass
16+
@Getter
17+
public class JpaBaseEntity {
18+
19+
@Column(updatable = false)
20+
private LocalDateTime createdDate;
21+
private LocalDateTime updatedDate;
22+
23+
@PrePersist
24+
private void prePersist() {
25+
final LocalDateTime now = LocalDateTime.now();
26+
createdDate = now;
27+
updatedDate = now;
28+
}
29+
30+
@PreUpdate
31+
private void preUpdate() {
32+
updatedDate = LocalDateTime.now();
33+
}
34+
}

book/spring-data-jpa/src/main/java/datajpa/entity/Member.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
query = "select m from Member m where m.username = :username"
2828
)
2929
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
30-
public class Member {
30+
// public class Member extends JpaBaseEntity {
31+
public class Member extends BaseEntity {
3132

3233
@Id
3334
@GeneratedValue

book/spring-data-jpa/src/main/java/datajpa/repository/MemberRepository.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import datajpa.dto.MemberDto;
2121
import datajpa.entity.Member;
2222

23-
public interface MemberRepository extends JpaRepository<Member, Long> {
23+
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
2424

2525
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
2626

@@ -83,12 +83,18 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
8383
List<Member> findMemberEntityGraph2();
8484

8585
// ===================== JPA Hint & Lock
86-
// read only를 모든 메소드에 넣는거 보다, 성능 테스트 후 얻는 이점이 있어야..
86+
// read only를 모든 메소드에 넣는거 보다, 성능 테스트 후 얻는 이점이 있어야 적용
8787
@QueryHints(value = {
8888
@QueryHint(name = "org.hibernate.readOnly", value = "true")
8989
})
9090
Member findReadOnlyByUsername(String username);
9191

92+
// forCounting : 추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용
93+
@QueryHints(value = {
94+
@QueryHint(name = "org.hibernate.readOnly", value = "true")
95+
}, forCounting = true)
96+
Page<Member> findReadOnlyAllByUsername(String name, Pageable pageable);
97+
9298
@Lock(LockModeType.PESSIMISTIC_WRITE)
9399
List<Member> findLockByUsername(String username);
94100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package datajpa.repository;
2+
3+
import java.util.List;
4+
5+
import datajpa.entity.Member;
6+
7+
public interface MemberRepositoryCustom {
8+
9+
List<Member> findMemberCustom();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package datajpa.repository;
2+
3+
import java.util.List;
4+
5+
import javax.persistence.EntityManager;
6+
7+
import datajpa.entity.Member;
8+
import lombok.RequiredArgsConstructor;
9+
10+
// 네이밍은 해당 "repository name + Impl"
11+
// (2.x 부터는 사용자 정의 인터페이스 + Impl 방식도 지원 <== 권장)
12+
// 변경 원하면 @EnableJpaRepositories에서 postfix 변경
13+
@RequiredArgsConstructor
14+
public class MemberRepositoryImpl implements MemberRepositoryCustom {
15+
16+
private final EntityManager em;
17+
18+
@Override
19+
public List<Member> findMemberCustom() {
20+
return em.createQuery("select m from Member m", Member.class)
21+
.getResultList();
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package datajpa.service;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.transaction.annotation.Transactional;
5+
6+
import datajpa.entity.Member;
7+
import datajpa.repository.MemberRepository;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
11+
/**
12+
*/
13+
@Slf4j
14+
@Service
15+
@Transactional(readOnly = true)
16+
@RequiredArgsConstructor
17+
public class MemberService {
18+
19+
private final MemberRepository memberRepository;
20+
21+
@Transactional(readOnly = false)
22+
public Long saveMember(Member member) {
23+
System.out.println("MemberService::saveMember is called");
24+
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
25+
26+
for (StackTraceElement elt : stackTrace) {
27+
System.out.println(elt);
28+
}
29+
30+
return memberRepository.save(member).getId();
31+
}
32+
}

book/spring-data-jpa/src/main/resources/application.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
spring:
2+
data:
3+
web:
4+
pageable:
5+
max-page-size: 200
6+
default-page-size: 20
27
datasource:
38
url: jdbc:h2:tcp://localhost/~/datajpa
49
username: sa
@@ -16,4 +21,7 @@ spring:
1621

1722
logging.level:
1823
org.hibernate.SQL: trace
24+
# org:
25+
# springframework:
26+
# transaction: trace
1927
# org.hibernate.type: trace

book/spring-data-jpa/src/test/java/datajpa/entity/MemberTest.java

+34
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package datajpa.entity;
22

3+
import static org.assertj.core.api.Assertions.assertThat;
4+
35
import java.util.List;
6+
import java.util.Optional;
47

58
import javax.persistence.EntityManager;
69
import javax.persistence.PersistenceContext;
710

811
import org.junit.jupiter.api.Test;
12+
import org.springframework.beans.factory.annotation.Autowired;
913
import org.springframework.boot.test.context.SpringBootTest;
1014
import org.springframework.test.annotation.Rollback;
1115
import org.springframework.transaction.annotation.Transactional;
1216

17+
import datajpa.repository.MemberRepository;
18+
1319
@SpringBootTest
1420
@Transactional
1521
@Rollback(false)
@@ -18,6 +24,9 @@ public class MemberTest {
1824
@PersistenceContext
1925
private EntityManager em;
2026

27+
@Autowired
28+
private MemberRepository memberRepository;
29+
2130
@Test
2231
public void testEntity() {
2332
Team teamA = new Team("teamA");
@@ -48,4 +57,29 @@ public void testEntity() {
4857
System.out.println("-> member.team = " + member.getTeam());
4958
}
5059
}
60+
61+
@Test
62+
public void testJpaEventBaseEntity() throws Exception {
63+
// given
64+
Member member = new Member("member1");
65+
memberRepository.save(member);
66+
67+
Thread.sleep(100L);
68+
member.setUsername("member2");
69+
70+
em.flush(); // @PreUpdate
71+
em.clear();
72+
73+
// when
74+
Optional<Member> findOptional = memberRepository.findById(member.getId());
75+
76+
// then
77+
assertThat(findOptional.isPresent()).isTrue();
78+
Member find = findOptional.get();
79+
assertThat(find.getCreatedDate()).isNotNull();
80+
// assertThat(find.getUpdatedDate()).isNotNull();
81+
assertThat(find.getLastModifiedDate()).isNotNull();
82+
assertThat(find.getCreatedBy()).isNotNull();
83+
assertThat(find.getLastModifiedBy()).isNotNull();
84+
}
5185
}

0 commit comments

Comments
 (0)