자바 ORM 표준 JPA 프로그래밍 | 김영한 | 에이콘출판- 교보ebook

스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임, <b>★ 이 책에서 다루는 내용 ★</b> ■ JPA 기초 이론과 핵심 원리 ■ JPA로 도메인 모델을 설계하는 과정을 예제 중심

ebook-product.kyobobook.co.kr

소스

https://github.com/rkwhr0010/jpa

 

SQL 직접 다룰 발생하는 문제점

지루한 반복 코드

JDBC API 사용해 DB CRUD 하려면 너무 많은 직접 작성한 유사한 SQL DB 연결을 위한 반복성 코드가 생긴다.

데이터 접근 계층(DAO) 대부분 반복성 코드와 유사한 SQL 많다.

 

SQL 의존적인 개발

비즈니스 요구사항은 언제나 변한다.

public class Member {
        private Long id;
        private String name;
        private String email; // 새로 추가된 컬럼
}

비즈니스 요구사항을 모델링한 객체를 엔티티라 한다.

요구사항의 변경으로 Member 엔티티에서 email 추가로 저장해야 하는 상황이 오면, 모든 관계된 CRUD 코드를 확인하고 SQL 수정해야 한다.

 

JPA 문제 해결

JPA 객체를 DB 저장할 JPA 제공하는 API 사용하면 된다.

JPA 자동으로 SQL 생성해서 DB에게 전달한다.

 

다음은 CRUD 대표적인 메서드들이다

저장

 

 

조회

 

 

수정

수정의 경우 특별하다. find() 조회한 엔티티를 수정하면, 트랜잭션을 커밋할 DB 수정사항이 반영된다.

 

삭제

 

 

페러다임의 불일치

애플리케이션은 발전하면서 복잡성도 증대된다.

개발자는 복잡성을 제어해 유지보수하기 좋은 코드를 만들려 노력한다.

 

객체지향 캡상추다, SOLID, 디자인패턴 등은 복잡성을 제어하기 위한 도구이다.

 

비즈니스 요구사항은 코드로 구현을 해도 결국 산출물로 데이터가 나오고 이는 영구히 저장할 곳이 필요하다. 이때 RDB(관계형 DB) 역할을 한다. 여기서 DB 편의상 RDB 한정한다.

 

런타임에 객체로 존재하는 데이터를 RDB 저장해야 한다.

문제는 객체와 RDB 지향하는 목적이 다르다는 것이다.

RDB에는 객체지향이라는 개념이 존재하지 않는다. 데이터 중심으로 구조화되어 있다.

객체 구조를 테이블 구조에 저장하는 것에 한계가 있다. 이를 패러다임 불일치라 한다.

 

패러다임 불일치를 해소하기 위해 결국 개발자가 많은 노력을 기울여야 한다.

 

상속

객체는 상속이라는 개념이 존재한다.

DB 모델링에는 유사한 개념인 슈퍼타입 서브타입 관계가 존재한다.

비슷해 보이지만, 다르다. 객체는 extends 키워드로 부모의 속성을 상속 받지만, DB 경우 슈퍼타입과 서브타입 KEY 로만 관계를 맺는다.

, 1개의 서브타입 객체를 DB 저장하려면 2개의 INSERT 쿼리가 필요하다.

이게 패러다임 불일치를 해소하기 위한 비용이다.

 

JPA 상속

JPA 마치 자바 컬렉션에 저장하듯 사용하면, 나머지는 JPA 알아서 처리해 준다.

JPA 알아서 2개의 INSERT 쿼리로 처리해줌

 

 

 

package study.jpa.entity;


import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;


@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
        @Id
        @GeneratedValue
        @Column(name = "ITEM_ID")
        private Long id;
        
        private String name;
        private Integer price;
        
        public Long getId() {
                return id;
        }
        public void setId(Long id) {
                this.id = id;
        }
        public String getName() {
                return name;
        }
        public void setName(String name) {
                this.name = name;
        }
        public Integer getPrice() {
                return price;
        }
        public void setPrice(Integer price) {
                this.price = price;
        }
}


package study.jpa.entity;


import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;


@Entity
@DiscriminatorValue("A")
public class Album extends Item{
        private String artist;


        public String getArtist() {
                return artist;
        }


        public void setArtist(String artist) {
                this.artist = artist;
        }
}
package study.jpa.entity;


import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;


@Entity
@DiscriminatorValue("B")
public class Book extends Item{
        private String author;
        private String isbn;
        public String getAuthor() {
                return author;
        }
        public void setAuthor(String author) {
                this.author = author;
        }
        public String getIsbn() {
                return isbn;
        }
        public void setIsbn(String isbn) {
                this.isbn = isbn;
        }
}

package study.jpa.entity;


import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;


@Entity
@DiscriminatorValue("M")
public class Movie extends Item{
        private String director;
        private String actor;
        
        public String getDirector() {
                return director;
        }
        public void setDirector(String director) {
                this.director = director;
        }
        public String getActor() {
                return actor;
        }
        public void setActor(String actor) {
                this.actor = actor;
        }
}



package study.jpa;


import java.util.function.Consumer;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import study.jpa.entity.Album;


public class Main {
        static Logger logger = LogManager.getLogger();
        static EntityManagerFactory emf =
                        Persistence.createEntityManagerFactory("studyjpa");
        
        public static void main(String[] args) {
                createPersistenceContext(em -> {
                        Album album = new Album();
                        album.setName("앨범");
                        album.setArtist("홍길동");
                        album.setPrice(1000);
                        
                        em.persist(album);
                });
        }


        private static void createPersistenceContext(Consumer<EntityManager> consumer) {
                EntityManager em = emf.createEntityManager();
                EntityTransaction tx = em.getTransaction();
                
                try {
                        tx.begin();
                        
                        consumer.accept(em);
                        
                        tx.commit();
                } catch (Exception e) {
                        tx.rollback();
                } finally {
                        em.close();
                }
                emf.close();
        }
}

 

자동으로 2 SQL

 

JPA 자동으로 객체와 테이블 간극을 매핑해준다.

 

 

연관관계

객체는 참조를 통해 해당 객체에 접근한다.

테이블은 외래 키를 통해 조인으로 연관 테이블을 조회한다.

 

객체의 참조와 RDB 외래 사이 패러다임 불일치는 RDB 구조를 객체지향 모델링에 적용 시키는 것이 어렵다.

 

참조의 경우 단방향이다.

 

테이블의 경우 외래 키로 관계를 맺으므로, 방향이라는 것이 없다. 굳이 따지면 양방향이다.

 

 

객체를 테이블에 맞추어 모델링

패러다임 불일치를 가장 크게 체감할 있는 방법은 객체를 테이블에 맞춰서 설계해보는 것이다.

 

위와 같은 설계는 데이터를 DB 저장하기는 편하다.

문제는 RDB 경우 값으로 조인을 관계를 맺어준다.

객체는 값으로 관계를 맺어줄 수가 없다. 참조로 연결해야 연관된 객체를 있다.

 

 

처럼 객체지향에 맞춰서 설계를 하면, DB 데이터를 객체에 맞춰야한다.

과정을 JPA 자동으로 해준다.

, 개발자는 마치 컬렉션에서 객체를 꺼내듯 사용하면, DB에서 엔티티가 조회된다.

 

 

 

 

JPA 알아서 패러다임 불일치를 해결해준다.

 

처럼 객체 참조를 통해 이동하는 것을 객체 그래프 탐색이라고 한다.

 


JPA 비교와 그냥 비교

 

 

객체 비교의 두가지 방법

동일성은 == 비교이다.

동등성 비교는 equals() 비교이다

 

동일성은 참조 변수의 경우 같은 인스턴스인지 확인한다.

동등성은 서로 다른 객체여도 내용이 같은지 확인한다.

 

 

 

여기서 중요한 점은 JPA 조회한 회원 객체이다.

같은 영속성 컨텍스트에서 조회한 멤버는 동일성을 보증한다.

 

정리

객체 모델과 RDB 모델은 지향하는 패러다임이 다르다.

객체지향 어플리케이션을 더욱 객체지향스럽게 디자인 수록

패러다임 불일치가 커진다.

JPA 이런 패러다임 불일치 문제를 해결해준다.

JPA 개발자로 하여금 계속 객체지향 모델링을 유지하도록 도와준다.

 

 

 

JPA 무엇인가

JPA Java Persistence API

자바 진영의 ORM 기술 표준

ORM Object-Relational Mapping

객체와 RDB 매핑한다는

 

JPA 제외하고도 모든 자바 기반 기술은 DB 접속은 JDBC API 사용한다.

 

 

ORM 프레임워크는 객체와 테이블 매핑에서 오는 패러다임 불일치 문제를 해결해준다.

개발자는 자바 코드에서 컬렉션에 객체를 저장하듯 ORM 프레임워크에 저장하면, 나머지는 작업은 ORM 프레임워크가 알아서 해준다.

가장 유명한 ORM 프레임워크는 하이버네이트다.

 

JPA 자바 ORM 기술에 대한 API 표준 명세로, 바꿔 말하면, 인터페이스 묶음이다.

, 실질적인 구현체 하나를 선택해 사용한다.

 

JPA 사용해야 하나

장점으로 생산성, 유지보수성, 패러다임 불일치 해결, 성능, 데이터 접근 추상화(벤더 독립성) 있다.

 

지루하고 반복적인 CRUD SQL SQL 객체 사이 데이터 매핑을 필요가 없다.

엔티티에 필드가 변경되어도 이전 JPA사용하기 전보다 훨씬 적은 양의 코드만 손보면 된다.

JPA 객체 RDB 매핑을 해주기 때문에 개발자는 객체의 참조를 그대로 사용해 객체 그래프 탐색을 있다. 원래라면 개발자가 조인 쿼리를 짜고, 나온 데이터를 전부 객체에 알맞게 매핑해줘야하는 번거로운 일이다. 때문에 과거에는 객체를 테이블에 맞춰 데이터 중심으로 사용했다.

JPA 애플리케이션과 DB 사이에서 동작한다. 사이에서 캐시 역할 등으로 성능 최적화 기회를 얻는다.

데이터 접근이 단계 추상화된 방언(Dialect) 계층이 있다.

따라서 DB 변경이 자유롭다. 알맞은 벤더의 방언으로 교체하기만 하면 방언이 알아서 SQL 변경해준다.

극단적으로 개발은 Mysql 운영은 오라클로 바꿀 있다.

 

 

 

 

'개발 > JPA' 카테고리의 다른 글

06 다양한 연관관계 매핑  (0) 2024.04.14
05 연관관계 매핑 기초  (1) 2024.03.22
04 엔티티 매핑  (1) 2024.02.26
03 영속성 관리  (1) 2024.02.19
01 JPA 환경 설정  (0) 2023.11.23

DB 설치

Mysql 진행

나는 도커로 Mysql 설치했으나, 로컬에 설치해도 상관 없음

도커

docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=root -p 55555:3306 -d mysql:8.1

로컬 포트는 55555 사용했다. 도커에서 Mysql 포트 3306 매핑한다.

 

DB 스키마 계정 생성

mysql -u root -p




CREATE DATABASE jpa CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'root' IDENTIFIED BY 'root';
GRANT ALL PRIVILEGES ON jpa.* TO 'root';

 

 

DB 접속 툴은 DBeaver

 

STS(이클립스) 기준

프레임워크 없이 단순 JPA만 실습하기 위한 프로젝트 설

 

 

메이븐 의존성 pom.xml



<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>jpa-basic</groupId>
    <artifactId>jpa-01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <hiberate.version>6.0.0.Final</hiberate.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hiberate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-hikaricp</artifactId>
            <version>${hiberate.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.21.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

 

영속성 유닛 설정

특이사항으로 이제 jakarta 패키지 명을 사용한다.

 



<?xml version="1.0" encoding="utf-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
    version="3.0">
    <persistence-unit name="studyjpa" transaction-type="RESOURCE_LOCAL">
        <properties>
            <!-- DB 연결 정보 -->
            <property name="jakarta.persistence.jdbc.driver"
                value="com.mysql.cj.jdbc.Driver" />
            <property name="jakarta.persistence.jdbc.url"
                value="jdbc:mysql://localhost:55555/jpa?characterEncoding=utf8" />
            <property name="jakarta.persistence.jdbc.user" value="root" />
            <property name="jakarta.persistence.jdbc.password" value="root" />
            <property name="hibernate.hikari.poolName" value="pool" />
            <property name="hibernate.hikari.maximumPoolSize" value="10" />
            <property name="hibernate.hikari.minimumIdle" value="10" />
            <property name="hibernate.hikari.connectionTimeout" value="1000" />
            <!--  MySql 방언 -->
            <property name="hibernate.dialect"
                value="org.hibernate.dialect.MySQLDialect" />
            <!-- SQL 출력 -->
            <property name="hibernate.show_sql" value="true" />
            <!-- SQL 이쁘게 출력 -->
            <property name="hibernate.format_sql" value="true" />
            <!-- 주석도 함께 출력 -->
            <property name="hibernate.use_sql_comments" value="true" />
            <!-- JPA 표준에 맞춘 새로운 키 생성 전략 사용 -->
            <property name="hibernate.id.new_generator_mappings" value="true" />
            <!-- 실습에서만 사용할 것, @Entity에 따라 DDL 명령을 자동 실행해 준다. -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

 

logf4.xml



<?xml version="1.0" encoding="UTF-8"?>
<configuration status="debug">


    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{DEFAULT} %5p [%c] %m%n"/>
        </Console>
    </Appenders>
   
    <loggers>
        <root level="debug" additivity="true">
            <AppenderRef ref="console"/>
        </root>
    </loggers>


</configuration>

 

테스트



package study.jpa.entity;


import java.time.LocalDateTime;


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;


@Entity
public class User {
        
        @Id
        @GeneratedValue
        private Long id;
        private String name;
        private LocalDateTime registDate;
        
        public Long getId() {
                return id;
        }
        public void setId(Long id) {
                this.id = id;
        }
        public String getName() {
                return name;
        }
        public void setName(String name) {
                this.name = name;
        }
        public LocalDateTime getRegistDate() {
                return registDate;
        }
        public void setRegistDate(LocalDateTime registDate) {
                this.registDate = registDate;
        }
}



import java.time.LocalDateTime;


import org.apache.logging.log4j.*;


import jakarta.persistence.*;
import study.jpa.entity.User;


public class Main {
        static Logger logger = LogManager.getLogger();
        
        public static void main(String[] args) {
                
                
                EntityManagerFactory emf = Persistence.createEntityManagerFactory("studyjpa");
                EntityManager em = emf.createEntityManager();
                EntityTransaction tx = em.getTransaction();
                
                try {
                        tx.begin();
                        
                        logger.info("#################로그 테스트#################");
                        User user = new User();
                        user.setName("홍길동");
                        user.setRegistDate(LocalDateTime.now());
                        em.persist(user);
                        
                        tx.commit();
                } catch (Exception e) {
                        tx.rollback();
                } finally {
                        em.close();
                }
                emf.close();
        }
}

 

 

 

 

 

 

 

'개발 > JPA' 카테고리의 다른 글

06 다양한 연관관계 매핑  (0) 2024.04.14
05 연관관계 매핑 기초  (1) 2024.03.22
04 엔티티 매핑  (1) 2024.02.26
03 영속성 관리  (1) 2024.02.19
02 JPA 소개  (0) 2023.12.01

+ Recent posts