[JPA]기초 사용 - 애플리케이션 개발

Inung_92·2023년 10월 19일
1

JPA

목록 보기
2/7
post-thumbnail

해당 포스팅의 내용은 김영한 강사님의 자바 ORM 표준 JPA 프로그래밍 책의 내용을 정리하여 작성하였습니다.


포스팅 목적

📖JPA를 사용해서 객체와 테이블을 매핑하고, 매핑 시 필요한 정보를 설정하는 방법에 대해서 알아보자.

애플케이션 개발

객체 매핑

객체 매핑은 엔티티 설계가 완료되고 설계된 엔티티를 객체로 정의하여 각 컬럼 및 제약조건 등을 매핑해주는 것을 의미한다.

테이블 생성

CREATE TABLE MEMBER(
	ID VARCHAR(255) NOT NULL, --ID
	NAME VARCHAR(255),        --이름
	AGE INTEGER,              --나이
	PRIMARY KEY (ID)
);

클래스 정의

public class Member {
	private String id;
	private String userName;
	private int age;

	...// getter, setter
}

테이블과 클래스를 매핑해주기 위해서는 어노테이션을 사용하여 매핑을 해준다. 아래 코드를 통해 알아보자.

매핑

// 어노테이션 패키지
import javax.persistence.*;

@Entity
@Table(name = "MEMBER")
public class Member{
	@Id
	@Column(name = "ID")
	private String id;
	@Column(name = "NAME")
	private String userName;
	// 매핑 정보가 없음
	private int age;

	... // getter, setter
}

위 코드에서 사용된 어노테이션에 대한 설명은 다음과 같다.

  • @Entity : Entity 클래스로서 DB에 존재하는 테이블과 매핑한다는 사실을 JPA에 전달한다.
  • @Table : Entity 클래스가 매핑 할 테이블의 정보를 알려준다. 기본으로 클래스명과 테이블 이름을 매핑하지만 조금 더 정확히 하기 위하여 name 속성을 사용한다.
  • @Id : 해당 필드를 테이블의 기본키에 매핑한다. @Id 가 사용된 필드를 식별자 필드라고 한다.
  • @Column : 클래스의 필드를 컬럼에 매핑한다. name 등의 다양한 속성이 있으며 해당 어노테이션을 명시하지 않는 경우 필드명과 컬럼명을 JPA가 자동으로 매핑해준다. age 의 경우가 자동으로 매핑된 경우이다.

추가적으로 궁금한 내용은 위 코드의 어노테이션 패키지를 참고하자.

기본 사용

객체 매핑을 통해 기본적인 준비는 완료되었으니 EntityManagerTransaction 을 통해 JPA를 사용하는 기본적인 방법에 대해서 알아보자.

persistence.xml

코드 작성에 앞서 Spring이 아닌 Java Project에서 JPA 사용을 위해 설정파일을 작성하자. 해당 내용은 복사해서 필요한 정보만 교체해서 사용하고, 설명은 생략하겠다.

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

        <!-- 클래스 매핑 -->
        <class>org.example.domain.example.TestMember</class>
        <!-- /.클래스 매핑 -->

        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/jpabook"/>
            <property name="javax.persistence.jdbc.user" value="test"/>
            <property name="javax.persistence.jdbc.password" value="1234"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>

            <!-- 옵션 속성 -->
            <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"/>
            <!-- DDL 생성전략 -->
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

코드

...// 패키지 생략

public class JpaMain{
	// persistence.xml에서 등록한 unit명을 인자로 전달
	// Factory 생성
	EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpabook");
	// Entity 매니저 생성
	EntityManager entityManager = factory.createEntityManager();
	// Transaction 생성
	EntityTransaction transaction = entityManager.getTransaction();
	
	public static void main(String[] args) {
		try{
			transaction.begin();
			...// 비즈니스 로직
			transaction.commit();
		} catch (Exception e) {
			transaction.rollback();
		} finally {
			entityManager.close(); // Entity 매니저 종료
		}
		factory.close(); // Factory 종료
	}
}

위 코드는 3가지의 파트로 구분이 된다.

  • EntityManager 설정
  • Transaction 관리
  • 비즈니스 로직

각 파트에 대한 내용을 차근차근 알아보자.

EntityManager 설정

EntityManager는 persistence.xml에 설정된 정보를 Persistence 객체가 조회해서 EntityManagerFactory를 통해 생성하게 된다. 아래 그림을 참고하자.

그렇다면 EntityManagerFactory와 EntityManager에 대해서 알아보자.

  • EntityManagerFactory : 설정 정보에 등록된 영속성 유닛을 찾아서 해당 정보를 통해 생성된다. 생성 시 DB의 커넥션 풀과 JPA 동작을 위한 기반 객체를 생성하기 때문에 비용이 매우 크다. 이러한 이유로 애플리케이션 전체에서 딱 한번만 생성해서 공유하여 사용해야한다.
  • EntityManager : EntityManagerFactory를 통해 생성된다. DB의 CRUD 기능을 사용 할 수 있으며, DB 커넥션을 유지하면서 통신한다. 비유하자면 가상의 데이터베이스를 생성한다고 생각 할 수 있다.

여기서 주의할 점은 EntityManager는 DB의 커넥션과 직접적으로 관계가 있기 때문에 스레드간에 공유 및 재사용해서는 안된다. EntityManagerFactory의 경우는 여러 스레드가 동시 접속해도 안전하기 때문에 스레드 간에 공유를 해도 되지만 EntityManager동시성 문제가 발생하므로 절대 공유해서는 안된다.

위 그림을 보면 EntityManager12Transaction이 발생 할 때 커넥션을 얻는다. 이러한 특성을 고려했을 때 여러 스레드에서 동시에 동일한 EntityManager에 접속한다면 커넥션을 얻어오는 시점 등에 대한 문제가 발생하기 때문이다. 또한, 사용이 완료된 자원은 close() 를 호출하여 반드시 종료하여 자원의 낭비를 방지해야한다.

Transaction 관리

JPA뿐만 아니라 DB를 연동하여 데이터를 변경 할 경우에는 Transaction을 항상 중요하게 생각해야한다. Transaction은 논리적인 작업의 한 단위이기 때문에 그 범위안에서 정상적인 작동 및 오류 발생 시 처리해야하는 기준 및 방법을 명확하게 해야한다.

  • begin() : Transaction을 시작하는 것을 의미한다. 해당 메소드 이후의 비즈니스 로직은 Transation의 범위에서 수행된다고 보면된다.
  • commit() : begin() 이후 수행된 작업에 대한 확정을 짓는다. commit() 을 호출하지 않을 경우 이전의 작업은 무효처리가 된다.
  • rollback() : 작업 수행 중 오류가 발생했을 때 Transaction을 시작하기 전으로 돌아간다. 예를 들어 Transaction 내에서 처리해야 할 작업 10번 중에 8번째에서 오류가 발생하면 1~7번 작업도 없던 것으로하고 이전 상태로 돌리는 것이다.

비즈니스 로직

비즈니스 로직은 실제로 수행되는 작업을 코드로 작성한 것이다. EntityManager를 활용하여 작성된 비즈니스 로직을 아래 코드로 확인해보자.

...생략
public void logic(EntityManager entityManager){
	String id = "id1";
	Member member = new Member();
	member.setId(id);
	member.setName("test");
	member.setAge(20);
	
	// 엔티티 등록
	entityManager.persist(member);
	// 엔티티 수정
	member.setAge(30);
	
	// 엔티티 조회
	Member findMember = entityManager.find(Member.class, id);
	System.out.println("findMember name ? " + findMember.getName());
	
	// 엔티티 삭제
	entityManager.remove(member);
}
...

위 코드에서 알 수 있듯이 EntityManager를 통해 CRUD가 이루어지고 있다.

수행된 각 메소드에 대해서 간단하게 알아보자.

  • persist() : DB의 Insert문과 관련된 메소드이다. 생성된 엔티티를 저장하는 역할을 한다.
  • set() : DB의 Update문과 관련된 메소드이다. 등록된 엔티티의 정보를 수정하는 역할을 한다. 여기서 set()은 객체가 보유한 setter 를 통칭해서 설명하는 것이다. JPA에는 별도로 update()와 같은 함수는 없다. 이것은 영속성 컨텍스트의 개념을 이해해야하니 뒤에서 설명하도록 하자.
  • find() : DB의 Select문과 관련된 메소드로 조회하는 역할을 한다.
  • remove() : DB의 Delete문과 관련된 메소드로 엔티티를 삭제하는 역할을 한다.

그렇다면 하나 이상의 엔티티를 조회하기 위해서는 어떻게 해야할까?

JPQL

EntityManager에 정해진 메소드 이외의 쿼리가 필요한 경우 JPQL을 사용한다. 그렇다면 JPQL이 뭔지 알아보자.

JPQL은 Java Persistence Query Language 로 SQL을 추상화한 객체지향 쿼리언어이다. 실질적으로 모든 엔티티를 호출해서 검색하는 것은 메모리 용량등의 문제로 불가능하기 때문에 필요한 데이터만 DB에서 조회하기 위해서는 검색 조건이 포함된 쿼리를 사용해야한다. 그 역할을 JPQL이 해주는 것이다.

JPQL은 테이블이 아닌 Entity 객체를 대상으로 쿼리한다. 즉, 클래스와 필드를 대상으로 하는 것이다. 형태는 다음과 같다.

// 쿼리 생성
String select = "select m from Member m";
TypeQuery<Member> query = entityManager.createQuery(select, Member.class);

// 목록 조회
List<Member> members = query.getResultList();

위 쿼리문에서 MemberMEMBER 테이블 이 아니다. Entity 클래스를 의미하는 것이니 혼동해서는 안된다. JPQL은 테이블을 대상으로 쿼리 할 수 없고, 대소문자 구분이 명확하다는 것을 인지하자.


마무리

Springboot를 사용하면서 어중간하게 검색을 통해서만 사용해 본 JPA를 동작 원리부터 알아가며 사용하니 조금 더 익숙하게 다가오는 것을 느낄 수 있었다.

기초 개념부터 활용하는 방법까지 지속적으로 습득해서 웹 애플리케이션 개발 시 실제로 사용하면서 시행착오를 겪으면 더욱 좋을 것 같다.

그럼 이만.👊🏽

profile
서핑하는 개발자🏄🏽

0개의 댓글