Spring Boot 2. Maria 데이터베이스 & Spring Data JPA
Spring Boot 2. Maria 데이터베이스 & Spring Data JPA
1. Maria DB 설치와 데이터베이스 생성
1) Maria DB 설치
맥에서는 홈브류를 이용해서 설치
brew install mariadb2) DB 생성
워크벤치를 이용해서 DB 생성
- 새로운 연결 생성
username : root
pw : 1234
- DB 생성
Schemas ->우클릭 create schema.. -> 이름 :bootex, Character set : utf8 -> apply
- 사용자 생성
Administration -> Users and Privileges -> Add Account
-> Login Name : bootuser, Limit to Hosts Matching : %(모든 곳에서 접근 가능), pw : bootuser
-> Schema Privileges -> Add Entry -> Selected schema -> bootex 선택 -> Select All -> Apply
2. Spring Data JPA 를 이용하는 프로젝트 생성
1) 프로젝트 생성
기존 프로젝트 생성방식과 동일. 단 Dependencies를 선택하는 부분에서 기존에 선택한 라이브러리에서 추가로 SQL 탭의 Spring Data JPA 선택
2) DB를 위한 Spring Boot 설정
Spring Boot에서는 특정한 라이브러리가 있다면 관련된 설정들을 자동으로 추가해줌
그러나 지금 상태에서 실행을 하면 에러가 발생하는데 이는 Spring Data JPA 라이브러리가 추가되면서 자동으로 이에 관련된 설정도 추가되었지만 구체적인 값이 지정되지 않았기 때문
-> 두 가지 설정 필요
- Maria DB를 위한 JDBC 드라이버
https://mvnrepository.com/artifact/org.mariadb.jdbc 접속 -> 1. MariaDB Java Client 클릭 -> 가장 높은 버전 선택 -> Gradle 탭의 내용 복사
-> 프로젝트 폴더 -> src -> build.gradle -> dependencies 블록 안에 붙여넣기
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 여기에 붙여넣기
// https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client',
version: '3.1.2'
}
라이브러리를 추가하거나 변경하면 gradle을 다시 실행해서 라이브러리를 처리할 것인지 작은 표시를 보여주는데 이 코끼리 아이콘을 클릭
- Spring Boot 내 Maria DB 설정
프로젝트 폴더 -> src -> main -> resources -> application.properties 클릭
파일 안에 아래 내용 작성
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/[DB 이름]
spring.datasource.username=[유저이름]
spring.datasource.password=[패스워드]
=> 실행
3. Spring Data JPA
ORM : 객체지향 페러다임을 관계형 DB에 저장하는 기술
JPA : Java 언어를 통해서 DB와 같은 영속 계층을 처리하는 스팩
JPA는 ORM을 Java 언어에 맞게 사용하는 스팩
4. 엔티티 클래스 & JpaResitory
Spring Data JPA가 개발에 필요한 두 종류의 코드
- 엔티티 클래스 : JPA를 통해 관리되는 객체(엔티티 객체)를 위한 클래스
- 엔티티 객체들을 처리하는 기능을 가진 JpaRepository
1) 엔티티 클래스 작성
entity 패키지 생성 -> Memo 클래스 정의
-> 아래 내용 작성
package kr.ac.poly.ex2.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "tbl_momo")
@ToString
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Memo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long mno;
@Column(length = 200, nullable = false)
private String memoText;
}
@Entity : 해당 클래스가 엔티티 클래스이며 해당 클래스의 인스턴스들이 JPA로 관리되는 엔티티 객체라는 것을 의미
@Table : DB상에서 엔티티 클래스를 어떤 테이블로 생성할 것인지에 대한 정보를 담기위한 어노테이션. () 안에 테이블 이름이나 인덱스 등 설정 가능
@ToString :객체가 가진 정보를 문자열로 반환하는 toString() 메소드 생성
@Getter : Getter메소드 생성 (get[프로퍼티 이름])
@Builder : 객체를 생성할 수 있게 처리
@AllArgsConstructor & @NoArgsConstructor : @Builder를 사용하려면 반드시 있어야 함
@Id : Primary Key에 해당하는 특정 필드를 가리킴. @Entity 가 붙은 클래스는 반드시 PK를 @Id를 이용해서 지정해야 함
@GeneratedValue : PK를 자동으로 생성( 키 생성 전략)
@Column : 테이블에 추가적인 컬럼을 추가. () 안에 다양한 속성 지정 가능
(nullable, name, length, columnDfinition 등)
* 키 생성 전략
AUTO(default) : JPA 구현체(스프링 부트에서는 Hibernate)가 생성 방식을 결정
IDENTITY : 사용하는 데이터베이스가 키 생성을 결정 MySQL이나 MariaDB의 경우 auto increment 방식을 이용 (새로운 레코드가 기록될 때마다 다른 번호를 가질 수 있도록 처리)
SEQUENCE : 데이터베이스의 sequence를 이용해서 ヲI를 생성. @SequenceGenerator와 같이 사용
TABLE : 키 생성 전용 테이블을 생성해서 키 생성. @TableGenerator와 함께 사용
2) Spring Data JPA를 위한 Spring Boot 설정
자동으로 필요한 테이블을 생성하거나 JPA를 이용할 때 발생하는 SQL 등을 확인하기 위해 추가설정 필요
application.properties 파일에 아래 내용 추가
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true
(1) 프로젝트 실행 시 자동으로 DDL(create, alter, drop 등)을 생성할 지 설정
(2) Hibernate가 동작하면서 발생하는 SQL을 포맷팅해서 출력(SQL의 가독성 높여줌)
(3) JPA 처리 시에 발생하는 SQL을 보여줄 지 설정
=> 실행
로그에 실행된 SQL을 확인할 수 있음
워크벤치를 확인해보면 새로운 테이블과 컬럼이 생성되어 있는 것을 확인할 수 있음
3) JpaRepository 인터페이스
Hibernate를 사용하기 위해서 Spring Data JPA에서 제공하는 여러 API 중에 개발자가 가장 많이 사용하는 것이 JpaRepository
CRUD 작업이나 페이징, 정렬등의 처리를 인터페이스의 메소드를 호출하는 형태로 처리
가장 하위 인터페이스이기 때문에 많은 부모 인터페이스의 기능도 제공
일반적인 기능만 사용할 때는 CrudRepository, 모든 JPA 관련 기능을 사용하고 싶을 때에는 JpaRepository를 이용
JpaRepository를 이용하는 것을 권장
- JpaRepository 이용하기
JpaRepository는 인터페이스이고 Spring Data JPA는 이를 상속하는 인터페이스를 선언하는 것 만으로 모든 처리가 끝남
repository 패키지 생성 -> MemoRepository 인터페이스 생성 -> extends 키워드로 JpaRepository 인터페이스를 상속받음 -> < > 안에 사용할 엔티티 클래스 타입과 @Id의 타입 지정
package kr.ac.poly.ex2.repository;
import kr.ac.poly.ex2.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
MemoRepository는 인터페이스 자체이고 JpaRepository 인터페이스를 상속하는 것 만으로 모든 작업이 끝남
JPA는 인터페이스의 선언만으로도 자동으로 Spring의 빈(bean)으로 등록됨
(Spring이 내부적으로 인터페이스 타입에 맞는 객체를 생성해 빈으로 등록)
4) 테스트 코드를 통한 CRUD 구현
위에서 작성한 MemoRepository를 이용해서 SQL문 없이 CRUD 작업 수행 가능
* 사용 메소드
insert : save()
select : findById(), getOne()
update : save()
delete : deleteById(), delete()
test폴더 안의 프로젝트 페키지 -> repository 페키지 생성 -> MemoRepositoryTest 클래스 생성
-> 아래와 같이 코드 작성
package kr.ac.poly.ex2.repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MemoRepositoryTest {
@Autowired
MemoRepository memoRepository;
@Test
public void testClass() {
System.out.println(memoRepository.getClass().getName());
}
}
@SpringBootTest : 테스트용 클래스임을 나타냄
@Autowired : 참조변수를 선언할 때 자동으로 참조값을 사용 가능하도록 해줌
memoRepository : 위에서 작성한 MemoRepository를 사용하기 위한 참조변수
@Test : 테스트 메소드임을 나타냄
-> 작성한 메소드를 통해서 MemoRepository 인터페이스 타입의 실제 객체 확인
<실행결과>
jdk.proxy3.$Proxy@@@ -> 동적 프록시라는 방식으로 Spring이 내부적으로 자동 생성
- insert
한 번에 100개의 엔티티 객체를 DB에 저장
save() 메소드 이용
@Test
public void testInsert() {
IntStream.rangeClosed(1, 100).forEach(i -> {
Memo memo = Memo.builder().memoText("Sample... " + i).build();
memoRepository.save(memo);
});
}
IntStream의 rangeClosed()와 forEach()를 이용해서 1~100까지 반복하면서 Memo 객체를 builder()로 생성해고 Not Null 조건으로 설정한 memoText에 데이터를 넣어줌
memoRepository의 save() 메소드를 이용해서 생성한 엔티티 객체를 DB에 저장
* @Builder
-> 해당 어노테이션을 작성하면 빌더 클래스가 자동 생성되어 Builder() 생성자를 사용할 수 있음. 생성자 다음에 .을 이용해서 해당 객체의 프로퍼티에 값을 할당하고 마지막에 .build()메소드를 사용해서 객체 생성.
<실행결과>
로그 창에서 실행된 SQL문을 확인할 수 있음
- select
findById() or getOne() 메소드 이용
findById()
@Test
public void testSelect1() {
// 조회할 id
Long mno = 100L;
Optional<Memo> result = memoRepository.findById(mno);
System.out.println("==========================");
if (result.isPresent()) {
Memo memo = result.get();
System.out.println(memo);
}
}
검색 조건으로 사용할 Long 타입 mno 선언 (Long으로 래핑했기 때문에 뒤에 'L'을 붙여줌)
Optional<> 을 이용해서 값이 없어도 에러가 발생하지 않도록 함(검색결과가 없는 경우 대비)
memoRepository의 findById() 메소드를 이용해서 mno와 일치하는 데이터 검색
-> findById() 메소드는 검색 결과를 옵셔널로 반환
.isPresent 메소드를 이용해서 참조할 것이 있다면 옵셔널 타입을 get()메소드를 이용해서 Memo 객체를 추출하고 콘솔에 출력
<실행결과>
-> findById() 메소드를 호출한 즉시 SQL문이 실행됨
getOne()
@Transactional
@Test
public void testSelect2() {
// 조회할 id
Long mno = 100L;
Memo memo = memoRepository.getOne(mno);
System.out.println("==========================");
System.out.println(memo);
}
getOne() 메소드를 사용하기 위해 메소드 위에 @Transaction (jakarta.transaction.Transactional) 어노테이션 추가 (트랜잭션 처리를 위한 어노테이션)
검색 조건으로 사용할 Long 타입 mno 선언 (Long으로 래핑했기 때문에 뒤에 'L'을 붙여줌)
Memo 객체를 생성하고 memoRepository의 getOne()메소드로 mno에 해당하는 데이터를 검색
-> getOne() 메소드는 결과를 Memo 객체로 반환
<실행결과>
-> getOne() 메소드를 호출한 시점이 아닌 실제 객체가 필요한 순간에 SQL 문을 실행
* findById() 메소드와 getOne() 메소드의 차이점
위의 결과와 같이 SQL문이 실제로 실행되는 시점이 다름
반환하는 객체가 다름 (findById : 옵셔널, getOne : 실제 객체)
findById() 메소드를 더 많이 사용하고 권장
- update
save() 메소드 사용
@Test
public void testUpdate() {
Memo memo = Memo.builder().mno(100L).memoText("Updated Text").build();
System.out.println(memoRepository.save(memo));
}
Memo 객체를 생성. 이때 변경할 Id(mno)와 변경할 내용(memoText)의 내용 작성 후 build() 메소드를 이용해서 객체 생성
memoRepository의 save() 메소드를 이용해서 DB의 데이터 변경
<실행결과>
-> save() 메소드를 실행하면 먼저 select 문을 실행해서 해당 데이터가 DB에 있는지 확인하고 있다면 해당 데이터의 내용을 update 문으로 수정
해당 데이터가 없다면 insert 문으로 새로운 데이터 추가
=> 새로운 데이터 추가와 기존 데이터 수정에 모두 save() 메소드 이용.
- delete
deleteById() 메소드 이용
@Test
public void testDelete() {
Long mno = 100L;
memoRepository.deleteById(mno);
}
Long 변수 mno를 선언해서 삭제할 ID 정보 할당
memoRepsitory의 deleteById() 메소드를 실행해서 해당 아이디에 해당하는 데이터 DB에서 삭제
<실행결과>
-> deleteById() 메소드가 호출되면 먼저 select문으로 삭제할 데이터가 있는지 확인하고 이후에 delete문으로 해당 데이터 삭제.
만일 삭제할 데이터가 DB에 없다면 org.springframework.dao.EmptyResultDataAccessException 예외 발생
5. 페이징 / 정렬 처리
Spring Data JPA는 내부적으로 클래스를 생성하는 방식으로 동작하면서 코드가 없는 형식의 개발 가능
JPA는 내부적으로 페이징 처리와 정렬을 Dialect라는 존재를 이용해서 처리
실제 DB에 사용하는 SQL의 처리를 자동으로 함 -> 개발자들이 SQL이 아닌 API의 객체와 메소드를 사용하는 형태로 페이징과 정렬 처리
1) Pageable 인터페이스 (org.springframework.data.domain.Pageable)
페이지 처리에 필요한 정보를 전달하는 용도의 타입
인터페이스이기 때문에 실제 객체를 생성할 때는 org.springframework.data.domain.PageRequest 라는 클래스 사용
PageRequest 클래스의 생성자는 protected로 선언되어 new를 이용할 수 없음
static 메소드인 of()를 이용해서 객체 생성 ->page, size, Sort 라는 정보를 이용해 객체 생성
* of() 메소드의 종류
- of(int page, int size) : 0부터 시작하는 페이지 번호, 페이지당 개수 (정렬이 지정되지 않음)
- of(int page, int size, Sort.Direction direction, String...props) : 0부터 시작하는 페이지 번호, 페이지당 개수, 정렬의 방향과 정렬 기준 필드들
- of(int page, int size, Sort sort): 페이지 번호, 페이지당 개수, 정렬 관련 정보
2) 페이징 처리
페이지는 반드시 0부터 시작
MemoRepositoryTest 클래스에 아래와 같이 작성
@Test
public void pageable() {
Pageable pageable = PageRequest.of(0, 10);
Page<Memo> result = memoRepository.findAll(pageable);
System.out.println(result);
}
Pageable 타입의 객체를 PageRequest 클래스의 of() 메소드를 이용해서 생성
(0, 10) -> 한 페이지당 10개의 데이터가 있을 때 0번째 페이지
memoRepository의 findAll() 메소드는 Pageable 타입의 객체를 전달받아 실행 결과를 Page<T> 타입으로 반환
Page 타입은 단순히 해당 목록만 가져오지 않고 실제 페이지 처리에 필요한 전체 데이터의 개수를 가져오는 SQL문도 같이 처리
<실행결과>
-> 첫번째 SQL 문에서 MariaDB에서 페이징 처리에 사용하는 limit 구분이 사용됨
-> 두번 째 SQL 문에서 count()를 이용해 전체 데이터 개수를 처리
* Page<T>은 SQL 결과를 사용하기 위한 여러 메소드 지원
System.out.println("총 페이지 수 : " + result.getTotalPages());
System.out.println("전체 데이터 개수 : " + result.getTotalElements());
System.out.println("페이지 번호 : " + result.getNumber());
System.out.println("피이지당 데이터 개수 : " + result.getSize());
System.out.println("다음 페이지 존재 여부 : " + result.hasNext());
System.out.println("시작 페이지 여부 : " + result.isFirst());
<실행결과>
* 실제 페이지의 데이터 처리
getContent() : List<엔티티 타입> 반환
get() : Stream<엔티티 타입> 반환
- getContent()
for (Memo memo : result.getContent()) {
System.out.println(memo);
}
- get()
result.get().forEach(memo -> {
System.out.println(memo);
});
<실행결과>
3) 페이징에 정렬 조건 추가
PageRequest 클래스의 of() 메소드로 Pageable 타입의 객체를 생성할 때 파라미터로 Sort 타입을 넘겨줄 수 있음 (org.springframework.data. domain.Sort 타입)
Sort는 한 개 이상의 필드의 값을 이용해 내림차순, 오름차순 정렬 가능
Sort 객체를 생성할 때는 Sort 클래스의 by() 메소스 사용 () 안에 정렬할 기준 전달
그 뒤에 descending()이나 ascending() 메소드를 붙여서 정렬 방향 지정
@Test
public void testSort() {
// 정렬 정보를 담은 Sort 객체 생성
Sort sort1 = Sort.by("mno").descending();
// 정렬 정보를 추가해서 페이징 정보를 가지는 Pageable 객체 생성
Pageable pageable = PageRequest.of(0, 10, sort1);
// memoRepository의 findAll() 메소드에 Pageable 객체를 전달하고 Page로 결과를 받아옴
Page<Memo> result = memoRepository.findAll(pageable);
// 해당 페이지의 데이터들 출력
for (Memo memo : result.getContent()) {
System.out.println(memo);
}
}
<실행결과>
-> SQL 문에 정렬을 위한 order by 절이 추가됨
-> 내림차순으로 정렬된 데이터
* Sort클래스의 and() 메소드를 이용하면 여러 정렬 조건 지정 가능
// 여러 정렬 정보를 담은 Sort 객체 생성
Sort sort1 = Sort.by("mno").descending();
Sort sort2 = Sort.by("memoText").ascending();
Sort sortAll = sort1.and(sort2);
// 정렬 정보를 추가해서 페이징 정보를 가지는 Pageable 객체 생성
Pageable pageable = PageRequest.of(0, 10, sortAll);
// memoRepository의 findAll() 메소드에 Pageable 객체를 전달하고 Page로 결과를 받아옴
Page<Memo> result = memoRepository.findAll(pageable);
<실행결과>
-> 실행된 SQL에 정렬 조건이 하나 더 추가됨.
6. 쿼리 메소드 & @Query
1) 쿼리 메소드
위에서 만들었던 JpaRepository를 상속받는 인터페이스 안에 작성
메소드의 이름 자체가 쿼리가 됨 -> 메소드의 이름을 정해진 형식대로 작성해야 함(자동완성 기능 활용)
주로 findBy나 getBy로 시작함
필요한 필드 조건이나 And, Or과 같은 키워드를 이용해서 메소드의 이름 자체로 쿼리의 조건을 만들어냄
주로 SQL 에서 사용되는 키워드와 동일하게 작성됨
(자세한 내용은 아래 링크에서 확인 가능)
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
쿼리 메소드의 반환 타입은 자유로운 편
select를 하는 작업 이라면 List 타입이나 배열 사용가능
파라미터에 Pageable 타입을 넣는 경우에는 반환 타입이 무조건 Page<E> 타입
* 예시 - mno값이 50~60인 객체들을 구하고 내림차순 정렬
MemoRepository.java
List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
반환타입은 List<Memo>
메소드의 이름을 보면 mno를 기준으로 between 조건이 들어가고 mno를 기준으로 내림차순 정렬한다는 것을 알 수 있음
MemoRepositoryTest.java (인터페이스에 선언한 메소드 사용)
@Test
public void testQueryMethod() {
List<Memo> result = memoRepository.findByMnoBetweenOrderByMnoDesc(50L, 60L);
for (Memo memo : result) {
System.out.println(memo);
}
}
<실행결과>
- 쿼리 메소드와 Pageable의 결합
Pageable을 결합해서 사용하면 메소드 이름에 OrderBy를 추가하지 않아도 Pageable을 이용해서 정렬 기능을 사용할 수 있음 -> 보다 간결하게 메소드 생성 가능
위에서 말한 것 처럼 Pageable을 사용할 때에는 메소드의 반환타입이 Page<E>여야 함
* 예시 - 위의 메소드를 Pageable을 이용해서 보다 간결하게 선언
MemoRepository.java
Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);
위의 메소드 보다 이름이 간결해진 것을 알 수 있음
매개변수로 Pageable 타입을 받음
MenoRepositoryTest.java
@Test
public void testQueryMethod2() {
// 정렬 정보를 포함한 PAgeable 객체 생성
Pageable pageable = PageRequest.of(0, 10, Sort.by("mno").descending());
// 메소드 호출
Page<Memo> result = memoRepository.findByMnoBetween(50L, 60L, pageable);
result.get().forEachOrdered(memo -> {
System.out.println(memo);
});
}
=> 위와 동일한 실행 결과
- deleteBy 로 시작하는 삭제 처리
쿼리 메소드를 이용해서 'deleteBy'로 메소드의 이름을 시작하면 특정한 조건에 맞는 데이터 삭제
메소드의 반환 타입은 void
메소드 안에서 deleteBy로 시작하는 메소드를 호출하는 경우 해당 메소드의 위에 @Transactional 어노테이션과 @Commit 어노테이션을 추가해야함
@Transactional : select문으로 해당 엔티티들을 가져오는 작업을 먼저 수행 (없으면 에러 발생)
@Commit : 최종 결과를 DB에 커밋 (없으면 DB에 적용 안됨)
* 예시 - mno가 10 미만인 데이터 삭제
MemoRepository.java
void deleteMemoByMnoLessThan(Long num);
MemoRepositoryTest.java
@Transactional
@Commit
@Test
public void testDeleteBy() {
Long num = 10L;
memoRepository.deleteById(num);
}
실행결과 => DB에서 mno가 10보다 작은 모든 행이 삭제됨
deleteBy를 실제 개발에서는 많이 사용하지 않음
-> 엔티티 객체를 하나씩 삭제해서 시간이 오래 걸리기 때문
=> deleteBy를 이용하기 보다 @Query를 이용해서 보다 효율적으로 작업 수행
2) @Query 어노테이션
조인이나 복잡한 조건을 처리하는 경우 쿼리 메소드로 하면 불편해질 수 있음
간단한 처리만 쿼리 메소드를 사용하고 대부분은 @Query를 이용
@Query를 붙이면 메소드의 이름과는 상관없이 메소드에 추가한 어노테이션을 통해서 원하는 처리 가능
( ) 안에 처리할 쿼리 작성
-> JPQL(Java Persistence Query Language) 로 작성(객체지향 쿼리)
* @Query를 이용해 할 수 있는 작업
- 필요한 데이터만 선별적으로 추출
- DB에 맞는 순수한 SQL을 사용하는 경우
- insert, update, delete와 같은 select가 아닌 DML등을 처리 (@Modifying과 함께 사용)
JPQL는 테이블 대신 엔티티 클래스를 이용하고 테이블의 칼럼 대신에 클래스에 선언된 필드를 이용해서 작성
JPQL 은 DB의 테이블 대신 엔티티 클래스와 멤버 변수를 이용해서 SQL문과 비슷하게 작성
* 예시 - 데이터를 모두 조회해서 내림차순 정렬
MemoRepository.java
@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc();
MemoRepositoryTest.java
@Test
public void testSelect() {
List<Memo> result = memoRepository.getListDesc();
for (Memo memo : result) {
System.out.println(memo);
}
}
<실행결과>
- @Query의 파라미터 바인딩
where절 등에서 필요한 파라미터를 처리하는 경우가 많음
메소드의 매게변수로 받아온 값을 파라미터로 JPQL에 전달
* 파라미터 전달 방식
- '?1', '?2'와 같이 '?1' 부터 시작하는 파라미터의 순서를 이용
- ':@@@'와 같이 '파라미터 이름'을 이용
- ':#{#파라미터 이름}'와 같이 자바 빈 스타일을 이용하는 방식
* 예시 - ':파라미터 이름' 방식으로 파라미터 전달
매개변수로 전달한 mno에 해당하는 행의 내용 변경
MemoRepository.java
@Transactional
@Modifying
@Query("update Memo m set m.memoText = :memoText where m.mno = :mno")
int updateMemoText(@Param("mno") Long mno, @Param("memoText") String memoText
update문을 이용하기 때문에 @Transactional, @Modifying 어노테이션 추가
파라미터로 전달할 매개변수의 앞에 @Param(파라미터 이름) 을 작성
JPQL에서 : 다음에 파라미터 이름을 작성해서 해당 파라미터로 전달되는 값 사용 가능
update문은 반환 타입으로 업데이트한 행의 개수를 int형으로 반환
MemoRepositoryTest.java
@Test
public void testUpdateMemoText() {
Long mno = 11L;
String memoText = "Hello World";
int countUpdated = memoRepository.updateMemoText(mno, memoText);
}
<실행결과>
* 예시 - ':#{#파라미터 이름}' 형식으로 파라미터 전달
-> 파라미터로 객체를 전달하기 때문에 여러개의 파라미터를 전달해야 하는 경우 보다 간결하게 작성 가능
MemoRepository.java
@Transactional
@Modifying
@Query("update Memo m set m.memoText = :#{#param.memoText}
where m.mno = :#{#param.mno}")
int updateMemoText2(@Param("param") Memo memo);
MemoRepositoryTest.java
@Test
public void testUpdateMemoText2() {
Memo memo = new Memo();
memo.setMno(12L);
memo.setMemoText("Hi Spring Boot");
int countUpdated = memoRepository.updateMemoText2(memo);
}
set 메소드를 이용하려면 Memo클래스에 @Setter 어노테이션 추가
<실행결과>
- @Query와 페이징 처리
Pageable 타입의 파라미터를 적용하면 페이징처리와 정렬에 관한 부분을 따로 작성하지 않아도 적용 가능
반환 타입을 Page<E>로 하면 count를 처리하는 쿼리를 적용할 수 있음
() 안에 JPQL을 작성할 때 'value=' 다음에 검색 하는 쿼리를 작성하고 쉼표를 붙인 뒤 'countQuery=' 다음에 count(페이징에 필요. 페이징 작업하는 전체 행의 개수)하는 쿼리를 작성
* 예시 - mno가 80 이상인 행들을 검색 (페이징처리 적용)
MemoRepository.java
@Query(value="select m from Memo m where m.mno > :mno",
countQuery = "select count(m) from Memo m where m.mno > :mno")
Page<Memo> getListWithQuery(@Param("mno") Long mno, Pageable pageable);
MemoRepositoryTest.java
@Test
public void testGetListWithQuery() {
Pageable pageable = PageRequest.of(0, 10, Sort.by("mno").descending());
Page<Memo> result = memoRepository.getListWithQuery(80L, pageable);
for(Memo memo : result) {
System.out.println(memo);
}
}
<실행결과>
- Object[] 반환
쿼리 메소드의 경우에는 엔티티 타입의 데이터만 추출하지만 @Query를 이용하면 필요한 데이터를 Object[] 형태로 선별적으로 추출 가능
join 이나 GROUP BY등을 이용할 때 적당한 엔티티 타입이 존재하지 않는경우 Object[] 타입을 반환 타입으로 지정 가능
JPQL에서는 CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP등의 구문을 제공해서 현재 DB의 시간을 구할 수 있도록 해줌. 이러한 데이터를 메소드의 반환값에 포함 시키려면 반환 타입을 Object[]으로 하면 됨
* 예시 - 테이블의 데이터와 현재시간 추출 메소드
MemoRepository.java
@Query(value = "select m.mno, m.memoText, CURRENT_DATE from Memo m where m.mno>:mno")
List<Object[]> getListWidthQueryObject(@Param("mno") Long mno);
현재 날짜 정보에 해당하는 CURRENT_DATE이용
메소드의 반환타입을 List<Object[]> 타입으로 지정 -> 쿼리문의 결과가 Object 타입의 배열에 담겨서 List의 요소로 반환
MemoRepositoryTest.java
@Test
public void testGetListWithQueryObject() {
List<Object[]> result = memoRepository.getListWithQueryObject(90L);
for (Object[] obj : result) {
System.out.println(obj[0] + "\t" + obj[1] +"\t" + obj[2]);
}
}
getListWithQueryObject() 메소드의 결과를 List<Object[]> 타입의 변수에 받아서 출력
<실행결과>
- Native SQL 처리
DB 고유의 SQL 구문을 그대로 활용 가능
JPA 자체가 DB에 독립적으로 구현이 가능한 장점을 잃어버리지만 경우에 따라 복잡한 join 구문등을 처리하기 위해서 어쩔 수 없는 선택을 하는 경우가 있음
@Query() 안에 value= 에 실행할 쿼리를 작성하고 , 후에 nativeQuery=true 작성
쿼리를 작성할 때는 엔티티 객체를 작성하는 것이 아닌 실제 DB 의 테이블 명을 작성
* 예시
MemoRepository.java
@Query(value = "select * from tbl_memo where mno > 90", nativeQuery = true)
List<Memo> getNativeResult();
value에 실제 DB에서 작동하는 쿼리를 작성하고 , 뒤에 nativeQuery를 True로 해서 native query임을 표시
MemoRepositoryTest.java
@Test
public void testGetNativeResult() {
List<Memo> result = memoRepository.getNativeResult();
for (Memo memo : result) {
System.out.println(memo);
}
}






















댓글
댓글 쓰기