본문 바로가기
3.1SpringBoot/3.1.2 묘공단 SpirngBoot3

[묘공단/spring] 5장 데이터베이스 조작이 편해지는 ORM

by Dohi._. 2023. 10. 4.

 

포스팅 목차  (책의 목차와 다릅니다 개인적으로 공부한 내용입니다)

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

  5-1. 데이터베이스 관리자, DBMS

  5-2. ORM이란?

  5-3. JPA와 하이버네이트

  5-4. 엔티티와 영속성 컨텍스트

  5-5. 스프링 데이터와 스프링 데이터 JPA

 

 

 

5-1. 데이터 베이스 관리자,DBMS 

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

DB와 DBMS는 예전 포스팅([대외/묘공단_spring] 0-1장 1-1서버 기본용어)에서 아주 간단하게 서술 한적이 있습니다.

 

이번에는 DBMS에 대해 이번엔 자세하게 설명을 하려고 합니다.

 

DBMS(Database Management System): DB를 관리하기 위한 소프트웨어 

 

DB는 많은 사람이 공유할 수 있어야 하며 동시 접근을 할 수 있어야 하며 그외 많은 요구사항들이 있습니다.

DBMS는 이러한 요구사항들을 만족하면서도 효율적으로 DB를 관리,운영을 합니다 

흔하게 우리가 DB하면 생각나는 MySQL,oracle(오라클),H2은 사실 DBMS인 셈입니다.

DBMS는 관리 특징에 따라 관계형(가장 많이 사용), 객체-관계형, 도큐먼트형, 비관계형 등으로 분류가 가능합니다.

 

관계형DBMS (Relational DBMS / RDBMS)

RDBMS는 말그대로 관계형 모델을 기반으로 하는 DBMS입니다.

간단하게 테이블 형태로 이루어진 데이터 저장소라고 생각하면 편합니다.

행(row)과 열(column)로 구분되어 있는 테이블(table)이 맞습니다,

예시로 테이블을 표로 표현 하겠습니다

ID 이름 성별
1 도히
2 소히
3 소도히

행에서는 데이터의 값(1 , 도히, 남)이 한줄을  행으로 말하고

열은 ID, 이름, 성별과 같은 데이터의 구분을 열이라고 합니다

 

책에서 사용하는 RDBMS는 2가지가 있습니다 H2, MySQL인데

저도 H2를 개발할때 많이 사용하고 실제 서비스는 MySQL를 사용합니다

H2는 데이터를 외부 스토리지가 아닌 메모리에 저장하기 때문에 애플리케이션이 종료되면 데이터는 삭제되기 떄문에

개발할때 많이 사용합니다.

 

알면 좋은 DB추가용어

쿼리(query)
DB에서 데이터를 조회하거나 삭제, 생성, 수정과 같은 처리를 하기 위해 사용되는 명령문
SQL이라는 DB전용 언어를 사용하여 작성합니다.

기본키(PK / primary key)
행을 구분할 수 있는 식별자입니다 따라서 이 값은 테이블에서 유일해야 하며 중복 값은 가질 수 없습니다.
보통 데이터를 수정,삭제,조회할때 사용하거나 다른 테이블과 관계를 맺어 데이터를 가져올때 사용합니다.
기본키의 값은 수정되어서는 안되며 유효한 값이 되어야 합니다. 이말은 즉슨 NULL이 될 수 없습니다. 

 

 

5-2. ORM이란?

ORM(Object-Relational Mapping): 자바의 객체와 DB를 연결하는 프로그래밍 기법으로 자바 언어로만 DB를 다룰 수 있게 하는 도구입니다.

  • 장점
    • SQL을 직접 작성하지 않고 DB에 접근 가능
    • 객체지향적으로 코드를 작성할 수 있어서 비즈니스 로직에 집중 가능
    • DB시스템이 추상화되어있어 DBMS를 전환하더라도 추가로 작업불필요함 즉, DB시스템에 대한 종속성이 줄어듬
    • 매핑하는 정보가 명확하기 때문에 ERD에 대한 의존도가 낮고 유지보수에 유리
  • 단점
    • 프로젝트의 복잡성이 커질수록 사용 난이도 증가
    • 복잡하고 긴 쿼리는 ORM으로 해결이 불가능할 수 있음

 

5-3. JPA와 하이버네이트

 ORM에는 여러 종류가 있습니다. 자바에서는 JPA(java persistence API)를 표준으로 사용합니다.

 

JPA (java persistence API)

자바에서 관계형 데이터베이스를 사용하는 방법을 정의한 인터페이스로써 자바 객체와 데이터베이스를 연결해 데이터를 관리합니다. 객체 지향 도메인 모델과 DB의 다리 역할을 합니다.

 

하이버네이트(Hibernate)

JPA 인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크, 내부적으로는 *JDBC API 사용합니다.

하이버네이트의 목표는 자바 객체를 통해 DB 종류에 상관없이 DB를 자유자재로 사용할 수 있는 것이다.

 

* JDBC  API (Java Database Connectivity API)

 Java 언어로 여러 가지 DB를 다룰 수 있게 하는 도구

 구성은 java.sql, javax.sql 패키지로 구성되어 있으며 사용할 DB에 맞는 JDBC 드라이버가 필요하다.

 

 

  5-4. 엔티티와 영속성 컨텍스트

엔티티(Entity)는 데이터베이스의 테이블과 매핑되는 객체를 의미합니다.

엔티티는 본질적으로 자바 객체이므로 일반 객체와 다르지는 않지만 데이터베이스의 테이블과 직접 연결된다는 특징이 있기 때문에 구분되어 불립니다.

즉슨, 객체이긴 하지만 데이터베이스에 영향을 미치는 쿼리를 실행하는 객체인 것입니다.

 

엔티티 매니저(Entity Manager)

엔티티를 관리해 DB와 애플리케이션 사이에서 객체를  생성,수정,삭제( Create(생성), Read(읽기), Update(갱신), Delete(삭제) / CRUD)하는 역할을 합니다 

이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리(Entity Manager Factory)입니다.

 

엔티티 매니저 팩토리(Entity Manager Factory)

엔티티 매니저를 만드는 곳으로서 스프링 부트 내부에서 1개만 생성해 관리합니다

EntityManager를 필드로 선언하고 @PersistenceContext, @Autowired 애너테이션을 사용하여 엔티티 매니저를 사용합니다.

@PersistenceContext
EntityManager em; //프록시 엔티티 매니저. 필요할떄마다 진짜 엔티티 매니저 호출

그리고 스프링 부트는 기본적으로 빈은 하나만 생성후 공유하므로 동시성 문제가 생길 수 있는데 실제 엔티티 매니저와 연결하는 프록시 엔티티 매니저를 호출함으로  필요할 때마다 실제 엔티티 매니저를 호출하는 식으로 해결하였습니다

 

 

영속성 컨텍스트

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

영속성 컨텍스트는 엔티티를 관리하는 가상의 공간(컨테이너)입니다.

영속성 컨텍스트에는 다음과 같이 4개의 특징이 있는데

  • 1차 캐시
    • 영속성 컨텍스트 내부에 1차 캐시를 가지고 있으며 이때 캐시의 키는 엔티티의 @Id애너테이션이 달린 기본키 역할을 하는 식별자이며 키로 하여 엔티티 자체를 저장합니다
    • 엔티티 조회 시 1차 캐시에서 조회하여 값이 있으면 반환 하는 식으로 임시적인 데이터 공간으로 DB를 거치지 않고 처리할 수 어 매우 빠르게 조회할 수 있습니다.
  • 쓰기 지연(Transactional write-behind)
    • 트랜잭션을 커밋하기 전까지는 실제로 데이터베이스에 질의문을 보내지 않고데이터를 추가하는 쿼리를 모았다가 트랜잭션 커밋 시 한 번에 실행하는 방식으로 데이터베이스 접근 횟수 감소로 시스템 부담 줄입니다.
  • 변경 감지
    • 트랜잭션을 커밋하면 1차 캐시에 저장된 엔티티의 값과 현재 엔티티의 값을 비교해 변경사항을 데이터베이스에 자동으로 반영합니다
    • 쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 데이터베이스 시스템의 부담을 줄일 수 있습니다.
  • 지연 로딩(Lazy Loading)
    • 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라  필요할 때 쿼리를 날려 데이터를 조회하는 방식입니다. 주로 엔티티 간 연관관계 매핑 시 사용됩니다
    • 반대로 조회할 때 쿼리를 보내 연관된 모든 데이터를 가져오는 즉시 로딩도 있습니다.

 

엔티티의 상태

엔티티는 4가지 상태를 가지고 있습니다.

  • 비영속 상태 (Transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 상태
    • 엔티티를 처음만들면 엔티티는 비영속 상태입니다.
  • 관리상태(Managed)
    • 영속성 컨텍스트가 관리하는 상태
    • persist()메서드를 사용해 관리 상태로 만들 수 있습니다
  • 분리 상태 (Detached)
    • 영속성 컨텍스트가 관리하고 있지 않는 상태
    • detach() 메서드를 사용해 분리 상태로 만들 수 있습니다.
  • 삭제 상태 (Removed)
    • 말그대로 상제 된 상태입니다
    • 더 이상 객체가 필요 없는 경우에 remove()메서드를 사용하여 엔티티를 영속성 컨텍스트와 데이터 베이스에서 삭제할 수 있습니다.

 

  5-5. 스프링 데이터와 스프링 데이터 JPA

스프링 데이터(Spring data)

비지니스 로직에 더 집중할 수 있도록 데이터베이스 사용 기능을 클래스 레벨에서 추상화하여 스프링 데이터에서 제공하는 인터페이스를 통해 스프링 데이터를 사용합니다.

이 인터페이스에서는 CRUD를 포함하여 여러 메서드 들이 포함되어 있고 쿼리 또한 알아서 만들어 주는 등 많은 기능이 제공되는 장점이 있습니다.

표준 스펙JPA는 스프링에서 구현한 Spring data JPA와 Spring data MongoDB를 사용합니다.

 

스프링 데이터 JPA

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

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

대표적인 메서드로는 save() findAll() findByid() deleteByid()가 있습니다.

 

//JPA를 상속받고 테이블 CRUD메서드 사용하는 리포지터리 인터페이스만들기
public interface UserRepository extends JpaRepository<User, Long>{
}   //JpaRepository<엔티티이름 , 기본키 타입>

 

Sevice에서 JpaRepository를 상속받은 EntityRepository를 사용해보겠습니다.

@Service //서비스
public class UserService{

	@Autowired
    UserRepository userrepository;
    
    public void example(){
    	//Create
        userrepository.save(new User(1L,"Dohi"));
    	
        // Read
        Optional<User> user = userrepository.findById(1L);//ID가 1L(Long변수라 L)인 entity 조회
        List<User> allUsers = userrepository.findAll();// 전체 조회하여 List

		// Delete
        userrepository.deleteByID(1L);
		


	}
	
}

어 그런데 "Dohi"로는 못찾나? 라고 생각이 들텐데요  일단 "Dohi"가 저장되는 곳을 name으로 가정하겠습니다.

리포지토리에 살포시 이렇게 적어보겠습니다

public interface UserRepository extends JpaRepository<User, Long>{
	User findByName(String name);
}

 

그후 한번 테스트 코드로 아래 코드를  입력해 볼까요?

@SpringBootTest
class ApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    void test() {
        User user = this.userRepository.findByName("Dohi");
        assertEquals(1L, user.getId());
    }
}

 

해당 방법은 JpaRepository를 상속한 UserRepository 객체가 생성될때 일어나는데

우선 DI에 의해 스프링이 자동으로 UserRepository 객체를 생성합니다.

이 때 프록시 패턴이 사용된다고 한다.

리포지터리 객체의 메서드가 실행될때 JPA가 해당 메서드명을 분석하여 쿼리를 만들고 실행합니다.

즉, 우리가 만든 findByName()이 덕분에 되는 셈이죠.

"findBy + 엔티티의 속성명()"  같은 리포지터리 메서드를 작성하면 해당 속성의 값으로 데이터를 조회 할 수 있게 됩니다.

 

그럼 다양한 방식으로 가능할까하고 찾아본 findBy 메서드입니다.

메서드 예제 메서드 *출처 해석 
And findByNameAndAge(String name, Long age) 여러 컬럼을 and 로 검색
Or findByNameOrAge(String name, Long  age ) 여러 컬럼을 or 로 검색
Between findByCreateDateBetween(LocalDateTime fromDate, LocalDateTime toDate) 컬럼을 between으로 검색
LessThan findByIdLessThan(Long  id) 작은 항목 검색
GreaterThanEqual findByIdGraterThanEqual(Long  id) 크거나 같은 항목 검색
Like findByNameLike(String name) like 검색
In findByNameIn(String[] names) 여러 값중에 하나인 항목 검색
OrderBy findByNameOrderByCreateDateAsc(String name) 검색 결과를 정렬하여 전달
응답 결과가 여러건인 경우에는 리포지터리 메서드의 리턴 타입을 단일 타입 User이 아닌 List<User>로 해야한다.

 

Optinal이란?

Optional은 Java 8에서 추가된 클래스로, null 값의 처리를 간편하게 할 수 있도록 도와주는 기능을 제공합니다.
Java에서 null 값은 오류를 발생시키는 원인 중 하나입니다. null 값이 반환될 수 있는 메소드를 호출하거나 null 값이
저장된 변수를 사용하면 NullPointerException과 같은 예외가 발생합니다.
Optional 클래스는 이러한 문제를 해결하기 위해 null 값 처리를 안전하게 수행할 수 있도록 도와줍니다. Optional 객체는 값이 존재할 수도 있고, 존재하지 않을 수도 있습니다.
값이 존재하는 경우에는 get() 메소드를 사용하여 값을 가져올 수 있고, 값이 존재하지 않는 경우에는 get() 메소드를 호출하면 예외가 발생하지 않고 빈 Optional 객체를 반환합니다.

public Optional<User> getUserById(Long id) {
}   // id에 해당하는 사용자 정보를 조회하여 반환하는 로직

Optional<User> user = getUserById(123L);

if (user.isPresent()) {
    User foundUser = user.get();
    // 사용자 정보가 존재하는 경우, 해당 정보를 사용하여 로직 수행
} else {
    // 사용자 정보가 존재하지 않는 경우, 로직 수행
}

위의 코드에서 getUserById 메소드는 id에 해당하는 사용자 정보를 조회하여 Optional<User> 객체를 반환합니다.
이후에는 Optional의 isPresent() 메소드를 사용하여 Optional 객체가 비어있지 않은지 확인하고,
get() 메소드를 사용하여 사용자 정보를 가져옵니다.
이를 통해 null 값 처리를 안전하게 수행할 수 있습니다

 

 

 

 

 


이 글은 골든래빗 《스프링 부트 3 백엔드 개발자 되기 - 자바 편》의 5장 써머리입니다.

 

728x90

댓글