DB/자바 ORM 표준 JPA 프로그래밍

[2장] JPA 시작

꾸준함. 2025. 2. 21. 01:15

객체 매핑 시작

회원 테이블이 다음과 같이 정의되었다고 가정하겠습니다.

 

CREATE TABLE MEMBER {
ID VARCHAR(255) NOT NULL, -- 아이디 (기본 키)
NAME VARCHAR(255), -- 이름
AGE INTEGER, -- 나이
PRIMARY KEY(ID)
}
view raw .sql hosted with ❤ by GitHub

 

그리고 회원 클래스가 다음과 같이 정의되었다고 가정하겠습니다.

 

@Data
public class Member {
private String id;
private String username;
private Integer age;
}
view raw .java hosted with ❤ by GitHub

 

JPA를 사용하려면 가장 먼저 회원 클래스와 회원 테이블을 매핑해야 하고 이를 위해 회원 클래스에 JPA가 제공하는 매핑 어노테이션을 추가하겠습니다.

 

@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
// 매핑 정보가 없는 필드로 기본 매핑
private Integer age;
}
view raw .java hosted with ❤ by GitHub

 

매핑 정보 회원 객체 회원 테이블
클래스와 테이블 Member MEMBER
기본 키 id ID
필드와 컬럼 username NAME
필드와 컬럼 age AGE

 

부연 설명

  • @Entity: 클래스를 테이블과 매핑한다고 JPA가 알리며 이렇게 @Entity가 사용된 클래스를 엔티티 클래스라고 부름
  • @Table: 엔티티 클래스에 매핑할 테이블 정보를 알려주며 여기서는 name 속성을 사용해서 Member 엔티티를 MEMBER 테이블에 매핑함
  • @Id: 엔티티 클래스의 필드를 테이블의 기본 키에 매핑하며 이렇게 @Id가 사용된 필드를 식별자 필드라고 부름
  • @Column: 필드를 컬럼에 매핑
  • 매핑 정보가 없는 필드: 매핑 어노테이션을 생략하면 필드명을 사용해서 컬러명으로 매핑, 만약 대소문자를 구분하는 데이터베이스를 사용할 경우 명시적으로 @Column(name="AGE")와 같이 매핑해야 함

 

persistence.xml 설정

책이 지필된 시점에는 JPA가 persistence.xml을 사용해서 필요한 설정 정보를 관리했지만 현재는 SpringBoot가 제공하는 Auto-Configuration 기능과 application.yml을 활용하여 JPA, 데이터소스 및 Hibernate 등의 설정을 간편하게 처리할 수 있습니다.

따라서 책에서 소개한 persistence.xml에 정의된 설정을 기반으로 Java Config 클래스로 변환한 예제를 소개하겠습니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="jpabook">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.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.id.new_generator_mappings" value="true" />
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
view raw .xml hosted with ❤ by GitHub

 

 

부연 설명

  • 설정 파일은 persistence로 시작하며 이곳에 XML 네임스페이스와 사용할 버전을 지정
  • JPA 설정은 영속성 유닛 (persistence-unit)이라는 것부터 시작하는데 일반적으로 연결할 데이터베이스 당 하나의 영속성 유닛을 등록하며 영속성 유닛에는 고유한 이름을 부여해야 하는데 여기서는 jpabook이라는 이름을 사용
  • JPA 표준 속성
    • javax.persistence.jdbc.driver: JDBC 드라이버
    • javax.persistence.jdbc.user: 데이터베이스 접속 아이디
    • javax.persistence.jdbc.password: 데이터베이스 접속 비밀번호
    • javax.persistence.jdbc.url: 데이터베이스 접속 URL

 

  • 하이버네이트 속성
    • hibernate.dialect: 데이터베이스 방언 설정

 

package com.tistory.jaimemin.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EntityScan(basePackages = "com.tistory.jaimemin.entity") // 엔티티 패키지 지정
@EnableJpaRepositories(basePackages = "com.tistory.jaimemin.repository") // 레포지토리 패키지 지정
public class JpaConfig {
// DataSource 빈 정의
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:tcp://localhost/~/test");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
// EntityManagerFactory 빈 정의
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emFactoryBean = new LocalContainerEntityManagerFactoryBean();
emFactoryBean.setDataSource(dataSource);
emFactoryBean.setPackagesToScan("com.tistory.jaimemin.entity"); // 엔티티 스캔 경로 지정
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
emFactoryBean.setJpaVendorAdapter(vendorAdapter);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
jpaProperties.put("hibernate.show_sql", "true");
jpaProperties.put("hibernate.format_sql", "true");
jpaProperties.put("hibernate.use_sql_comments", "true");
jpaProperties.put("hibernate.id.new_generator_mappings", "true");
// 필요에 따라 아래 hbm2ddl.auto 옵션 활성화 할 수 있습니다.
// jpaProperties.put("hibernate.hbm2ddl.auto", "create");
emFactoryBean.setJpaProperties(jpaProperties);
emFactoryBean.setPersistenceUnitName("jpabook"); // persistence-unit 이름 설정
return emFactoryBean;
}
// TransactionManager 빈 정의
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
view raw .java hosted with ❤ by GitHub

 

 

1. 데이터베이스 방언

  • JPA는 특정 데이터베이스에 종속적이지 않은 기술이기 때문에 다른 데이터베이스로 손쉽게 교체 가능
  • 그런데 각 데이터베이스가 제공하는 SQL 문법과 함수가 조금씩 다르다는 문제점 존재
    • ex) 가변 문자 타입으로 MySQL은 VARCHAR, ORACLE은 VARCHAR2를 사용
    • ex) 문자열을 자르는 함수로 SQL 표준은 SUBSTRING()을 사용하지만 오라클은 SUBSTR()을 사용

 

  • 이처럼 SQL 표준을 지키지 않거나 특정 DB만의 고유한 기능을 JPA에서는 Dialect (방언)이라고 부름
  • 애플리케이션 개발자가 특정 데이터베이스에 종속되는 기능을 많이 사용하면 나중에 데이터베이스를 교체하기 어려움
    • 하이버네이트를 포함한 대부분의 JPA 구현체들은 이런 문제를 해결하기 위해 다양한 데이터베이스 방언 클래스 제공
    • 개발자는 JPA가 제공하는 표준 문법에 맞추어 JPA를 사용하면 되고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해 주기 때문에 DB가 변경되어도 애플리케이션 코드를 변경할 필요 없이 데이터베이스 방언만 교체하면 됨

 

https://velog.io/@hiyeeluca/JPA-%ED%95%99%EC%8A%B5-2 출처: https://jaimemin.tistory.com/2637 [꾸준함:티스토리]

 

애플리케이션 개발

앞서 객체 매핑을 완료하고 persistence.xml로 JPA 설정도 완료했으므로 JPA 애플리케이션을 시작하는 코드를 작성할 차례입니다.

 

public class JpaMain {
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
emf.close(); //엔티티 매니저 팩토리 종료
}
public static void logic(EntityManager em) {
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("jaimemin");
member.setAge(30);
//등록
em.persist(member);
//수정
member.setAge(20);
//한 건 조회
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
//목록 조회
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
//삭제
em.remove(member);
}
}
view raw .java hosted with ❤ by GitHub

 

 

코드는 다음과 같이 크게 세 부분으로 나뉘어 있습니다.

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

 

1. 엔티티 매니저 설정

  • JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해서 EntityManagerFactory를 생성해야 함
    • 이떄 Persistence 클래스를 사용하는데 해당 클래스는 EntityManagerFactory를 생성해서 JPA를 사용할 수 있게 준비
    • EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); 라인이 동작하면 META-INF/persistence.xml에서 이름이 jpabook인 persistence-unit을 찾아서 EntityManagerFactory를 생성
    • JPA 구현체에 따라서는 데이터베이스 커넥션 풀도 생성하기 때문에 EntityManagerFactory를 생성하는 비용은 비싼 편
    • 이에 따라 EntityManagerFactory는 빈으로 선언하여 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용

 

  • 이후 EntityManagerFactory에서 JPA의 기능 대부분을 제공하는 EntityManager를 생성
    • EntityManager를 사용해서 엔티티를 데이터베이스에 등록, 수정, 삭제 및 조회 가능
    • EntityManager는 내부에 데이터소스를 유지하면서 DB와 통신하므로 가상의 데이터베이스로 생각할 수 있음
    • EntityManager는 데이터베이스 커넥션과 밀접한 관계가 있으므로 쓰레드 간 공유하거나 재사용해서는 안됨

 

  • 마지막으로 사용이 끝난 EntityManager는 close() 메서드를 통해 반드시 종료해야 함
  • 애플리케이션을 종료할 때는 EntityManagerFactory를 close() 메서드를 통해 종료해야 함

 

2. 트랜잭션 관리

  • JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 함
    • 트랜잭션을 시작하려면 EntityManager에서 트랜잭션 API를 받아와야 함
    • 트랜잭션 없이 데이터 변경 시도하면 예외 발생

 

try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
view raw .java hosted with ❤ by GitHub

 

 

3. 비즈니스 로직

  • 코드에서 비즈니스 로직을 보면 등록, 수정, 삭제, 조회 작업이 EntityManager를 통해 수행되는 것을 확인 가능
    • EntityManager는 객체를 저장하는 가상의 데이터베이스처럼 보임
    • 등록: 엔티티를 저장하려면 EntityManager의 persist() 메서드에 저장할 에티티를 넘겨주면 되며 JPA는 회원 엔티티의 매핑 정보를 분석해서 INSERT문을 만들어 데이터베이스에 전달
    • 수정: 엔티티를 수정한 후에 수정 내용을 반영하려면 em.update() 같은 메서드를 호출해야 할 것 같은데 단순히 엔티티의 값만 변경한 것을 확인 가능, JPA는 더티 체킹을 통해 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있어 트랜잭션 내 값을 변경할 경우 UPDATE SQL을 생성해서 데이터베이승에 값을 변경
    • 삭제: 엔티티를 삭제하려면 EntityManager의 remove() 메서드에 삭제하려는 엔티티를 넘겨주면 되며 JPA는 DELETE SQL을 생성해서 실행
    • 한건 조회: find() 메서드는 조회할 엔티티 타입과 @Id로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 가장 단순한 조회 메서드이며 호출 시 SELECT SQL을 생성해서 데이터베이스에 결과를 조회

 

//등록
em.persist(member);
//수정
member.setAge(20);
//한 건 조회
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
//삭제
em.remove(member);
view raw .java hosted with ❤ by GitHub

 

 

4. JPQL

예제에서 하나 이상의 회원 목록을 조회하는 코드는 다음과 같습니다.


//목록 조회
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
view raw .java hosted with ❤ by GitHub

 

JPA.는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 합니다.

  • 그런데 테이블이 아닌 엔티티 객체를 대상으로 검색하기 위해서는 DB의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경한 다음 검색해야 하는데, 이는 사실상 불가능
  • JPA는 JPQL (Java Persistence Query Lanaguage)이라는 쿼리 언어로 이런 문제를 해결
  • JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어를 제공
  • JPQL은 엔티티 객체를 대상으로 쿼리 하는 반면 SQL은 데이터베이스 테이블을 대상으로 쿼리 하는 차이점이 존재하지만 문법 자체는 거의 유사해서 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등을 사용 가능
  • 위 코드에서 `select m from Member m`이 바로 JPQL이며 여기서 from Member는 회원 엔티티 객체를 말하는 것이지 MEMBER 테이블이 아님
  • JPQL을 사용하려면 먼저 em.createQuery(JPQL, 반환타입) 메서드를 실행해서 쿼리 객체를 생성한 뒤 쿼리 객체의 getResultList() 메서드를 호출하면 됨

 

참고

자바 ORM 표준 JPA 프로그래밍 - 김영한 저

반응형

'DB > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

[6장] 다양한 연관관계 매핑  (0) 2025.02.27
[5장] 연관관계 매핑 기초  (1) 2025.02.24
[4장] 엔티티 매핑  (0) 2025.02.21
[3장] 영속성 관리  (0) 2025.02.21
[1장] JPA 소개  (0) 2025.02.18