관리 메뉴

나구리의 개발공부기록

JPA 소개, SQL 중심적인 개발의 문제점, JPA 소개, JPA 시작하기, 프로젝트 생성, 애플리케이션 개발 본문

인프런 - 스프링부트와 JPA실무 로드맵/자바 ORM 표준 JPA 프로그래밍 - 기본편

JPA 소개, SQL 중심적인 개발의 문제점, JPA 소개, JPA 시작하기, 프로젝트 생성, 애플리케이션 개발

소소한나구리 2024. 10. 4. 13:44

출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편(유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. SQL 중심적인 개발의 문제점 및 소개


2. 프로젝트 생성 - HelloJPA

** 참고

  • 실무에서는 프로젝트를 띄울 때 스프링과 통합해서 사용하다보니(스프링부트로 프로젝트를 생성) 스프링이 버전관리를 해주지 않는 라이브러리들의 버전은 사용할 스프링부트 버전의 doc에서 Dependency Versions에 들어가서 버전을 맞춰서 설치
  • Dependency Versions -> Managed Dependency Coordinates에 들어가면 확인할 수 있음
  • https://spring.io/projects/spring-boot#learn

1) Spring 통합 없이 순수하게 JPA만 세팅하기

  • 2019년 강의로 이번 실습은 maven으로 실습하나 현재 추세는 gradle로 프로젝트를 생성함
  • 의존성이 많아지면 가독성이 떨어지는 xml로 작성하는 maven에 비해 gradle은 그루비라는 프로그래밍 언어로 작성하여 가독성이 좋고 구동 속도도 조금 빠르다고 함
  • 실무에서는 maven 프로젝트가 아직 많이 남아있어서 둘다 다룰줄 알면 도움이 됨 

(1) 프로젝트 생성 - pom.xml 설정

  • 강의에서 제공하는 프로젝트로 진행
  • java 17
  • maven 1.1.0
  • hibernate 6.4.2
  • jakarta.xml.binding api 4.0.1 -> 기존 javax 패키지를 jakarta로 변경(오라클과의 자바 라이선스 문제)
  • h2database 2.2.224 -> 사이트에서 다운로드 진행
<?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>ex1-hello-jpa</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- JPA 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.4.2.Final</version>
        </dependency>

        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>4.0.1</version>
        </dependency>


        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
        </dependency>

        <!-- logback -->
<!--
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.14</version>
        </dependency>
-->
    </dependencies>

</project>

 

(2) JPA설정 - persistence.xml

  • ~/main/resources/META-INF/persistence.xml에 위치해야함
  • persistence-unit name 으로 이름을 지정
  • jakarta.persistence로 시작하는 옵션
    • JPA 표준 속성(driver, user, password, url 정보 입력)
  • hibernate로 시작하는 옵션: 하이버네이트 전용 속성
    • show_sql: 실행시 JPA가 작성한 SQL 보여주기
    • hdm2ddl.auto value="create": 실행할 때마다 테이블을 새로 생성
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="jakarta.persistence.jdbc.user" value="sa"/>
            <property name="jakarta.persistence.jdbc.password" value=""/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments"  value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>

</persistence>

2) 데이터베이스 방언

  • JPA는 특정 데이터베이스에 종속적이지 않게 설계되어 있는데, 각각의 데이터베이스가 제공하는 SQL 문법과 함수가 조금씩 다른 것을 JPA 입장에서는 방언이라고 표현함
  • 방언은 SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능이라고 보면 됨 
    • 가변문자: MySQL - VARCHAR, Oracle - VARCHAR2
    • 문자열 자르는 함수: SQL 표준 - SUBSTRING(), Oracle - SUBSTR()
    • 페이징: MySQL - LIMIT, Oracle - ROWNUM
    • 등등..
  • 이렇게 각각의 DB(약 40가지의 실무에서 사용하는 DB)마다 다른 방언을 JPA가 매핑을 해두었기 때문에 hibernate.dialect 옵션에 입력만 해주면 어떤 DB를 사용하더라도 JPA를 사용할 수 있음
  • 아래처럼 설정의 DB Dialect 정보만 변경해주면 됨(IDE의 자동 완성 도움을 받을 수 있음)
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!-- H2 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/> <!-- 오라클 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <!-- MySQL -->
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> <!-- PostgreSQL -->
등등

3. 애플리케이션 개발 - HelloJPA

1) JPA 구동방식

  1. Persistence 클래스에서 persistence.xml에 작성한 설정 정보를 조회
  2. EntityManagerFactory라는 클래스를 생성
  3. 이후에 필요할 때마다 EntityManager를 생성해서 사용

2) 코드 작성

  • 다운받은 h2 데이터베이스를 실행 후(mac의 경우 ~H2/bin/h2.sh 파일) 작성했던 persistence.xml의 속성정보와 일치하도록 입력 후 연결하면 접속됨

(1) JpaMain

package hellojpa;

public class JpaMain {

    public static void main(String[] args) {

        // EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        // EntityManager 생성
        EntityManager em = emf.createEntityManager();
        
        em.close();
        emf.close();
    }
}

 

(2) 실행 결과

  • JpaMain의 실행 결과가 아래처럼 보이면 정상적으로 동작이 된 것

실행 결과

(2) Member - Entity 생성

  • 자바 클래스로 table을 생성하는 SQL을 작성한 것처럼 table이 생성
  • name은 varchar(255), id는 bigint, 프라이머리키는 id로 적용
package hellojpa;

@Entity
public class Member {

    @Id
    private Long id;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

 

(3) 저장해보기 - JpaMain 코드 추가

  • JPA에서 transaction이라는 단위가 매우 중요한데, 모든 데이터를 변경하는 작업은 transaction 안에서 해야하므로 트랜잭션을 얻고 시작후 실제 DB에 반영하기 위해서는 작업 후에 commit or rollback을꼭 해줘야 함
  • 성공하면 commit, 실패하면 rollback, 작업 후에는 EntityManager를 종료하도록 하는 것이 정석적인 JPA 사용법이나 스프링과 통합하여 사용하면 이러한 코드들을 전부 생략해서 간편히 작성할 수 있음
package hellojpa;

public class JpaMain {
    public static void main(String[] args) {

        // ... 기존 코드 생략

        // 트랜잭션 얻고 시작
        EntityTransaction tx = em.getTransaction();
        tx.begin();
		
        // 트랜잭션 얻고 시작
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");
            em.persist(member); // 저장
            tx.commit();        // 커밋 - 실제 db에 반영
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
        
        }
    }
}

실행결과

(4) 조회, 수정, 삭제

  • 조회: em.find로 id값이 1인 Member 데이터를 조회 -> 조회는 commit 필요 없음
  • 삭제: 조회된 회원을 em.remove()에 아규먼트 값으로 입력후 commit 하면 회원데이터를 삭제
  • 수정: 조회된 회원에 변경하고자 하는 컬럼의 값을 수정후 commit하면 DB에 반영됨
  • 마치 자바 컬렉션을 이용하는 것처럼 개발자가 코드를 사용하여 DB의 데이터를 CRUD 할 수 있음
  • 특히 수정의 경우에는 저장에 했던 것처럼 persist()를 해야할 것 같지만 JPA가 동작할 때 내부에서 변경이 일어났는지, 트랜잭션이 신규인지 등의 여부를 체크를 하고 이에 맞춰서 변경이 일어났으면 UPDATE 쿼리를 만들어서 날림
// ... 코드 생략
        try {
            /* 단건 조회 */
            Member findMember = em.find(Member.class, 1L);
            System.out.println("findMember.getId() = " + findMember.getId());
            System.out.println("findMember.getName() = " + findMember.getName());
            
            /* 삭제 */
            em.remove(findMember);
            tx.commit();
            
            /* 수정 */
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("HelloJPA");
            tx.commit();
        } catch (Exception e) {
        
// ... 코드 생략

좌) 조회 실행결과 / 우) 수정 실행결과와 DB에 수정된 사항이 반영된 내역

(5) 주의

  • EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유함
  • EntityManager는 쓰레드간에 공유하면 안됨(사용하고 버려야 함)
  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행됨

3) JPQL 소개

  • JPA를 사용하면 엔터티 객체를 중심으로 개발하는데 검색 쿼리에 대한 부분의 문제를 JPQL로 해결
  • 테이블이 아닌 Entity를 대상으로 작성하는 쿼리 -> SQL을 추상화한 객체 지향 쿼리라고 보면 됨
  • DB에 종속적인 SQL을 날리는 것이 아니기 때문에 만약 페이지네이션을 적용해두었다면 DB가 바뀌더라도 라이브러리 방언세팅을 바뀐 DB로 설정하면 해당 DB에서 사용하는 SQL 쿼리가 날라감
  • 해당 쿼리에 대한 내용은 뒤에서 자세하게 다룸
/* JPQL 페이지네이션 예시*/
List<Member> result = em.createQuery("select m from Member m", Member.class)
        .setFirstResult(4)  // index번호 4번째 결과부터
        .setMaxResults(8)   // 8개 출력
        .getResultList();

좌) H2DB 방언 설정 시 SQL쿼리 / 우) MySQL 방언 설정 시 SQL쿼리