스프링

 

초급자를 위해 준비한
[백엔드, 웹 개발] 강의입니다.

JPA와 스프링 데이터 JPA의 기본 사용법을 알아봅니다.

✍️
이런 걸
배워요!

JPA 기본 매핑

스프링 데이터 JPA 기본 사용법

DB 연동의 열쇠 JPA! 
실무 중심의 핵심 기본기를 빠르게 ????

백엔드 실무자를 위한 
JPA & 스프링 데이터 JPA

 

 

 

인프런 강의
https://www.inflearn.com/course/jpa-spring-data-기초

 

 

유튜브
https://www.youtube.com/playlist?list=PLwouWTPuIjUi9Sih9mEci4Rqhz1VqiQXX

 

 

 

1.JPA 기초 01 일단 해보기

 

 



•ORMObject-Relational Mapping 스펙
자바 객체와 관계형 DB 간의 매핑 처리를 위한 API
Java Persistence API (2.2 버전)
Jakarta Persistence API (3.0 버전)
 2.2 버전부터 JCP
이클립스 재단으로 이관 진행
Jakarta EE 9 버전: JPA 3.0
보통 JPA만 단독으로 사용하기 보다는 스프링과 함께 사용
스프링 6 버전부터 Jakarta EE 9+ 지원

 



애노테이션을 이용한 매핑 설정

•XML 파일을 이용한 매핑 설정도 가능
String, int, LocalDate 등 기본적인 타입에 대한 매핑 지원
커스텀 타입 변환기 지원

내가 만든 Money 타입을 DB 칼럼에 매핑 가능
밸류 타입 매핑 지원
한 개 이상 칼럼을 한 개 타입으로 매핑 가능
클래스 간 연관 지원 : 1-1, 1-N, N-1, N-M
상속에 대한 매핑 지원

 

 

MySQL 준비
 일단 해보기 : 준비 사항 데이터베이스 생성 자바 프로젝트
 메이븐
 사용자 생성 및 권한 부여 테이블 생성
 pom.xml에 관련 의존 추가 persistence.xml 파일 작성

• User 클래스 작성 Main 클래스 작성 실행
 https://github.com/madvirus/jpa-basic/jpa-01

 

 

 

MySQL 설치
・직접 다운 받아서 설치

•https://dev.mysql.com/downloads/


•도커로 설치
docker create --name mysql8 -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 mysql:8.0.27
docker start mysql8

 

 


데이터베이스, DB 사용자, 권한



create database jpabegin CHARACTER SET utf8mb4;
CREATE USER 'jpauser '@'localhost' IDENTIFIED BY 'jpapass';
CREATE USER 'jpauser'@'%' IDENTIFIED BY 'jpapass';
GRANT ALL PRIVILEGES ON jpabegin.* TO 'jpauser '@'localhost'; GRANT ALL PRIVILEGES ON jpabegin.* TO 'jpauser'@'%';

 

 

테이블 생성

create table jpabegin.user (
email varchar(50) not null primary key, name varchar(50),
create_date datetime
) engine innodb character set utf8mb4;

 

 

 

 

•메이븐 프로젝트
• JDK 17
자바 프로젝트 생성

프로젝트 이름 : jpa-01

 

<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.27</version>
    </dependency>
    <dependency>
        <groupId>ch.qos. logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.6</version>
    </dependency>
</dependencies>

 

 

persistence.xml (src/main/resources/META-INF)

<persistence...>
    <persistence-unit name="jpabegin" transaction-type="RESOURCE_LOCAL">
        <class>jpabasic.reserve.domain.User</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver" />
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost/jpabegin? characterEncoding=utf8" />
            <property name="jakarta.persistence.jdbc.user" value="jpauser" />
            <property name="jakarta.persistence.jdbc.password" value="jpapass" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <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" /> <!-- 1s -->
        </properties>
    </persistence-unit>
    </persistence>

 

 

 

 

 

package jpabasic.reserve.domain;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "user") public class User {
        @Id
        private String email;
        private String name;
        @Column(name = "create_date")
        private LocalDateTime createDate;
        protected User() {}
        public User(String email, String name, LocalDateTime createDate) {}
    }
    ...생략
    ...get 메서드 생략

 

 

클래스와 테이블 매핑
@Entity DB 테이블과 매핑 대상
@Table(name = "user") user El
public class User {
    @Id 식별자에 대응
    private String email;
    email
    private String name;
    name
    @Column(name = "create_date") create_date
    private LocalDateTime createDate;

 

 

UserSaveMain 클래스

등록

import jakarta.persistence.*;
import jpabasic.reserve.domain.User;
import java.time.LocalDateTime;
public class UserSaveMain {}
public static void main(String[] args) {
        EntityManagerFactory emf Persistence.createEntityManagerFactory("jpabegin");
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        try {
            transaction.begin();
            User user = new User("user@user.com", "user", LocalDateTime.now());
            entityManager.persist(user);
            transaction.commit();
        } catch (Exception ex) {
            ex.printStackTrace();
            transaction.rollback();
        } finally {}
        entityManager.close();
        emf.close();

 

값가져오기

EntityManagerFactory emf Persistence.createEntityManagerFactory("jpabegin");
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
    transaction.begin();
    User user = entityManager.find(User.class, "user@user.com");
    if (user == null) {
        System.out.println("User");
    } else {}
    System.out.printf("User : email=%s, name=%s, createDate=%s\n", user.getEmail(), user.getName(), user.getCreateDate());
    transaction.commit();
} catch (Exception ex) {
    ex.printStackTrace();
    transaction.rollback();
} finally {}
entityManager.close();
emf.close();

 

 

수정

EntityManagerFactory emf Persistence.createEntityManagerFactory("jpabegin");
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
    transaction.begin();
    User user = entityManager.find(User.class, "user@user.com");
    if (user == null) {
        System.out.println("User");
    } else {}
    String newName = "0" + (System.currentTimeMillis() % 100);
    user.changeName(newName);
    transaction.commit();
} catch (Exception ex) {
    ex.printStackTrace();
    transaction.rollback();
} finally {}
entityManager.close();
emf.close();

 

 

01. 일단 해보기 정리
•간단한 설정으로 클래스와 테이블 간 매핑 처리
・EntityManager를 이용해서 DB 연동 처리
•객체 변경만으로 DB 테이블 업데이트
•쿼리 작성 X

 

 

 

 

 

 

 

 

2.JPA 기초 02 코드 구조 & 영속 컨텍스트

 

 

 

 

EntityManager로 DB 연동
나중에 시...
    EntityManager
Entity Transaction 7
트랜잭션 시작
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction entityManager.getTransaction();
try {
    transaction.begin();
    ...entityManager DB
    트랜잭션 커밋
    transaction.commit();
} catch (Exception ex) {
    트랜잭션 롤백
    transaction.rollback();
} finally {
    EntityManager 닫음
    entityManager.close();
}

 

 

저장과 쿼리 실행 시점

 

transaction.begin();
User user = new User("user@user.com", "user", LocalDateTime.now());
entityManager.persist(user);
logger.info("EntityManager.persist ");
transaction.commit();
logger.info("EntityTransaction.commit1");



INFO jpabasic.reserve.domain.UserSaveMain - EntityManager.persist DEBUG org.hibernate.SQL
insert into user(create_date, name, email) values( ? , ? , ? ) EntityTransaction.commit
INFO jpabasic.reserve.domain.UserSaveMain

 

 

 

JPA 기초 02 코드 구조 & 영속 컨트

 

transaction.begin();
수정과 쿼리 실행 시점
나중에 시...
    User user = entityManager.find(User.class, "user@user.com");
if (user == null) {
    System.out.println("User 없음");
} else {}
String newName = "이름" + (System.currentTimeMillis() % 100);
user.changeName(newName);
logger.info("User.changeName 호출함");
transaction.commit();
logger.info("EntityTransaction.commit 호출함");

 

DEBUG org.hibernate.SQL - select ul_0.email, ul_0.create_date, ul_0.name from user ul_0 where ul_0.email = ? INFOj.reserve.domain.UserUpdateMain - User.changeName 호출함
DEBUG org.hibernate.SQL - update user set create_date = ? , name ? where email = ? INFOj.reserve.domain.UserUpdateMain - EntityTransaction.commit 호출함

 


정리

EntityManagerFactory 초기화

 

DB 작업이 필요할 때마다 Entity Manager 생성, EntityManager로 DB 조작 , EntityTransaction으로 트랜잭션 관리


하지만 스프링과 연동할 때는 대부분 스프링이 대신 처리하므로 매핑 설정 중심으로 작업


영속 컨텍스트 엔티티를 메모리에 보관 변경을 추적해서 트랜잭션 커밋 시점에 DB에 반영

 

 

 

 

 

 

 

 

 

 

 

 

3.JPA 기초 03 엔티티 CRUD 처리

 

 

시작전에 보조 클래스 만들기・

EntityManager를 쉽게 구하기 위한 클래스

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class EMF {
    private static EntityManagerFactory emf;
    public static void init() {}
    emf = Persistence.createEntityManagerFactory("jpabegin");
    public static EntityManager createEntityManager() {}
    return emf.createEntityManager();
    public static void close() {}
}
emf.close();

 

 

엔티티 단위 CRUD 처리
EntityManager가 제공하는 메서드 이용



persist()
find()
remove() merge()

 

저장

EntityManager #persist(Object entity)
public class NewUserService {
    public void saveNewUser(User user) {
        EntityManager em = EMF.createEntityManager();
        EntityTransaction tx em.getTransaction();
        try {
            tx.begin();
            em.persist(user);
            tx.commit();
        } catch (Exception ex) {
            tx.rollback();
            throw ex;
        } finally {
            em.close();
        }
    }
}

 

 

조회

EntityManager #find(Class <T> entityClass, Object primaryKey)
public class GetUserService {
    public User getUser(String email) {
        EntityManager em EMF.createEntityManager();
        try {
            User user em.find(User.class, email);
            if (user == null) {}
            throw new NoUserException();
            return user;
        } finally {}
    }
}
em.close();

 

 

조회
엔티티 타입, ID 타입이 맞아야 함
일치하지 않으면 익셉션

 

String str = em.find(String.class, "1");
User user = em.find(User.class, 11);

 

 

 

수정

트랜잭션 범위 내에서 변경된 값을 자동 반영

public class Change NameService {
    public void changeName(String email, String newName) {
            EntityManager em = EMF.createEntityManager();
            EntityTransaction tx = em.getTransaction();
            try {
                tx.begin();
                User user = em.find(User.class, email);
                if (user == null) {}
                `throw new NoUserException();
   user.changeName(newName);
     tx.commit();
} catch(Exception ex) {
    tx.rollback();
}
   throw ex;
}
finally {
   em.close();
}
}

 

 


삭제

EntityManager#remove(Object entity)

public class RemoveUserService {
    public void removeUser(String email) {
        EntityManager em = EMF.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            User user = em.find(User.class, email);
            if (user = null) {}
            throw new NoUserException();
            em.remove(user);
            tx.commit();
        } catch (Exception ex) {
            tx.rollback();
            throw ex;
        } finally {
            em.close();
        }
    }
}

 

 

 

 

정리

EntityManager를 사용해서 엔티티 단위로 CRUD 처리 변경은 트랜잭션 범위 안에서 실행
1) persist()
2) 수정
3) remove()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4.JPA 기초 04 엔티티 매핑 설정

 

 

기본 애노테이션

엔티티 매핑


@Entity: 엔티티 클래스에 설정, 필수


@Table : 매핑할 테이블 지정


@ld : 식별자 속성에 설정, 필수 @Column : 매핑할 칼럼명 지정
지정하지 않으면 필드명/프로퍼티명 사용


@Enumerated: enum 타입 매핑할 때 설정

@Temporal: java.util.Date, java.util.Calendar 매핑 자바8 시간/날짜 타입 등장 이후로 거의 안 씀


@Basic : 기본 지원 타입 매핑 (거의 안 씀)

 

 

 

 


@Table 애노테이션
애노테이션을 생략하면 클래스 이름과 동일한 이름에 매핑

 속성
 예
 name: 테이블 이름 (생략하면 클래스 이름과 동일한 이름)

catalog : 카탈로그 이름 (예, MySQL DB 이름)
schema : 스키마 이름 (예, 오라클 스키마 이름)
@Table
@Table(name="hotel_info")
@Table(catalog = "point", name = "point_history") @Table(schema = "crm" name="cust_stat")

 

 



·
설정 값
@Enumerated 애노테이션
EnumType.STRING: enum 타입 값 이름을 저장
문자열 타입 칼럼에 매핑
・EnumType. ORDINAL (기본값): enum 타입의 값의 순서를 저장
•숫자 타입 칼럼에 매핑
public enum Grade {
S1, S2, S3, S4, S5, S6, S7
}
EnumType.STRING → "S1"
Grade.S1.name()
EnumType. ORDINAL 0 Grade.S1.ordinal()

 

 

 

매핑 설정 예

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

5.JPA 기초 05 엔티티 식별자 생성 방식

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6.JPA 기초 06 @Embeddable

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

@Embeddable 은 다음을 참조

 

https://macaronics.net/index.php/user/search/lists/s/%40Embeddable/page/1

 

연관된  엔티티들이 전부 변경 될 수 있으므로,

항상 불변 객체로 만들어야 한다.  추가 삭제를 항상 새로운 객체로 생성해서 처리해야 한다.

 

값 타입은 변경 불가능하게 설계해야 한다.

 

> @Setter 를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자. JPA 스펙상 엔티티나 임베디드 타입( @Embeddable )은 자바 기본 생성자(default constructor)를 public 또는 protected 로 설정해야 한다. public 으로 두는 것 보다는 protected 로 설정하는 것이 그나마 더  안전하다.


> JPA가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플랙션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다.

 

/**
 *  엔티티의 값이 공유되면 전 a, b,c.. 모두 변경 처리 될수 있기때문에
 *   @Embeddable클래스를 불변 절대 공유되서는 안된다.
 *  따라서, setter 은 사용하지 않는다.
 */
@Embeddable
@Getter
public class Address {
    private String city;
    private String street;
    private String zipcode;
 
    //함부로 new 로 생성처리 하면 안된다.
    protected Address() {
 
    }
 
    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7.JPA 기초 07 @Embeddable 다른 테이블에 매핑하기

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

종교는 모든 인민들의 아편이다. -마르크스

댓글 ( 4)

댓글 남기기

작성