
'스프링 부트3 백엔드 개발자 되기' 책을 참고하며 작성 중 입니다.
데이터를 매우 효율적으로 보관하고 꺼내볼 수 있는 곳
많은 사람이 안전하게 데이터를 사용하고, 관리할 수 있다는 이점
데이터베이스를 관리하기 위한 소프트웨어
DBMS ( DataBase Management System)
관계형 모델을 기반으로 한 DBMS, 테이블 형태로 이루어진 데이터 저장소
자바로 작성되어 있는 RDBMS, 스프링 부트가 지원하는 인메모리 관계형 데이터베이스
애플리케이션 자체 내부에 데이터를 저장하기 때문에 휘발성
개발 및 테스트 시에는 H2, 서비스 배포 시에는 MySQL
테이블 : 데이터를 구성하기 위한 가장 기본적인 단위, 행과 열로 구성
행 : 테이블 구성 요소 중 하나, 테이블의 가로로 배열된 데이터의 집합
반드시 고유한 식별자인 기본키를 가져야 하며,
레코드 라고 부르기도 한다.
열 : 테이블의 구성 요소 중 하나, 행에 저장되는 유형의 데이터
각 요소에 대한 속성을 나타내며 무결성 보장
기본키 : 행을 구분할 수 있는 식별자
테이블에서 유일 해야 하며, 수정 불가, NULL값 불가
쿼리 : 데이터베이스에서 데이터 처리를 위해 사용하는 명령문
SQL 이라는 데이터베이스 전용 언어를 사용하여 작성
SELECT <무엇을> FROM <어디에서> WHERE <무슨>
SELECT * FROM customers;
| 명령어 | 설명 | 예시 |
|---|---|---|
| = | 동일한 값을 가진 행 조회 | |
| != 또는 <> | 동일하지 않은 행 조회 | |
| <,>,<=,>= | 대소 비교하여 조회 | |
| BETWEEN | 지정된 값의 사이 값을 조회 | age BETWEEN 10 AND 20 |
| LIKE | 패턴 매칭을 위해 사용, %를 사용하면 와일드 카드로 사용 가능 | name LIKE ' 김%' |
| AND | 두 조건 모두 참이면 조회 | |
| OR | 두 조건 중 하나라도 참이면 조회 | |
| IS NULL, IS NOT NULL | NULL 값의 존재 여부 검사 | name IS NULL |
INSERT INTO <어디에> VALUES <어떤 값을>
INSERT INTO customers(name, age) VALUES ('A', 30);
DELETE FROM <어디에서> WHERE <어떤 조건으로>
DELETE FROM customers WHERE id=5;
UPDATE <어디에> SET <무슨 칼럼을 어떤 값으로> WHERE <어떤 조건으로>
UPDATE customers SET age=11 WHERE name='A';
주의 WHERE 절을 생략하면 테이블의 모든 레코드가 수정된다
Object-Relational Mapping
자바의 객체와 데이터베이스를 연결하는 프로그래밍 기법
객체와 데이터베이스를 연결해 자바 언어로만 데이터베이스를 다룰 수 있게 하는 도구
- SQL을 직접 작성하지 않고 사용하는 단어로 데이터베이스에 접근
- 객체지향적으로 코드를 작성할 수 있어, 비즈니스 로직에만 집중가능
- 데이터베이스 시스템이 추상화되어 있기 때문에 종속성이 줄어듬
- 매핑하는 정보가 명확하기 때문에 ERD 의존도를 낮출 수 있고, 유지보수에 유리
- 프로젝트의 복잡성이 커질수록 사용 난이도 UP
- 복잡하고 무거운 쿼리는 ORM으로 해결이 불가능할 수 있음
DBMS도 종류가 있듯이 ORM에도 여러 종류가 존재
자바가 표준으로 사용하는 것은
자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
이를 실제 사용하기 위해서는 ORM 프레임워크를 추가로 선택해야 한다
그것이 바로
JPA 인터페이스를 구현한 구현체이자 ORM 프레임워크
내부적으로는 JDBC API를 사용
Java DataBase Connectivity
DBMS에 상관없이 하나의 문법으로 처리해주는 API
정리
JPA : 자바 객체와 데이터베이스를 연결해 데이터 관리,
객체 지향 도메인 모델과 데이터베이스의 다리 역할
Hibernate : JPA의 인터페이스 구현, 내부적으로 JDBC API 사용
데이터베이스의 테이블과 매팽되는 객체
일반 객체와 다르지 않지만 데이터베이스의 테이블과 직접 연결된다는 특징에서 구분
Entity를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 역할
이런 Entity Manager을 만드는 곳이 Entity Manager Factory

요청에 대해서 Entity Manager Factory가 Entity Manager을 생성하면 이를 통해 데이터베이스에 연결한 뒤 쿼리
스프링 부트 내에서는 Entity Manager Factory를 하나만 생성해서 관리
@PersistenceContext, @Autowired 애너테이션을 사용하여 Entity Manager를 사용
스프링 부트는 기본적으로 Bean을 하나만 생성하여 공유 -> 동시성 문제
실제로는 Entity Manager가 아닌 Proxy(가짜) Entity Manager을 사용
필요할 때 데이터베이스 트랜잭션과 관련된 실제 Entity Manager을 호출
@PersistenceContext
EntityManager em; //Proxy Entity Manager, 필요할 때 실제 EntityManger 호출
결론적으로 Entity Manager은 Spring Data JPA에서 관리하므로 직접 생성하거나 관리할 필요 X
Entity Manager은 Entity를 영속성 컨텍스트에 저장한다는 특징
Persistence Context는 Entity를 관리하는 가상의 공간, JPA의 중요한 특징 중 하나
1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩
- 1차 캐시
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있음
Key = Entity의 @Id 애너테이션이 달린 기본키 역할을 하는 식별자
Value = Entity
캐시된 데이터를 조회할 때는 데이터베이스를 거치치 않아도 되어 매우 빠르게 조회 가능
- 쓰기 지연
트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가
트랜잭션을 커밋하면 모았던 쿼리를 한번에 실행하는 것을 의미
적당한 묶음으로 쿼리를 요청할 수 있어 데이터베이스 시스템의 부담을 줄일 수 있음
- 변경 감지
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 Entity의 값과 현재 Entity의 값을 비교하여
변경된 값이 있으면 감지하여 변경된 값을 데이터베이스에 자동으로 반영
쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있어 데이터베이스 시스템의 부담을 줄일 수 있음
- 지연 로딩 (lazy loading)
쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라
필요할 때 쿼리를 날려 데이터를 조회하는 것
영속성 컨테스트와의 관계에 따라 Entity가 4가지 상태를 가짐
관리하지 않는 분리(detached), 관리(managed), 전혀 관계가 없는 비영속(transient), 삭제(removed)
@Autowired
EntityManager em;
public void example(){
Member m = new Member(1L, 'A');
//비영속 상태
em.persist(m);
//관리
em.detach(m);
//분리
em.remove(m);
//삭제, Entity를 영속성 컨텍스트와 데이터베이스에서 삭제
}
해당 Entity를 영속성 컨텍스트와 관계를 지어 관리되게 할 것인지를 선택
Entity의 상태를 직접 관리하고 필요한 시점에 커밋을 해야하는 불편함 ..
영속성 컨텍스트에 관계를 짓고 커밋하고 삭제하고 ..
비즈니스 로직에 더 집중할 수 있게 데이터베이스 사용 기능을 Class 레벨에서 추상화
Spring Data에서 제공하는 인터페이스를 통해서 사용 가능
CRUD를 포함한 여러 메서드가 포함되어 있으며, 알아서 쿼리를 만들어 주고
이외에도 페이징 처리 기능, 메서드 이름과 자동으로 쿼리를 빌딩하는 기능
각 데이터베이스의 특성에 맞춰 기능을 확장해 제공하는 기술도 제공
표준 스펙인 JPA는 스프링에서 구현한 Spring Data JPA를,
몽고디비는 Spring Data MongoDB를 사용
Spring Data의 공통적인 기능에서 JPA의 유용한 기술이 추가된 기술
Spring Data의 PagingAndSortingRepository Interface를 상속받아
Spring Data JPA의 JpaRepository Interface 제작
@Autowired
EntityManager em;
public void join(){
//기존에 Entity 상태를 바꾸는 방법
//메소드를 호출하여 상태 변경
Member m = new Member(1L, 'A');
em.persist(m);
}
Repository 역할을 하는 인터페이스를 만들어 데이터베이스의 테이블 조회, 수정, 생성, 삭제 같은 작업을 간단하게 가능
public interface MemberRepository extends JpaRepository<Member,Long>{
}
JpaRepository 인터페이스를 상속받아,
제네릭에는 관리할 <엔티티 이름, 엔티티 기본키의 타입>을 입력하면 기본 CRUD 메서드 사용 가능
우리가 사용하는 라이브러리, 프레임워크에서 지원하는 기능을 검증하며 어떻게 동작하는지 파악하는 테스트
테스트용 데이터 추가
//insert-members.sql
INSERT INTO member (id,name) VALUES (1,'A')
INSERT INTO member (id,name) VALUES (2,'B')
INSERT INTO member (id,name) VALUES (3,'C')
기존에 있던 data.sql 실행 X
spring:
sql:
init:
mode: never
#data.sql 실행 X
@DataJpaTest
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
/* Member 가져오기 */
@Sql("/insert-members.sql")
//테스트 실행 전 sql 스크립트 실행
@Test
void getAllMembers(){
//SELECT * FROM member;
//when
List<Member> members = memberRepository.findAll();
//then
assertThat(members.size()).isEqualTo(3);
}
@Sql("/insert-members.sql")
@Test
void getMemberById(){
//SELECT * FROM member WHERE id = 2;
//when
Member member = memberRepository.findById(2L).get();
//then
assertThat(member.getName()).isEqualTo("B");
}
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByName(String name);
//쿼리 메소드
@Query("select m from Member m where m.name = ?1")
Optional<Member> findByNameQuery(String name);
}
@Sql("/insert-members.sql")
@Test
void getMemberByName(){
//SELECT * FROM member WHERE name = 'C';
//when
Member member = memberRepository.findByName("C").get();
//then
assertThat(member.getId()).isEqualTo(3);
}
@Test
void saveMember(){
//INSERT INTO member (id, name) VALUES (1, 'A');
//given
Member member = new Member(1L, "A");
//when
memberRepository.save(member);
//then
assertThat(memberRepository.findById(1L).get().getName()).isEqualTo("A");
}
@Test
void saveMembers(){
//given
List<Member> members = List.of(new Member(2L, "B"),
new Member(3L, "C"));
//when
memberRepository.saveAll(members);
//then
assertThat(memberRepository.findAll().size()).isEqualTo(2);
}
/* Member 삭제하기 */
@Sql("/insert-members.sql")
@Test
void deleteMemberById(){
//DELETE FROM member WHERE id = 2;
//when
memberRepository.deleteById(2L);
//then
assertThat(memberRepository.findById(2L)).isEmpty();
assertThat(memberRepository.findById(2L).isEmpty()).isTrue();
//둘이 같은 표현
}
@Sql("/insert-members.sql")
@Test
void deleteMembers(){
//DELETE FROM member;
//when
memberRepository.deleteAll();
//then
assertThat(memberRepository.findAll()).isEmpty();
assertThat(memberRepository.findAll().size()).isZero();
//둘이 같은 표현
}
//모두 삭제하는 메소드는 테스트 간의 격리를 보장하기 위해 사용
//즉, 한 테스트의 실행으로 데이터베이스가 변경 된 것이 다른 테스트에 영향을 주지 않도록 하기 위함
//따라서 보통 아래처럼 @AfterEach로 사용
@AfterEach
public void cleanUp(){
memberRepository.deleteAll();
}
@Sql("/insert-members.sql")
@Test
void update(){
//UPDATE member SET name = 'BC' WHERE id = 2;
//given
Member member = memberRepository.findById(2L).get();
//when
member.changeName("BC");
//then
assertThat(memberRepository.findById(2L).get().getName()).isEqualTo("BC");
}
@Transactional 을 사용 하지 않았는데 수정이 적용된 이유 ?
-> 맨 위에 @DataJpaTest에 포함되어 있음
@NoArgsConstructor(access = AccessLevel.PROTECTED) //접근 제어자가 protected인 기본 생성자 생성
@AllArgsConstructor
@Getter
@Entity(name="member_list") //엔티티 지정
public class Member {
@Id // id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY)
//기본키 자동으로 1씩 증가
@Column( name = "id", updatable = false)
private Long id;
@Column(name = "name", nullable = false)
//name 이라는 notnull 컬럼과 매핑
private String name;
public void changeName(String name){
this.name = name;
}
}
@Entity : Member 객체를 JPA가 관리하는 Entity로 지정, 데이터베이스 테이블을 매핑
name - 테이블 이름을 지정(member_list), 없으면 클래스 이름으로 테이블 이름 매핑 (member)
@NoArgsConstructor : 기본 생성자 생성
access - 기본 생성자의 접근 제어자 (protected)
@Id : Long 타입의 id 필드를 테이블의 기본키로 지정
@GeneratedValue : 기본키의 생성 방식 결정
strategy - 자동키 생성 설정 방식 (아래 참고)
@Column : 데이터베이스의 칼럼과 필드 매핑
name - 필드와 매핑할 칼럼 이름 (default : 필드 이름)
nullable - 칼럼의 null 허용 여부 (default : true)
unique - 칼럼의 유일한 값 여부 (default : false)
columnDefinition - 칼럼 정보 설정, default를 줄 수 있음
자동키 생성 설정 방식
AUTO : 선택한 데이터베이스 방언에 따라 방식을 자동으로 선택 (Default)
IDENTITY : 기본키 생성을 데이터베이스에 위임 (AUTO_INCREMENT)
SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본키를 할당하는 방법. 오라클에서 주로 사용
TABLE : 키 생성 테이블 사용
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByName(String name);
//쿼리 메소드
@Query("select m from Member m where m.name = ?1")
Optional<Member> findByNameQuery(String name);
}
Repository는 Entity에 있는 데이터들을 조회, 저장, 변경, 삭제를 할 때 사용하는 Interface로,
Spring Data JPA에서 제공하는 JpaRepository Interface를 상속받아 간단하게 구현
Entity(Member)와 Entity의 기본키 타입(Long)을 인수로 사용

ORM은 객체와 데이터베이스를 연결하는 프로그래밍 기법
JPA는 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
Entity : Persistence을 가진 객체로, 분리, 관리, 비영속, 삭제 상태를 가짐
Entity Manager : Entity를 Persistence Context에 저장하여 Entity를 관리하며 조회, 삭제, 수정, 생성
Entity Manager Factory: Entity Manager을 만드는 곳
Persistence Context : 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩의 특징
Hibernate는 JPA의 대표적인 구현체, 자바 언어를 위한 ORM 프레임워크
Spring Data JPA는 JPA를 쓰기 편하게 만들어놓은 모듈