분류 전체보기 (108)

 

 

 

 

트랜잭션(Transaction)이란?

 

 

트랜잭션 : 여러개의 작업들을 하나의 그룹으로 묶어서 처리하는 처리 단위

두 개 이상의 작업들이 그룹처럼 묶여서 처리될 수 있는데 하나라도 실패할 경우 애플리케이션의 신뢰성이 깨어지는 상황이 발생할 수 있다.

애플리케이션에서 신뢰성이 깨어지는 상황이 발생하면 트랜잭션이라고 부를 수 없다.

 

물리적으로는 여러 개의 작업이지만 논리적으로는 마치 하나의 작업으로 인식하는 경우

전부 성공 또는 전부 실패 (All or Nothing)로만 처리되어야 트랜잭션의 의미를 가진다.

 

 

 

➤ ACID 원칙

 

데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어이다.

 

 

1. 원자성 (Atomicity)

트랜잭션에서 작업을 더 이상 쪼갤 수 없다는 것을 의미한다.

모두 성공하거나 모두 실패하거나(All or Nothing) 

 

2. 일관성 (Consistency)

트랜잭션이 에러없이 성공적으로 끝날 경우, 비즈니스 로직에서 의도하는대로 일관성있게 저장되거나 변경되는 것을 의미한다.

 

3. 격리성 (Isolation)

여러 개의 트랜잭션이 실행될 경우 각각 독립적으로 실행되어야 함을 의미한다.

 

4. 지속성 (Durability)

트랜잭션이 완료되면 그 결과는 지속되어야 한다는 의미이다.

 

 

 

➤ 트랜잭션 커밋(commit)과 롤백(rollback)

 

 

▶️ commit

  • 모든 작업을 최종적으로 데이터베이스에 반영하는 명령어
  • 커밋을 수행하면 변경된 내용이 DB에 영구적으로 저장된다.
  • 커밋 명령을 수행하면 하나의 트랜젝션 과정은 종료하게 된다.

 

 

▶️ rollback

  • 작업 중 하나라도 문제가 발생했을 경우 트랜잭션 내에서 수행된 작업들을 취소한다.
  • 트랜잭션 시작 이전의 상태로 되돌아간다.

 

 

 

➤ JPA의 Entity Transaction commit() 내부 들여다보기

 

EntityTransaction 객체 commit() 메서드 호출 시 어떤 일이 일어날까?

 

EntityTransaction 인터페이스의 구현 클래스인 TransactionImpl 클래스의 commit()을 호출한다.

-> TransactionDriverControlImpl (물리적인 트랜잭션을 제어하기 위한 로컬 트랜잭션 드라이버 구현 객체) 를 얻은 후 구현 메서드인 commit()을 다시 호출한다.

-> TransactionDriverControlImpl 에서 JDBC Connection의 액세스 방법을 제공하는 JdbcResourceTransaction의 구현객체인 AbstractLogicalConnectionImplementor 의 commit()을 호출한다. (여기까지가 Hibernate ORM에서의 영역)

-> 물리적인 JDBC Connection을 통해 데이터베이스와 인터랙션하기 위해서 JDBC API의 구현체인 JdbcConnection 영역으로 이동한다.

-> (여기부터 JDBC API의 구현체인 H2의 영역) 데이터베이스에 commit 명령을 준비(prepareCommand)한 후, 명령을 실행(executeUpdate)한다.

-> Command 클래스에서 메서드 실행된다.

데이터베이스에 commit 명령을 전달하기 위해 commitIfNonTransactional 메서드 호출한다.

commitIfNonTransactional() 메서드 내부에서 auto commit 여부를 체크한 후

데이터베이스 세션에 해당하는 Session 객체를 통해 commit 명령을 수행한다.

만약에 commitIfNonTransactional() 수행 과정에서 예외가 발생하면 rollback을 수행한다.

-> SessionLocal 클래스에서 트랜잭션에 대한 commit이 수행된다.

 

 

 

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 늘 환영입니다.

 

 

 

 

 

 

 

 

Spring Data JPA

 

Spring Data JDBC와 Spring Data JPA는 모두 Spring Data라는 프로젝트에서 지원하는 PSA(일관된 서비스 추상화)가 적용되어 있기 때문에 사용법이 거의 동일하다.

 

Spring Data JPA는 Spring Data 패밀리 기술 중 하나이다.

JPA 기반의 데이터 액세스 기술을 좀 더 쉽게 사용할 수 있게 해준다.

 

 

➤ JPA vs Hibernate ORM vs Spring Data JPA

 

JPA

  • 엔터프라이즈 Java 애플리케이션에서 관계형 데이터베이스를 사용하기 위해 정해 놓은 표준 스펙(사양 또는 명세, Specification)

Hibernate ORM

  • JPA의 표준 스펙을 구현한 구현체, 실제로 사용할 수 있는 API라고 보면 된다.

Spring Data JPA

  • JPA 스펙을 구현한 구현체의 API(일반적으로 Hibernate ORM)을 조금 더 쉽게 사용할 수 있도록 해주는 모듈이다.

 

 

 

 

Repository에 JPA 기능 추가하기

 

엔티티 클래스들 정의 -> 엔티티 클래스 매핑 -> repository 인터페이스 작성

 

repository 인터페이스에서 spring data jdbc와 달라진 점 : CrudRepository를 상속하는 대신 JpaRepository를 상속한다.

JpaRepository : CrudRepository보다 JPA에 특화된 더 많은 기능을 포함하고 있다.

 

 

▶️ JPQL을 통한 객체 지향 쿼리 사용

 

  • JPA에서는 JPQL이라는 객체 지향 쿼리를 통해 데이터베이스 내의 테이블을 조회할 수 있다.
  • JPQL은 엔티티 클래스 객체를 대상으로 객체를 조회하는 방법이다.  (데이터베이스의 테이블을 대상으로 조회 x)
  • JPQL의 문법을 사용해서 객체를 조회하면 JPA가 내부적으로 JPQL을 분석해서 적절한 SQL을 만든 후 데이터베이스를 조회하고 조회한 결과를 엔티티 객체로 매핑한 뒤 반환한다.
  • ‘SELECT c’와 같이 별칭으로 생략한 형태로 사용 가능하다.

 

 

▶️ 네이티브 SQL을 통한 조회

 

  • Spring Data JDBC와 JPA는 네이티브 SQL 쿼리를 작성해서 사용할 수 있다.
  • nativeQuery 애트리뷰트 값을 true로 설정하면 value 애트리뷰트에 작성한 SQL 쿼리가 적용된다.

 

 

➤ Spring Data JDBC와 Spring Data JPA의 @Query 차이

 

둘다 애너테이션 이름은 같지만 패키지 자체가 다르다.

➡️ Starter 모듈이 둘 다 의존 라이브러리에 포함되어 있는 경우 패키지 경로를 혼동하지 않도록 주의한다.

 

Spring Data JDBC의 @Query 애너테이션 패키지 경로

import org.springframework.data.jdbc.repository.query.Query

 

Spring Data JPA의 @Query 애너테이션 패키지 경로

org.springframework.data.jpa.repository.Query

 

 

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 늘 환영입니다.

 

 

 

 

 

JPA 엔티티(Entity) 매핑

 

 

➤ 엔티티 매핑

 

JPA의 매핑 애너테이션을 이용하면 데이터베이스의 테이블과 엔티티 클래스를 매핑할 수 있다.

 

 

▶️ @Entity

  • 엔티티 클래스와 테이블을 자동으로 매핑해준다.
  • 이 애너테이션을 붙이면 JPA 관리 대상 엔티티가 된다.
  • name attribute를 설정하지 않으면 기본적으로 클래스명과 같은 테이블 명을 매핑한다.
  • 테이블 명과 엔티티 클래스 명이 다를 경우 name attribute를 설정해주어야 한다.

 

 

▶️ @Table

  • 데이터베이스의 테이블 이름을 설정할 때 사용한다.
  • name 애트리뷰트를 설정하지 않으면 기본값으로 클래스 이름을 테이블 이름으로 사용한다.
  • @Table 은 옵션이고 추가하지 않을 경우 클래스 이름을 테이블 이름으로 사용한다.

 

 

 

⚠️ 주의 사항

  • @Table ➡️ 옵션
  • @Entity, @Id ➡️ 필수
  • 파라미터가 없는 기본 생성자는 필수로 추가해주자

 

 

➤ 기본키 매핑

 

데이터베이스 테이블에 기본키 설정은 필수이다.

JPA에서 기본키 매핑은 @Id 를 필드에 추가해주면 되는데 JPA에서 어떤 방식으로 기본키를 생성할 것인지에 대한 다양한 전략을 지원해준다.

 

✅ 기본키 직접 할당

애플리케이션 코드 상에서 기본키를 직접 할당해주는 방식이다.

 

 

✅ 기본키 자동 생성

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;

 

  • IDENTITY
    • 기본키 생성을 데이터베이스에 위임한다.
    • 데이터베이스에서 기본키를 생성해주는 대표적인 방식은 MySQL의 AUTO_INCREMENT 기능으로 자동 증가 숫자를 기본키로 사용하는 방식이 있다.
  • SEQUENCE
    • 데이터베이스에서 제공하는 시퀀스를 사용해서 기본키를 생성하는 전략이다.
  • TABLE
    • 별도의 키 생성 테이블을 사용하는 전략이다.
  • AUTO
    • JPA가 데이터 베이스 Dialect에 따라서 적절한 전략을 자동으로 선택한다.

 

 

TABLE 전략은 키 생성 전용 테이블을 별도로 만들어야 되고, 키를 조회하고 업데이트할 때 쿼리를 전송해야하기 때문에 성능면에서 떨어진다.

 

https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-table

 

Hibernate ORM 5.6.10.Final User Guide

Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much dat

docs.jboss.org

 

 

 

▶️ 참고) Dialect란?

 

Dialect는 JDBC 타입과 SQL 타입 간에 연결하는 역할을 한다.

Dialect를 사용하면 Hibernate 가 특정 관계형 데이터베이스에 최적화된 SQL을 생성할 수 있다.

Hibernate Dialect 클래스를 기반으로 특정 데이터베이스에 대한 쿼리를 만든다.

hibernate dialect 는 프레임워크에 어떻게 hibernate 쿼리를 SQL 쿼리로 바꿀 지에 대한 정보를 제공해준다.

 

  • 적절한 SQL 쿼리를 생성한다.
  • 애플리케이션이 두 개 이상의 데이터베이스와 연결되어 있을 경우 특정 데이터베이스와 상호작용할 수 있다.
  • 구성 파일에 지정되지 않은 경우에도 데이터베이스 소프트웨어를 기반으로 hibernate 구성 파일 속성에 대한 기본값을 설정한다.

 

https://www.geeksforgeeks.org/hibernate-sql-dialects/

 

Hibernate - SQL Dialects - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

 

 

➤ 필드(멤버 변수)와 칼럼간의 매핑

 

 

▶️ @Column

 

  • 필드와 데이터베이스의 칼럼을 매핑해주는 애너테이션이다.
  • 이 애너테이션이 없고 필드만 정의되어 있다면 JPA는 기본적으로 이 필드가 테이블의 칼럼과 매핑되는 필드라고 간주하고 @Column 애너테이션에 사용되는 attribute 값은 모두 디폴트로 적용된다.

Attribute

  • nullable
    • default : true
    • 칼럼에 null을 허용할지 여부를 설정
  • updatable
    • 등록한 이후 칼럼 데이터를 수정할 수 있는지 여부를 지정한다.
    • default : true
  • unique
    • 하나의 칼럼에 unique 제약 조건을 설정한다. (중복 허용 x)
    • default : false
  • length
    • 칼럼에 저장할 수 있는 문자 길이를 지정한다.
    • default : 255
  • name
    • default : 필드 명
    • 필드와 매핑되는 칼럼의 이름이 필드명과 다를 경우 지정해 줄 수 있다.

 

 

▶️ @Temporal

 

  • java.util.Date, java.util.Calendar 타입을 매핑하기 위해서 이 애너테이션을 추가해야 한다.
  • LocalDate, LocalDateTime 타입일 경우는 생략가능하다.

 

 

 

▶️ @Transient

 

  • 이 애너테이션을 필드에 추가하면 테이블 칼럼과 매핑하지 않는다.
  • 데이터베이스에 저장하지 않고 조회할 때도 매핑되지 않는다.
  • 주로 임시 데이터를 메모리에서 사용하기위한 용도로 사용된다.

 

 

▶️ @Enumerated

 

  • enum 타입과 칼럼을 매핑할 때 사용한다.
  • Attribute
    • EnumType.ORDINAL : enum의 순서를 나타내는 숫자를 테이블에 저장한다.
    • EnumType.STRING : enum 이름을 테이블에 저장한다.

 

⚠️ 주의

EnumType.ORDINAL 로 지정할 경우

기존에 정의되어 있는 enum 사이에 새로운 enum을 추가한다면 그때부터 테이블에 이미 저장되어 있는 enum 순서 번호와 enum 에 정의되어 있는 순서가 일치하지 않게 되는 문제가 발생한다.

 

 

 

@Column doc

https://docs.oracle.com/javaee/7/api/

 

Java(TM) EE 7 Specification APIs

 

docs.oracle.com

 

 

 

 

 

엔티티 간의 연관관계 매핑

 

 

➤ 연관관계 매핑이란?

 

엔티티 클래스 간의 관계를 만들어 주는 것이다.

 

엔티티 간의 참조할 수 있는 객체수에 따라서

일대다(1:N), 다대일(N:1), 다대다(N:N), 일대일(1:1) 관계로 나눌 수 있다.

 

 

➤ 단방향 연관 관계

 

한 쪽 클래스만 다른 쪽 클래스의 참조 정보를 가지고 있는 관계

 

 

➤ 양방향 연관 관계

 

양 쪽 클래스 모두 서로의 객체를 참조 정보를 가지고 있는 관계

 

JPA는 단방향, 양방향 연관 관계 모두 지원하지만 Spring Data JDBC는 단방향 연관 관계만 지원한다.

 

 

➤ 일대다(1:N) 단방향 연관 관계

 

일(1)에 해당하는 클래스가 다(N)에 해당하는 객체를 List 등을 사용해서 참조할 수 있다.

일대다 단방향 매핑은 잘 사용하지 않는다.

 

 

➤ 다대일(N:1) 연관 관계

 

다(N)에 해당하는 클래스가 일(1)에 해당하는 객체를 참조할 수 있는 관계를 의미한다.

 

▶️ @ManyToOne

 

  • 이 애너테이션으로 다대일 관계를 명시한다.
  • @JoinColumn(name = “MEMBER_ID”)
    • 매핑하는 테이블의 외래키(부모 테이블의 기본키)에 해당하는 칼럼명을 적어준다.

 

 

➤ 일대다(1:N) 연관 관계

 

다대일 연관관계를 설정한 이후에 자식 테이블에도 일대다 관계를 만들어줘서 양방향 관계를 만들어 준다.

 

 

▶️ @OneToMany

 

  • 이 애너테이션을 부모테이블에 추가하여 일대다 관계를 만들어준다.

Attribute

  • mappedBy
    • 값은 관계를 소유하고 있는 필드(N(다)에 해당하는 필드)를 지정한다.
    • mappedBy는 참조할 대상이 있어야 한다.
    • 일대다 단방향 매핑의 경우에는 필요하지 않다.

 

 

➤ 다대다(N:N) 연관 관계

 

다대다는 관계는 중간에 테이블을 하나 추가해서 두 개의 일대다 관계로 만들어주는 것이 일반적이다.

 

 

➤ 엔티티 간의 연관 관계 매핑 권장 방법

 

  • 일대다 매핑은 사용하지 않는다.
  • 제일 먼저 다대일 단방향 매핑부터 적용한다.
  • 다대일 단방향 매핑을 통해 객체 그래프 탐색으로 조회할 수 없는 정보가 있을 경우, 양방향 매핑을 적용한다.

 

 

 

➤ Cascading

 

부모테이블에서 자식테이블로 데이터의 상태 변화를 전파(?)할 때 사용한다.

JPA javax.persistence.CacadeType 에 정의되어 있다.

부모 테이블의 @OneToMany 애너테이션에 Attribute로 추가한다.

 

@OneToOne(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private Stamp stamp;

 

Attribute

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 늘 환영입니다. 😄

 

 

 

 

 

 

 

 

 

JPA(Java Persistence API)란?

 

 

➤ JPA(Java Persistence API)란?

 

  • ORM(Object-Relation Mapping) 기술의 표준 사양(또는 명세, Specification)
  • Java의 인터페이스로 사양이 정의되어 있고 JPA라는 표준 사양을 구현한 구현체는 따로 있다.

 

 

➤ Hibernate ORM

 

  • JPA 표준 사양을 구현한 구현체 : Hibernate ORM, EcliipseLink, DataNucleus
  • Hibernate ORM은 JPA에서 정의해둔 인터페이스를 구현한 구현체 중 하나이다.
  • JPA에서 지원하는 기능 이외에도 자체적으로 사용하도록 지원하는 API 도 있다.

 

 

※ Jakarta Persistence

 

Java Persistence API 의 약자이지만 현재는 Jakarta Persistence 라고도 불린다.

2019년에 나온 JPA 3.0 버전 부터 Jakarta Persistence 라는 이름으로 바뀌었다.

Jakarta Persistence 라고 바뀌면서 패키지와 속성도 javax.persistence 에서 jakarta.persistence 로 이름이 바뀌었다.

엔터프라이즈 Java 애플리케이션에서 관계형 데이터 관리를 설명하는 Jakarta EE 애플리케이션 프로그래밍 인터페이스 사양(specification)이다.

 

 

 

➤ 데이터 액세스 계층에서의 JPA 위치

 

 

 

데이터 액세스 계층에서 JPA는 상단에 위치한다.

데이터 저장, 조회 등의 작업은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM을 통해서 이뤄진다.

Hibernate ORM은 내부적으로 JDBC API를 이용해서 데이터베이스에 접근한다.

 

 

 

➤ JPA(Java Persistence API)에서 P(Persistence)의 의미

 

Persistence는 영속성, 지속성이라는 뜻이다.

그렇다면 무엇을 지속시키는 것일까?

엔티티 객체 정보를 지속시킨다는 의미이다.

 

 

▶️ 영속성 컨텍스트(Persistence Context)

 

영속성 컨텍스트(Persistence Context)

 

ORM(Object-relational-mapping)은 객체(Object)와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 객체 안에 포함된 정보를 테이블에 저장하는 기술이다.

JPA에서는 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트(Persistence Context)라는 곳에 보관해서 애플리케이션 내에서 오래 지속되도록 한다.

엔티티 정보는 데이터베이스 테이블에 데이터를 저장, 수정, 조회, 삭제하는데 사용된다.

 

 

JPA API 중 엔티티 정보를 영속성 컨텍스트에 저장(persist)하는 API를 사용하면 영속성 컨텍스트의 1차 캐시에 엔티티 정보가 저장된다.

 

 

JPA 영속성 컨텍스트

  • EntityManager 클래스에 의해서 관리된다. -> EntityManagerFactory 객체를 Spring으로부터 DI 받을 수 있다.
  • EntityManagerFactory의 createEntityManager() 메서드를 이용해서 EntityManager 객체를 얻을 수 있다. -> JPA API 메서드 사용 가능
  • persist() 메서드: 영속성 컨텍스트에 객체의 정보들이 저장된다.
  • 저장되었는지 확인하려면 find(조회할 엔티티 클래스의 타입, 조회할 엔티티 클래스의 식별자 값) 메서드 사용

 

 

 

➤ JPA API로 영속성 컨텍스트 이해하기

 

 

▶️ JPA API를 사용하기 위한 사전 준비

 

 

✅ build.gradle 설정

 

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

Spring Data JPA 기술을 포함한 JPA API를 사용할 수 있다.

 

 

✅ application.yml에 아래 코드 추가

jpa:
	hibernate:
		ddl-auto: create  # (1) 스키마 자동 생성
	show-sql: true      # (2) SQL 쿼리 출력

 

※ yml에서 추가한 코드가 제대로 적용되지 않을 때는 공백 레벨을 잘 확인해보자.

 

Spring Data JDBC에서는 schema.sql 파일에 테이블 생성을 위한 스키마를 직접 작성해주어야 하지만 JPA는 (1)의 코드 하나를 추가함으로써 JPA가 자동으로 데이터베이스에 테이블을 생성해 준다.

 

 

▶️ @GeneratedValue

 

식별자를 생성해주는 전략을 지정할 때 사용

이 애너테이션을 엔티티의 멤버 변수에 추가하면 데이터베이스 테이블에서 기본키가 되는 식별자를 자동으로 설정해준다.

 

 

▶️ 영속성 컨텍스트에 저장하기

 

persist() 를 호출 -> 1차 캐시에 객체가 저장되고, 객체가 쓰기 지연 SQL 저장소에 INSERT 쿼리 형태로 등록이 된다.

영속성 컨텍스트에 객체를 저장하지만 실제 테이블에 정보를 저장하지는 않는다.

 

JPA가 내부적으로 테이블을 자동으로 생성하고, 테이블의 기본키를 할당해준다.

 

 

▶️ 데이터베이스 테이블에도 데이터 저장하기

 

EntityManagergetTrancsation() 메서드를 이용하여 Transaction 객체를 얻는다.

(JPA에서는 Transaction 객체를 기준으로 DB 테이블에 데이터를 저장한다.)

Transaction을 시작하기 위해서 begin() 메서드 사용

➡️ persist() 로 객체를 영속성 컨텍스트에 저장

➡️ commit() 메서드 호출하여 영속성 컨텍스트에 저장되어 있는 객체를 DB 테이블에 저장한다.

 

 

✔️ persist() : 영속성 컨텍스트의 1차 캐시에 엔티티 클래스의 객체가 저장 + ‘쓰기 지연 저장소’에 INSERT 쿼리가 등록

✔️ commit() : ‘쓰기 지연 SQL 저장소’에 등록된 모든 INSERT 쿼리가 실행 + 실행된 INSERT 쿼리는 쓰기 지연 SQL 저장소에서 제거됨

✔️ find() : 1차 캐시에서 해당 객체가 있는지 조회하고, 없으면 테이블에 SELECT 쿼리를 전송해서 조회

 

 

 

➤ 영속성 컨텍스트와 테이블에 엔티티 업데이트

 

find() 메서드로 1차 캐시에 저장된 객체를 불러와서 객체에 setter로 데이터를 변경

commit() 메서드로 SQL 저장소에 등록된 UPDATE 쿼리가 실행된다.

 

✅ setter 메서드로 값을 변경만 해도 commit() 시점에 UPDATE 쿼리가 실행되는 이유?

영속성 컨텍스트에 엔티티가 저장될 경우 저장되는 시점의 상태를 그대로 가지고 있는 스냅샷을 생성한다.

그 후 엔티티의 값을 setter 메서드로 변경한 후, commit()을 하면 변경된 엔티티와 이전에 이미 떠 놓은 스냅샷을 비교한 후 변경된 값이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행

 

 

➤ 영속성 컨텍스트와 테이블의 엔티티 삭제

 

EntityManager의 remove() 메서드를 통해 1차 캐시에 있는 엔티티를 제거하도록 요청

Transaction의 commit() 을 실행하면 영속성 컨텍스트에 1차 캐시에 있는 엔티티를 제거하고, 쓰기 지연 SQL 저장소에 등록된 DELETE 쿼리가 실행된다.

 

 

 

▶️ EntityManager의 flush() API

 

transaction의 commit() 메서드를 호출하면 JPA 내부적으로 flush() 메서드가 호출되어 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

 

 

 

※ 추가로 학습해야할 자료들

 

JPA와 Hibernate 에서 Entity Lifecycle Model

https://thorben-janssen.com/entity-lifecycle-model/

 

Entity Lifecycle Model in JPA & Hibernate

The entity lifecycle model is one of the core concepts of JPA. It defines if an entity gets loaded, if changes get persisted, and much more

thorben-janssen.com

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 언제나 환영입니다. 😄

 

 

 

 

 

 

 

Spring Data JDBC를 통한 데이터 액세스 계층 구현 (서비스, 레포지토리 구현)

 

 

➤ Repository Interface

 

Spring Data JDBC, Spring Data JPA에서 데이터 액세스 계층에서 데이터베이스와 상호작용하는 역할을 하는 인터페이스를 repository라고 한다.

 

기본적으로 CrudRepository를 상속하는 repository 인터페이스 작성한다.

ex) public interface MemberRepository extends CrudRepository<Member, long> { }

 

CrudRepository 인터페이스를 통해 데이터를 데이터베이스 테이블에 저장, 조회, 수정, 삭제가 가능하다.

 

 

 

➤ 쿼리 메서드(Query Method)

 

Spring Data JDBC에서 쿼리 메서드(Query Method)를 지원한다.

 

✅ ‘find + By + SQL 쿼리문에서 WHERE 절의 칼럼명 + (WHERE 절 칼럼의 조건이 되는 데이터)’

형식으로 쿼리 메서드(Query Method)를 정의하면 조건에 맞는 데이터를 테이블에서 조회한다.

ex) findByName(String name);

 

리턴값으로 SQL 질의를 통한 결과 데이터를 받아서 엔티티 클래스의 객체로 지정해준다.

그리고 Spring Data JDBC 에서 Optional을 지원하기 때문에 리턴값을 Optional로 래핑해준다.

래핑해주는 이유는?

Optional을 사용해서 리턴값을 래핑하면 Service 클래스에서 이 Optional을 이용해서 코드를 더 효율적이고 간결하게 구성할 수 있다.

 

WHERE 절의 조건 칼럼을 여러 개 지정하고 싶다면 ‘And’를 사용하면 된다.

ex) findByPhoneAndName(String phone, String name);

 

쿼리 메서드에 작성한 칼럼명은 내부적으로 테이블의 칼럼명으로 바뀌지만

Spring JDBC 입장에서는 엔티티 클래스를 바라보고 작업한다.

반드시 엔티티 클래스의 멤버 변수명을 적어주어야 한다.

테이블의 칼럼명으로 적는다. ❌

 

(단어를 일치시키면 가장 좋지만 Java에서는 CamelCase 표기법을 사용하고 테이블 칼럼명은 언더스코어(_) 표기법을 사용하기 때문에 2 단어 이상 작성시 이름이 다를 수 있다.)

 

 

➤ @Query 메서드

 

쿼리 메서드명에 작성하지 않고 직접 쿼리문을 작성해서 질의를 할 경우 사용

Attribute로 쿼리문을 작성한다.

동적 쿼리 파라미터(named parameter)

콜론(:) 뒤에는 findBy~()의 괄호안에 동적 쿼리 파라미터(named parameter)를 작성해준다.

ex) @Query(“SELCET * FROM ORDER WHERE ORDER_ID = :orderId”);

 

단순한 쿼리의 경우 쿼리 메서드를 이용하는 것이 간결한 코드 유지와 생산성 면에서 바람직하다.

 

 

➤ Repository 인터페이스 Service 에서 사용하기

 

Service에서 DI를 주입해서 Repository 생성

 

Repository는 인터페이스인데 구현 클래스를 작성하지 않았지만 사용 가능하다.

Repository 인터페이스 구현 클래스는 Spring Data JDBC에서 내부적으로 Java 리플렉션 기술과 Proxy 기술을 이용해서 repository 인터페이스의 구현 클래스 객체를 생성해준다.

 

비즈니스 로직에서 어떤 검증이 필요한 로직은 검증하는 로직을 추출해서 메서드를 작성 후 호출한다. ➡️ 코드의 간결성, 가독성 향상

ex) 회원 정보 리소스를 데이터베이스에 Insert할 경우 이미 Insert된 리소스인지 여부를 검증하는 로직 분리 (findVerifiedMember)

 

 

✅ Optional.ofNullable(...)

파라미터로 전달받은 엔티티 클래스 객체는 클라이언트 쪽에서 선택적으로 수정할 수 있기 때문에 멤버 변수에 null 이 있을 수도 있다.

이처럼 멤버 변수 값이 null 일 경우

Optional.of()가 아닌 Optional.ofNullable()을 이용해서 null 값을 허용할 수 있다.

null 이더라도 NullPointerException이 발생하지 않고 원하는 다음 메서드를 호출할 수 있다.

ifPresent() 메서드를 이용해서 null 값이 아니라면 코드가 실행 되도록 작성한다. (람다식으로 작성)

이 때 값이 null 이라면 실행되지 않는다.

ex) Optional.ofNullable(coffee.getName).ifPresent(name -> findCoffee.setName(name));

 

 

✅ delete 를 사용하여 회원 정보 삭제 ?

실무에서는 회원 정보 자체를 테이블에서 삭제하기 보다 MEMBER_STATUS 같은 칼럼을 두어 상태 값만 변경한다.

회원의 회원 가입 상태를 ‘가입’ , ‘휴면’ , ‘탈퇴’ 등의 상태 정보로 나누어 관리하는 것이 바람직하다.

 

 

✅ orElseThrow()

Optional의 메서드인 orElseThrow()를 이용하면 해당 객체가 null 이 아닐경우에는 해당 객체를 리턴하고 null 이라면 괄호 안의 예외를 던진다.

 

 

✅ 참고) @Builder

Lombok에서 지원

Builder를 사용하고자 하는 클래스 위에 @Builder 애너테이션을 추가한다.

객체를 생성할 때 빌더를 함수를 통해 호출하고 셋팅하고자 하는 필드값을 하나씩 지정해주고 마지막에 build()로 닫아서 작동시킨다.

생성자 파라미터가 많을 경우 builder를 사용하면 가독성이 좋아진다.

자기 자신을 리턴하기 때문에 체인형식으로 생성해줄 수 있다.

최종적으로 build()를 해야 만들어진다.

Member.builder()
	.name("김아무개")
   	.city("서울")
   	.email("kim123@gmail.com")
   	.phone("010-1111-2222")
   	.build();

 

 

https://projectlombok.org/features/Builder

 

@Builder

 

projectlombok.org

 

 

 

 

읽어주셔서 감사합니다. ^^ 좋은하루 보내세요.

오개념에 대한 지적은 늘 환영입니다.

 

 

 

 

 

 

 

❑ Spring Data JDBC

 

 

Spring Data JDBC는 나온지 얼마안되서 아직은 Spring Data JPA를 현업에서 사용한다.

 

 

➤ DDD란?

 

도메인 주도 설계(Domain Driven Design)

모든 기능을 도메인 모델 위주로 돌아가는 설계 기법이다.

 

 

▶️ 도메인(Domain)이란?

 

도메인이란 엔티티의 속성들이 가질 수 있는 값들의 집합이다.

쉽게 생각하면 비즈니스적인 어떤 업무 영역이다.

도메인 지식(Domain knowledge)들을 서비스 계층에서 비즈니스 로직으로 구현해야 한다.

현실세계에서 접하는 업무의 한 영역이다.

 

 

도메인은 두가지 모델이 있다.

 

 

▶️ 빈약한 도메인 모델

 

편의상 완벽한 DDD를 완벽하게 적용하지 못하는 것을 빈약한 도메인 모델이라 부른다.

도메인 객체들에 비즈니스 로직이 거의 없거나 아예 없는 모델이다.

Service 클래스에 기능 집중 + 기능이 없는 빈약한 도메인 모델(Domain Entity Classs)

 

 

▶️ 풍부한(rich) 도메인 모델

 

서비스 클래스의 기능 축소 + 기능이 많은 풍부한 도메인 모델(rich Domain)

➡️ DDD

 

 

 

 

➤ Aggregate(애그리거트)란?

 

비슷한 업무 도메인들(하위 수준 도메인)의 묶음

비슷한 범주의 연관된 업무들을 하나로 그룹화 해놓은 그룹이다.

 

 

➤ Aggregate Root(애그리거트 루트)란?

 

aggregate안에는 1개 이상의 도메인이 있고

각각의 aggregate를 대표하는 도메인이 존재한다.

DDD에서 하나의 애그리거트를 대표하는 도메인을 애그리거트 루트(Aggregate Root) 라고 한다.

 

▶️ Aggregate Root 선정 기준

  • 다른 모든 도메인들과 직간접적으로 연관되어 있는 도메인
  • 데이터베이스의 테이블 간 관계에서 aggregate root 테이블은 부모 테이블이 되고 다른 도메인들은 자식 테이블이 된다.

 

 

➤ Aggregate 간의 관계

 

  • 1:N (1 대 다)
  • N:N (다 대 다)

 

 

애플리케이션에서 Domain Entity 클래스 간의 관계를 먼저 설계한다.

➡️ 도메인의 Aggregate Root를 찾는다.

 

ORM(Object-Relational Mapping)기반의 데이터 액세스 기술인 Spring Data JDBC에서는 엔티티 클래스 간의 관계를 정의하는 것이 필요하다.

ORM은 객체와 테이블을 매핑하는 기술이기 때문에 클래스 간의 연관 관계를 찾아야 한다.

데이터베이스 테이블 간의 관계는 외래키를 통해 맺어진다.

클래스끼리 관계는 객체의 참조를 통해 맺어진다.

 

 

 

 

 

도메인 엔티티 클래스 정의

 

 

➤ 애그리거트 객체 매핑 규칙

 

1. 모든 엔티티 객체의 상태는 aggrigate root를 통해서만 변경할 수 있다.

  • 직접 엔티티 객체의 상태를 변경하지 않고 애그리거트 루트를 거쳐서만 엔티티 상태를 변경해야 한다.

 

2. 하나의 동일한 aggrigate 내에서의 엔티티 객체 참조

  • 동일한 하나의 애그리거트 내에서는 엔티티 간에 객체로 참조된다.

 

3. 애그리거트 루트 대 애그리거트 루트 간의 엔티티 객체 참조

  • 애그리거트 루트 간의 참조는 객체 참조 대신 ID로 참조한다.
  • 1대1과 1대N 관계일 때는 테이블 간의 외래키 방식과 동일하다.
  • N대N 관계일 때는 외래키 방식과 객체 참조 방식이 함께 사용된다. 

 

 

 

➤ Entity 구현

 

 

▶️ @Entity

해당 클래스가 엔티티 클래스임을 알려준다.

해당 클래스는 데이터베이스 테이블과 매핑된다.

클래스명 위에 작성한다.

 

▶️ @Id

해당 멤버변수가 ID값 임을 알려준다.

애그리거트 간의 매핑에서 루트 간에는 객체로 직접 참조하는 것이 아니라 ID로 참조한다.

 

 

▶️ @Table(“”)

이 애너테이션을 추가하지 않으면 기본적으로 클래스명과 같은 테이블 이름과 매핑된다.

하지만 클래스명과 다른 테이블명을 가진 테이블과 매핑하려면 이 애너테이션을 사용하고 attribute로 이름을 지정해주어야 한다.

 

▶️ AggregateReference<T, ID>

1:N 관계에서 애그리거트 루트와 애그리거트 루트 간에 객체로 참조할 때 사용하는 객체이다.

ID로 참조한다.

직접적으로 객체를 참조하지 않고 AggregateRefence로 객체를 한 번 감싸준다.

 

 

▶️ N:N 관계에서의 매핑

 

N:N관계일 경우 직접 매핑할 수 없기 때문에 1:N, N:1 관계로 풀어줄 엔티티가 중간에 하나 필요하다.

데이터베이스에서 join table 해주는 것과 같다.

중간에서 매핑해줄 엔티티 클래스를 하나 작성해서 매핑해준다.

 

▶️ @MappingCollection(idColumn = “”)

엔티티 클래스 간에 연관 관계를 맺어주는 정보를 의미한다.

idColumn attribute 값으로 외래키에 해당하는 column명을 적어준다.

N:N 관계에서는 AggrementReference로 기본키를 감싸줄 필요 없다.

 

 

 

 

 

읽어주셔서 감사합니다. 좋은 하루 되세요

오개념에 대한 지적은 환영입니다. 😄

 

 

 

 

 

 

 

 

 

JDBC 기반 데이터 액세스 계층

 

 

JDBC란?

 

 

JDBC(Java Database Connectivity)

Java 기반 애플리케이션의 코드 레벨에서 사용하는 데이터를

데이터베이스에 저장, 업데이트 하거나

데이터베이스에 저장된 데이터를 Java 코드 레벨에서 사용할 수 있게

해주는 Java에서 제공하는 표준 API이다.

 

 

JDK 1.1 버전부터 제공됐다. 엄청 오래됐다..!

데이터베이스 액세스를 위해 Java에서 제공하는 API라고 보면 된다.

다양한 벤더(Oracle, MS SQL, MySQL 등)의 데이터베이스와 연동할 수 있다.

 

etc..

  • Java 기반의 애플리케이션에서 사용하는 데이터 액세스 기술의 기본이 되는 저수준(low level) API 이다.
  • Spring에서는 JDBC API를 직접 사용하기 보다는 Spring Data JDBC, Spring Data JPA 같은 기술이 편리해서 더 많이 사용한다. (Spring Data JDBC나 Spring Data JPA가 더 편리해서 현업에서는 JDBC를 많이 쓰지 않지만 예전 기술로 만든 애플리케이션을 그대로 사용할 경우 Spring JDBC를 사용할 수도 있다.)
  • Spring Data JDBC나 Spring Data JPA 같은 기술도 내부적으로 JDBC를 이용하기 때문에 JDBC의 흐름정도는 알면 데이터 액세스 기술을 구현하는데 도움이 된다.

 

 

 

 

➤ JDBC의 동작 흐름

 

JDBC의 동작 흐름

 

 

 

▶️ JDBC 드라이버(JDBC Driver)

 

JDBC 드라이버는 Database와의 통신을 담당하는 인터페이스이다.

Oracle, MS SQL, MySQL 등에서 해당 벤더에 맞는 JDBC 드라이버를 구현해서 제공을 한다.

JDBC 드라이버 구현체를 이용해서 특정 벤더의 Database에 액세스 할 수 있다.

 

 

 

➤ JDBC API 사용 흐름

 

JDBC API 사용 흐름

 

 

1. JDBC 드라이버 로딩

사용하고자 하는 JDBC 드라이버를 로딩한다.

JDBC 드라이버는 DriverManager라는 클래스를 통해서 로딩된다.

 

2. Connection 객체 생성

JDBC 드라이버가 정상적으로 로딩되면 DriverManager를 통해 데이터베이스와 연결되는 세션(Session)인 Connection 객체를 생성한다.

 

3. Statement 객체 생성

Statement 객체는 작성된 SQL 쿼리문을 실행하기 위한 객체이다.

객체 생성 후에 정적인 SQL 쿼리 문자열을 입력으로 가진다.

 

4. Query 실행

생성된 Statement 객체를 이용하여 입력한 SQL 쿼리를 실행한다.

 

5. ResultSet 객체로부터 데이터 조회

실행된 SQL 쿼리문에 대한 결과 데이터 Set으로 부터 데이터를 조회한다.

 

6. ResultSet 객체 Close, Statement 객체 Close, Connection 객체 Close

JDBC API 를 통해 사용된 객체들은 사용이 끝나면 실행한 역순으로 차례대로 Close 해준다.

 

 

 

➤ Connection Pool 이란?

 

Connection Pool

 

데이터베이스와의 연결을 위한 Connection 객체를 생성하는 작업은 비용이 많이 드는 작업이다.

왜 그런지는 아래 MySQL에서 제공하는 페이지를 들어가서 보면 알 수 있다.

 

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

 

MySQL :: MySQL 8.0 Reference Manual :: 8.2.5.1 Optimizing INSERT Statements

8.2.5.1 Optimizing INSERT Statements To optimize insert speed, combine many small operations into a single large operation. Ideally, you make a single connection, send the data for many new rows at once, and delay all index updates and consistency checkin

dev.mysql.com

 

 

효율을 위해서 Connection Pool에서 Connection을 미리 만들어서 보관한다.

그리고 애플리케이션이 필요할 때마다 이 Connection 객체를 제공하는 역할을 한다. (Connection 관리자)

애플리케이션 로딩 시점에 Connection 객체를 미리 생성해두고

애플리케이션에서 데이터베이스에 연결이 필요할 때마다

Connection 객체를 생성하지 않고(비용이 많이 듬)

미리 만들어둔 Connection 객체를 사용한다.

-> 애플리케이션의 성능 향상

 

Spring Boot 2.0 이전 버전에서는 Apache 재단의 오픈 소스인 Apache Commons DBCP(Database Connection Pool, DBCP)를 주로 사용했다.

2.0 이후 부터는 성능면에서 더 나은 HikariCP를 기본 DBCP로 채택했다.

 

※ 추가학습) HikariCP 란?

  • Brett Wooldridge가 2012년에 개발한 JDBC Connection Pool이다.
  • 가볍고 빠르고 안정적이다. (히카리는 일본어로 빛 광이다.)
  • Spring Boot 2.0부터 default DBCP로 채택되었다.

 

 


https://github.com/brettwooldridge/HikariCP

 

GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

光 HikariCP・A solid, high-performance, JDBC connection pool at last. - GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

github.com

 

 

 

※ 추가학습) JDBC 장단점

 

- 장점

  • 깨끗하고 심플한 SQL 처리가 가능하다
  • 용량이 큰 데이터에서 우수한 성능을 보인다
  • 구현하기 쉽다
  • 작은 애플리케이션에서 아주 좋은 성능을 보인다

 

- 단점

  • 대형 프로그래밍에서 많은 비용, 시간이 든다
  • OOP의 구현이 지원되지 않는다
  • JDBC를 이용한 MVC 패턴 구현이 어렵다
  • JDBC에서 사용되는 쿼리문은 database에 한정된다
  • 예외 처리를 위한 try, catch, finally 블록에 모든 것을 작성해야 한다.
  • 개발자가 connection을 닫지 못하면 JDBC가 자동으로 connection을 닫지 않아 문제가 발생할 수 있다.
  • 많은 곳에 SQL 쿼리문을 작성해야하고 일단 프로그램이 작성되면 테이블 구조를 수정할 수 없다.
  • JDBC는 데이터베이스에 특정 오류코드를 생산하고 개발자는 데이터베이스의 오류 코드에 대해 전혀 알지 못 할수 있다.
  • JDBC를 사용하면 Java 객체를 DB 객체로 변환하거나 그 반대로 변환하므로 변환 및 역변환에 많은 시간이 소요되어 프로세스가 느려질 수 있다.

 

출처 : 

https://techxyte.com/tutorials/hibernate/pros-and-cons-of-jdbc.php

 

Premium Bootstrap 5 HTML, Angular 11, VueJS, React & Laravel Admin Dashboard Themes

Metronic - #1 Selling Premium Bootstrap Admin Theme of All Time. Built with Twitter Bootstrap 5 HTML, Angular 11, VueJS, React and Laravel. Trusted By Tens of Thousands Users.

keenthemes.com

 

 

 

 

Spring Data JDBC 란?

 

 

➤ Data Access 

 

Spring 에서 데이터베이스에 액세스하는 다양한 기술을 사용할 수 있다.

대표적인 데이터 액세스 기술: mybatis, Spring JDBC, Spring Data JDBC, JPA, Spring Data JPA 

Spring에서 사용하는 데이터베이스 액세스 기술은 크게 두 가지로 나눌 수 있다.

 

 

➤ SQL 중심 기술

 

  • mybatis와 Spring JDBC가 대표적
  • 애플리케이션에서 데이터베이스에 접근하기 위해 SQL 쿼리문을 애플리케이션 내부에 Java 코드로 직접적으로 작성하는 것
  • 과거부터 현재까지 많이 사용하고 있지만 Java 진영에서는 SQL 중심보다 객체(Object) 중심의 기술로 지속적으로 이전하고 있는 추세이다.

 

 

mybatis 에서 사용하는 SQL Mapper 예시

<select id="findMember" resultType="Member">

  SELECT * FROM MEMBER WHERE member_id = #{memberId}

</select>

 

Spring JDBC의 JdbcTemplate 사용 예

Member member = this.jdbcTemplate.queryForObject(

"select * from member where member_id=?", 1, Member.class);

 

 

 

➤ 객체(Object) 중심 기술

 

  • 모든 데이터를 객체(Object) 관점으로 바라보는 기술이다.
  • SQL 쿼리문을 직접적으로 작성하기 보다 Java 객체를 이용해 애플리케이션 내부에서 Java 객체를 SQL 쿼리문으로 자동 변환해준다.
  • ORM(Object-Relational Mapping) 이라고 한다.
  • Java 에서 대표적 ORM 기술 : JPA(Java Persistence API), Spring Data JDBC

 

 

 

➤ Spring Data JDBC란?

 

  • 심플하다.
  • Spring Data JDBC는 ORM 기술을 사용하지만 JPA의 기술적 복잡도를 낮춘 기술이다.

 

 

▶️ Spring Data JDBC vs JPA vs Spring Data JPA

 

Spring Data JDBC 

  • 2018년에 처음 릴리즈 됐다.
  • 아직까지는 JPA보다 상대적으로 적게 사용된다.
  • 애플리케이션의 규모가 상대적으로 크지 않고, 복잡하지 않을 경우 Spring Data JDBC가 뛰어난 생산성을 보여준다.

 

JPA

  • 실무에서 가장 많이 사용하고 있는 기술이다.

 

Spring Data JPA

  • Spring Data JPA는 Spring에서 JPA 기술을 편리하게 사용하기 위한 기술이다.
  • JPA에 대한 선행 지식이 필요하다.

 

 

 

 

Spring Data JDBC 실습

 

 

➤ 의존 라이브러리 추가

 

implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

runtimeOnly 'com.h2database:h2'

 

Spring Data JDBC를 사용하기 위한 의존 라이브러리 코드 추가

데이터베이스에서 인메모리(In-memory) DB인 H2를 사용하기 위한 의존 라이브러리 설정 추가

 

 

 

▶️ 인메모리(In-memory) DB란?

 

메모리 안에 데이터를 저장하는 데이터베이스이다.

메모리는 휘발성이라 컴퓨터 전원을 껐다가 켜면 메모리에 저장되어 있는 데이터는 모두 지워진다.

인메모리 DB는 애플리케이션이 실행되는 동안에만 데이터를 저장하고 있다.

테스트에 필요한 데이터 외에는 테이블에 없는 것이 테스트의 정확도 면에서 유리하기 때문에 인메모리 DB를 사용한다.

로컬 개발 환경에서는 테스트가 끝나면 테이블을 비우는 것이 좋다.

 

 

▶️ application.yml파일에 H2 Browser 활성화 설정 추가

 

Spring Boot Initializr을 통해 샘플 프로젝트 생성 -> src/main/resources 에 application.properties 또는 appication.yml 파일을 통해 Spring 에서 사용하는 다양한 설정 정보들을 입력

application.yml로 확장자 변경 후 아래 코드 입력

spring:

  h2:

    console:

      enabled: true

 

<애플리케이션 실행 시 콘솔 창>

HikariPool-1 - Starting...

HikariPool-1 - Start completed.

H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:97f0b3b1-369a-498d-9abe-633d5d10acaa'

 

<웹 브라우저>

localhost:8080/h2-console

주소창에 입력후 엔터

 

H2 Browser 초기 화면 나옴

JDBC URL 에 jdbc:h2:mem:97f0b3b1-369a-498d-9abe-633d5d10acaa 입력

-> Connect

 

 

 

▶️ H2 DB 디폴트 설정의 문제점

 

애플리케이션 재시작 할때마다 애플리케이션 로그에 출력되는 JDBC URL이 매번 랜덤하게 바뀌기 때문에 매번 변경된 JDBC URL을 다시 입력해야 한다.

이 문제는 application.yml 파일에 H2에 대한 설정을 추가해서 해결한다.

 

jdbc:h2:mem:test 로 설정

spring:

  h2:

    console:

      enabled: true

      path: /h2     # (1) Context path 변경

  datasource:

    url: jdbc:h2:mem:test     # (2) JDBC URL 변경

 

주소창에

localhost:8080/h2 로 접속

 

 

▶️ PostMan 으로 Post

 

Post 하고 body 에 JSON 형식으로 message 값 전달

➡️ 오류 발생! ➡️ Message 테이블이 생성되지 않았음

 

 

 

 

 

 

 

 

읽어주셔서 감사합니다. 좋은 하루 되세요 😄

 

 

 

 

 

 

비즈니스 로직은 서비스 계층에 있다.

 

 

 

비즈니스 로직 예외 던지기(throw) 및 예외 처리

 

 

➤ 체크 예외(Checked Exception)와 언체크 예외(Unchecked Exception)

 

애플리케이션에서 발생하는 예외는 크게 Checked ExceptionUnchecked Exception으로 구분된다.

 

▶️ Checked Exception

  • 발생한 예외를 잡아서(catch) 체크한 후 해당 예외를 복구하거나 회피하거나 하는 등의 구체적인 처리를 해야하는 예외이다.
  • 대표적인 예 : ClassNotFoundException

 

 

▶️ Unchecked Exception

  • 예외를 잡아서(catch) 해당 예외에 대한 어떤 처리를 할 필요가 없는 예외이다.
  • 명시적으로 잡아서 어떤 처리를 할 필요는 없다.
  • 대표적인 예 : NullPointerException, ArrayIndexOutOfBoundsException

 

 

코드를 잘못 작성해서 생기는 이런 오류들은 모두 RuntimeException을 상속한 예외들이다.

Java나 Spring에서 수많은 RuntimeException을 지원하지만 RuntimeException을 이용해서

직접 예외(Exception)을 만들어야 하는 경우도 있다.

 

 

 

➤ 의도적으로 예외를 던질 수 (throw) 있는 상황

 

  • 백엔드 서버와 외부 시스템과의 연동에서 발생하는 예외 처리
  • 시스템 내부에서 조회하려는 리소스(자원)가 없는 경우

 

의도적으로 예외를 던져서 클라이언트 쪽에 발생한 에러 정보를 알려줄 수 있다.

 

 

 

➤ 의도적인 예외 throw / catch

 

java에서 throw 키워드는 예외를 메서드 바깥으로 던져준다.

던져진 예외는 메서드 바깥 즉, 메서드를 호출한 지점으로 던져지게 된다.

 

그렇다면 서비스 계층에서 예외를 던지면 어디로 던져질까?

서비스 계층의 메서드는 API 계층인 Controller의 Handler Method가 이용한다.

-> 서비스 계층에서 던져진 예외는 Controller의 Handler Method 쪽에서 잡아서 처리할 수 있다.

 

따라서 Controller에서 발생하는 예외 처리를 위한 Exception Advice 클래스에 서비스 계층에서 던진 예외도 작성해서 처리하면 된다.

 

 

✅ 서비스 계층에서 의도적으로 던질 수 있는 예외는 1가지만 존재하진 않는다.

예를 들어 아래의 경우 모두 예외 상황이 생길 수 있다.

  • 회원 정보가 존재하지 않을 경우
  • 회원 등록 시 이미 존재하는 회원일 경우
  • 로그인 패스워드 검증에서 일치하지 않는 경우

 

이럴 때 handle에러이름Exception()과 같이 메서드 이름을 짓는 것은 적절하지 않다.

그리고 추상적인 RuntimeException 을 그대로 전달 받는 것도 바람직 하진 않다.

 

서비스 계층에서 RuntimeException 을 그대로 throw하고, Exception Advice에서 RuntimeException을 그대로 catch 하는 것은 예외의 의도가 명확하지 않고 구체적으로 어떤 예외가 발생했는지에 대한 정보를 얻는 것이 어렵다.

 

 

 

사용자 정의 예외(Custom Exception) 사용

 

ExceptionCode 를 enum으로 미리 직접 작성해둔다.

BusinessLogicException 클래스를 생성하여 RuntimeException을 상속한다.

super() 상위 클래스 생성자로 RuntimeException에 구체적인 예외 정보 (Exception Code)를 던진다.

 

Exception Advice 클래스에서

메서드 명이 서비스 계층의 비즈니스 로직 처리에서 발생하는 예외를 처리하는 것을 목적으로 한다.

-> handleBusinessLogicException

 

RuntimeException을 파라미터로 전달 받던 것을 BusinessLogicException을 전달 받는 것으로 변경한다.

 

@ResponseStatus(HttpStatus.NOT_FOUND) 제거

ResponseStatus는 고정된 HttpStatus를 지정하므로 다양한 Status를 동적으로 처리할 수 없다.

-> 대신 ResponseEntity를 사용해서 커스텀한 예외 코드(상태)를 전달한다.

 

 

 

▶️ @RestControllerAdvice 에서 ResponseStatus vs ResponseEntity

 

@ResponseStatus : 

한가지 유형으로 고정된 예외를 처리할 경우 HttpStatus를 지정해서 사용

 

@ResponseEntity :

BusinessLogicException 처럼 다양한 유형의 Custom Exception을 처리하고자 할 경우

 

 

▶️ of() 정적 팩토리 메서드

 

new를 직접 사용하지 않고 클래스의 인스턴스를 생성한다.

장점 : 이름을 통해 객체의 의미를 알 수 있다.

 

 

 

 

 

읽어주셔서 감사합니다^^ 좋은하루 되세요 🤗

 

 

 

 

 

 

Spring MVC에서의 예외 처리

 

 

Spring MVC는 애플리케이션에서 발생할 수 예외를 효율적으로 처리하도록 몇 가지 방법을 제공한다.

 

 

 

➤ @ExceptionHandler를 이용한 Controller 레벨에서의 예외 처리

 

유효성 검증에서 실패했을 때 클라이언트가 전달 받는 Response Body는 애플리케이션에서 예외(Exception)가 발생했을 때, 내부적으로 Spring에서 전송해주는 에러 응답 메시지 중 하나이다.

 

Spring에서 예외는 유효성 검증이 실패했을 때 등 문제가 발생하면 실패를 하나의 예외로 간주하여 이 예외를 던져서 (throw) 예외 처리를 유도한다.

 

 

▶️ @Slf4J

Simple Logging Facade for Java

Logging

  • 개발 중이나 완료 후 발생할 수 있는 오류에 대해 디버깅하거나 운영중인 프로그램 상태를 모니터링 하기 위해 필요한 정보(로그)를 기록하는것

 

Logback과 같은 백엔드 Logging Framework의 facade pattern

 

https://www.slf4j.org/

 

SLF4J

Simple Logging Facade for Java (SLF4J) The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framewor

www.slf4j.org

 

 

▶️ @ExceptionHandler

예외 처리 메서드로 선언

 

 

▶️ .getBindingResult().getFieldErrors()

에러 정보를 확인할 수 있다

 

 

➤ ErrorResponse 클래스 만들기

 

DTO 클래스 유효성 검증 실패 시

실패한 필드에 대한 Error 정보만 담아서 응답으로 전송하기 위한 클래스

 

Response Body의 JSON 응답 객체가 배열이다.

✅ 배열인 이유?

DTO 클래스에서 유효성 검증에 실패하는 멤버변수가 하나 이상이 될 수 있기 때문에 유효성 검증 실패 에러 역시 하나 이상이 될 수 있다.

-> 유효성 검증에 실패한 필드의 에러 정보를 담기 위해서 List 객체를 이용

FieldError라는 별도의 static class를 ErrorResponse 클래스의 안에 정의한다.

FieldError클래스는 ErrorResponse 클래스의 내부(Inner) 클래스라고 부르기 보다는

ErrorResponse 클래스의 static 멤버 클래스라고 부르는 것이 적절하다.

필요한 정보들만 골라서 Response body에 담을 수 있다.

 

 

 

➤ @ExceptionHandler 의 단점

 

1. 코드 중복이 발생한다.

각각의 Controller 클래스에서 @ExceptionHandler 애너테이션을 사용해서 유효성 검증 실패에 대한 에러 처리를 해야한다.

 

2. 예외 마다 에러 처리 핸들러 메서드가 늘어난다.

Controller 클래스 내에서 발생할 수 있는 예외(Exception)은 한가지만 있는게 아니다.

그러면 각각의 예외 마다 @ExceptionHandler를 추가한 에러 처리 핸들러 메서드가 늘어나게 된다.

 

 

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more com

docs.spring.io

 

 

 

 

❑ @RestControllerAdvice 를 이용한 예외 처리

 

➤ 예외 처리 공통화

 

특정 클래스에 @RestControllerAdvice 애너테이션을 추가하면 여러개의 Controller 클래스에서 @ExceptionHandler, @InitBinder, @ModelAttribute가 추가된 메서드를 공유해서 사용할 수 있다.

 

 

@RestControllerAdvice 애너테이션을 추가한 클래스

➡️ 예외 처리를 모든 Controller에 공통화 할 수 있게 된다.

 

 

▶️ @InitBinder, @ModelAttribute 애너테이션

 

JSP, Thymeleaf 같은 서버 사이드 렌더링(SSR, Server Side Rendering) 방식에서 주로 사용된다.

 

 

 

Controller 클래스에서 @ExceptionHandler 로직 제거

-> ExceptionAdvice 클래스 정의 (@RestControllerAdvice)

-> 모든 Controller의 에러를 동시에 처리할 수 있게 된다.(AOP, 공통 관심사)

 

에러를 정보를 전달하는 객체(Error Response)와 에러 정보를 추출하고 가공하는 static 멤버 클래스로 역할을 분리한다.

 

기능이 늘어남에 따라 Error Response 클래스의 구현 복잡도가 늘어나지만

에러 유형에 따른 에러 정보 생성 역할을 분리함으로써 사용자 입장에서 한층 더 편리해진다.

 

 

▶️ of() 메서드

  • 네이밍 컨벤션(Naming Convention)
  • 객체 생성시 어떤 값들의(of~) 객체를 생성한다는 의미
  • 객체.of(원하는 값)

 

ExceptionAdvice 클래스에서 ErrorResponse 클래스에 of를 사용해서 에러 객체 전달한다.

ResponseEntity 대신 ErrorResponse 객체 그대로 반환하고

@ResponseStatus 애너테이션을 이용해서 attribute로 HTTP Status 전달한다.

 

 

※ @RestControllerAdvice vs @ControllerAdvice

 

SpringMVC 4.3버전 이후부터 @RestControllerAdvice 지원

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

JSON 형식의 데이터를 Response Body로 전송하기 위해서 ResponseEntity로 데이터를 래핑할 필요가 없다.

 

 

 

 

읽어주셔서 감사합니다. 좋은하루 되세요 🤩

 

 

 

 

 

 

 

❑ Service 계층

 

 

Spring MVC layer architecture

 

 

➤ DI를 통한 서비스 계층과 API 계층 연동

 

✔️ API 계층과 서비스 계층을 연동한다?

API 계층에서 구현한 Controller 클래스가 서비스 계층의 Service 클래스와 메서드 호출을 통해 서로 상호작용한다.

 

 

▶️ Service의 의미

 

애플리케이션에서 Service는 도메인 업무 영역을 구현하는 비즈니스 로직과 관련이 있다.

Serivce는 쉽게 말해 비즈니스 로직을 처리하기 위한 Service 클래스이다.

애플리케이션의 서비스 계층은 대부분 도메인 모델을 포함하고 있다.

도메인 모델은 빈약한 모델(anemic domain model)풍부한 도메인 모델(rich domain model)로 구분할 수 있다.

이러한 도메인 모델은 DDD(Domain Driven Design, 도메인 주도 설계)와 관련있다.

(DDD는 현업에서 클래스 설계 경험이 풍부해야 제대로 사용할 수 있다.)

 

 

 

➤ 도메인 엔티티(Domain Entity) 클래스

 

서비스 계층에서 데이터 엑세스 계층과 연동하면서 비즈니스 로직을 처리하기 위해 필요한 데이터를 담는 클래스

보통 리소스 이름으로 만든다. ex) Member, Coffee, Order

 

 

➤ 도메인 엔티티 클래스에 붙는 애너테이션

 

lombok 라이브러리에서 제공한다.

 

@Getter / @Setter

  • getter/setter 메서드를 일일이 작성할 필요 없도록 추가해준다.

 

@AllArgsConstructor

  • 클래스에 존재하는 모든 필드를 매개변수로 갖는 생성자를 자동으로 생성해준다.
  • 만약에 필드 중에 @NotNull 애너테이션이 붙어 있다면 해당 부분은 생성자 내에서 null-check로직을 자동으로 생성한다.

 

@NoArgsConstructor

  • 매개변수가 없는 생성자를 생성한다.
  • 필드에 final 키워드가 있을 경우 초기화 할 수 없기 때문에 에러가 발생한다.
  • 필드에 @NotNull 같은 제약조건이 설정되어 있을 경우, 생성자 내 null-check 로직이 생성되지 않는다.

 

@RequiredArgsConstructor

  • 초기화 되지 않은 모든 final 필드, @NotNull이 설정되 있는 필드들에 대한 생성자를 자동으로 생성해준다.

 

 

 

※ lombok 라이브러리의 더 많은 애너테이션

https://projectlombok.org/features/all

 

Stable

 

projectlombok.org

 

 

 

➤ DI 주입

 

Service 클래스에 @Service 애너테이션 추가해서 Spring Bean으로 등록해주고

Controller 클래스와 Service 클래스간에 느슨한 결합(Losse Coupling)을 위해 DI를 주입해준다.

(Controller는 @RestController 로 이미 Spring Container에 등록되어 있다.)

 

※ 일반적으로 생성자가 하나일 경우에는 @Autowired 를 붙이지 않아도 Spring이 알아서 DI를 적용한다.

하지만 생성자가 하나 이상일 경우, DI를 적용하기 위해선 반드시 @Autowired를 붙여야 한다!

 

 

 

 

❑ Mapper(매퍼)

 

 

➤ Mapper를 이용한 DTO 클래스와 Entity(엔티티) 클래스 매핑

 

▶️ Mapper를 사용하지 않을 경우

 

  • Controller의 핸들러 메서드가 DTO 클래스를 엔티티 클래스로 변환 하는 작업까지 하게 되어 여러 역할을 가지게 된다.
  • 서비스 계층인 엔티티(Entity) 클래스 객체를 클라이언트의 응답으로 전송(API 계층의 역할)해서 계층 간의 역할 분리가 이루어지지 않는다.

 

 

▶️ Mapper 사용법

 

  • 클래스를 Spring Bean으로 등록하기 위해 @Component 애너테이션을 추가한다.
  • 등록된 빈은 Controller에서 사용된다.

 

 

➤ MapStruct를 이용한 Mapper 자동 생성

 

Mapper를 수작업으로 작성하는 것은 비효율 적이다.

Mapper를 자동으로 만들 수 있는 MapStruct을 이용하면 편리하다.

DTO클래스 처럼 Java Bean 규약을 지키는 객체들 간의 변환 기능을 제공하는 매퍼(Mapper) 구현 클래스를 자동으로 생성해주는 코드 자동 생성기이다.

 

MapStruct를 사용하려면 먼저 의존 라이브러리를 추가 해주어야 한다.

Gradle

build.gradle파일 dependencies에 아래 코드 추가한다.

dependencies {

implementation 'org.mapstruct:mapstruct:1.4.2.Final'

annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

}

 

 

Mapper 인터페이스 추가하고 인터페이스에 @Mapper 애너테이션 추가한다.

-> 인터페이스가 MapStruct의 매퍼 인터페이스로 정의된 것임을 알려준다.

attribute로 componentModel = “spring” 지정해서 Spring Bean으로 등록한다.

MapStruct가 Mapper 인터페이스를 기반으로 매퍼(Mapper) 구현 클래스를 자동으로 생성해준다!

Gradle에 build task를 실행하면 자동으로 생성된다.

intelliJ에서 build task 실행하기

Gradle 탭 -> 해당 프로젝트 명 -> Tasks 디렉토리 -> build 디렉토리 -> build 더블클릭

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 늘 환영입니다. 😆

1 ··· 3 4 5 6 7 8 9 ··· 11