본문 바로가기

신세계 국비과정/신세계 - Spring 공부

19주차 배운점 - JPA 설정, CRUD

728x90
반응형

 

 

객체와 관계형 데이터베이스 차이

  1. 상속
  2. 연관관계
  3. 데이터 타입
  4. 데이터 식별 방법

 

 

 

 

 

JDBC API 같이 CRUD 를 반복해서 작성하면 비생산적임

 

객체와 관계형 데이터베이스 간의 차이를 중간에 해결해주는 ORM 프레임워크

 

JPA는 반복적인 CRUD SQL을 알아서 처리해 줌 , 객체 모델링과 관계형 데이터베이스 사이의 차이점도 해결

 

객체 중심으로 개발하니 생산성과 유지보수가 확연히 좋아졌고 테스트를 작성하기도 편리해짐, 버그도 많이 줆, 코드를 거의 수정하지 않고 데이터베이스를 손쉽게 변경

 

JPA란
왜쓰는지? /

 

hibernat dialect 란?


Hibernate가 다양한 데이터베이스와 통신할 수 있도록 해주는 구성 요소
SQL 문법과 데이터 타입, 특정 기능들이 조금씩 다르기 때문에, Hibernate는 이러한 차이를 추상화하고 각각의 데이터베이스에 맞게 SQL을 생성할 수 있도록 도와주는 '방언(Dialect)'을 제공

 

영속성 컨텍스트란

  • 영속성 컨텍스트 정의 : orm에서 테이블 참조하기 전 중간에서 잠시 들고 있는 곳 똑같은쿼리 백번한다 했을 때 , 결과 값 캐시로 들고있다가 재활용
  • 영속성 컨텍스트의 생명주기 : 1. 비영속, 2. 영속, 3. 준영속(참조), 4. 삭제

 

영속성 컨텍스트의 장점

 

 

persistance.xml 설정

<?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="jpabegin" transaction-type="RESOURCE_LOCAL">
        <class>com.ssg.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>

 

 

 

이 코드는 JPA(Java Persistence API) 설정을 위한 persistence.xml 파일의 일부입니다.

persistence-unit은 데이터베이스와의 상호작용을 관리하기 위한 설정을 포함하고 있습니다. 각 줄에 대한 설명은 다음과 같습니다:
<persistence-unit name="jpabegin" transaction-type="RESOURCE_LOCAL">: jpabegin이라는 이름의 영속성 단위를 정의합니다. transaction-type="RESOURCE_LOCAL"은 이 영속성 단위가 자체적으로 트랜잭션 관리를 수행하며, JTA(Java Transaction API) 대신 로컬 트랜잭션을 사용함을 의미합니다.

 

: 이 줄은 주석 처리되어 있으며, 일반적으로 영속성 단위에 포함될 엔티티 클래스를 명시적으로 지정하는 데 사용됩니다. 여기서는 jpabasic.reserve.domain.User 클래스를 엔티티로 지정하려 했으나, 현재 활성화되지 않았습니다.
 

true</exclude-unlisted-classes>: 이 설정은 persistence.xml에 명시적으로 나열되지 않은 클래스를 영속성 단위에서 제외시키도록 합니다. 여기서는 true로 설정되어 있어, 명시적으로 나열된 클래스만 포함됩니다.


<properties>: 데이터베이스 연결 및 하이버네이트 관련 설정을 포함하는 섹션을 시작합니다.
<property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>: 데이터베이스 연결을 위한 JDBC 드라이버를 지정합니다. 여기서는 MySQL용 JDBC 드라이버 com.mysql.cj.jdbc.Driver를 사용합니다.
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost/jpabegin?characterEncoding=utf8"/>: JDBC를 통해 연결할 데이터베이스의 URL을 지정합니다. characterEncoding=utf8은 문자 인코딩으로 UTF-8을 사용함을 의미합니다.
<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"/>: 사용할 데이터베이스 방언을 지정합니다. MySQL을 위한 방언을 설정합니다.
<property name="hibernate.hikari.poolName" value="pool"/>: HikariCP 커넥션 풀의 이름을 설정합니다.
<property name="hibernate.hikari.maximumPoolSize" value="10"/>: HikariCP 커넥션 풀의 최대 크기를 설정합니다.
<property name="hibernate.hikari.minimumIdle" value="10"/>: 커넥션 풀에 유지될 최소한의 유휴 커넥션 수를 설정합니다.
<property name="hibernate.hikari.connectionTimeout" value="1000"/>: 커넥션 풀에서 커넥션을 얻기 위해 대기할 최대 시간(밀리초 단위)을 설정합니다. 여기서는 1000ms, 즉 1초로 설정되어 있습니다.
이 설정들은 JPA 기반 애플리케이션에서 데이터베이스 연결과 관련된 중요한 세부 사항을 구성하는 데 사용됩니다.

 

 

EMF


팩토리

 

// EMF 클래스는 EntityManagerFactory의 싱글턴 인스턴스를 관리하기 위한 유틸리티 클래스입니다.
public class EMF {
// EntityManagerFactory의 정적(private) 인스턴스를 선언합니다. 애플리케이션 전역에서 하나의 인스턴스만 유지됩니다.
private static EntityManagerFactory emf;

// init 메서드는 EntityManagerFactory를 초기화합니다. "jpabegin"은 persistence.xml 파일에 정의된 persistence unit의 이름입니다.
// 이 메서드는 애플리케이션 시작 시 한 번만 호출되어야 합니다.
public static void init(){
  emf = Persistence.createEntityManagerFactory("jpabegin");
}

// createEntityManager 메서드는 EntityManager 인스턴스를 생성하여 반환합니다.
// 이 메서드는 데이터베이스 작업이 필요할 때마다 호출될 수 있습니다.
public static EntityManager createEntityManager(){
  return emf.createEntityManager();
}

// close 메서드는 EntityManagerFactory를 닫습니다. 이 메서드는 애플리케이션 종료 시 호출되어야 합니다.
// EntityManagerFactory를 닫으면 관련 리소스가 정리됩니다.
public static void close(){emf.close();}
}

 

 

domain 만들기

 

@Entity
@Table(name="user") //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) {
    this.email = email;
    this.name = name;
    this.createDate = createDate;
  }

  public String getEmail() {
    return email;
  }

  public String getName() {
    return name;
  }

  public LocalDateTime getCreateDate() {
    return createDate;
  }
}

 

 

logback.xml


로그남기기

 

<configuration>
  <!-- 콘솔에 로그를 출력하기 위한 appender 정의 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <!-- 로그 메시지의 형식을 정의하는 encoder -->
      <encoder>
          <!-- 로그 패턴 정의: 시간 [스레드] 로그 레벨 로거 이름 - 메시지 -->
          <!-- %d{HH:mm:ss.SSS}: 로그 이벤트의 시간을 시:분:초.밀리초 형식으로 표시 -->
          <!-- [%thread]: 로그 이벤트를 생성한 스레드의 이름 -->
          <!-- %-5level: 로그 레벨을 표시 (예: INFO ), -5는 필드 너비를 의미, 좌측 정렬 -->
          <!-- %logger{36}: 로거의 이름, 최대 36자까지 표시 -->
          <!-- %msg: 로그 메시지 -->
          <!-- %n: 플랫폼에 종속적인 줄바꿈 문자 -->
          <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
  </appender>

  <!-- 애플리케이션의 기본 로그 레벨을 INFO로 설정하고, STDOUT appender를 사용하여 로그 출력 -->
  <root level="INFO">
      <appender-ref ref="STDOUT" />
  </root>

  <!-- 특정 로거에 대한 로그 레벨 설정 -->
  <!-- org.hibernate.SQL 로거의 로그 레벨을 DEBUG로 설정, SQL 문 출력에 사용 -->
  <logger name="org.hibernate.SQL" level="DEBUG" />
  <!-- org.hibernate.transaction 로거의 로그 레벨을 DEBUG로 설정, 트랜잭션 관련 로그 출력에 사용 -->
  <logger name="org.hibernate.transaction" level="DEBUG" />
</configuration>

 

 

 

 

유닛은 딱 한번만 생성
이 팩토리는 공유해서 사용

 

UserSaveMain


트랜잭션 없이하면 에러남

 

// JPA의 EntityManagerFactory를 생성합니다. "jpabegin"은 persistence unit의 이름으로, persistence.xml 파일에 정의되어 있습니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabegin");

// EntityManager 인스턴스를 생성합니다. EntityManager는 엔티티에 대한 데이터베이스 작업을 수행하는 주체입니다.
EntityManager entityManager = emf.createEntityManager();

// 데이터베이스 작업을 위한 트랜잭션을 시작합니다. JPA에서 데이터 변경 작업은 트랜잭션 내에서 수행되어야 합니다.
EntityTransaction transaction = entityManager.getTransaction();

try{
// 트랜잭션을 시작합니다.
transaction.begin();

// 새로운 User 엔티티 인스턴스를 생성합니다. 생성자에는 이메일, 사용자명, 가입 시각이 전달됩니다.
User user = new User("user@user.com", "user", LocalDateTime.now());

// 생성된 User 엔티티를 영속성 컨텍스트에 저장합니다. 이 시점에서는 아직 데이터베이스에 저장되지 않았습니다.
entityManager.persist(user);

// 트랜잭션을 커밋합니다. 이때 변경된 내용(여기서는 새로운 User 엔티티의 추가)이 데이터베이스에 반영됩니다.
transaction.commit();
}catch (Exception ex){
// 예외 발생 시 예외 내용을 출력하고, 현재 진행 중인 트랜잭션을 롤백합니다. 이는 데이터 일관성을 유지하기 위함입니다.
ex.printStackTrace();
transaction.rollback();
}finally {
// EntityManager를 클리어하여 영속성 컨텍스트를 비웁니다. 이는 영속성 컨텍스트에 남아있는 모든 엔티티를 초기화합니다.
entityManager.clear();
}
// EntityManagerFactory를 닫습니다. 이는 사용된 모든 리소스를 정리합니다.
emf.close();

 

 

 

 

GetUserService

 

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();
    }
  }
}

 

 

NewUserService

 

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();
  }
}
}

 

 

RemoveUserService

 

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();
  }
}
}

 

 

ChangeNameService

 

public class {
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.setName(newName);
    tx.commit();
  } catch (Exception ex) {
    tx.rollback();
    throw ex;
  }finally {
    em.close();
  }
}
}

 

 

NoUserException

 

public class NoUserException extends RuntimeException{
}

 

 

logger 찍기

 

private static Logger logger = LoggerFactory.getLogger(UserSaveMain.class);

  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("user1@user.com", "user", LocalDateTime.now());
      entityManager.persist(user);
      logger.info("EntityManager.persist 호출");
      transaction.commit();
      logger.info("EntityTransaction 호출");
    } catch (Exception ex) {
      ex.printStackTrace();
      logger.info("Entity Transaction error "+ex.getMessage(), ex);
      transaction.rollback();
    } finally {
      entityManager.clear();
    }
    emf.close();
  }

 

 

 

 

Main

 

public class Main {
  private static Logger logger = LoggerFactory.getLogger(Main.class);

  private static NewUserService newUserService = new NewUserService();
  private static GetUserService getUserService = new GetUserService();
  private static ChangeNameService changeNameService = new ChangeNameService();
  private static RemoveUserService removeUserService = new RemoveUserService();

  public static void main(String[] args) throws IOException {
    EMF.init();
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
      while (true) {
        System.out.println("명령어를 입력하세요:");
        String line = reader.readLine();
        if (line == null) break;
        if (line.startsWith("new ")) {
          handleNew(line);
        } else if (line.startsWith("get ")) {
          handleGet(line);
        } else if (line.startsWith("change name ")) {
          handleChangeName(line);
        } else if (line.startsWith("remove ")) {
          handleRemove(line);
        } else if (line.equals("exit")) {
          break;
        }
      }
    } finally {
      EMF.close();
    }
  }

  private static void handleNew(String line) {
    String[] v = line.substring(4).split(" ");
    User u = new User(v[0], v[1], LocalDateTime.now());
    try {
      newUserService.saveNewUser(u);
      logger.info("새 사용자 저장: {}", u);
    } catch (EntityExistsException e) {
      logger.info("사용자가 이미 존재함: {}", u.getEmail());
    }
  }

  private static void handleGet(String line) {
    String email = line.substring(4);
    try {
      User user = getUserService.getUser(email);
      logger.info("사용자 정보: {}", user);
    } catch (NoUserException e) {
      logger.info("사용자가 존재하지 않음: {}", email);
    }
  }

  private static void handleChangeName(String line) {
    String[] v = line.substring(12).split(" ");
    String email = v[0];
    String newName = v[1];
    try {
      changeNameService.changeName(email, newName);
      logger.info("사용자 이름 변경: {}, {}", email, newName);
    } catch (NoUserException e) {
      logger.info("사용자가 존재하지 않음: {}", email);
    }
  }

  private static void handleRemove(String line) {
    String email = line.substring(7);
    try {
      removeUserService.removeUser(email);
      logger.info("사용자 삭제함: {}", email);
    } catch (NoUserException e) {
      logger.info("사용자가 존재하지 않음: {}", email);
    }
  }
}

 

 

 

 

영속 단위 기준으로 초기화

 

 

 

EntityManager로 DB 연동

 

 

 

저장과 쿼리실행 시점

 

 

 

수정과 쿼리 실행 시점

 

 

 

영속 컨텍스트

 

 


EntityManager 단위로 영속 컨텍스트 관리
커밋 시점에 영속 컨텍스트의 변경 내역을 DB에 반영(변경 쿼리 실행)

 

기본 구조
EntityManagerFactory 초기화
DB 작업이 필요할 때마다 EntityManager 생성 , EntityManager로 DB조작 , EntityTransaction으로 트랜잭션 관리
하지만 스프링과 연동할 때는
대부분 스프링이 대신 처리하므로 매핑 설정 중심으로 작업
영속 컨텍스트
엔티티를 메모리에 보관
변경을 추적해서 트랜잭션 커밋 시점에 DB반영

 

 

엔티티 단위 CRUE 처리

 

 

삭제 대상이 존재하지 않으면

 

 
반응형