Blog

[SpringIntro] 스프링 DB 접근 기술 - 2, JPA

Category
Author
citeFred
citeFred
Tags
PinOnMain
1 more property
JPA 활용
Table of Content

JPA란?

JDBC, JDBC Template을 사용하면서 중복 코드를 크게 제거했지만 여전히 쿼리문을 직접 작성하는 문제가 있었다. 이는 IDE에서도 오류를 발견하기 어렵고 개발자의 실수가 나타날 수 있는 단점이 있다. JPA를 통해 객체 중심 설계로 전환 할 수 있다.
Java Persistance API의 약자로 ORM(Object-Relational-Mapping) 기술 표준으로 사용되는 인터페이스의 모음
ORM이란?
ORM은 객체와 DB 테이블이 매핑을 이루는 것을 의미
'ORM(Object Relational Mapping)'은 '객체로 연결을 해준다'는 의미로, 어플리케이션과 데이터베이스 연결 시 SQL언어가 아닌 어플리케이션 개발언어로 데이터베이스를 접근할 수 있게 해주는 툴

1. JPA 설정

build.gradle 에서 dependencies 부분에 JPA 의존성을 추가해준다.
dependencies { ... // JPA 추가 implementation 'org.springframework.boot:spring-boot-starter-data-jpa' ... }
Java
복사
application.properties에서 JPA를 통한 쿼리문을 확인 할 수 있도록, JPA가 테이블을 생성 할 수 있도록 하기 위해서는 ddl-auto를 create 속성을 주면 된다.
spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=none
Java
복사

2. Entity 생성

DB의 저장 대상이자 프로젝트에서 객체로 사용되고 있는 Member.java 는 JPA를 통해 관리될 데이터베이스 대상임을 알 수 있도록 @Entity 라는 어노테이션을 추가해준다. 이러면 DB에서 해당 객체를 데이터로 맵핑(연결) 해주게 된다.
@Id 를 통해 PK값을 표시해주고
@Column 을 통해 해당 필드가 데이터의 컬럼에 맵핑되도록 한다.
package com.example.demoapp.domain; import javax.persistence.*; @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") 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
복사

3. Repository 변경

이전 순수 JDBC, JDBC Template까지 빈을 교체하던 부분인 SpringConfig.java에서 이번엔 JpaRepository를 사용하도록 변경해본다.
Jpa는 EntityManager를 통해서 데이터의 영속성이 관리된다. 해당 클래스는 em이란 객체에 할당해 두었다.
package com.example.demoapp.config; import com.example.demoapp.repository.JpaMemberRepository; import com.example.demoapp.repository.MemberRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.persistence.EntityManager; @Configuration public class SpringConfig { private EntityManager em; @Autowired public SpringConfig(EntityManager em) { this.em = em; } @Bean public MemberRepository memberRepository(){ //return new MemoryMemberRepository(); //return new JdbcMemberRepository(dataSource); //return new JdbcTemplateMemberRepository(dataSource); return new JpaMemberRepository(em); } }
Java
복사

4. JpaRepository 구현

JpaMemberRepository.java 라는 구현체를 작성해본다. EntityManager를 통해 CRUD Operation을 실행하게 된다. JDBC 템플릿 코드보다 상당히 많은량을 줄일 수 있으며 특히 특별한 쿼리문 없이 관리되는 것을 볼 수 있다.
조회문 중에서 findByName로직 같은 경우에 쿼리문이 사용된 부분이 있는데 이는 JPQL이라는 객체지향 쿼리 언어로 JPA의 자동 생성 쿼리를 튜닝하고자 할 때 사용 할 수 있는 방법이다.
EntityManager의 createQuery 메소드를 통해서 사용 할 수 있다.
package com.example.demoapp.repository; import com.example.demoapp.domain.Member; import javax.persistence.EntityManager; import java.util.List; import java.util.Optional; public class JpaMemberRepository implements MemberRepository{ private final EntityManager em; 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 member = em.find(Member.class, id); return Optional.ofNullable(member); } // JPQL @Override public Optional<Member> findByName(String name) { List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class) .setParameter("name", name) .getResultList(); return result.stream().findAny(); } // JPQL @Override public List<Member> findAll() { return em.createQuery("select m from Member m", Member.class) .getResultList(); } }
Java
복사

5. 트랜잭션

클래스 또는 메서드 위에 @Transactional을 붙이면, 트랜잭션 기능이 적용된 프록시 객체가 생성되며, 트랜잭션 성공 여부에 따라 Commit 또는 Rollback 작업이 이루어진다.
서비스 레이어의 회원 가입 메서드 레벨에서 트랜잭션을 사용하였다.
package com.example.demoapp.service; import com.example.demoapp.domain.Member; import com.example.demoapp.repository.MemberRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class MemberService { private final MemberRepository memberRepository; @Autowired public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } /** * 회원 가입 */ @Transactional public Long join(Member member) { //같은 이름 제외하는 경우 validateDuplicateMemberName(member); memberRepository.save(member); return member.getId(); } private void validateDuplicateMemberName(Member member) { 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로 Repository를 변경한 부분을 기존 통합 테스트로 한번 더 테스트해본다. 교체된 JpaRepository로 정상적으로 EntityManager를 통해서 서비스 로직이 작동 하는 것을 확인 할 수 있다.
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio