[SpringBoot] Spring Data Jpa와 관계형 데이터베이스
--- 저를 위한 공부입니다
많은 부분이 생략되어 있습니다 ---
1. MyBatis와 JPA
1) MyBatis - SQL Mapper - 쿼리를 매핑
2) JPA - 자바 표준 ORM(Object Relational Mapping) - 객체를 매핑
2. 관계형 데이터베이스와 객체지향 프로그래밍
1) 관계형 데이터베이스 : 어떻게 데이터를 저장할지에 초점
2) 객체지향 프로그래밍 : 메시지를 기반으로 기능과 속성을 한 곳에서 관리
* 먼저 JPA가 어떻게 등장하게 되었나?
→ 관계형 데이터 베이스와 객체지향 프로그래밍 언어의 패러다임이 서로 다른데, 객체를 데이터베이스에 저장하려고 하니 여러 문제가 발생 = 패러다임 불일치
→ 상속, 1:N 등 다양한 객체 모델링을 데이터베이스로는 구현할 수 X
→ 웹 애플리케이션 개발이 점점 데이터베이스 모델링에만 집중하게 됨
→ JPA는 이런 문제점을 해결하기 위해 등장!
* JPA(Java Persistence API)
: 객체-관계 매핑(ORM)을 위한 API를 제공
쉽게 말하면 객체지향 프로그래밍 언어와 관계형 데이터베이스를 중간에서 패러다임 일치를 시켜주기 위한 기술
즉, 개발자는 객체지향적으로 프로그래밍을 하고 JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행함.
→ 개발자는 이제 객체지향적으로 코드를 표현할 수 있으니 더는 SQL에 종속적인 개발을 하지 않아도 됨! 자바 객체와 데이터베이스 테이블 간의 매핑을 간편하게 처리 가능!@
객체 중심 개발의 장점은? 생산성이 향상되고, 유지보수가 편리하다는 것
3. Entity 클래스
: 실제 DB와 매칭될 클래스. DAO 패키지와는 결이 다름
(보통 도메인을 담을 패키지를 하나 생성하고 그 패키지에 Entity클래스 추가함
여기서 도메인이란? 게시글, 댓글, 회원, 결제 등 소프트웨어에 대한 요구사항 or 문제 영역)
그간 xml에 쿼리를 담고, 클래스는 쿼리의 결과만 담던 일들이 모두 도메인 클래스라고 불리는 곳에서 해결됨
1) 이 Entity 클래스에서는 절대 setter 메소드를 만들지 않음!!!
→ 값 변경이 필요하면 명확히 그 목적과 의도를 나타낼 수 있는 메소드를 추가할 것
- 그럼 setter가 없는데 어떻게 값을 채워서 DB에 삽입하지?
① 기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입
② 값 변경이 필요한 경우, 해당 이벤트에 맞는 public 메소드를 호출하여 변경할 것
①의 경우에서 생성자를 대신해 @Builder를 통해 제공되는 빌더 클래스를 사용할 것
생성자나 빌더나 생성 시점에 값을 채워주는 역할은 같음
but, 생성자의 경우 지금 채워야 할 필드가 뭔지 명확히 지정할 수 X
빌더를 사용하면 어느 필드에 어떤 값을 채울건지 명확하게 지정할 수 있음
2) 수많은 서비스 클래스나 비즈니스 로직들이 Entity 클래스를 기준으로 동작하기 때문에 Request, Response 클래스로 사용해선 X
→ Request, Response 용 DTO는 View를 위한 클래스라 자주 변경이 필요하고, Controller에서 결괏값으로 여러 테이블을 조인해서 줘야 할 경우가 많아 Entity 클래스와 분리해서 사용해야 함
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@Entity
public class Posts {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 웬만하면 PK는 Long 타입의 Auto_increment를 추천
@Column(length = 500, nullable = false)
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
// 컬럼 선언 안 해도 해당 클래스의 모든 필드는 모두 컬럼이 됨
// 기본값 이외에 추가로 변경이 필요한 옵션이 있을 때 사용
// 문자열의 경우 VARCHAR(255)가 기본값
private String author;
@Builder
public Posts(String title, String content, String author){
this.title = title;
this.content = content;
this.author = author;
}
public void update(String title, String content){
this.title = title;
this.content = content;
}
}
4. Service 역할에 대해
: Service는 트랜잭션, 도메인 간 순서를 보장하는 역할만 함(비즈니스 로직을 처리해야 한다는 건 오해임!)
그럼 비즈니스 로직은 누가 처리해???
→ 일단 먼저 스프링 웹 계층을 봐보자
① Web Layer
: Controller와 JSP 등 뷰 템플릿 영역. 필터, 인터셉터 등 외부 요청과 응답에 대한 전반적인 영역
② Service Layer
: Controller와 DAO의 중간 영역. @Transactional이 사용되어야 하는 영역
③ Repository Layer
: 데이터베이스와 같이 데이터 저장소에 접근하는 영역(DAO 영역으로 이해하면 됨)
④ DTO(Data Transfer Object)
: 계층 간 데이터 교환을 위한 객체. 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨준 객체
⑤ Domain Model
: 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있도록 단순화시킨 것.
예를 들면, 택시 앱에서 배차, 탑승, 요금 등이 모두 도메인이 될 수 있음.
@Entity가 사용된 영역 역시 도메인 모델에 해당함
무조건 데이터베이스의 테이블과 관계가 있어야만 하는 건 아님. VO처럼 값 객체들도 이 영역에 해당함
다시 돌아와서 비즈니스 로직은 누가 담당해?????
→ Domain 임!!!
기존에 Service에서 처리하던 방식을 트랜잭션 스크립트라고 함
모든 로직이 서비스 클래스 내부에서 처리되고, 그러다보니 서비스 계층이 무의미하며, 객체란 단순히 데이터 덩어리 역할만 하게 됨.(회사에서 egov 쓰는데 이렇게 했다)
하지만 도메인 모델에서 처리하면, 서비스 메소드는 트랜잭션과 도메인 간 순서만 보장하게 됨
5. 스프링에서 Bean을 주입받는 방식
- Autowired
- setter
- 생성자
⇒ 이 중에서 가장 권장하는 방식이 생성자로 주입받는 방식
@RequiredArgsConstructor 로 final이 선언된 모든 필드가 포함된 생성자를 생성(final이 없는 필드는 생성자에 포함되지 X)
생성자를 직접 안 쓰고 롬복 어노테이션을 사용하는 이유 : 해당 클래스의 의존성 관계가 변경될 때마다 생성자 코드를 계속 수정할 필요가 없음!
6. 더티 체킹
: 데이터베이스와 연결된 애플리케이션에서 주로 사용되는 개념으로, 데이터베이스에 대한 변경 사항을 추적하고 변경 사항을 저장하는 기술
Hibernate와 같은 ORM 프레임워크에서 자주 볼 수 있음
* 더티 체킹의 원리
① 변경 감지 : 객체의 상태가 변경되면 ORM 프레임워크는 이를 감지
→ 변경된 속성이나 연관된 엔티티 등이 확인됨
② 변경 추적 : 변경된 엔티티나 속성은 추적되어 관리 대상으로 표시됨
→ 이를 통해 ORM은 어떤 속성이 변경되었는지 추적할 수 있음
③ 변경 저장 : 트랜잭션이 커밋되거나 영속성 컨텍스트를 플러시할 때, 더티 체킹은 변경된 엔티티를 데이터베이스에 반영
→ 변경된 엔티티만을 식별하여 데이터베이스에 업데이트함
* 더티 체킹의 장점
- 효율적인 업데이트 : 변경된 엔티티만 데이터베이스에 반영 → 전체 엔티티를 업데이트하는 것보다 효율적
- 트랜잭션 내 일관성 유지 : 트랜잭션 내에서 변경된 엔티티를 추적하고 일관성을 유지할 수 있음
7. JPA Auditing
: 보통 엔티티에는 해당 데이터의 생성, 수정시간을 포함함(언제 등록되었고 수정되었는지는 유지보수에 있어 굉장히 중요)
but, 등록 or 수정 할 때마다 반복적인 코드가 들어감 → 이 문제를 해결하는 게 JPA
AuditingBaseTimeEntity 클래스를 새로 만들고, Entity 클래스에서 BaseTimeEntity를 상속받으면 반복적인 코드를 작성하지 않고 사용할 수 있음
(Application 클래스에 @EnableJpaAuditing을 추가하여 JPA Auditing을 활성화시켜야 함)
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
// JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들도(createdDate, modifiedDate) 칼럼으로 인식하도록 함
@EntityListeners(AuditingEntityListener.class) // BaseTimeEntity 클래스에 Auditing 기능을 포함시킴
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate; // Entity가 생성되어 저장될 때 시간 자동 저장
@LastModifiedDate
private LocalDateTime modifiedDate; // Entity의 값을 변경할 때 시간 자동 저장
}
'스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책으로 공부했습니다. 책 짱조음