01_JPA(Java Persistence API)
📍JPA(Java Persistence API)
객체와 관계형 데이터베이스의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙
JPA를 실제 코드로 작성한 가장 유명한 프레임워크가 Hibernate
Hibernate는 내부적으로 JDBC를 사용한다.
src/main/resources/application.yml
spring:
jpa:
hibernate:
ddl-auto: none
properties:
hibernate:
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
🔍 코드 설명
- spring.jpa.hibernate.ddl-auto : 스프링이 시작할 때 DB에 있는 테이블을 어떻게 처리할지에 대한 옵션
- create : 기존 테이블이 있다면 삭제 후 다시 생성한다.
- create-drop : 스프링이 종료될 때 테이블을 삭제한다.
- update : 객체와 테이블이 다른 부분만 변경한다.
- validate : 객체와 테이블이 동일한지 확인한다.
- none : 별다른 조치를 하지 않는다.
- spring.jpa.properties.hibernate.show_sql :JPA를 사용해 DB에 SQL을 날릴 때 SQL을 보여줄지 결정
- spring.jpa.properties.hibernate.format_sql :JPA를 사용해 DB에 SQL을 날릴 때 SQL을 예쁘게 포맷팅할지 결정
- spring.jpa.properties.hibernate.dialect :JPA가 알아서 Database끼리 다른 SQL을 조금씩 수정
@Entity
이 클래스가 DB에 저장되는 객체라고 JPA에게 알려준다.
JPA에 의해 테이블과 매핑된 객체는 파라미터를 가지지 않은 기본 생성자가 꼭 필요하다.
domain/user/User
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
@Column(nullable = false, length = 20)
private String name;
private Integer age;
protected User() { // 기본 생성자 추가
}
(중략)
}
🔍 코드 설명
- @Id : 이 필드를 primary key로 간주한다.
- @GeneratedValue : primary key는 DB에서 자동 생성해 주기 때문에 이 어노테이션을 붙여야 한다.
- @Column : 이 필드가 DB 테이블의 컬럼과 연결된다는 것을 알려준다.
- 기본 생성자 : JPA에 의해 테이블과 매핑된 객체는 기본 생성자가 반드시 필요
02_Spring Data JPA
📍POST API 수정
domain/user/UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
service/user/UserServiceV2
@Service
public class UserServiceV2 {
private final UserRepository userRepository;
public UserServiceV2(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void saveUser(UserCreateRequest request) {
User u = userRepository.save(new User(request.getName(), request.getAge()));
}
}
🔍 코드 설명
- save : 주어지는 객체를 저장하거나 업데이트해준다. JpaRepository에 내장되어 있는 기능.
📍GET API 수정
service/user/UserServiceV2
@Service
public class UserServiceV2 {
(중략)
public List<UserResponse> getUsers() {
return userRepository.findAll().stream()
.map(UserResponse::new)
.collect(Collectors.toList());
}
}
🔍 코드 설명
- findAll : 주어지는 객체가 매핑된 테이블의 모든 데이터를 가져온다.
📍PUT API 수정
service/user/UserServiceV2
@Service
public class UserServiceV2 {
(중략)
public void updateUser(UserUpdateRequest request) {
// select * from user where id = ?
User user = userRepository.findById(request.getId())
.orElseThrow(IllegalAccessError::new);
user.updateName(request.getName());
userRepository.save(user);
}
}
🔍 코드 설명
- findById : id를 기준으로 특정한 1개의 데이터를 가져온다. 이때 Java 라이브러리의 Optional 이 반환되는데, orElseThrow 를 사용하면 User가 비어있는 경우 에러를 던지게 된다.
📍DELETE API 수정
이름을 기준으로 User 정보 확인하기 위해 repository에 findByName 메소드 시그니처를 작성해야 한다.
domain/user/UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
User findByName(String name);
}
🔍 코드 설명
- findByName : 함수 이름만 작성하면, 알아서 SQL이 조립된다.
- find라고 작성하게 되면, 1개의 데이터를 가져온다.
- By 뒤에 붙는 필드 이름으로 SELECT 쿼리의 WHERE 문이 작성된다.
service/user/UserServiceV2
@Service
public class UserServiceV2 {
(중략)
public void deleteUser(String name){
// select * from user where name = ?
User user = userRepository.findByName(name).orElseThrow(IllegalArgumentException::new);
userRepository.delete(user);
}
}
🔍 코드 설명
- delete : 주어지는 객체를 삭제한다.
03_트랜잭션(Transaction)
📍트랜잭션(Transaction)
쪼갤 수 없는 업무의 최소 단위
여러 SQL을 사용해야 할 때 한 번에 성공시키거나 하나라도 실패하면 모두 실패시키는 기능
1. 서비스 메소드가 시작할 때 트랜잭션이 시작되어
START TRANSACTION;
2-1. 서비스 메소드 로직이 모두 정상적으로 성공하면 commit 되고
COMMIT;
2-2. 서비스 메소드 로직 실행 도중 문제가 생기면 rollback
ROLLBACK;
트랜잭션을 사용하면 영속성 컨텍스트가 생겨난다.
1. 변경 감지(Dirty Check) :영속성 컨텍스트 안에서 불러와진 Entity는 명시적으로 save 해주지 않아도 알아서 변경 감지하여 저장
@Transactional
public void updateUser(UserUpdateRequest request) {
User user = userRepository.findById(request.getId())
.orElseThrow(IllegalAccessError::new);
user.updateName(request.getName());
// userRepository.save(user); // 생략 가능
}
2. 쓰기 지연 : 트랜잭션이 commit 되는 시점에 SQL을 모아서 한 번만 날린다.
3. 1차 캐싱 : ID 기준으로 Entity를 기억한다.
📍트랜잭션 적용하기
대상 메소드에 @Transactional 어노테이션을 붙여주면 된다.
Unchecked Exception에 대해서만 롤백이 일어난다.
service/user/UserServiceV2
@Service
public class UserServiceV2 {
(중략)
@Transactional
public void saveUser(UserCreateRequest request) {
(생략)
}
@Transactional(readOnly = true)
public List<UserResponse> getUsers() {
(생략)
}
@Transactional
public void updateUser(UserUpdateRequest request) {
(생략)
}
@Transactional(readOnly = true)
public void deleteUser(String name){
(생략)
}
}
🔍 코드 설명
- @Transaction : 함수가 시작될 때 start transaction; 을 해준다.
- 함수가 예외 없이 잘 끝났다면 commit / 혹시라도 문제가 있다면 rollback
- readOnly : 데이터의 변경이 없고 조회 기능만 있을 때 true
'Back-end > SpringBoot' 카테고리의 다른 글
| [SpringBoot] Chap 3 - 도서 관리 서비스 역할 분리 및 스프링 컨테이너 (0) | 2026.01.21 |
|---|---|
| [SpringBoot] Chap 2 - 도서 관리 서비스 DB 조작하기 (0) | 2026.01.19 |
| [SpringBoot] Chap 1 - 도서 관리 서비스 API 생성하기 (0) | 2026.01.18 |
| [SpringBoot] 5장 게시글 조회(Read) (0) | 2026.01.15 |
| [SpringBoot] 3-4장 게시글 생성(CREATE), 롬복(lombok)과 리팩터링 (0) | 2026.01.11 |
