leehyeon-dv 님의 블로그

데이터베이스 조작이 편해지는 ORM 본문

spring/spring 이론

데이터베이스 조작이 편해지는 ORM

leehyeon-dv 2024. 12. 24. 09:47

✨목차

  1. 데이터베이스란?
  2. 알아야할 DB용어
  3. SQL 문으로 데이터베이스 조작하는 연습하기
  4. 연습문제
  5. 조건 넣어보기: WHERE절
  6. 데이터 추가하기: INSERT문
  7. 데이터 삭제하기: DELETE문
  8. 데이터 수정하기: UPDATE문
  9. 연습문제
  10. ORM이란?
  11. JPA와 하이버네이트
  12. 엔티티 매니저란?
  13. 영속성 컨텍스트란?
  14. 엔티티의 상태
  15. 스프링 데이터와 스프링 데이터 JPA
  16. 예제 코드 살펴보기

📌데이터베이스란?

데이터베이스는 데이터를 매우 효율적으로 보관하고 꺼내볼 수 있는 곳이다

데이터베이스를 사용하면 얻을 수 있는 가장 큰 이점은 굉장히 많은 사람이 안전하게 데이터를 사용하고 관리할 수 있음

 

  • DBMS(데이터 베이스 관리자)
    • 많은 요구사항을 만족하면서도 효율적으로 데이터베이스를 관리하고 운영함
    • MySQL, 오라클 등이 해당함
    • 관리 특징에 따라 관계형, 객체 관계형, 도큐먼트형, 비관계형 등으로 분휴 (관계형을 가장 많이 사용)
  • 관계형 DBMS (RDBMS)
    • 테이블형태로 이루어진 데이터 저장소
    • 예)
회원테이블
id 이메일 나이
1 a@test.com 10
2 b@test.com 20
3 c@test.com 30
  • H2, MySQL
    • H2 = 자바로 작성된 RDBMS ( 스프링부트가 지원하는 인메모리 관계형 데이터베이스)
      • 애플리케이션 자체 내부에 데이터를 저장함
      • 애플리케이션을 다시 실행하면 데이터가 초기화됨, 간편하게 사용하기 좋아서 테스트 용도로 사용
    • MySQL = 실제 서비스에 사용

 

 

 

2.📌알아야할 DB용어

  • 테이블 = 데이터베이스에서 데이터를 구성하기 위한 가장 기본적인 단위 (행과 열로 구성)
  • 행 = 고유한 식별자인 기본키를 가짐 , 레코드라고 부르기도 함
  • 열 = 각 요소에 대한 속성을 나타내며 무결성을 보장( 나이 = 숫자, 이메일 = 문자)
  • 기본키 = 행을 구분할 수 있는 식별자 , 테이블에서 유일해야하고 중복값이 없어야함( 수정되면 안되며 null이면 안됨)
  • 쿼리 = db에서 데이터를 조회하거나 삭제 , 생성, 수정과 같은 처리를 하기위해 사용하는 명령문
    • SELECT * FROM customers;     → customers 테이블에 있는 모든 데이터 가져와

 

3.📌SQL 문으로 데이터베이스 조작하는 연습하기

 

🔻데이터 조회하기: SELECT문

테이블에 저장한 데이터를 조회시에 SELECT문을 사용한다 

//기본구조
SELECT <무엇을>
FROM <어디에서>
WHERE <무슨?>

 

🔻costomers 테이블에서 id=2인 손님 이름 가져오기

id name phone_number age
1 김일번 010-1111-1111 15
2 이이번 010-2222-2222 20
3 김삼번 010-3333-3333 35

 

SELECT <이름>
FROM <customers 테이블>
WHERE <id가 2>

이걸 쿼리문으로 바꾸면 

SELECT name
FROM customers
WHERE id=2

* 조건이 없으면 where절은 생략가능

칼럼의 모두를 가져오고 싶으면 SELECT * 사용하면됨

 

4.📌연습문제

Q1. customers 테이블에서 id=1인 손님의 데이터 가져오려면 어떻게 쿼리를 짜야할까 

SELECT *
FROM customers
WHERE id=1

 

Q2.customers 테이블에서 핸드폰 번호가 010-2222-2222인 손님의 id와 이름을 가져오려면 어떻게 해야할까 

SELECT id, name
FROM customers
WHERE phone_numbers = '010-2222-2222'

 

 

5.📌조건 넣어보기 : WHERE절

 

명령어 설명 예시
= 특정 값과 동일한 값 가진 행 조회 age =10
!=   또는 <> 동일하지 않은 행 조회 age != 10
<,>,<=,>= 값 비교해 조회 age > 10
age <= 10
BETWEEN 지정된 값 사이 값 조회 age BETWEEN 10 AND 20
LIKE 패턴매칭을 위해 사용, %을 사용하면 와일드 카드로 사용할 수 있음 name LIKE'감%'
AND 두 조건 모두 참 name LIKE'감%'AND '이%'
OR 두 조건 중 하나라도 참이면 조회 name LIKE'감%'OR '이%'
IS NULL, IS NOT NULL NULL 값의 존재여부 검사 name IS NULL

 

🔻문제

Q1. 나이가 20살 이상인 손님 조회 

WHERE age >= 20

 

Q2 나이가 10살 이상, 30살 이하인 손님 조회

WHERE age BETWEEN 10 AND 30

 

Q3 성이 김이거나 이로 시작하는 손님을 조회하려면?

WHERE name LIKE '김%' OR '이%'

 

 

6.📌데이터 추가하기: INSERT문

데이터베이스의 테이블에 새로운 행을 추가하고 싶으면 INSERT문

삭제하고 싶으면 DELETE문을 사용한다 

 

<기본형태>

INSERT INTO <어디에?>
VALUES <어떤 값을?>

 

예를들어 customers라는 테이블에 {'박사번', 010-4444-4444','40'}이라는 행을 추가하려면 다음과 같이 쿼리를 작성해야한다

INSERT INTO custormers (name, phone_number,age)
VALUES ('박사번', '010-4444-4444', '40')

이렇게 하면 id가 4인 행에 새 레코드가 생긴다 

테이블을 만들때 id라는 컬럼은 AUTO_INCREMENT속성을 추가하는데 이 속성은 해당 컬럼값이 추가될때 1씩 증가하는 자동값을 만들어주면 레코드의 고윳값으로 관리할 수 있게 해준다 

 

7.📌데이터 삭제하기 : DELETE문

<기본형태>

DELETE FROM <어디에서> WHERE <어떤 조건으로?>;

 

쿼리작성해보자 → id=4 삭제

DELETE FROM customers WHERE id=4

 

 

8.📌데이터 수정하기: UPDATE 문

<기본형태>

UPDATE <어디에?>
SET <무슨 컬럼을? = 어떤 값으로?>
WHERE <어떤 조건으로?>

 

id name phone_number age
1 김일번 010-1111-1111 15
2 이이번 010-2222-2222 20
3 김삼번 010-3333-3333 35

여기에서  id=1의 age를 11로 바꾸고 싶으면 다음과 같이 하면 된다

UPDATE customers
SET age = 11
WHERE name = '김일번';

 

※ where절을 수정하면 테이블의 모든 레코드가 수정되니 주의 

 

9.📌연습문제

Q1 customers 테이블에서 id가 1번인 손님의 name을 김일로 바꾸려면 어떻게 쿼리를 짜야할까 

UPDATE customers
SET name = '김일'
WHERE id = 1;

 

Q2. customers 테이블의 모든 phone_number 값을 공백으로 바꾸려면 어떻게 쿼리를 짜야할까 

UPDATE customers
SET phone_number = ''

 

 

10.📌 ORM이란?

자바의 객체와 데이터베이스를 연결하는 프로그래밍 기법

 

 

ORM이 있으면 데이터베이스 값을 마치 객체처럼 쓸 수 있다 

쉽게말해 SQL을 전혀 몰라도 자바 언어로만 데이터베이스에 접근해서 원하는 데이터를 받아올 수 있다

 

장점

  • SQL을 직접 작성하지 않고 사용하는 언어로 데이터베이스에 접근할 수 있다 
  •  객체지향적으로 코드를 작성할 수 있기 때문에 비지니스 로직에만 집중할 수 있다
  • 데이터 베이스 시스템이 추상화되어있기 때문에 MySQL에서 PostgreSQL로 전환한다해도 추가로 드는 작업이 거의 없다 . 데이터베이스에 대한 종속성이 줄어든다
  • 매핑하는 정보가 명확하기 때문에 ERD에 대한 의존도를 낮출 수 있고 유지보수할때 유리하다

단점

  • 프로젝트의 복잡성이 커질수록 사용 난이도도 올라갑니다
  • 복잡하고 무거운 쿼리는 ORM으로 해결이 불가능한 경우가 있습니다

 

11.📌JPA와 하이버네이트

 

자바에서는 ORM의 여러 종류중 JPA를 표준으로 사용합니다 

JPA = 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스 , 인터페이스이므로 실제 사용을 위해 ORM 프레임워크를 추가로 선택해야한다 

 

🔻대표적으로 하이버네이트를 많이 사용한다 

하이버네이트 = JPA인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크 

하이버네이트의 목표 = 자바객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유자재로 사용할 수 있게 하는데 있다 

 

12.📌엔티티 매니저란?

엔티티 = 데이터베이스의 테이블과 매핑되는 객체

: 객체이긴 하지만 데이터베이스에 영향을  미치는 쿼리를 실행하는 객체이다

 

엔티티 매니저 = 엔티티를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 한다

팩토리 = 이러한 엔티티 매니저를 만드는 곳

 

스프링부트 → 내부에서 엔티티 매니저 팩토리를 하나만 생성해 관리하고 @PersistenceContext 또는 @Autowired 애너테션을 사용해 엔티티 매니저를 사용합니다

 

🔻스프링 부트가 엔티티매니저를 사용하는 방법

@PersistenceContext
EntityManager em;

 

스프링은 빈을 하나만 생성해 공유하기 때문에 동시성문제가 발생할 수도 있지만 실제로는 엔티티 매니저가 아닌 실제 엔티티 매니저와 연결하는 프록시(가짜) 엔티티 매니저를 사용한다 

필요할 때 데이터베이스 트랜젝션과 관련된 실제 엔티티 매니저를 호출한다

 

 

 

 

 

13.📌영속성 컨텍스트란?

엔티티 매니저는 엔티티를 영속성 컨텍스트에 저장한다는 특징이 있다 

영속성 컨텍스트는 JPA의 중요한 특징 중 하나로 엔티티를 관리하는 가상의 공간이다 (이것으로 DB에서 효과적으로 데이터를 가져오고 엔티티를 편하게 사용가능한것) 

 

영속성 컨텍스트에는 1차캐시, 쓰기 지연, 변경감지, 지연로딩이 있다

 

1차 캐시

캐시의 키는 엔티티의 @Id 애너테이션이 달린 기본키 역할을 하는 식별자이며 값은 엔티티이다 

엔티티를 조회하면 1차 캐시에서 데이터를 조회하고 값이 있으면 반환한다 

값이 없으면 데이터베이스에서 조회해 1차 캐시에 저장한다음 반환한다 (캐시된데이터를 조회할 때는 데이터베이스를 거치지 않아도 되므로 매우 빠르게 데이터를 조회할 수 있다)

 

 쓰기 지연

트랙젝션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한번에 실행하는 것을 의미한다 

예를 들어, 

데이터 추가 쿼리가 3개면 영속성 컨텍스트는 트랜젝션을 커밋하는 시점에 3개의 쿼리를 한꺼번에 전송한다 

이를 통해 적당한 묶음으로 쿼리를 요청할 수 있어 데이터 베이스 시스템의 부담을 줄일 수 있다 

 

변경 감지 

트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해서 변경된 값이있으면 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영한다 

이를통해, 

쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고 데이터베이스 시스템의 부담을 줄일 수 있다 

 

지연로딩

쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미

 

이 특징들이 갖는 공통점은 모두 데이터베이스의 접근을 최소화해 성능을 높이는 것이다 

캐시를하거나, 자주 쓰지않게 하거나, 변화를 자동 감지해서 미리 준비하거나 하는 등의 방법을 통해서 한다 

 

 

 

 

14.📌엔티티의 상태

엔티티는 4가지 상태를 가진다

  • 영속성 컨텍스트가 관리하고 있지 않는 분리상태
  • 영속성 컨텍스트가 관리하는 관리상태
  • 영속성 컨텍스트와 전혀 관계없는 비영속 상태
  • 삭제된 상태 

 

 

 

15.📌스프링 데이터와 스프링 데이터 JPA

스프링데이터는 비지니스 로직에 더 집중할 수 있게 데이터베이스 사용기능을 클래스 레벨에서 추상화했다 

스프링 데이터에서 제공하는 인터페이스를 통해 스프링 데이터를 사용 가능하다 

이 인터페이스는 CRUD를 포함한 여러 메서드가 포함되어 있어 알아서 쿼리를 만들어준다 

또한 이외에도 페이징 처리기능과 메서드 이름으로 자동으로 쿼리를 빌딩하는 기능이 제공되는 등 많은 장점이 있다 

 

🔻스프링데이터 JPA란

스프링 데이터의 공통적인 기능에서 JPA의 유용한 기능이 추가된 기술이다 

스프링 데이터 JPA에서는 스프링데이터의 인터페이스인 PagingAndSortingRepository를 상속받아 JpaRepository 인터페이스를 만들었으며 JPA를 더 편리하게 사용하는 메서드를 제공한다 

 

<메서드 호출로 엔티티 상태변경>

@PersistenceContext
EntityManager em;

public void join(){
    Member member = new Member(1L, "홍길동");
    em.persist(member);
}

 

JpaRepository 인터페이스를 우리가 만든 인터페이스에 상속받고, 제너릭에는 관리할 <엔티티 이름, 엔티티 기본키의 타입>을 입력하면 기본 CRUD 메서드를 사용할 수 있다 

public interface MemberRepository extends JpaRepository<Member, Long> { 
}

 

 

 

🔻스프링 데이터 JPA에서 제공하는 메서드 사용해보기

조회메서드 사용

 

데이터를 조회하기 위한 데이터 만들기(/test/resources/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');

 

/test/resources/application.yml

spring:
  sql:
    init:
      mode: never

이 옵션은 src/main/resources 폴더 내에 data.sql 파일의 자동실행을 하지 않게 하는 옵션

 

실제 데이터를 가져오는지 검증하는 테스트 코드 작성

/test/MemberRepositoryTest.java

@DataJpaTest
class MemberRepositoryTest {
    @Autowired
    MemberRepository memberRepository;

    @Sql("/insert-members.sql")
    @Test
    void getAllMembers() {
        List<Member> members = memberRepository.findAll();

        assertThat(members.size()).isEqualTo(3);
    }
}

@Sql = 테스트 실행 전에 sql 스크립트를 실행시킬 수 있음

성공~! →

 

 

쿼리 메서드 사용

id가 아니라 name으로 찾기 

(MemberRepository.java)

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
    Optional<Member> findByName(String name);
}

이 메서드를 사용하면 된다 

잘 작동하는지 확인

(MemberRepositoryTest.java)

@Sql("/insert-members.sql")
@Test
void getMemberByName() {
    Member member = memberRepository.findByName("C").get();
    assertThat(member.getId()).isEqualTo(3L);
}

테스트를 실행하면 테스트가 성공적으로 동작하는 것을 볼수 있다 

이런 기능을 쿼리 메서드라고 한다

이는 JPA가 정해준 메서드 이름 규칙을 따르면 쿼리문을 특별히 구현하지 않아도 메서드 처럼 사용할 수 있다 

 

조회 메서드 정리 

  • 전체 조회 → findAll()메서드 사용
  • 아이디로 조회 → findById()
  • 특정 칼럼으로 조회  → 쿼리 메서드 명명 규칙에 맞게 정의 후 사용

 

추가, 삭제 메서드 사용해보기 

 

(MemberRepositoryTest.java)

@Test
void saveMember() {
    Member member = new Member(1L, "A");

    memberRepository.save(member);
    
    assertThat(memberRepository.findById(1L).get().getName()).isEqualTo("A");
   
}

 

만약 여러 엔티티를 한꺼번에 저장하고 싶으면 saveAll() 메서드 사용

추가할 멤버 객체들을 리스트로 만들고 saveAll()메서드로 한꺼번에 추가한 후 추가한 멤버 객체 수 만큼 데이터에 있는지 확인하기

@Test
void saveMembers() {
    List<Member> members = List.of( new Member(2L, "B"), new Member(3L, "C"));
    
    memberRepository.saveAll(members);
    
    assertThat(memberRepository.findAll().size()).isEqualTo(2);
}

 

멤버 삭제는 deleteById()사용해 레코드를 삭제할 수 있음

@Sql("/insert-members.sql")
@Test
void deleteMemberById() {
    memberRepository.deleteById(2L);

    assertThat(memberRepository.findById(2L).isEmpty()).isTrue();
}

 

모든 데이터를 삭제하고 싶으면 deleteAll()메서드 사용할 수 있다 

@Sql("/insert-members.sql")
@Test
void deleteAll() {
    memberRepository.deleteAll();
    
    assertThat(memberRepository.findAll().size()).isZero();
}

 

정리

  • 레코드 추가 → save()
  •  한번에 여러 레코드 추가 → saveAll()
  • 아이디로 레코드 삭제 → deleteById()
  • 모든 레코드 삭제 → deleteAll()

 

수정 메서드 사용하기

JPA는 트랙잭션 내에서 데이터를 수정해야한다 따라서 @Transactional 애너테이션을 메서드에 추가해야한다

public class Member {
    public void changeName(String name) {
        this.name = name;
    }
}

 

만약 이 메서드가 @Transactional 애너테이션이 포함된 메서드에서 호출되면 JPA는 변경 감지 기능으로 엔티티 필드값이 변경될 때 그 변경 사항을 데이터베이스에 자동으로 반영한다 

엔티티가 영속 상태일때 필드값을 변경하고 트랜젝션이 커밋되면 JPA는 변경사항을 데이터베이스에 자동으로 적용한다

 

(MemberRepository.java)

@Sql("/insert-members.sql")
@Test
void undate() {
    Member member = memberRepository.findById(2L).get();
    member.changeName("BC");
    assertThat(memberRepository.findById(2L).get().getName()).isEqualTo("BC");
}

@Transactional을 사용하지 않았는데 이름이 BC로 변경된 이유 = @DataJpaTest를 사용해서

 

 

16.📌예제 코드 살펴보기

(Member.java)

@Getter
@Entity  //엔티티로 지정
@NoArgsConstructor(access = AccessLevel.PROTECTED)  //기본 생성자
@AllArgsConstructor
public class Member{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTTITY)
    @Colume(name = "id", updatable = flase)
    private Long id;
    
    @Colume(name = "name", nullable = false)
    private String name;
    
    public void changeName(String name) { 
        this.name;
    }
}

엔티티에서 테이블 이름 지정안할경우 → 클래스 이름과 같은 이름의 테이블과 매핑

                                       지정할경우  →  @Entity(name = "member_list")처럼 사용

 

자동키 생성설정방식

  • AUTO : 선택한 데이터베이스 방언에따라 방식을 자동으로 선택
  • IDENTITY : 기본키 생성을 데이터베이스에 위임(=AUTO_INCREMENT)
  • SEQUENCE : 데이터베이스 시퀀스를 사용해 기본키를 할당하는 방법, 오라클에서 주로 사용
  • TABLE : 키 생성 테이블 이용

@Column 애너테이션의 속성

  • name : 필드와 매핑할 컬럼이름, 설정하지 않으면 필드 이름으로 지정
  • nullable : 컬럼의 null허용 여부 설정하지 않으면 true
  • unique : 컬럼의 유일한 값 여부. 설정하지 않으면 false
  • columnDefinition : 컬럼 정보 설정. default 값을 줄 수