반응형

 

도서 '자바 ORM 표준 JPA 프로그래밍' 을 보고 책 내용과 그 이외의 부족한 부분을 채워가며 공부한 내용입니다.

 


키워드 : JPA, ORM, SQL


1. JPA(Java Persistence API)

ORM(Object Relational Mapping)

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

 

1. SQL을 직접 다룰 때 발생하는 문제점

  • 데이터베이스에 데이터를 관리 -> SQL 사용 필수
  • 자바로 작성한 애플리케이션 -> JDBC API를 사용해서 SQL을 데이터베이스에 전달

 

DAO(데이터 접근 객체)

 

보통 회원 조회하는 기능을 개발시

1. SQL문 작성
String sql = "SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?"

2. JDBC API를 사용해서 SQL 실행
ResultSet rs = stmt.executeQuery(sql);

3. 조회 결과를 Member 객체로 매핑
String memberId = rs.getStirng("MEMBER_ID");
String name = rs.getString("NAME");

Member member = new Member();
member.setMemberId(memberId);
member.setName(name);

 

  • SQL에 의존적인 개발(Member 객체에 특정 속성을 추가하게 되면 데이터베이스에도 저장하기 위해 SQL과 JDBC API도 수정해야함)
  • 데이터 접근 계층을 사용해서 SQL을 숨겨도 DAO를 열어서 어떤 SQL이 실행되고 있는지를 확인할 필요가 있음

 

해당 방식의 문제점

  1. 진정한 의미의 계층 분할이 어려움
  2. 엔티티를 신뢰할 수 없음
  3. SQL에 의존적인 개발을 피하기 어려움

 

 

※ Entity

위 예시의 Member처럼 비즈니스 요구사항을 모델링한 객체를 Entity라 부름

 

JPA

자바 진영의 ORM(객체와 관계형 데이터베이스를 매핑) 기술 표준

 

JPA 표준 인터페이스

하이버네이트가 가장 대중적
  • Hibernate
  • EclipseLink
  • DataNucleus

 

JPA가 제공하는 CRUD API(가볍게 맛보기)

1. 저장기능

jpa.persist(member);    //저장
  • persist

 

2. 조회기능

Stirng memberId = "helloId";
Member member = jpa.find(Member.class, memberId);  //조회
  • find

 

3. 수정기능

Member member = jpa.find(Member.class, memberId);
member.setName("이름변경");   //수정
  • 객체를 조회해서 값을 변경하면 트랜잭션을 커밋할 때 데이터베이스에 적절한 UPDATE SQL이 전달

 

4. 연관된 객체 조회

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam():  //연관된 객체 조회
  • JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL 실행

 

JPA를 사용하는 이유

  • 생산성
    • 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 됨
    • 지루하고 반복적인 코드와 CRUD용 SQL을 개발자가 직접 작성하지 않아도 됨
  • 유지보수
    • SQL을 직접 다루면 엔티티에 필드를 하나만 추가해도 관련된 등록, 수정 조회 SQL 결과를 매핑하기 위한 JDBC API 코드를 모두 변경해야 함
    • JPA는 이를 대신해줌
  • 패러다임 불일치 해결
  • 성능
    • JPA를 사용하면 회원을 조회하는 SELECT SQL을 한번만 데이터베이스에 전달하고 2번째는 조회한 회원의 객체에서 재사용
  • 데이터 접근 추상화와 벤더 독립성
    • 관계형 데이터베이스 -> 같은 기능도 벤더마다 사용법이 다른 경우가 많음
    • JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 함

 

 

 

2. 패더다임의 불일치

객체 = 속성(필드) + 기능(메서드)

 

직열화 기능

  • 객체를 파일로 저장
  • 상속받았거나 다른 객체를 참조하고 있는 경우 객체의 상태를 저장하기 쉽지 않음
    • 객체 저장시 해당객체 + 상속받은 or 참조하고 있는 객체를 함께 저장해야 해당 객체에 관련된 정보를 잃어버리지 않음.
  • 직열화된 객체는 검색하기 어렵다는 문제 발생
  • ↔역직열화(저장된 파일을 객체로 복구)

 

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

  • 객체와 비교했을 때 지향하는 목적이 서로 다르기에 표현방법도 달라 패러다임이 불일치
  • SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐
    • ex) member.getOrder().getOrderItem()... // 자유로운 객체 그래프 탐색
    • SQL을 직접 다루면 위처럼 자유로운 객체 그래프 탐색이 힘듬
      • 객체지향 개발자에겐 큰 제약
    • JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행
      • 지연로딩을 통해 자유로운 객체 그래프 탐색 가능
        • 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룸
        • 사용되는 시점에 연관된 객체를 조회할지 즉시 함께 조회할지는 간단한 설정을 통해 정의 가능

 

 

객체

참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회

관계형 데이터 베이스

외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회

 

비교

  • 객체
    • 동일성 비교
      • == 비교
      • 객체 인스턴스의 주소값을 비교
    • 동등성 비교
      • equals() 메서드를 사용해서 객체 내부 값을 비교
  • 데이터 베이스
    • 기본키 값으로 각 로우(row)를 구분

 

3.JPA 연결

<MEMBER.sql>

CREATE TABLE MEMBER(
	ID VARCHAR(255) NOT NULL,
	NAME VARCHAR(255), 
	AGE INTEGER,   
	PRIMARY KEY(ID)
)

<Member.java>

@Data
@Entity
@Table(name="member")
public class Member {
	
	@Id
	@Column(name = "ID")
	private String id;
	
	@Column(name = "NAME")
	private String username;
	
	private Integer age;
	
	//게터와 세터는 롬복을 통해 @Data 어노테이션으로 생성
}

 

어노테이션

JPA 어노테이션의 패키지는 javax.persistence
  • @Entity
    • 해당 클래스를 테이블과 매핑한다고 JPA에 알려줌
    • @Entity가 사용된 클래스를 엔티티 클래스라 함
  • @Table
    • 엔티티 클래스에 매핑할 테이블 정보를 알려줌
  • @Id
    • 엔티티 클래스의 필드를 테이블의 기본 키에 매핑
  • @Column
    • 필드를 컬럼에 매핑
    • name 속성을 이용해 Member 엔티티 username필드를 MEMBER 테이블의 NAME 컬럼에 매핑

 

※ 데이터베이스 방언

SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서 방언(Dialect)라 부름
  • 개발자는 JPA가 제공하는 표준 문버에 맞추어 JPA를 사용하면 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리

<persistence.xml>

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">

    <persistence-unit name="jpa_Test">

        <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>

 

 

<어플리케이션 개발>

public class JpaTestApplication {

	public static void main(String[] args) {
		
		//엔티티 매니저 팩토리 - 생성
		EntityManagerFactory emf =
				Persistence.createEntityManagerFactory("jpa_Test");
		
		//엔티티 매니저 - 생성
		EntityManager em = emf.createEntityManager();
		
		//트랜잭션 - 획득
		EntityTransaction tx = em.getTransaction();
		
		try {
			tx.begin(); //트랜잭션 - 시작
			logic(em); //비즈니스 로직 실행
			tx.commit(); //트랜잭션 - 커밋
		} catch(Exception e) {
			tx.rollback();  //트랜잭션 - 롤백
		} finally {
			em.close();  //엔티티 매니저 - 종료
		}
		
		emf.close(); //엔티티 매니저 팩토리 - 종료
	}
}

 

  • 엔티티 매니저 팩토리 생성
    • JPA 시작 -> persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리 생성
      • 이때 Persistence 클래스 사용 -> 엔티티 매니저 팩토리를 생성해 JPA를 사용할 수 있게 준비
      • persistence.xml의 설정 정보를 읽어 JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체에 따라서 데이터베이스 커넥션 풀도 생성
      • 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한번만 생성하고 공유해서 사용
  • 엔티티 매니저 생성
    • JPA의 기능 대부분은 엔티티 매니저가 제공
    • 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회 가능
    • 엔티티 매니저는 내부에 데이터베이스 커넥션을 유지하면서 데이터베이스와 통신
    • 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드 간에 공유하거나 재사용하면 X
  • 종료
    • 사용이 끝난 엔티티 매니저는 반드시 종료
    • 애플리케이션을 종료할 때 엔티티 매니저 팩토리도 종료
  • 트랜잭션 관리
    • JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경
    • 트랜잭션 없이 데이터를 변경하면 예외가 발생
    • 엔티티 매니저에서 트랜잭션 API를 받아와야 함

JPA를 이용한 CRUD

  • 등록, 수정, 삭제, 조회 작업은 엔티티 매니저를 통해서 수행

1. Create

em.persist(member);
  • 엔티티를 저장하려면 엔티티 매니저의 persist() 메서드에 저장할 엔티티를 넘겨주면 됨

 

2. Read

//1개 조회
Member findMember = em.find(Member.class, 1L);
//목록 조회 (JPQL)
List<Member> result = em.createQuery("select m from Member as m", Member.class) .getResultList();
  • find()를 이용하면 SELECT SQL을 생성하고 실행
  • 필요한 데이터만 데이터베이스에서 불러오려면 결구 검색 조건이 포함된 SQL을 사용
    • JPA는 JPQL(Java Persistence Query Language) 제공

 

※ SQL vs JPQL

  • SQL은 데이터베이스 테이블을 대상으로 쿼리
  • JPQL은 엔티티 객체를 대상으로 쿼리
    • 클래스와 필드를 대상으로 쿼리 
    • from Member는 회원 엔티티 객체를 뜻한다. JPQL은 데이터베이스 테이블을 전혀 알지 못함

 

3. Update

updateMember.setName("helloJPA");
  • 단순히 엔티티의 값만 변경하면 JPA가 어떤 엔티티가 변경되었는지 추적하여서 UPDATE SQL을 생성하고 실행

 

4. Delete

em.remove(deleteMember);
  • DELETE SQL을 생성해서 실행

 

 

 

 

 

 


참고 자료

1. 김영한, 자바 ORM 표준 JPA 프로그래밍(출판지 : 에이콘, 2015)

반응형

'JPA' 카테고리의 다른 글

JPA - 영속성 관리  (0) 2022.06.02

+ Recent posts