[JPA] JPA 시작

오현석·2024년 9월 16일

JPA

목록 보기
2/9

JPA 설정 파일은 /META-INF/persistence.xml에 위치해있다. jakarta.persistence로 시작하면 JPA 표준 속성, hibernate로 시작하면 하이버네이트 전용 속성이라고 생각하면 된다.

그러나, Spring에서는 추후에 build.gradle, appliaction.properties 등에서 해당 설정들을 관리하기 때문에 간단하게만 언급하고 넘어가겠다.

Dialect

Dialect는 사투리라는 뜻이다. 이 개념은 무엇일까?

JPA는 특정 데이터베이스에 종속된 기술이 아니다. 또한, 데이터베이스 각각은 제공하는 SQL문법과 함수가 조금씩 다르기 때문에 SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능을 JPA가 인지할 수 있게 설정해두어야 한다. 여기서 나오는 SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능을 Dialect라고 하며, 이를 hibernate.dialect 속성에 지정해줘야 한다. 참고로 하이버네이트는 40개 이상의 Dialect를 지원한다. 각 Dialect를 지정하는 이름은 조금씩 다르다.

MySQL : org.hibernate.dialect.MySQL5InnoDBDialect
Oracle 10g : org.hibernate.dialect.Oracle10gDialect

JPA 구동 방식

설정 정보를 확인 후, EntityManagerFactory라는 객체를 생성하고, 이 EMF는 다시 EntityManager 여러 개를 생성하는 구조이다.
여기서 조심할 점은 EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유하는 방식이라는 점이다.
또한, EntityManager는 쓰레드 간 절대 공유되서는 안되고 사용하고 버려야 하며, 기본적인 사항이지만 모든 데이터 변경은 트랜잭션 안에서 실행되어야 한다. EntityManager는 트랜잭션 단위로 실행하고 트랜잭션이 끝나면 버려야 하는데, 이 EntityManager를 공유해버리면 트랜잭션의 경계가 모호해져 데이터 일관성에 문제가 생길 수 있고 트랜잭션에 문제가 생길 수 있기 때문이다.

일단 Entity부터 생성해보자.

@Entity
// JPA가 관리하는 Entity라는 표시로 JPA를 사용하는 클래스에는 반드시 필요
//@Table(name = "MEMBER")     // 엔티티와 매핑할 테이블을 지정. 생략하면 클래스 이름을 테이블 이름으로 매핑.
// 우리는 지금 테이블 이름이 Member이기 때문에 괜찮지만, Client라는 이름을 가진 테이블인데 객체는 Member라는 이름이라면
//name = "Client"라고 적어주어야 매핑된다.
public class Member {
    @Id
    private long id;
//    @Column(name = "USERNAME")     // 객체 필드를 테이블 컬럼에 매핑. 생략하면 필드명을 사용해서 컬럼명으로 매핑
    //여기도 현재는 필드명이 name이고 테이블 컬럼명도 name이기 때문에 생략했지만,
    // 필드명과 테이블 컬렴명이 다르다면, 테이블 컬렴명을 @Column(name="username")으로 지정해주어야 한다.
    private String name;

    // Getter, Setter...
}

예전에 Entity 설계할 때 헷갈렸던 부분인데, @Table과 Column의 name 속성 잘 숙지하기

또한, Setter를 엔티티에 사용하는 것은 지양해야 한다. 이는 캡슐화 원칙에 어긋나기 때문에 사용하면 안된다. 캡슐화 원칙은 객체의 상태(필드)를 보호하고, 객체 내부에서만 필드의 상태를 변경할 수 있도록 해야 하는 원칙이다. 이 원칙을 지켜야 하는데 Setter를 사용하면 외부에서 객체를 수정할 수 있기 때문에 데이터의 일관성을 해칠 수 있고, 외부에서 필드를 무분별하게 변경할 수 있다. 그래서 Setter의 사용은 최소한으로 하고 비즈니스 로직을 포함하는 의미있는 메소드를 사용해서 값을 수정할 수 있도록 해야 한다. (예를 들어, 공란 핸들링과 같은 예외 처리 기능이 포함된 메소드 등)

이 Setter를 엔티티 코드에서 실수로 안 지웠다가 GDSC 면접에서 면접 질문으로 나왔다는... 앞으로나 항상 주의할 것...

public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        //code
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            //수정의 경우를 예시로 생각해보자. 
            Member findMember = em.find(Member.class, 1L);
            //알아서 수정사항을 감지하기 때문에 따로 persist로 저장할 필요가 없다.
            findMember.setName("HelloJPA");
            tx.commit();
        }catch (Exception e){
            tx.rollback();}
        finally {
            em.close();
            emf.close();
        }

    }
}

다음은 JpaMain의 코드로 JPA를 사용해서 데이터를 수정하는 예시를 보여주는 코드이다. 참고로, 저장의 경우에는 persist, 삭제의 경우에는 remove를 사용하면 된다.

단순 조회의 경우 em.find처럼 사용할 수 있다. 하지만, 내가 원하는 조건에 맞는 엔티티들을 불러오고 싶다면 어떻게 해야할까? 예를 들어, 나이가 22세 이상인 회원들만 불러오고 싶다거나, 이름이 같은 회원들만 보고싶다거나 하는 경우 말이다.

이런 경우를 위해 JPA는 JPQL을 지원한다.

JPQL

JPA를 사용하면 엔티티 객체를 중심으로 개발할 수 있다. 위에서 말한 경우는 검색 쿼리를 날리고 싶을 때이다. 그러나, 모든 DB 데이터를 객체로 변환해서 검색할 순 없기 때문에 결국 SQL이 포함되어야 하는데, 이러면 특정 기술에 종속될 수 있다.

그래서 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다. SQL과 유사한 문법을 가지며, SELECT, FROM, WHERE, JOIN 등등을 지원할 수 있다.

JPQL은 엔티티 객체를 대상으로 쿼리를 보내고, SQL은 DB 테이블을 대상으로 쿼리를 날린다는 것이 큰 차이이다. 이렇게 엔티티 객체를 대상으로 쿼리를 보내는 SQL을 추상화한 기술이기 때문에 특정 DB SQL에 의존하지 않는다.

자세한 내용은 추후에 따로 다룰 예정이다.

profile
Backend Engineer(FastAPI, Spring, ...) / Prompt & Context Engineer / DevOps

0개의 댓글