JPA in Spring Boot
SpringBoot 환경에서의 JPA
메모장 프로젝트 JPA 설정
build.gradle : spring-boot-starter-data-jpa 추가
// JPA 설정
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
application.properties : Hibernate 설정
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
show_sql, format_sql, use_sql_comments 옵션
- Hibernate가 DB에 요청하는 모든 SQL을 보기좋게 출력
ddl-auto
- create : 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
- create-drop : create와 같으나 종료시점에 테이블을 DROP
- update : 변경된 부분만 반
- validate : Entity와 테이블이 정상 매핑되었는지만 확인
- none: 아무것도 하지 않음
Memo Entity
package com.sparta.memo.entity;
import com.sparta.memo.dto.MemoRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Getter
@Setter
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
@NoArgsConstructor
public class Memo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)//autoincrement
private Long id;
@Column(name = "username", nullable = false)
private String username;
@Column(name = "contents", nullable = false, length = 500)
private String contents;
public Memo(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
public void update(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
}
SpringBoot 환경에서는 EntityManagerFactory와 EntityManager가 자동으로 생성됨
- application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory가 생성됨
Java환경에서는 META-INF폴더에 persistence.xml에 정보들을 줬었음
데이터베이스 접근 정보 DTL auto 옵션주기 등등 Memo entity 위치도 알려줬었다고 함
@PersistenceContext
EntityManager em;
- @PersistenceConext 애너테이션을 사용하면 자동으로 생성된 EntityManager를 주입받아 사용
Spring의 트랜잭션
- Spring 프레임워크에서는 DB의 트랜잭션 개념을 애플리케이션에 적용할 수 있도록 트랜잭션 관리자를 제공
- 검색 → SimpleJpaRepository → Classes
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
...
}
- 예시 코드 처럼 @Transactional 애너테이션을 클래스 상단이나 메서드에 추가하면 쉽게 트랜잭션 개념을 적용할 수 있다!
- 메서드가 호출되면, 해당 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶임
- 이때, 해당 메서드가 정상적으로 수행되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백
- 클래스에 선언한 @Transactional은 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여
- 맨 위의 SimpleJpaRepository에 @Tracsactional(readOnly=true)가 걸려있음 → 모든 transactional에 그 옵션을 부여
- 이때, save 메서드는 @Transactional 애너테이션이 추가되어있기 때문에 readOnly = true 옵션인 @Transactional을 덮어쓰게 되어 readOnly = false 옵션으로 적용됨
readOnly = true 옵션
- 트랜잭션에서 데이터를 읽기만 할 때 사용됨
- 이 속성을 사용하면 읽기 작업에 대한 최적화를 수행할 수 있음
- 만약, 해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생하기 때문에 주의해야함
- 읽기만 해서는 쓰기를 할 수가 없어서 save에서는 덮어써줌!
@Transactional
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Transactional
ex) 트랜잭션 테스트 성공
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
package com.sparta.memo;
import jakarta.persistence.PersistenceContext;
public class TransactionTest {
@PersistenceContext//스프링부트에서 자동으로 생성해주는 EntityManager를 주입받아옴
EntityManager em;
}
→ java.lang.NullPointerException: Cannot invoke "jakarta.persistence.EntityManager.persist(Object)" because "this.em" is null
에러남 SpringBootTest annotation 추가해줘야 한다고 함
@SpringBootTest//중요
public class TransactionTest {
@PersistenceContext//스프링부트에서 자동으로 생성해주는 EntityManager를 주입받아옴
EntityManager em;
package com.sparta.memo;
import com.sparta.memo.entity.Memo;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
public class TransactionTest {
@PersistenceContext//스프링부트에서 자동으로 생성해주는 EntityManager를 주입받아옴
EntityManager em;
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
}
- 트랜잭션이 적용되어 DB 작업이 성공
Hibernate:
/* insert for
com.sparta.memo.entity.Memo */insert
into
memo (contents,username)//autoincrement가 걸려있어서 id 따로 안 넣어줌
values
(?,?)
ex) 트랜잭션 테스트 실패
@Test
@DisplayName("메모 생성 실패")
void test2() {
Memo memo = new Memo();
memo.setUsername("Robbie");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
jakarta.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call 데이터를 수정할때는 무조건 transaction환경이 필요한데 tracsaction 환경이 잡혀있지 않은 상태에서 persist를 호출해서 오류가 발생
@Disabled //옵션을 걸면 Tests ignored 라고 뜸
영속성 컨텍스트와 트랜잭션의 생명주기
스프링 컨테이너 환경) 영속성 컨텍스트의 생명주기 == 트랜잭션의 생명주기
트랜잭션이 유지되는 동안 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능을 사용할 수 있다
→ 앞에서 작성한 테스트 코드 메서드에 트랜잭션이 적용되지 않았기 때문에 영속성 컨텍스트가 유지되지 못해 오류가 발생, persist call이 이루어지지 못함
Spring은 어떻게 Service 부터 Repository 까지 Transaction을 유지할 수 있는 걸까요?
Service의 트랜잭션이 적용된 메서드에서 Repository의 메서드를 호출할 때 무언가 처리되고 있는 것이 있는 걸까요?
Spring에서는 이러한 상황에서 트랜잭션을 제어할 수 있도록 트랜잭션 전파 기능을 제공하고 있습니다.
트랜잭션 전파
@Transactional에서 트랜잭션 전파 옵션을 지정할 수 있습니다.
트랜잭션 전파의 기본 옵션은 REQUIRED 입니다.
REQUIRED 옵션은 ****부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랜잭션에 합류하게됩니다.
트랜잭션 전파 테스트
ex) 트랜잭션 전파
MemoRepository에 다음 함수 추가
@Transactional
public Memo createMemo(EntityManager em) {
Memo memo = em.find(Memo.class, 1);
memo.setUsername("Robbie");
memo.setContents("@Transactional 전파 테스트 중!");
System.out.println("createMemo 메서드 종료");
return memo;
}
TransactionTest 복붙
package com.sparta.memo;
import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
public class TransactionTest {
@PersistenceContext
EntityManager em;
@Autowired
MemoRepository memoRepository;
@Test
@Transactional
@Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
@DisplayName("메모 생성 성공")
void test1() {
Memo memo = new Memo();
memo.setUsername("Robbert");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Test
@Disabled
@DisplayName("메모 생성 실패")
void test2() {
Memo memo = new Memo();
memo.setUsername("Robbie");
memo.setContents("@Transactional 테스트 중!");
em.persist(memo); // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
@Test
@Transactional
@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3() {
memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
}
}
- 실행 결과 자식 메서드 createMemo가 종료될 때 update가 실행되는 것이 아니라 부모 메서드에 트랜잭션이 합류되면서 부모 메서드가 종료된 후 트랜잭션이 커밋될 때 update가 실행된 것 확인가능
- 부모 메서드 test3의 @Transactional @Rollback(value = false)을 주석 처리하고 다시 한번 update를 시도해보면 합류할 부모 트랜잭션이 없기 때문에
- 자식 메서드가 종료된 후 트랜잭션이 커밋되면서 update가 실행되었음
자식 메서드/ 부모 메서드
class TransactionTest에서 MemoRepository를 주입받아와서 사용하고있음
@Autowired
MemoRepository memoRepository;
@Test
@Transactional
@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3() {
memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
}
test3에서 MemoRepository에 있는 createMemo를 부르고 있음
test3 method : 부모 메서드
create method: 자식 메서드
코드 살펴보기
@Transactional
public Memo createMemo(EntityManager em) {
Memo memo = em.find(Memo.class, 1);
memo.setUsername("Robbie");
memo.setContents("@Transactional 전파 테스트 중!");
System.out.println("createMemo 메서드 종료");
return memo;
}
Hibernate:
select
m1_0.id,
m1_0.contents,
m1_0.username
from
memo m1_0
where
m1_0.id=?
createMemo 메서드 종료
테스트 test3 메서드 종료
Hibernate:
/* update
for com.sparta.memo.entity.Memo */update memo
set
contents=?,
username=?
where
id=?
createMemo 메서드 종료 → 메서드가 끝났는데 트랜잭션이 종료되면 commit되면서 쿼리가 날라가야 하는거 아닌가?
test3로 날아와서 종료된 다음에야 업데이트가 날아가서 찍힌다고 함
자식 메서드의 transaction이 부모 메서드의 transaction에 합쳐졌다고 함
왜 이렇게 될 수 있냐면 @Transactional 에는
@Transactional(propagation = Propagation.REQUIRED)//이라는 게 존재함!
Propagation.*REQUIRED* : 디폴트, 부모 메서드에 tracsactional이 존재한다면 자식 메서드의 transaction은 부모 메서드의 transaction에 합류함!
디폴트 알아내기!
ctrl + Transactional 클릭 → find : propagation → default Propagation.*REQUIRED* 라고 써 있다
ex) 트랜잭션 not 전파, 자식은 transactional이 있는데 부모는 없는 경우
@Test
//@Transactional(propagation = Propagation.REQUIRED)
//@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3() {
memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
}
@Transactional
public Memo createMemo(EntityManager em) {
Memo memo = em.find(Memo.class, 1);
memo.setUsername("Madison");
memo.setContents("@Transactional not 전파 테스트 중!");
System.out.println("createMemo 메서드 종료");
return memo;
}
Hibernate:
select
m1_0.id,
m1_0.contents,
m1_0.username
from
memo m1_0
where
m1_0.id=?
createMemo 메서드 종료
Hibernate:
/* update
for com.sparta.memo.entity.Memo */update memo
set
contents=?,
username=?
where
id=?
테스트 test3 메서드 종료
→ 자식 메서드 끝나고 바로 transaction이 일어남
더 이상 사용 안 할 테스트는 커밋 전에 Disabled를 걸어줌
@Test
@Disabled
//@Transactional(propagation = Propagation.REQUIRED)
//@Rollback(value = false)
@DisplayName("트랜잭션 전파 테스트")
void test3_() {
//memoRepository.createMemo(em);
System.out.println("테스트 test3 메서드 종료");
}
Spring Data JPA 란 무엇일까?
Spring Data JPA
JPA를 쉽게 사용할 수 있게 만들어놓은 하나의 모듈
- JPA를 추상화시킨 Repository 인터페이스를 제공
- Hibernate와 같은 JPA구현체를 사용해서 ****구현한 클래스로 Repository 인터페이스를 사용가능
- 개발자들은 Repository 인터페이스를 통해 JPA를 간편하게 사용
Spring Data JPA의 SimpleJpaRepository
- Spring Data JPA에서는 JpaRepository 인터페이스를 구현하는 클래스를 자동으로 생성해줌
- Spring 서버가 뜰 때 JpaRepository 인터페이스를 상속받은 인터페이스가 자동으로 스캔이 되면,
- 해당 인터페이스의 정보를 토대로 자동으로 SimpleJpaRepository 클래스를 생성해 주고, 이 클래스를 Spring ‘Bean’으로 등록함
- 따라서 인터페이스의 구현 클래스를 직접 작성하지 않아도 JpaRepository 인터페이스를 통해 JPA의 기능을 사용할 수 있다
Spring Data JPA 사용방법
JpaRepository 등록
- JpaRepository<**"@Entity 클래스", "@Id 의 데이터 타입">**를 상속받는 interface 로 선언
- Spring Data JPA에 의해 자동으로 Bean 등록됨
- 제네릭스의 @Entity 클래스 위치에 Memo Entity를 추가했기 때문에 해당 MemoRepository는 DB의 memo 테이블과 연결되어 CRUD 작업을 처리하는 인터페이스가 됨
메모장 프로젝트 Spring Data JPA 적용
- public class MemoRepository →
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
SimpleJpaRepository에 @Repository 가 달려있어서 여기는 안 달아도 된다고 함
Memo service 원래 코드
package com.sparta.memo.service;
import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
Memo saveMemo = memoRepository.save(memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll();
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id);
if (memo != null) {
// memo 내용 수정
memoRepository.update(id, requestDto);
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
return id;
}
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id);
if (memo != null) {
memoRepository.delete(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
}
createMemo
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
Memo saveMemo = memoRepository.save(memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
수정할 게 전혀 없음
save는 구현을 안 했는데 SimpleRepository에 있는 save를 그냥 가져다가 쓰는 거라고 함
getMemos
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll();
}
→
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
}
stream에서 memo가 하나씩 빠져나올거고 map에서 변환이 될 거라고 함
updateMemo
type 이 Optional이면 null check를 해줘야 한다고 한다
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id).orElseThrow(()->
new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
);
이렇게 하거나 아니면 Optional타입으로 받으면 되는데 그냥 한번에 Memo 타입으로 받으려고 저렇게 했다고 함 이제 null check하는 부분 필요 없어서 뺌
public Long updateMemo(Long id, MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = memoRepository.findById(id).orElseThrow(()->
new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
);
// memo 내용 수정
memo.update(requestDto);
return id;
}
겹치는 부분 먼저 함수로 뺌
private Memo findMemo(Long id){
Memo memo = memoRepository.findById(id).orElseThrow(()->
new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
);
return memo;
}
최종 수정
@Transactional
public Long updateMemo(Long id, MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findMemo(id);
// memo 내용 수정
memo.update(requestDto);
return id;
}
@Transactional 을 꼭 해줘야 변경 감지가 일어난다고 함
Memo의 update 함수
public void update(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
deleteMemo
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findMemo(id);
if (memo != null) {
memoRepository.delete(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
→
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findMemo(id);
memoRepository.delete(memo);
return id;
}
JPA Auditing
메모가 저장될때마다 시간을 보여줄 수 있도록 구현을 하고싶대
데이터의 생성(created_at), 수정(modified_at) 시간은 포스팅, 게시글, 댓글 등 다양한 데이터에 매우 자주 활용되는데 각각의 Entity의 생성 수정 시간을 매번 작성하는건 너무 비효율적임
- Spring Data JPA에서는 시간에 대해서 자동으로 값을 넣어주는 기능인 JPA Auditing을 제공
- @MappedSuperclass
- JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 createdAt, modifiedAt 처럼
- 추상 클래스에 선언한 멤버변수를 컬럼으로 인식할 수 있다.
- 자바 객체지향에서 상속받으면 상위클래스 필드도 가지는거랑 똑같은 개념
- @EntityListeners(AuditingEntityListener.class)
- 해당 클래스에 Auditing 기능을 포함시켜줌
- 이걸 달아줘야 자동으로 시간 넣는 기능이 수행됨
- @CreatedDate
- Entity 객체가 생성되어 저장될 때 시간이 자동으로 저장됨
- 최초 생성 시간이 저장되고 그 이후에는 수정되면 안되기 때문에 updatable = false 옵션을 추가함
- @LastModifiedDate
- 조회한 Entity 객체의 값을 변경할 때 변경된 시간이 자동으로 저장됨
- 처음 생성 시간이 저장된 이후 변경이 일어날 때마다 해당 변경시간으로 업데이트됨
- ←updatable = false 옵션을 추가하지 않아서!
- @Temporal
- 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용함
- DB에는 Date(날짜), Time(시간), Timestamp(날짜와 시간)라는 세 가지 타입이 별도로 존재함
- DATE : ex) 2023-01-01
- TIME : ex) 20:21:14
- TIMESTAMP : ex) 2023-01-01 20:22:38.771000
<aside> 💡 @SpringBootApplication 이 있는 class에 @EnableJpaAuditing 추가!
- JPA Auditing 기능을 사용하겠다는 정보를 전달해주기 위해 @EnableJpaAuditing 을 추가해야함 </aside>
- entity 디렉토리 → Timestamped 클래스 생성
package com.sparta.memo.entity;
import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime modifiedAt;
}
- @EnableJpaAuditing 애너테이션을 추가한다.
MemoApplication(main method있는 클래스) 가서
@EnableJpaAuditing//이거 추가해줌!
@SpringBootApplication
public class MemoApplication {
public static void main(String[] args) {
SpringApplication.run(MemoApplication.class, args);
}
}
- 적용하고자하는 Entity 클래스에서 Timestamped를 상속받는다.
public class Memo extends Timestamped{
- 반환할 때 시간도 같이 반환해줘야 해서 MemoResponseDTO 수정한다
package com.sparta.memo.dto;
import com.sparta.memo.entity.Memo;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
public class MemoResponseDto {
private Long id;
private String username;
private String contents;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.username = memo.getUsername();
this.contents = memo.getContents();
}
}
→
package com.sparta.memo.dto;
import com.sparta.memo.entity.Memo;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
public class MemoResponseDto {
private Long id;
private String username;
private String contents;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.username = memo.getUsername();
this.contents = memo.getContents();
this.createdAt = memo.getCreatedAt();
this.modifiedAt = memo.getModifiedAt();
}
}
Query Methods란 무엇일까?
Query Methods란
- Spring Data JPA에서는 메서드 이름으로 SQL을 생성할 수 있는 Query Methods 기능을 제공
- JpaRepository 인터페이스에서 해당 인터페이스와 매핑되어있는 테이블에 요청하고자 하는 SQL을 메서드 이름을 사용하여 선언할 수 있다.
MemoRepository
package com.sparta.memo.repository;
import com.sparta.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo, Long> {
List<Memo> findAllByOrderByModifiedAtDesc();
}
- SimpleJpaRepository 클래스가 생성될 때 위처럼 직접 선언한 JpaRepository 인터페이스의 모든 메서드를 자동으로 구현해줌
- JpaRepository 인터페이스의 메서드 즉, Query Methods는 개발자가 이미 정의 되어있는 규칙에 맞게 메서드를 선언하면 해당 메서드 이름을 분석하여 SimpleJpaRepository에서 구현됨
- 따라서 우리는 인터페이스에 필요한 SQL에 해당하는 메서드 이름 패턴으로 메서드를 선언 하기만 하면 따로 구현하지 않아도 사용할 수 있음
- findAllByOrderByModifiedAtDesc : Memo 테이블에서 ModifiedAt 을 기준(OrderByModifiedAt)으로 전체 데이터를 내림차순(Desc)으로 select 해오는(find) SQL을 실행하는 메서드를 생성
- List<Memo> findAllByUsername(String username);: select * from … where username = …
ByUsername 에 값을 전달해줘야하기 때문에 파라미터에 해당 값의 타입과 변수명을 선언
Query Methods 는 메서드의 파라미터를 통해 SQL에 필요한 값을 동적으로 받아 처리!
메모장 프로젝트 Query Methods 적용
최신 메모가 가장 상단에 나올 수 있도록 수정
MemoService
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
}
→
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAllByOrderByModifiedAtDesc().stream().map(MemoResponseDto::new).toList();
}
'TIL(Develop)' 카테고리의 다른 글
인증과 인가 (2) | 2023.11.20 |
---|---|
Spring Bean (0) | 2023.11.20 |
[JPA Core] (0) | 2023.11.15 |
IoC와 DI (5) | 2023.11.09 |
3 Layer Architecture 역할 분리 (1) | 2023.11.09 |