카테고리 없음

JPA란 무엇인가?

연향동큰손 2025. 2. 10. 00:08

기존의 데이터베이스 문제점

 

1. Jdbc나 MyBatis,SQL Mapper는 SQL을 개발자가 직접 작성해야 한다.

 

 

2. 객체 - 관계의 불일치 

 

  • 데이터 저장 방식의 차이
    • 객체는 필드(속성) + 메서드(행위) 를 가지지만, 관계형 데이터베이스는 테이블(행, 열) 구조를 가짐.
  • 연관관계 표현의 차이
    • 객체는 참조(Reference) 를 통해 관계를 표현하지만, RDBMS는 외래키(Foreign Key) 를 사용하여 관계를 표현.
    • 이를 수동으로 처리하면 조인(Join)과 SQL 작성이 복잡해짐.
  • 상속 구조의 차이
    • Java는 클래스 상속(Inheritance) 을 지원하지만, 관계형 데이터베이스는 직접적인 상속 개념이 없음.
    • 상속을 표현하기 위해 JOIN 테이블을 사용하거나 단일 테이블 전략 등을 적용해야 함.

 

이러한 문제를 JPA는 획기적으로 해결해준다.

 

JPA란?

 

JPA는 ORM(Object-Relational Mapping) 데이터 접근 기술을 제공하는 자바 엔터프라이즈 시장의 주력 기술이다.

 

여기서 ORM이란 객체와 데이터베이스를 자동으로 매핑 시켜주는 기술이다.

 

JPA 장점

  • SQL을 개발자가 직접 작성하지 않아도 JPA가 대신 작성하고 처리해준다.
  • 복잡하고 어려운 SQL이 아닌 객체(Entity)를 중심으로 데이터를 조작할 수 있기 때문에 유지보수가 쉬워진다.
  • SQL을 직접 작성하지 않기 때문에 DBMS에 종속되지 않고 코드를 작성 할 수 있다.

 

 

JPA 사용해보기

 

<의존관계 추가>

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

 

<application.properties>

#JPA log
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE

 

위 코드를 통해 하이버네이트가 생성하고 실행하는 SQL을 확인할 수 있다.

 

 

<Item 도메인>

package hello.itemservice.domain;

import lombok.Data;

import javax.persistence.*;


@Data
@Entity
public class Item {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // PK를 DB에서 값을 넣어줌
    private Long id;

    @Column(name="item_name", length=10)
    private String itemName;
    private Integer price;
    private Integer quantity;

    public Item() { //JPA는 기본 생성자가 필수이다!!!
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

@Entity : JPA가 사용하는 객체라는 뜻이다. 해당 애노테이션이 붙은 객체는 관계형 데이터베이스에서 엔티티로 정의된다.

 

@Id : 테이블의 기본키(PK)에 해당

 

@GeneratedValue(strategy = GenerationType.IDENTITY) : PK 생성 값을 데이터베이스에서 생성하는 IDENTITY 방식을 사용한다.

 

@Column : 객체의 필드를 테이블의 컬럼과 매핑한다.

 

@Column을 생략하면 필드의 이름이 테이블 컬럼 이름으로 사용된다. (만약 스프링 부트와 통합해서 사용할 경우 카멜 케이스를 테이블 컬럼의 언더스코어로 자동 변환해줌)

 

 

<JpaItemRepository>

package hello.itemservice.repository.jpa;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Optional;

@Slf4j
@Repository
@Transactional //일반적으로는 서비스 계층에 들어가야함!!!
public class JpaItemRepository implements ItemRepository {

    private final EntityManager em;

    public JpaItemRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Item save(Item item) {
        em.persist(item);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findItem = em.find(Item.class, itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    @Override
    public Optional<Item> findById(Long id) {
        Item item = em.find(Item.class, id);
        return Optional.ofNullable(item);
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String jpql = "select i from Item i";
        Integer maxPrice = cond.getMaxPrice();
        String itemName = cond.getItemName();
        if (StringUtils.hasText(itemName) || maxPrice != null) {
            jpql += " where";
        }
        boolean andFlag = false;
        if (StringUtils.hasText(itemName)) {
            jpql
                    += " i.itemName like concat('%',:itemName,'%')";
            andFlag = true;
        }
        if (maxPrice != null) {
            if (andFlag) {
                jpql += " and";
            }
            jpql += " i.price <= :maxPrice";
        }

        log.info("jpql={}", jpql);
        TypedQuery<Item> query = em.createQuery(jpql, Item.class);
        if (StringUtils.hasText(itemName)) {
            query.setParameter("itemName", itemName);
        }
        if (maxPrice != null) {
            query.setParameter("maxPrice", maxPrice);
        }
        return query.getResultList();
    }
}

 

 

1. 저장

 

저장은 EntityManager가 제공하는 persist()메서드를 사용하면 된다.

@Override
public Item save(Item item) {
    em.persist(item);
    return item;
}

 

 

2. 수정

 

em.update같은 메서드가 필요할 것 같지만 update가 정상적으로 수행된다.

왜냐하면 JPA는 트랜잭션이 커밋되는 시점에, 변경된 엔티티 객체가 있는지 확인하고 특정 엔티티 객체가 변경된 경우에 UPDATE SQL을 수행하기 때문이다.

@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
    Item findItem = em.find(Item.class, itemId);
    findItem.setItemName(updateParam.getItemName());
    findItem.setPrice(updateParam.getPrice());
    findItem.setQuantity(updateParam.getQuantity());
}

 

 

 

 

3. 검색

검색의 경우 find()메서드를 사용하면 된다.

@Override
public Optional<Item> findById(Long id) {
    Item item = em.find(Item.class, id);
    return Optional.ofNullable(item);
}

 

 

4. findAll - 목록 조회

public List<Item> findAll(ItemSearchCond cond) {
 String jpql = "select i from Item i";
 //동적 쿼리 생략
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
 return query.getResultList();
 }

 

JPA에서는 JPQL이라는 객체지향 쿼리 언어를 제공한다.

이는 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용된다.

※ 주의 : JPQL은 SQL과 달리 대소문자 구분을 한다!!

 

 

순수 JPA만을 사용하면 동적 쿼리의 복잡성 문제가 아직 해결 안된다.

 

동적쿼리를 깔끔하게 사용하기 위해서는  Querydsl을 사용하면 된다.( 다음 포스팅에 업로드 예정 )

 

 

JPA는 데이터에 쉽게 접근할 수 있으며 무엇보다 SQL을 자동으로 작성해준다는 점에서 뛰어난 장점을 가지는 것 같다. 

실무에서도 많이 쓰이는 만큼 깊게 공부할 필요가 있다고 느꼈다..