JPA 개념
- Java Persistence API 의 약자
- JAVA에서 제공하는 API이다.
- RDBMS와 OOP 객체 사이의 불일치에서 오는 패러다임을 해결하기 위해 만들어졌다.
- ORM(Object-Relational Mapping) 기술이다.
- 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스다.
- JPA를 사용하기 위해서는 JPA를 구현한 ORM 프레임워크를 사용해야 한다. (Hibername, EclipseLink, DataNucleus 등)
ORM
ORM이란 객체와 DB의 테이블이 매핑을 이루는 것 (객체가 테이블이 되도록 매핑시켜주는 것)
ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메소드)로 데이터를 조작할 수 있다.
예
MySQL ORM SELECT * FROM user; user.findAll()
장점
- 생산성이 높아진다. (간단한 메소드로 CRUD가 가능하다.)
- 유지보수가 쉽다. (필드 변경 시 모든 SQL을 수정하지 않고 필드만 추가하면 된다.)
단점
- Query가 복잡해질 경우 ORM으로 표현하는 데 한계가 있다.
- 성능이 raw query에 비해 느리다. (한 프로젝트 내에서Mybatis와 같이 사용하기도 한다.)
Spring Data JPA
Spring Data JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크이다.
JPA를 한 단계 추상화시킨
Repository
라는 인터페이스를 제공하면서 JPA를 더 쉽고 편하게 사용할 수 있도록 도와준다.Repository
인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.추상화했다는 말은, Spring Data JPA로
Repository
를 구현할 때 JPA를 사용하고 있다는 것이다.Repository 인터페이스 기본 구현체인 SimpleJpaRepository의 코드에서 내부적으로 EntityManager를 사용하고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> { private final EntityManager em; public Optional<T> findById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); Class<T> domainType = getDomainClass(); if (metadata == null) { return Optional.ofNullable(em.find(domainType, id)); } LockModeType type = metadata.getLockModeType(); Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap(); return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints)); } // Other methods... }
- JPA, Hibernate, Spring Data JPA의 전반적인 개념
- 추상화 정도는
Spring Data JPA > Hibernate > JPA
이다.- Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만
구현체 교체의 용이성, 저장소 교체의 용이성
때문에 Spring Data JPA를 사용하는 것이 좋다.
- Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만
스프링에서 JPA 사용하기
JPA 개발 환경 구성
- 라이브러리 설정
MAVEN 프로젝트 : pom.xml
에 의존성 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- H2 database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 스프링부트용 Spring Data JPA 추상화 라이브러리 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Lombok 라이브러리 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
- h2
- 인메모리 관계형 데이터베이스
- 별도의 설치없이 프로젝트 의존성만으로 관리할 수 있다.
- 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다.
- spring-boot-starter-data-jpa
- 스프링 부트용 Spring Data JPA 추상화 라이브러리
- 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리들의 버전을 관리해준다.
- lombok
- 반복되는 getter, setter, tostring 등의 메서드 작성코드를 줄여주는 라이브러리
Gradle 프로젝트 : build.gradle
에 의존성 추가
1
2
3
4
5
6
7
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-stater-data-jpa')
compile('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
- 환경 설정
application.properties
에 설정
1
2
3
4
5
6
7
8
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=false
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=1234
spring.datasource.driver-class-name=org.h2.Driver
- spring.jpa.hibernate.ddl-auto
- 애플리케이션 구동 시(실행 시) JPA의 데이터베이스 초기화 전략
- none : 사용하지 않음
- create : 기존 테이블 삭제 후 테이블 생성
- create-drop : 기존 테이블 삭제 후 테이블 생성, 종료 시점에 테이블 삭제
- update : 변경된 스키마 적용
- validate : 엔티티와 테이블 정상 매핑 확인
- 애플리케이션 구동 시(실행 시) JPA의 데이터베이스 초기화 전략
- spring.jpa.generate-ddl
- true로 설정 시
@Entity
가 명시된 클래스를 찾아서 DDL을 생성하고 실행
- true로 설정 시
- spring.jpa.show-sql
- 콘솔창에 출력되는 쿼리를 가독성 좋게 포맷팅
- spring.datasource
- h2 데이터베이스 설정
Spring Data JPA 사용
- 엔티티 설계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name="member")
@Getter @Setter
@ToString
public class Member {
@Id
@Column(name="member_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@Column(unique = true)
private String email;
}
- @Entity
- JPA 엔티티 클래스라는 것을 알려준다.
- 속성
- name : JPA에서 사용할 엔티티의 이름을 지정한다. 같은 클래스 이름이 없으면 가급적 기본값을 사용 (기본값 : 클래스이름)
- Entity 클래스는 반드시 기본키를 가져야 한다. (@Id 어노테이션 사용)
- @Table
- 엔티티와 매핑할 테이블을 지정한다.
- 생략 시 엔티티 이름을 테이블 이름으로 사용한다.
- @Column
- 필드와 컬럼 매핑
- 속성
- name : 필드를 매핑할 컬럼의 이름 설정
- unique : 유니트 제약 조건 설정
- @GeneratedValue
- 기본키를 생성하는 전략(방법)
- GenerationType_AUTO(default) : JPA 구현체가 자동으로 생성 전략 결정 (IDENTITY, SEQUENCE, TABLE 중 하나)
- GenerationType.IDENTITY : 기본키 생성을 데이터베이스에 위임
- GenerationType.SEQUENCE : 데이터베이스 시퀀스 오브젝트를 이용한 기본키 생성 (@SequenceGenerator를 사용하여 시퀀스 등록 필요-순차적으로 증가하는 값을 반환하는 데이터베이스 객체)
- GenerationType.TABLE : 키 생성용 테이블 사용 (@TableGenerator 필요)
- 기본키를 생성하는 전략(방법)
- Repository 설계
1
2
public interface MemberRepository extends JpaRepository<Member, Long>{
}
Repository는 JPARepository를 상속받는다.
- JpaRepository<T, ID> : 제네릭은 사용하고자 하는 엔티티와 엔티티의 식별자 타입을 입력한다.
JPARepository는 기본적인 CRUD 및 페이징 처리를 위한 메소드가 정의되어 있다.
메소드 설명 <S extends T> save(S entity) 엔티티 저장 및 수정 void delete(T entity) 엔티티 삭제 count() 엔티티 총 개수 반환 Iterable<T> findAll() 모든 엔티티 조회
- 메인 어플리케이션 작성
- 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Slf4j
@SpringBootApplication
public class JpaExam8Application {
public static void main(String[] args) {
SpringApplication.run(JpaExam8Application.class, args);
}
@Bean
public CommandLineRunner Member(MemberRepository repository) {
return (args) -> {
//엔티티 생성
Member member1 = new Member("홍길동");
Member member2 = new Member("이순신");
Member member3 = new Member("김철수");
//엔티티 저장
repository.save(member1);
repository.save(member2);
repository.save(member3);
//엔티티 조회
for(Member m : repository.findAll()) {
log.info("회원 명 : {}", m.getName());
}
//엔티티 삭제
repository.delete(member2);
for(Member m : repository.findAll()) {
log.info("회원 명 : {}", m.getName());
}
};
}
}
- Service Class 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService implements UserDetailsService{
private final MemberRepository memberRepository;
public Member saveMember(Member member){
validationDuplicateMember(member);
return memberRepository.save(member);
}
private void validationDuplicateMember(Member member){
Member findMember = memberRepository.findByEmail(member.getEmail());
if(findMember != null){
throw new IllegalStateException("이미 가입된 회원입니다.");
}
}
}
JPA 기능
- 쿼리 메소드
스프링 데이터 JPA에서 제공하는 핵심 기능 중 하나
문법 예시 : find + (엔티티 이름) + By + 변수이름
- 엔티티 이름 생략 가능
- 호출 시 해당 변수를 조건으로하는 select 쿼리문 실행
Repository 인터페이스에 메소드를 추가하면 원하는 쿼리를 실행할 수 있다.
1 2 3
public interface MemberRepository extends JpaRepository<Member, Long>{ Member findByEmail(String email); }
- @Query 어노테이션
JPQL(Java Persistence Query Language) 라는 객체지향 쿼리 언어를 통해 복잡한 쿼리도 처리가 가능하다.
- SQL과 문법이 유사하기 때문에 쉽게 배울 수 있다.
- 엔티티 객체를 대상으로 쿼리를 수행한다.
- 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리이다.
- SQL을 추상화해서 사용하기 때문에 특정 데이터베이스 SQL에 의존하지 않는다. (데이터베이스가 변경되어도 영향받지 않는다.)
1 2 3 4
public interface MemberRepository extends JpaRepository<Member, Long>{ @Query("select m from Member m where m.email = :email") Member findByEmail(@Param("email") String email); }
@Param 어노테이션 : 파라미터로 넘어온 값을 JPQL에 들어갈 변수로 지정한다.
nativeQuery 속성 : 기존 데이터베이스에서 사용하던 쿼리를 그대로 사용해야할 때 사용한다.
- 특정 DB에 종속되는 쿼리를 사용하게 되므로 DB에 대해 독립적이라는 장점을 잃는다.
- @NamedQuery 어노테이션
엔티티
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Entity @Table(name="member") @NamedQuery( name = "member.findByEmail" query = "select m from Member m where m.email = :email" ) @Getter @Setter @ToString public class Member { @Id @Column(name="member_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @Column(unique = true) private String email; }
Repository
1 2 3
public interface MemberRepository extends JpaRepository<Member, Long>{ Member findByEmail(@Param("email") String email); }
- Querydsl
- 페이징 처리
- Pageable 이용
출처📎
개념
사용법
- https://velog.io/@swchoi0329
- 백견불여일타 스프링부트 쇼핑몰프로젝트