새소식

Database

데이터 접근 기술 (4) - JPA

 

 

개요

 

이번 글은 데이터 접근 기술 시리즈의 네 번째로, JPA에 대한 글이다.

김영한 님의 스프링 DB 강의와 개인 공부를 통해 학습한 내용을 정리하고자 한다.

 

Spring Boot 3.2.2, JDK 17 버전이며 H2를 데이터베이스로 사용하니 참고하자.

 

 

 

 

 

JPA란?

 

JPA(Java Persistence API)는 자바 어플리케이션에서 관계형 데이터베이스를 다루기 위한 표준 인터페이스이다.

객체와 관계형 데이터베이스 간의 매핑을 지원하고 DB 조작을 위해 편리한 API를 제공하기 때문에 SQL 쿼리를 직접 작성할 필요가 없으며 객체 지향적인 방식으로 DB를 다룰 수 있다.

 

JPA는 인터페이스이기 때문에 구현체를 통해 사용할 수 있는데, `Hibernate`가 구현체로 가장 많이 사용된다.

 

 

JPA의 주요 개념을 살펴보면 다음과 같다.

 

  • `Entity`
    • 데이터베이스의 테이블과 매핑되는 자바 객체
    • `@Entity` 애너테이션을 사용하여 JPA의 객체로 인식할 수 있도록 함
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;

    //기타 내용 생략
}

 

 

  • `EntityManager`
    • `Entity`의 생명주기 관리, 데이터베이스와의 트랜잭션 처리, 영속성 컨텍스트 관리 등 JPA의 모든 동작은 `EntityManager`를 통해 이루어짐
    • 내부에 `DataSource`를 가지고 있으며 데이터베이스에 접근할 수 있음
@Repository
public class JpaItemRepository implements ItemRepository {

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

    public Item save(Item item) {
        em.persist(item); //테이블에 객체 저장
        return item;
    }
}

 

 

  • JPQL(Java Persistence Query Language)
    • 객체지향 쿼리 언어로, `Entity`를 대상으로 쿼리 작성이 가능함
    • 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용함
    • "from" 다음에 `Entity` 객체명을 써야 하며 대소문자를 구분해야 함
public List<User> findUser(String userName) {
	
    String jpql = "select u from User u where u.username = :username";

    TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
    query.setParameter("userName", userName);

    return query.getResultList();
}

 

 

 

기본 설정

 

1. build.gradle

dependencies {
	
    //JPA, 스프링 데이터 JPA 사용
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    //H2 데이터베이스 사용
    runtimeOnly 'com.h2database:h2'

}

 

 

📌 참고

H2 데이터베이스 설치 및 실행 방법은 아래의 글을 참고하자.

https://hyunrian.tistory.com/88

 

Mac에서 H2 설치 및 실행하기

H2 Database는 간단한 개발이나 테스트 용도로 사용하기 좋은 DB이다. 이번 글에서는 맥에서 H2를 설치하고 실행하는 과정을 진행해보려 한다. H2 설치하기 아래의 링크로 들어가면 최신 버전을 OS에

hyunrian.tistory.com

 

 

2. application.properties

#DB 연결
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

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

 

  • `logging.level.org.hibernate.SQL=DEBUG` : `Hibernate`가 생성하고 실행하는 SQL을 로그로 확인할 수 있음
  • `logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE` : SQL에 바인딩되는 파라미터를 로그로 확인할 수 있음

 

 

 

코드 작성

 

간단한 CRUD 기능을 구현할 것이며 작성한 클래스는 아래와 같다.

 

  • `User` : 도메인 객체. 아이디(pk), 유저명, 핸드폰 번호를 필드로 가짐
  • `UserRepository` : 리포지토리 인터페이스
  • `JpaUserRepository` : 리포지토리 구현체
  • `UserSearchCondition` : 데이터 조회 시의 검색어를 필드로 갖고 있는 검색 조건 객체
  • `UserUpdateDto` : update에서 사용할 데이터 객체. `User` 대신 사용할 것

 

이 중에서 중요하지 않은 몇 가지 클래스는 코드를 생략하고 핵심이 되는 두 개만 살펴보겠다.

 

📌 참고

데이터베이스(H2)에 테이블은 아래와 같이 생성해 두었다.

create table users (
    id        bigint generated by default as identity,
    user_name varchar(10),
    phone_num varchar(25),
    primary key (id)
);

 

 

1. `User`

@Data
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name", length = 10)
    private String userName;
    private String phoneNum;

    public User(String userName, String phoneNum) {
        this.userName = userName;
        this.phoneNum = phoneNum;
    }
}

 

  • JPA는 `public` 또는 `protected`인 기본 생성자가 반드시 필요함
  • `@Table` : 데이터베이스의 테이블명을 지정하며 객체명과 같으면 생략이 가능함
  • `@Id` : 식별자(primary key)로 사용하는 경우 지정함. `jakarta.persistence` 패키지 내에 있는 애너테이션을 사용함
  • `@GeneratedValue`
    • `Entity`의 식별자 값을 자동으로 생성하는 전략을 지정할 수 있음
    • 주로 데이터베이스의 자동 증가(auto increment) 기능과 연동하여 사용함
    • `GenerationType.IDENTITY` : 데이터베이스의 자동 증가 컬럼 사용(MySQL, PostgreSQL 등)
    • `GenerationType.SEQUENCE` : 데이터베이스의 시퀀스 사용(Oracle 등)
    • `GenerationType.TABLE` : 별도의 테이블을 사용하여 식별자 생성
    • `GenerationType.AUTO` : 특정 데이터베이스에 맞게 자동으로 전략 선택
  • `@Column`
    • 테이블의 컬럼명과 길이 등을 지정할 수 있으며 필드명과 컬럼명이 동일한 경우 생략할 수 있음
    • 데이터베이스 컬럼명이 user_name(`snake_case`)이고 객체의 필드명이 userName(`camelCase`)인 경우 자동으로 변경되며, 이 경우에도 생략 가능함

 

2. `JpaUserRepository`

@Repository
@Transactional
public class JpaUserRepository implements UserRepository {

    private final EntityManager em;

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

    @Override
    public User save(User user) {
        em.persist(user); //테이블에 객체 저장
        return user;
    }

    @Override
    public void update(Long id, UserUpdateDto userDto) {
        User user = em.find(User.class, id);
        user.setUserName(userDto.getUserName());
        user.setPhoneNum(userDto.getPhoneNum());
        //entity의 변경 내용을 기반으로 transaction이 커밋되는 시점에 UPDATE SQL 실행
    }

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

    @Override
    public List<User> findAll(UserSearchCondition searchCondition) {
        String jpql = "select u from User u";

        String userName = searchCondition.getUserName();

        //검색어가 있는 경우에 where문 추가
        if (StringUtils.hasText(userName)) {
            jpql += " where u.userName like concat('%', :userName, '%')";
        }

        TypedQuery<User> query = em.createQuery(jpql, User.class);

        if (StringUtils.hasText(jpql)) {
            query.setParameter("userName", userName); //파라미터 바인딩
        }

        return query.getResultList();
    }

    @Override
    public void delete(Long id) {
        User user = em.find(User.class, id);
        if (user != null) {
            em.remove(user);
        }
    }
}

 

  • JPA에서 데이터를 변경(등록, 수정, 삭제)할 때는 항상 Transaction 내에서 이루어져야 함
    • 여기서는 `Service` 계층을 만들지 않고 `Repository`로만 간단히 작성했기 때문에 `Repository`에 `@Transactional`을 적용하였음
  • Spring Boot를 사용하면 `EntityManagerFactory`, `JpaTransactionManager` 등 JPA의 다양한 설정을 자동으로 해줌
  • 예외가 발생하는 경우 `@Repository`에 의해 JPA 예외가 스프링 예외 추상화로 변환됨
  • `EntityManager`
    • JPA의 모든 동작을 위한 인터페이스로, 주입을 받아 사용함
    • `persist(entity)` : 테이블에 entity를 저장함
    • `find(entityClass, primaryKey)` : PK값을 가진 대상을 조회함
    • `createQuery(jpql, entityClass)` : JPQL 쿼리를 생성하며 반환 타입을 `List<Entity>`로 매핑함
    • `remove(entity)` : entity를 삭제함

 

 

 

참고

 

 

Contents

Copied URL!

Liked this Posting!