능글맞은 구렁이
spring boot-JPA 본문
Spring Data JPA와 JPA
1. Springboot는 JPA 구현체 중 Hibernate를 이용한다.
2. Hibernate는 ORM을 지원하는 오픈소스 프레임워크이다. 그리고 단독으로 프로젝트에 적용이 가능하다.
3. 스프링 부트 프로젝트생성시 추가한 Spring Data JPA은 Hibernate를 쉽게 사용할 수 있도록
추가적인 API를 제공한다.
ORM과JPA
ORM(Object Relational Mapping)
1.객체 지향 개념을 이용하여 관계형데이터베이스에 적용(보존)하는 기술 즉, 객체지향을 관계형으로 매핑하는 개념
2. 객체지향 구조와 관계형데이터베이스와 구조가 유사하다.
3. 즉, ORM이란 객체지향과 관계형 사이의 변환 기법을 의미한다.
객체지향 | 관계형데이터베이스 | ||
공통점 | 데이터구조 | 클래스 | 테이블 |
데이터저장 | 맴버변수(데이터타입) | 컬럼(데이터타입) | |
데이터보관 | 인스턴스 | Row(레코드) | |
차이점 | 행위를 하기위한 메소드가 존재, 즉 데이터와 행위를 다룬다. | 데이터만 다룬다. |
JPA(Java Persistence API)
1. ORM을 java언어에 맞게 사용할 수 있도록 제공되는 스펙
2. 따라서 ORM이 상위개념이고, JPA는 java언어에 극한된 개념
3. JPA는 단순한 스펙이기 때문에 구현체(Hibernate 등)마다 프레임워크가 다를 수 있다.
Spring Data JPA를 이용하여 개발 시, 필요한 코드
1. 엔티티 클래스(@Entity)
: JPA를 통하여 관리되는 객체(엔티티객체)를 위한 클래스
2. Repository
1) 엔티티 객체들을 처리하는 기능을 보유한 인터페이스
2) Repository인터페이스
ㄱ) Spring Data JPA에서 제공하는 인터페이스로 설계한다. 스프링 내부에서 자동으로 객체를 생성하고,
실행하는 구조이기 때문에 개발자는 단순히 인터페이스를 하나 정의 하기만 하면된다.
ㄴ) Spring Data JPA에는 여러 종류의 인터페이스 기능을 이용하여 JPA관련 작업을 별도의 코드 개발 없이
처리할 수 있도록 지원한다.
ㄷ) CRUD 작업, 페이징 처리, 정렬 검색 등의 처리도 인터페이스의 메서드를 호출하는 형태로 처리가 되는데,
기능에 따라 상속 구조로 추가적인 기능을 제공
ㄹ) CrudRepository : 일반적인 CRUD 작업만 할 경우 사용
ㅁ) PagingAndSortRepository : 일반적인 CRUD 작업 + 페이징, 정렬 작업을 사용할 경우
ㅂ) JpaRepository : JPA관련 모든 기능을 사용할 경우(개발자가 가장많이 사용하는 인터페이스)
a) Spring Data JPA는 이를 상속하는 인터페이스를 선언만으로 모든 작업에 대한 개발이 끝난다.
b) 실제 동작시에는 스프링이 내부적으로 해당 인터페이스에 맞는 코드를 자동생성한다.
c) JpaRepository를 사용하는 경우에는 @Entity 타입 정보와 @id 타입 정보를 Map형태로 지정한다.
d) Spring Data JPA는 인터페이스 선언만으로도 자동으로 스프링 빈(Bean)으로 등록한다.
즉, 스프링이 내부적으로 인터페이스 타입에 맞게 객체를 생성하여 빈(Bean)으로 등록한다.
Repository | ← | CrudRepository | ← | PagingAndSortRepository | ← | JpaRepository(가장많이 사용) |
3. 사용방법과정
1) Hibernate를 단독으로 사용할 경우: 모든 코드 직접 작성, 트랜잭션 처리도 직접 처리한다.
2) Spring Data JPA를 이용할 경우:모든 코드가 자동으로 생성되기 때문에 CRUD작업, 페이징 작업
개발을 하지 않아 된다.
Project생성 test
1. New Project생성과 파일구조
2. Memo.Java
import lombok.*;
import javax.persistence.*;
@Entity
@Table(name="tbl_memo")
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Memo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mno;
@Column(length = 200, nullable = false)
private String memoText;
@Transient
private int t;
}
1) @Entity : Spring Data JPA사용시 필수요소
해당 클래스는 엔티티 클래스이고, 해당 클래스의 인스턴스들이 JPA로 관리되는 엔티티 객체라는 것을 의미
옵션에따라 자동으로 테이블을 생성할 수도 있다. 이경우 해당 클래스의 멤버변수 설정에 따라 자동으로 컬럼까지 생성된다.
2) @Table : 반드시 @Entity 어노테이션과 함께 사용 가능
관계형 데이터베이스에서 엔티티 클래스를 어떤 테이블로 생성할 것인지에 대한 정보를 담기위한 어노테이션
예)@Table(name="tbl_memo")의 경우
테이블 이름이 "tbl_memo"인 테이블을 생성 옵션을 이용하면 인덱스설정도 가능
3) @Id :
@Entity가 붙은 클래스 내에는 반드시 Primary key(PK)에 해당하는 특정 멤버변수를 @Id로 설정해야 한다.
만약 @Id가 붙은 멤버변수에 입력되는 값이 사용자 입력이 아니라면 자동으로 생성되는 번호를 사용하기 위해서
@GeneratedValue어노테이션을 사용한다.
예) @GeneratedValue(strategy=GeneratoionType.IDENTITY)
pk를 자동으로 생성할 때 사용 (이것을 키 생성전략이라고 부름)
4) @GeneratedValue(strategy = GeneratoionType.IDENTITY)
만약, 연결되는 데이터 베이스가 오라클일 경우: 별도의 번호를 저장을 위한 테이블이 자동 생성
연결되는 데이터 베이스가 MySQL, MairaDB일 경우 :auto increment를이용
strategy설정값
GeneratoionType.AUTO:기본값으로 JPA구현체(Hivernate)가 가 생성방식을 결정
GeneratoionType.IDENTITY:사용하는 데이터베이스가 키 생성을 결정
GeneratoionType.SEQUENCE:데이터베이스의 시퀀스를 이용하여 키를 생성
GeneratoionType.TABLE:키 생성 전용 테이블을 생성하여 키 생성.@TableGenerator와 함께사용
5) @Column : 추가적인 필드(컬럼)가 필요할 경우에 사용
이 어노테이션은 다양한 설정을 위한 옵션을 제공
name="컬럼명"
nullable="NULL값 허용 여부(true/false)"
length=크기(20)
columnDefinition을 이용하여 기본값을 설정할 수도 있다.
예) MySQl, MariaDB일 경우 : columnDefinition="varchar(255) default 'YES'"
오라클의 경우 : columnDefinition="varchar2(255) default 'YES'"
만약 값을 사용자로부터 입력받지만, 테이블에 저장을 하지않을 경우에는
해당 멤버변수 위에 @Transient를 이용하여 제외 시킬 수 있다.
6) 그외 JPA는 다양한 어노테이션이 있음
JPA 어노테이션 :import javax.persistence.*;
3. 각종 대표 설정 파일 : application.properties
server.port=9090
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/bootex
spring.datasource.username=jbr
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true;
spring.jpa.show-sql=true
1) 서버 포트 번호 변경 : server.port=새로운 포트번호
새로운 포트 번호는 1000이후 번호를 사용(이하 번호는 운영체제에서 기본으로 사용하는 번호가 다수있음)
2) 데이터베이스(DataSource)설정
spring.datasource.driver-class-name=접속데이터베이스 드라이버 이름
spring.datasource.url=데이터베이스 접속 주소
spring.datasource.username=사용자 접속 게정
spring.datasource.password=계정 비밀번호
※spring.datasource.url=데이터베이스 접속 주소
ㄱ)MySQL, MariaDB일 경우 : 데이터베이스 접속 주소/데이터베이스명
ㄴ)오라클일 경우 : 데이터베이스 접속 주소
3) Spring Data JPA를 위한 설정
ㄱ) spring.jpa.hibernate.ddl-auto=프로젝트 실행시 자동으로 DDL을 생성할것인지 결정
ㄴ) create:매번 테이블을 새롭게 생성
ㄷ) update:변경이 필요한 경우에만 alter되고,테이블이 없을 경우에는 create
ㄹ) create-drop:매번 테이블을 생성하고, 작업 종료 직전 생성된 테이블을 삭제
ㅁ) validate:테이블에 대한 유효성 검사
ㅂ) spring.jpa.show-sql=true.false :실제 JPA 구현체인 Hibernate가 처리 시에 발생하는 SQL울 보여줄 것인지 여부
ㅅ) spring.jpa.properties.hibernate.format-sql=true또는 false : 실제 JPA 구현체인 Hibernate가 동작하면
발생하는 SQL을 포맷팅(들여쓰기등..)하여 출력한다. 실행되는 SQL에 대한 가독성을 높일 경우에 설정
4. MemoRepository.java인터페이스 설정
package com.example.demo.repository;
import com.example.demo.entity.Memo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import javax.transaction.Transactional;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo,Long>{
//Memo의 mno 값이 70~80사이의 객체들을 조회하고, mno의 역순으로 정렬할 수 있는 쿼리 메서드
List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
//쿼리 메서드와 pageable을 결합하는 쿼리 메서드
Page<Memo> findByMnoBetween(long from, long to, Pageable pageable);
//특정 조건에 맞는 엔티티 객체를 삭제하는 쿼리 메서드
//Memo의 mno가 10 보다 작은 엔티티 객체를 삭제하는 메서드
void deleteMemoByMnoLessThan(Long num);
//mno 역순으로 정렬하는 @Query를 이용한 쿼리 메서드
@Query("select m from Memo m order by m.mno desc ")
List<Memo> getListDesc();
//JPQL : Entity class기준
//SQL : Table기준
//JPA에서는
//SQL : "select * from tbl_memo m order by m.mno desc "
//JPQL: "select m from Memo(Entity class로 변경) m order by m.mno desc "
@Transactional
@Modifying
@Query("update Memo m set m.memoText = :memoText where m.mno = :mno")
int updateMemoText(@Param("mno") Long mno, @Param("memoText") String memoText);
@Transactional
@Modifying
@Query("update Memo m set m.memoText = :#{#p.memoText} where m.mno =:#{#p.mno}")
int updateMemoText(@Param("p") Memo memo);
}
5. MemoRepositoryTests.java 테스트 코드를 이용하여 CRUD 및 페이징 작업 테스트
@SpringBootTest
public class MemoRepositoryTests {
@Autowired//자동주입
MemoRepository memoRepository;
@Test
public void testClass() {
System.out.println(memoRepository.getClass().getName());//해당클래스명확인
//츨력결과값: com.sun.proxy.$Proxy92
//스프링이 MemoRepository 객체를 자동으로 생성
//자동으로 생성할 때 프록시 방법으로 생성하기 때문에 위와같은 이름이 출력된다.
}
1) MemoRepository를 이용하여 작성된 테이블에 SQL없이 CRUD 작업 테스트
ㄱ) CRUD작업 지원 메서드(JpaRepository)
insert 작업 | save(엔티티객체) |
select 작업 | findById(키타입 : 실행할때 동작) / getOne(키타입 : 사용할때 동작) |
update작업 | save(엔티티 객체) |
delete작업 | deleteById(키 타입)/delete(엔티티 객체) |
ㄴ) insert 작업과 update 작업: save(엔티티 객체)
- JPA의 구현체가 메모리상에 객체를 비교하고 없으면 insert 동작/ 존재하면 update 동작
2) insert작업: 등록테스트 메서드: 100개의 새로운 memo객체 생성
ㄱ) IntStream : 숫자를 연속적으로 만들어주는 클래스
ㄴ) intStream길이 설정은 range로 설정한다.
@Test
public void testInsertDummies() {
IntStream.range(1, 100).forEach(i -> {
Memo memo = Memo.builder().memoText("Sample.." + i).build();
memoRepository.save(memo);
});
}
3) select 조회 메서드
ㄱ) findById()를 이용하여 조회된 결과 : 해당 메서드가 호출되어 실행되는 순간 SQL이 바로 처리한다.
//조회 작업 테스트 메서드: finById()활용
//finById()는 반환 타입이 Optical 타입으로 반환
@Test
public void testSelect(){
//데이터베이스에 존재하는 mno 값을 이용
Long mno=99L;
Optional<Memo> result=memoRepository.findById(mno);
System.out.println("=================");
if(result.isPresent()){
Memo memo=result.get();
System.out.println(memoRepository.findById(mno));
}
System.out.println("=================");
}
Hibernate: select memo0_.mno as mno1_0_0_, memo0_.memo_text as memo_tex2_0_0_ from tbl_memo memo0_ where memo0_.mno=? ================================== Memo(mno=99, memoText=Sample...99, t=0) |
ㄴ) getOne( )를 이용하여 조회된 결과 : getOne()는 해당객체를 반환하는데,
메서드 실행 시 SQL 이 실행되지 않고 실제 객체가 필요한 시점에서 SQL 이 실행된다.
@Transactional
@Test
public void testSelect2(){
Long mno=99L;
Memo memo = memoRepository.getOne(mno);
System.out.println("====================");
System.out.println(memo);
//Memo는 @ToString을 이용하여 toString() 이 재정이 되었기 때문에
//Meno객체를 출력하면 자동으로 toString(0메서드가 호출되어 각 변수의 값을 확인할 수있다.
System.out.println("====================");
}
=============================== Hibernate: select memo0_.mno as mno1_0_0_, memo0_.memo_text as memo_tex2_0_0_ from tbl_memo memo0_ where memo0_.mno=? Memo(mno=99, memoText=Sample...99, t=0) |
4) save()는 id 값이 있으면(존재하면) update , 존재하지 않으면 insert
따라서 save()는 select를 이용하여 해당 값이 존재하는지 확인 후, update, insert를 결정하는 메서드.
@Test
public void testUpdate(){
Memo memo=Memo.builder().mno(99L).memoText("update text").build();
System.out.println(memoRepository.save(memo));
}
Hibernate: select memo0_.mno as mno1_0_0_, memo0_.memo_text as memo_tex2_0_0_ from tbl_memo memo0_ where memo0_.mno=? Hibernate: update tbl_memo set memo_text=? where mno=? |
5) deleteById() : 리턴값이 없다.
※참고 사항※
만약 해당 데이터가 존재하지 않으면 예외발생(=> org.springframework.dao.EmptyResultDataAccessException)
@Test
public void testDelete(){
Long mno= 99L;
memoRepository.deleteById(mno);
}
Hibernate: select memo0_.mno as mno1_0_0_, memo0_.memo_text as memo_tex2_0_0_ from tbl_memo memo0_ where memo0_.mno=? Hibernate: delete from tbl_memo where mno=? |
6) 페이지 처리 메서드
findAll( ) : Spring Data JPA에서 페이징 처리와 정렬 작업에서 사용되는 메서드이다. findAll( )는
PagingAndSortRepository 에서 정의된 메서드이고 findAll( )에 전달할 수 있는 데이터는 Pageable 객체이다.
따라서 이 메서드를 사용하려면 먼저 Pageable 객체를 생성해야 하고 이 객체는 PageRequest.of(시작, 갯수) 같이 지정하여 얻어 낼 수 있다..
@Test
public void testPageDefault() {
Pageable pageable = PageRequest.of(0, 10);
Page<Memo> result = memoRepository.findAll(pageable);
System.out.println(result);
System.out.println("========================");
System.out.println("Total pages:" + result.getTotalPages()); //총 몇 페이지
System.out.println("Total Count:" + result.getTotalElements()); //전체 개수
System.out.println("Current Page Number:" + result.getNumber()); //현재 페이지 번호
System.out.println("Page Size:" + result.getSize());//페이지당 데이터 개수
System.out.println("has next page?:" + result.hasNext());//다음페이지 존재여부
System.out.println("first page?" + result.isFirst());//시작페이지여부
}
7) 페이징과 정렬 시 사용되는 findAll() 결과
//정렬 조건 추가 및 조회된 데이터 출력 : FindAll()메서드 이용
@Test
public void testSort() {
Sort sort = Sort.by("mno").descending();
Pageable pageable = PageRequest.of(3, 10, sort);
Page<Memo> result = memoRepository.findAll(pageable);
result.get().forEach(memo -> {
System.out.println(memo);
});
}
@Test
public void testQueryMethods(){
List<Memo> list=memoRepository.findByMnoBetweenOrderByMnoDesc(70L, 88L);
for(Memo memo : list){
System.out.println(memo);
}
}
//쿼리 메서드와 Pageable결합
//Memo의 mno값이 10~50 사이의 객체들 조회하고,
//그중에 첫페이지에 해당하는 10개의 객체를 mno를 기준으로 역순 정렬
@Test
public void testQueryMethodWithPageable(){
Pageable pageable=PageRequest.of(0,10,Sort.by("mno").descending());
Page<Memo> result=memoRepository.findByMnoBetween(10L,50L,pageable);
result.get().forEach(memo -> System.out.println(memo));
}
//Memo의 mno가 10 보다 작은 엔티티 객체를 삭제하는 메서드
@Commit
@Transactional
@Test
public void testDeleteQueryMethods(){
memoRepository.deleteMemoByMnoLessThan(20L);
}
@Test
public void testSelectAll(){
List<Memo> list=memoRepository.getListDesc();
for (Memo memo:list){
System.out.println(memo);
}
}
//mno가 20인 엔티티 객체의 memoText를
@Test
public void testUpdateMemoText(){
int n= memoRepository.updateMemoText(20L, "update Text");
System.out.println("===>"+n);
}
//no을 이용하여 해당 memoText를 변경하는 메서드
@Test
public void testUpdateMemoText2(){
Memo memo= Memo.builder().mno(22L).memoText("UpdateMemo").build();
int n=memoRepository.updateMemoText(memo);
}
Hibernate:
select
memo0_.mno as mno1_0_,
memo0_.memo_text as memo_tex2_0_
from
tbl_memo memo0_ limit ? // <=MySQL, MariaDB(limit) 와 Oracle(inline view)이 다르게 실행된다.
Hibernate:
select
count(memo0_.mno) as col_0_0_
from
tbl_memo memo0_
Page 1 of 10 containing com.freeflux.ex2.entity.Memo instances
---------------------------------------
Total Pages: 10
Total Count: 98
Page Number: 0
Page Size: 10
has next page?: true
first page?: true
7. 페이지 처리의 핵심은 Pageable !!! (org.springframework.data.domain.Pageable)
Pageable : 페이지 처리에 필요한 정보를 전달하는 용도로 설계된 인터페이스!!
따라서 객체를 생성할 때는
org.springframework.data.domain.PageRequest 클래스를 이용..
단, PageRequest는 생성자가 protected 로 되어 있기 때문에 new를 사용하지 못하고
PageRequest의 static of()를 이용하여 Pageable 객체를 반환 받아야 한다!!!!!
PageRequest.of(int page, int size) : 0번부터 시작하는 페이지 번호와 갯수
int page : 페이지 번호
int size : 페이지당 데이터 갯수
PageRequest.of(int page, int size, Sort.Direction direction, String ...props)
Sort.Direction direction : 정렬 방향(방법)
String ...props : 정렬 기준 필드들...
PageRequest.of(int page, int size, Sort sort)
Sort sort : 정렬 관련 정보 (즉, 정렬 방향과 정렬 기준 필드들을 하나로 묶은 객체)
----------------------------------------------------
findAll(Pageable pageable)
반환 타입 : Page<엔티티 객체 타입>
findAll() 메서드는 두 가지의 SQL을 실행
1. limit 를 사용하는 select SQL : 지정한 시작 페이지 번호부터 지정한 갯수를 select 하기 위한 SQL
2. count() 함수를 사용하는 select SQL : 전체 레코드(엔티티) 갯수를 확인하기 위한 select SQL
-------------------------------------------------------
Page<엔티티 객체 타입> 객체는 결과를 확인하기 위한 다양한 메서드를 제공
getTotalPages() ~~~ isFirst() 등,,,
----------------------------------------------------
정렬 및 조회 데이터 출력 결과
Hibernate:
select
memo0_.mno as mno1_0_,
memo0_.memo_text as memo_tex2_0_
from
tbl_memo memo0_
order by
memo0_.mno desc limit ?
Hibernate:
select
count(memo0_.mno) as col_0_0_
from
tbl_memo memo0_
Memo(mno=98, memoText=Sample...98, t=0)
Memo(mno=97, memoText=Sample...97, t=0)
Memo(mno=96, memoText=Sample...96, t=0)
Memo(mno=95, memoText=Sample...95, t=0)
Memo(mno=94, memoText=Sample...94, t=0)
Memo(mno=93, memoText=Sample...93, t=0)
Memo(mno=92, memoText=Sample...92, t=0)
Memo(mno=91, memoText=Sample...91, t=0)
Memo(mno=90, memoText=Sample...90, t=0)
Memo(mno=89, memoText=Sample...89, t=0)
Pageable pageable = PageRequest.of(3, 10, sort);
Memo(mno=68, memoText=Sample...68, t=0)
Memo(mno=67, memoText=Sample...67, t=0)
Memo(mno=66, memoText=Sample...66, t=0)
Memo(mno=65, memoText=Sample...65, t=0)
Memo(mno=64, memoText=Sample...64, t=0)
Memo(mno=63, memoText=Sample...63, t=0)
Memo(mno=62, memoText=Sample...62, t=0)
Memo(mno=61, memoText=Sample...61, t=0)
Memo(mno=60, memoText=Sample...60, t=0)
Memo(mno=59, memoText=Sample...59, t=0)
============================================================
2021-07-07 15:48:17.009 INFO 4328 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-07-07 15:48:17.010 INFO 4328 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2021-07-07 15:48:17.012 INFO 4328 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 4s
================================================================
'Framework > SpringBoot' 카테고리의 다른 글
springBoot-MVC/View Templates (0) | 2021.07.08 |
---|---|
SpringBoot-Thymeleaf (0) | 2021.07.08 |
SpringBoot-Query (0) | 2021.07.07 |
Spring Boot-Server.port (0) | 2021.07.06 |
Spring Boot란?(환경설정, Maven vs Gradle) (0) | 2021.07.06 |