Search

[springboot] μŠ€ν”„λ§ μž…λ¬Έ - 6. μŠ€ν”„λ§ DB 연동

νƒ€μž…
μŠ€ν„°λ””
νƒœκ·Έ
springboot
μƒνƒœ
Published
생성일
2022/07/22 09:46
μ΅œμ’… νŽΈμ§‘ μΌμ‹œ
2023/02/27 03:14
μƒμœ„ ν•­λͺ©
1 more property

mysql μ—°κ²°

1.
mysql μ„€μΉ˜
2.
build.gradle
mysql connector λ₯Ό gradle 에 μΆ”κ°€ν•΄μ£Όκ³  import μ‹œν‚¨λ‹€.
implementation 'mysql:mysql-connector-java'
Plain Text
볡사
3.
application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/<db>?serverTimezone=Asia/Seoul username: <username> password: <password>
YAML
볡사
create table member ( id bigint not null auto_increment, name varchar(255), primary key (id) );
SQL
볡사

JPA

jpa κ΄€λ ¨ 라이브러리 μΆ”κ°€
build.gradle
implementation 'mysql:mysql-connector-java' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Plain Text
볡사
jpa μ„€μ • μΆ”κ°€
application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Seoul username: <username> password: <pw> jpa: show-sql: true hibernate: ddl-auto: none
YAML
볡사
β€’
show-sql : true
JPA κ°€ μƒμ„±ν•˜λŠ” SQL 좜λ ₯
β€’
ddl-auto : none
ν…Œμ΄λΈ”μ„ μžλ™μœΌλ‘œ μƒμ„±ν•˜μ§€ μ•ŠλŠ”λ‹€. (create μ‹œ ν…Œμ΄λΈ”λ„ 직접 생성해쀀닀.)
jpa λŠ” μžλ°” μ§„μ˜μ˜ ν‘œμ€€ ORM μΈν„°νŽ˜μ΄μŠ€μ΄κ³ , hibernate λŠ” jpa λ₯Ό κ΅¬ν˜„ν•œ ν”„λ ˆμž„μ›Œν¬μ΄λ‹€.
μ—”ν‹°ν‹° μž‘μ„±
domain/Member.java
@Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // PK private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Java
볡사
Β @Entity μ–΄λ…Έν…Œμ΄μ…˜μ„ λ‹¬μ•„μ€˜μ•Όν•œλ‹€.
μŠ€ν”„λ§ 섀정에 JPA μ„€μ • μΆ”κ°€
SpringConfig.java
@Configuration public class SpringConfig { private final EntityManager em; // 1️⃣ @Autowired public SpringConfig(EntityManager em) { this.em = em; } @Bean public MemberService memberService() { return new MemberService(memberRepository()); } @Bean public MemberRepository memberRepository() { // return new MemoryMemberRepository(); return new JpaMemberRepository(em); // 2️⃣ 객체지ν–₯ λ‹€ν˜•μ„±μ˜ μž₯점 } }
Java
볡사
Β JPA κ°€ EntityManager λ₯Ό μžλ™μœΌλ‘œ bean 에 등둝해주고, 이 EntityManager λ₯Ό 톡해 μ—”ν‹°ν‹°λ₯Ό κ΄€λ¦¬ν•œλ‹€.
Β : μŠ€ν”„λ§μ˜ μž₯점으둜 객체지ν–₯적인 섀계가 νŽΈλ¦¬ν•˜λ‹€λŠ” 것인데, 특히 μΈν„°νŽ˜μ΄μŠ€λ₯Ό 두고 κ΅¬ν˜„μ²΄λ§Œ λ°”κΏ” 끼울 수 μžˆλŠ” λ‹€ν˜•μ„± 이 큰 μž₯점이닀.
Β : Β JpaMemberRepository 객체에 em 을 μ£Όμž…ν•œλ‹€.
Β μŠ€ν”„λ§ λΉˆμ„ 직접 μž‘μ„±ν•΄μ„œ μƒμ„±ν•˜λŠ” λ°©μ‹μ΄λ―€λ‘œ MemberService 와 MemberRepository ν΄λž˜μŠ€μ—μ„œ @Service, @Repository, @Autowired μ–΄λ…Έν…Œμ΄μ…˜μ„ μ—†μ• μ•Ό ν•œλ‹€.
JPA 멀버 리포지토리 κ΅¬ν˜„
repository/JpaMemberRepository.java
public class JpaMemberRepository implements MemberRepository{ private final EntityManager em; // JPA λ₯Ό μ“°λ €λ©΄ em 에 DI λ₯Ό λ°›μ•„μ•Όν•œλ‹€. public JpaMemberRepository(EntityManager em) { this.em = em; } @Override public Member save(Member member) { em.persist(member); // μ˜μ†ν™”(μ˜κ΅¬μ €μž₯) return member; } @Override public Optional<Member> findById(Long id) { // Member.class : μ‘°νšŒν•  νƒ€μž… // id : μ‘°νšŒν•  νƒ€μž…μ˜ κ°’ Member member = em.find(Member.class, id); return Optional.ofNullable(member); } @Override public Optional<Member> findByName(String name) { // jpql 쿼리 μ–Έμ–΄ ( μ—”ν‹°ν‹° 객체λ₯Ό λŒ€μƒμœΌλ‘œ λ‚ λ¦¬λŠ” 쿼리 ) // 쿼리 결과도 객체가 됨 List<Member> result = em.createQuery("select m from Member as m where m.name = :name", Member.class) .setParameter("name", name) .getResultList(); return result.stream().findAny(); } @Override public List<Member> findAll() { // jpql 쿼리 μ–Έμ–΄ ( μ—”ν‹°ν‹° 객체λ₯Ό λŒ€μƒμœΌλ‘œ λ‚ λ¦¬λŠ” 쿼리 ) // 쿼리 결과도 객체가 됨 return em.createQuery("select m from Member as m", Member.class) .getResultList(); } }
Java
볡사
Β μ—¬κΈ°μ„œλŠ” 순수 JPA λΌμ„œ JPQL 을 μ‚¬μš©ν•΄μ„œ 쿼리 λΉ„μŠ·ν•œ μ–Έμ–΄λ₯Ό μ‚¬μš©ν–ˆμ§€λ§Œ μŠ€ν”„λ§ 데이터 JPA λ₯Ό μ‚¬μš©ν•˜λ©΄ 쿼리문도 μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
Β select m from Member m ~
1.
select m Member as m ~ μ—μ„œ as λ₯Ό μƒλž΅ν•œ κ²°κ³Ό
2.
m 은 DB ν…Œμ΄λΈ”μ΄ μ•„λ‹ˆλΌ JPA 에 μ˜ν•΄ λ§€ν•‘λœ μžλ°” 객체λ₯Ό μ˜λ―Έν•¨
Member μ„œλΉ„μŠ€
MemberService.java
@Transactional // JPA λ₯Ό μ‚¬μš©ν•œ λͺ¨λ“  데이터 변경은 νŠΈλž™μž­μ…˜ μ•ˆμ—μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό 함 public class MemberService { private final MemberRepository memberRepository; @Autowired // MemberService 객체λ₯Ό 생성할 λ•Œ, memberRepository μ˜μ‘΄μ„± μ£Όμž… public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } // νšŒμ›κ°€μž… public Long join(Member member) { // 같은 이름이 μžˆλŠ” 쀑볡 νšŒμ› 검증 ν›„ 쀑볡 있으면 μ˜ˆμ™Έμ²˜λ¦¬ validateDuplicateMember(member); memberRepository.save(member); return member.getId(); } private void validateDuplicateMember(Member member) { // ifPresent : optional 객체 λ©”μ„œλ“œ memberRepository.findByName(member.getName()) .ifPresent(m -> { throw new IllegalStateException("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); }); } // νšŒμ› 전체 쑰회 public List<Member> findMembers() { return memberRepository.findAll(); } // νšŒμ› ν•œ λͺ… 쑰회 public Optional<Member> findOne(Long memberId) { return memberRepository.findById(memberId); } }
Java
볡사
Β JPA λ₯Ό μ‚¬μš©ν•œ λͺ¨λ“  데이터 변경은 νŠΈλž™μž­μ…˜ μ•ˆμ—μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λ―€λ‘œ, 클래슀 μœ„μ— @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ„ 달아쀀닀.

ν†΅ν•©ν…ŒμŠ€νŠΈ μž‘μ„±

β€’
λ‹¨μœ„ ν…ŒμŠ€νŠΈ : μž‘μ„±ν•œ μ½”λ“œ λ‚΄μ—μ„œλ§Œ μž‘λ™ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈ ( μˆœμˆ˜ν•œ μžλ°” μ½”λ“œκ°€ 잘 μž‘λ™ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈ )
β€’
톡합 ν…ŒμŠ€νŠΈ : μž‘μ„±ν•œ μ½”λ“œ 뿐만 μ•„λ‹ˆλΌ μ™ΈλΆ€ λͺ¨λ“ˆ(ex. μŠ€ν”„λ§ λΆ€νŠΈ, DB, μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆ) κΉŒμ§€ 같이 μ‹€ν–‰μ‹œμΌœμ„œ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈ
MemberServiceIntegrationTest.java
@SpringBootTest // 1️⃣ μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ λ„μš°λŠ” ν†΅ν•©ν…ŒμŠ€νŠΈ @Transactional // 2️⃣ ν…ŒμŠ€νŠΈ μ‹€ν–‰ 후에 둀백을 ν•΄μ€˜μ„œ 각 ν…ŒμŠ€νŠΈκ°€ λλ‚œ 후에 DB λ‹€μ‹œ 원상볡ꡬ public class MemberServiceIntegrationTest { // 3️⃣ ν•„λ“œ 기반 auto injection λ°›μŒ @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Test // @Commit // 4️⃣ @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλ”λΌλ„ DB에 μ»€λ°‹κΉŒμ§€ ν•΄μ„œ ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ„ DB에 λ‚¨μŒ void νšŒμ›κ°€μž…() { // given (주어진 상황) Member member = new Member(); member.setName("hello"); // when (κ²€μ¦ν•˜λ €κ³  ν•˜λŠ” 상황) Long saveId = memberService.join(member); // then (검증) Member findMember = memberService.findOne(saveId).get(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test public void 쀑볡_νšŒμ›κ°€μž…_μ˜ˆμ™Έ() { // given Member member1 = new Member(); member1.setName("spring"); // member1 κ³Ό μ€‘λ³΅λœ μ΄λ¦„μœΌλ‘œ μ„€μ • Member member2 = new Member(); member2.setName("spring"); // when memberService.join(member1); // then IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); /* 같은 둜직 try{ memberService.join(member2); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€."); } */ } }
Java
볡사
Β @SpringBootTest : μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ λ„μš°λŠ” ν†΅ν•©ν…ŒμŠ€νŠΈ
Β @Transactional : ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ— 이 μ• λ…Έν…Œμ΄μ…˜μ΄ 있으면, ν…ŒμŠ€νŠΈ μ‹œμž‘ 전에 νŠΈλžœμž­μ…˜μ„ μ‹œμž‘ν•˜κ³ , ν…ŒμŠ€νŠΈ μ™„λ£Œ 후에 항상 λ‘€λ°±ν•œλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ DB에 데이터가 남지 μ•ŠμœΌλ―€λ‘œ λ‹€μŒ ν…ŒμŠ€νŠΈμ— 영ν–₯을 주지 μ•ŠλŠ”λ‹€.
β‡’ 반볡적인 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ μ‹€ν–‰ κ°€λŠ₯
@Transactional λ₯Ό μ‚¬μš©ν•˜λ©΄ @AfterEach 둜 DB 데이터 μ‚­μ œν•˜λŠ” λ‘œμ§μ„ μ•ˆ μ§œλ„ λœλ‹€
Β μŠ€ν”„λ§ 톡합 ν…ŒμŠ€νŠΈμ΄κΈ° λ•Œλ¬Έμ— @Autowired λ₯Ό μ‚¬μš©ν•΄μ„œ μŠ€ν”„λ§ λΉˆμ— μ˜ν•΄ μ›ν•˜λŠ” 객체λ₯Ό μ£Όμž…λ°›μ„ 수 μžˆλ‹€.
Β @Commit : @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλ”λΌλ„ DB에 μ»€λ°‹κΉŒμ§€ ν•΄μ„œ ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ„ DB에 λ‚¨μŒ
Recall ν†΅ν•©ν…ŒμŠ€νŠΈ : μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ λ„μ›Œμ§€κΈ° λ•Œλ¬Έμ— μŠ€ν”„λ§ 빈 μ‚¬μš© κ°€λŠ₯
μœ λ‹›ν…ŒμŠ€νŠΈ(λŒ μœ„ν…ŒμŠ€νŠΈ) : μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ λ„μ›Œμ§€μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μŠ€ν”„λ§ 빈 μ‚¬μš© λΆˆκ°€

μŠ€ν”„λ§ 데이터 JPA

μŠ€ν”„λ§ 데이터 JPA ν”„λ ˆμž„μ›Œν¬λŠ” JPA λ₯Ό μ‚¬μš©ν•˜κΈ° μ‰½κ²Œ λ§Œλ“€μ–΄μ£ΌλŠ” ν”„λ ˆμž„μ›Œν¬μ΄λ‹€.
기본적인 CRUD λ©”μ„œλ“œλ“€μ„ 기본적으둜 μ œκ³΅ν•œλ‹€.
μŠ€ν”„λ§λ°μ΄ν„°JPA 리포지토리 κ΅¬ν˜„
SpringDataJpaMemberRepository.java
// μΈν„°νŽ˜μ΄μŠ€μ—μ„œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ°›μ•„μ˜¬ 땐 extends λ₯Ό μ‚¬μš© // μΈν„°νŽ˜μ΄μŠ€λŠ” 닀쀑상속이 κ°€λŠ₯ // @Bean μ„€μ • μ•ˆν•΄μ€˜λ„ μŠ€ν”„λ§μ΄ μ•Œμ•„μ„œ λ“±λ‘ν•΄μ€Œ public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository { // μ»€μŠ€ν…€ λ©”μ„œλ“œ μΆ”κ°€ // μΈν„°νŽ˜μ΄μŠ€λ§Œ λ§Œλ“€μ–΄μ€˜λ„ μŠ€ν”„λ§ 데이터 JPA κ°€ μ•Œμ•„μ„œ κ΅¬ν˜„ν•΄μ€Œ // select m from Member as m where m.name = ? JPQL 둜 λ²ˆμ—­λΌμ„œ 싀행됨 @Override Optional<Member> findByName(String name); }
Java
볡사
Β JpaRepository<Member, Long>
첫 번째 인자 : λ ˆν¬μ§€ν† λ¦¬ 객체 νƒ€μž…
두 번째 인자 : μ‹λ³„μž(PK) νƒ€μž…
Β findByName λ©”μ„œλ“œλŠ” 기본적으둜 μ œκ³΅ν•˜λŠ” λ©”μ„œλ“œκ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 직접 μž‘μ„±(Override) ν–ˆλ‹€.
Β λ‚΄λΆ€μ μœΌλ‘œ findByName λŠ” select m from Member as m where m.name = ? 의 JPQL 둜 λ²ˆμ—­λΌμ„œ μ‹€ν–‰λœλ‹€.
μŠ€ν”„λ§ 데이터 JPA 원리 JpaRepository<T, ID> μΈν„°νŽ˜μ΄μŠ€λ₯Ό 상속받고 μžˆλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μŠ€ν”„λ§μ΄ μžλ™μœΌλ‘œ κ΅¬ν˜„μ²΄λ₯Ό λ§Œλ“  λ‹€μŒ bean으둜 등둝해쀀닀.
μŠ€ν”„λ§ μ„€μ • λ³€κ²½
μŠ€ν”„λ§λ°μ΄ν„°JPA λ₯Ό μœ„ν•œ μŠ€ν”„λ§ 섀정을 λ³€κ²½ν•œλ‹€.
SpringConfig.java
@Configuration public class SpringConfig { private final MemberRepository memberRepository; // 1️⃣ μŠ€ν”„λ§ 데이터 JPA κ°€ 등둝해쀀 MemberRepository bean 을 μ΄μš©ν•΄μ„œ DI λ°›μŒ @Autowired public SpringConfig(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Bean public MemberService memberService() { return new MemberService(memberRepository); } }
Java
볡사
Β  이 μ½”λ“œ μƒμœΌλ‘œλŠ” MemberRepository bean 이 μ—†λŠ” 것 처럼 λ³΄μ΄μ§€λ§Œ, μ‹€μ œλ‘œλŠ” μŠ€ν”„λ§λ°μ΄ν„°JPA κ°€ MemberRepository bean 을 μžλ™μœΌλ‘œ λ“±λ‘ν•΄μ€¬μŒ ( JpaRepository μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†λ°›λŠ” κ²½μš°μ—λ§Œ )
β‡’ μŠ€ν”„λ§λ°μ΄ν„°JPA λ₯Ό μ‚¬μš©ν•˜κ²Œ 되면, λ¦¬ν¬μ§€ν† λ¦¬λŠ” λ”°λ‘œ Bean 으둜 등둝해쀄 ν•„μš”κ°€ μ—†λ‹€. (μ•Œμ•„μ„œ ν•΄μ€Œ)
μŠ€ν”„λ§λ°μ΄ν„° JPA 기본지원 λ©”μ„œλ“œ
β€’
기본적인 CRUD λ©”μ„œλ“œ 제곡
β€’
findByName(), findByEmail() 처럼 λ©”μ„œλ“œ μ΄λ¦„λ§ŒμœΌλ‘œ 쑰회 κΈ°λŠ₯ 제곡
β€’
νŽ˜μ΄μ§• κΈ°λŠ₯ 제곡