관계형 데이터베이스 RDBMS - Relational Database Management System은 테이블 형태로 이루어진 데이터 저장소입니다.
예를 들어 회원 테이블이 있다고 가정할 때, 각 행은 고유의 값을 가지고 있는 아이디를 가지고 있고, 이메일, 나이와 같은 회원에 관련된 정보들이 들어가게 됩니다. 이때, 각 속성(ID, 이메일, 나이)는 고유한 값을 가지고 있어야 합니다.
ID 이메일 나이 1 kim@naver.com 20 2 Lee@gmail.com 22 3 Seo@nate.com 24
1
,kim@naver.com
,20
→ 행
1
,2
,3
또는kim@naver.com
,Lee@gmail.com
,Seo@nate.com
과 같은 각 속성을 구분하는 것을 열이라고 합니다.
H2
는 자바로 작성되어 있는RDBMS
로, 스프링 부트가 지원하는 인메모리 관계형 데이터베이스입니다.H2는 휘발소 저장소
입니다. 즉, 데이터를 다른 공간에 따로 보관하는 것이 아니라 애플리케이션 자체 내부에 데이터를 저장합니다. 애플리케이션이 재실행 되면 저장된H2
데이터들은 초기화가 됩니다.이러한 특징으로 간편하고, 사용하기 좋아서 테스트 용도로 많이 사용됩니다.
MySQL
은 서버에서 데이터를 따로 저장하는 관계형 데이터베이스 입니다. 즉, 애플리케이션의 실행과 종료에 영향을 받지 않고 독립적으로 존재하게 됩니다. 따라서개발과 테스트에는 H2를 사용
하게 되고,실제 서비스를 올릴 떄는 MySQL을 사용
하게 됩니다.
ORM(Object-Relational Mapping)
은 자바의 객체와 데이터베이스를 연결하는 프로그래밍 기법입니다. 보통 데이터베이스에 접근할 때는 SQL문을 사용하게 됩니다. 그래서 SQL문을 알고 있어겠죠 ?하지만
ORM
을 알고 있다면 SQL문을 전혀 몰라도 자바 언어로만 데이터베이스에 접근해서 원하는 데이터를 받아올 수 있습니다. 자바의 객체가 데이터베이스를 연결해 자바 언어로 가져다 쓰는 것이죠 !당연히
ORM
에도 단점이 있습니다.
설명 장점 1 SQL을 사용하지 않고 데이터 베이스에 접근 가능 장점 2 객체지향적으로 코드를 작성할 수 있기 때문에 비즈니스 로직에만 집중 장점 3 데이터베이스 시스템이 추상화되어 있기 때문에 MySQL에서
PostgreSQL 전환이 자유롭다. → 데이터베이스 시스템에 대한 종속성 줄어든다.단점 1 프로젝트의 복잡성이 커질수록 사용 난이도 향상 단점 2 복잡하고 무거운 쿼리는 ORM으로 해결이 불가능한 경우가 있음
ORM
의 종류는 여러가지가 있습니다. 자바에서는JPA(Java Persistence API)
를 표준으로 사용하죠.JPA
는 자바에서RDB
를 사용하는 방식을 정의한 인터페이스인데,실제 사용을 위해서는 ORM 프레임워크
를 추가로 선택해야 합니다.대표적인 프레임워크가
Hibernate
입니다.
Hibernate
는JPA 인터페이스
를 구현한 구현체이자자바용 ORM 프레임워크
입니다.내부적으로는 JDBC API
를 사용합니다.
Hibernate
는 자바 객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유자주로 사용할 수 있도록 해줍니다.🔍 용어 정리
JPA(Java Persistence API)
→ 자바 객체와 데이터베이스를 연결해 데이터를 관리. 객체지향 도메인으로 모델과 데이터베이스의 다리 역할을 한다.
Hibernate
→JPA
의 인터페이스를 구현. 내부적으로는JDBC API
를 사용
Entity
는 DB 테이블과 매핑되는 객체입니다.
Entity
는 본질적으로 자바 객체이므로 일반 객체와 다르지 않지만, 데이터베이스의 테이블과 직접 연결된다는 아주 특별한 특징이 있기 때문에 구분되어 부릅니다.즉,
Entity
는 객체이긴 하지만 데이터베이스에 영향일 미치는 쿼리를 실행하는 객체인 것입니다.
Entity Manager
는Entity
를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제 등의 역할을 합니다. 그리고 이러한Entity Manager
을 만드는 곳이Entity Manager Factory
입니다.
Entity Manage Factory
가Entity Manager
를 만들면, 이Entity Manager
가Entity
를 관리하게 되는 것이죠.만약 회원 가입 요청이 생기게 되면,
Entity Manager Factory
가Entity Manager
를 생성하고,Entity Manager
가 가입 처리를 해 데이터베이스에 회원 정보를 저장하는 흐름입니다.
스프링 부트에서는 내부에서
Entity Manager Factory
를 하나만 생성해서 관리하고,@PersistenceContext
또는@Autowired
애너테이션을 사용해서Entity Manager
를 사용합니다.@PersistenceContext EntityManager em; // 프록시 엔티티 매니저. 필요할 때 진짜 엔티티 매니저 호출
스프링 부트는 기본적으로 하나의 빈을 생성해서 공유하므로 동시성 문제가 발생할 수 있습니다. 그래서 실제로는
Entity Manger
가 아닌 실제Entity Manager
와 연결하는 프록시(가짜)Entity Manager
를 사용합니다.필요할 때 데이터베이스 트랜잭션과 관련된 실제
Entity Manager
를 호출하는 것입니다. 즉,Entity Manager
는Spring Data JPA에서 관리
하므로 직접 생성하거나 관리할 필요가 없습니다.
Entity Manager
는Entity
를 영속성 컨텍스트에 저장하는 특징을 가지고 있습니다.영속성 컨텍스트는 JPA의 중요 특징 중 하나로, Entitiy를 관리하는 가상의 공간입니다.
영속성 컨텍스트를 사용함으로 데이터베이스에서 효과적으로 데이터를 가져올 수 있고,
Entity
를 편하게 사용할 수 있습니다.
영속성 컨텍스트에는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩의 특징이 있습니다.
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있습니다. 이때, 캐시의 키는 엔티티의
@ID 애너테이션
이 달린 기본키 역할을 하는 식별자이며 값은 엔티티입니다.엔티티를 조회하면 1차 캐시에서 데이터를 조회하고, 값이 있으면 반환합니다. 만약 값이 없다면 데이터베이스에서 조회해 1차 캐시에 저장한 다음 반환합니다.
이를 통해 캐시된 데이터베이슬 거치지 않아도 되므로 매우 빠르게 조회가 가능합니다.
쓰기 지연은 트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한번에 실행하는 것을 의미합니다.
만약, 쿼리가 3개라면 영속성 컨텍스트는 트랜잭션을 커밋하는 시점에 3개의 쿼리를 한꺼번에데이터베이스로 전송합니다.
이를 통해 데이터베이스 시스템의 부담을 줄일 수 있습니다.
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해, 변경된 값이 있다면 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영합니다.
쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 데이터베이스 시스템의 부담을 줄일 수 있습니다.
쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아닌, 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미합니다.
반대로
즉시 로딩
은 쿼리를 보내 연관된 모든 데이터를 가져오는 방식도 있습니다.
이 모든 특징들은 모두 데이터베이스의 접근을 최소화해 성능을 향상시킬 수 있습니다. 캐시를 하거나, 자주 쓰지 않게 하거나, 변화를 미리 준비하거나 하는 등의 방법들을 이용하는 것이 !
엔티티는 4가지 상태가 있습니다.
분리(detached)
→ 영속성 컨텍스트가 관리하고 있지 않는 상태
관리(managed)
→ 영속성 컨텍스트가 관리하고 있는 상태
비영속(transient)
→ 영속성 컨텍스트와 전혀 관계가 없는 상태
삭제(removed)
→ 삭제된 상태
이 상태는 특정 메서드를 호출해 변경할 수 있는데, 필요에 따라 엔티티의 상태를 조절해 데이터를 올바르게 유지 관리할 수 있습니다.
public class example {
@Autowired
EntityManager em;
public void example_EntityStatus() {
// 비영속 상태
Member member = new Member(1L, "홍길동");
// 관리 상태
em.persist(member);
// 비영속 상태
em.detach(member);
// 삭제 상태
em.remove(member);
}
}
Member member = new Member(1L, "홍길동");
→ 엔티티 매니저가 엔티티를 처음 생성, 관리하고 있지 않는 상태(비영속 상태)
em.persist(member);
→ 엔티티가 영속성 컨텍스트에서 상태가 관리된다.
em.detach(member);
→ 엔티티가 영속성 컨텍스트에서 분리된다.
em.remove(member);
→ 객체가 필요하지 않아서 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
스프링 데이터는 비즈니스 로직에 더 집중할 수 있게 데이터베이스 사용 기능을 클래스 레벨에서 추상화
스프링 데이터에서 제공하는 인터페이스를 통해서 스프링 데이터를 사용
이 인터페이스에는 CRUD를 포함한 여러 메서드가 포함되어, 쿼리를 자동적으로 생성해준다. 이외에도 페이징 처리 기능, 메서드 이름을 이용해 자동으로 쿼리 빌딩하는 기능이 제공되는 등 많은 장점이 있다.
스프링 데이터 JPA는 스프링 데이터의 공통적인 기능에서 JPA의 유용한 기술이 추가된 기술입니다. 스프링 데이터 JPA는 스프링 데이터의 인터페이스인
PagingAndSortingRepository
를 상속받아 JpaRepository` 인터페이스를 만들었으며, jPA를 더 편리하게 사용하는 메서드를 제공합니다.
기존의 엔티티 매니저를 통해서 엔티티 상태를 바꾸는 방식은 아래와 같았습니다.
@Autowired
EntityManager em;
public void example_EntityStatus() {
// 비영속 상태
Member member = new Member(1L, "홍길동");
// 관리 상태
em.persist(member);
// 비영속 상태
em.detach(member);
// 삭제 상태
em.remove(member);
}
하지만 스프링 데이터 JPA를 사용하면 리포지터리 역할을 하는 인터페이스를 만들어 데이터에비스의 테이블 조회, 수정, 생성, 삭제 같은 작업을 간단히 할 수 있습니다.
JpaRepository
인터페이스를 구현한 인터페이스에서 상속받고 제네릭에는 관리할<엔티티 이름, 엔티티 기본키의 타입>
을 입력해 기본 CRUD를 위해 만든 메서드를 사용할 수 있습니다.
@Service
public class MemberService {
@Autowired
MemberRepository memberRepository;
public void test() {
// 생성(Create)
memberRepository.save(new Member(1L, "A"));
// 조회(Read)
Optional<Member> member = memberRepository.findById(1L);
List<Member> allMember = memberRepository.findAll();
// 삭제(Delete)
memberRepository.deleteById(1L);
}
}
스프링 부트3 - 구조 (퍼시스턴스 계층 코드)에서 Member.java 파일을 확인해봅시다 !
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity // (1) 엔티티로 지정
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // (2) 기본 생성자
@AllArgsConstructor
public class Member {
@Id // (3) id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // (4) 기본키 자동으로 1씩 증가
@Column(name = "id", updatable = false)
private Long id; // DB 테이블의 'id' 컬럼과 매칭
@Column(name = "name", nullable = false) // (5) name이라는 not null 컬럼과 매핑
private String name; // DB 테이블의 'name' 컬럼과 매칭
}
이제 코드를 이해 이해해보겠습니다 : >
@Entity // (1) 엔티티로 지정
→
@Entity
애너테이션은 Member 객체를 JPA가 관리하는 엔티티로 지정합니다.public class Member{ }
클래스와 실제 데이터베이스의 테이블을 매핑시킵니다.@Entity
의 속성 중에 name을 사용하면 name의 값을 가진 테이블 이름과 매핑되고, 테이블을 지정하지 않으면 클래스 이름과 같은 이름의 테이블과 매핑됩니다.예제 코드에서는 따로 테이블을 지정하지 않았으므로 클래스 이름과 같은 데이터베이스의 테이블인 member 테이블과 매핑됩니다.
만약
@Entity
애너테이션에서 테이블을 지정한다면 다음과 같이 name 파라미터에 값을 넣어주시면 됩니다.
🔽name 파라미터로 Article 클래스와 member_list 테이블 매핑 예시
@Entity(name = "member_list") // 'member_list'라는 이름을 가진 테이블과 매핑 public class Article { ... }
@NoArgsConstructor(access = AccessLevel.PROTECTED) // (2) 기본 생성자
→
protected
는 기본 생성자입니다.엔티티는 반드시 기본 생성자가 있어야 하며
,접근 제어자는 public 또는 protected
여야 합니다.아무래도 public 보다는 protected가 더 안전하기 때문에
접근 제어자의 default는 protected
입니다.
@Id // (3) id 필드를 기본키로 지정
→
@Id
는 Long 타입의 id 필드를 테이블의 기본키로 지정합니다.
@GeneratedValue(strategy = GenerationType.IDENTITY) // (4) 기본키 자동으로 1씩 증가
→
@GeneratedValue
는 기본 키의 생성 방식을 결정합니다. 해당 코드는 자동으로 기본키가 증가되도록 설정되어 있습니다.
🔽 자동 키 생성 설정 방식
Auto
: 선택한 데이터베이스의 방언(dialect)에 따라 방식을 자동으로 선택(기본값)IDENTITY
: 기본 키 생성을 데이터베이스에 위임(AUTO_INCREMENT)SEQUENCE
: 데이터베이스 시퀀스를 사용해 기본 키를 할당 (오라클에서 주로 사용)TABLE
: 키 생성 테이블 사용
@Column(name = "name", nullable = false) // (5) name이라는 not null 컬럼과 매핑
@Column
애너테이션은 데이터베이스의 컬럼과 필드를 매핑해줍니다. 대표적인@Column
애너테이션의 속성은 아래와 같습니다.
name
: 매핑할 컬럼 이름(설정하지 않으면 필드 이름으로 지정)nullable
: 컬럼의 null 허용 여부. 설정하지 않으면 trueunique
: 컬럼의 유일한 값 여부. 설정하지 않으면 falsecolumnDefinition
: 컬럼 정보 설정. default 값을 줄 수 있다.
마지막으로
MemberRepository.java
의 코드입니다.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이 리포지토리는 엔티티에 있는 데이터들을 조회하거나 저장, 변경, 삭제를 할 때 사용하는 인터페이스 입니다. 스프링 데이터 JPA에서 제공하는 인터페이스인 JpaRepository 클래스를 상속
받아 간단하게 구현할 수 있습니다.