2. Spring Data JPA

KKH_94·2023년 8월 9일
0

SpringBoot

목록 보기
6/9

JDBC(Java Database Connectivity)

1. JPA


1.1 JPA

Java Persistent API

JPA란 Java Persistence API의 약자로, Java 언어를 사용하여 객체 관계 매핑(Object-Relational Mapping, ORM)을 구현하는 API입니다.

ORM은 객체와 관계형 데이터베이스 간의 불일치로 인한 문제를 해결하기 위해 등장한 기술로, 객체 지향적인 프로그래밍 모델을 그대로 유지하면서 관계형 데이터베이스를 사용할 수 있게 합니다.

JPA는 이러한 ORM을 구현하기 위한 표준 인터페이스를 제공하며, 여러 ORM 프레임워크(예: Hibernate, EclipseLink 등)에서 JPA를 구현하여 사용하고 있습니다. JPA를 사용하면 SQL 쿼리를 직접 작성하지 않고도 객체를 통해 데이터베이스를 다룰 수 있으며, 이를 통해 개발자는 보다 객체 지향적인 코드를 작성할 수 있습니다.

또한 JPA는 관계형 데이터베이스 스키마를 자동으로 생성하거나 업데이트할 수 있는 기능을 제공하며, 데이터베이스의 변화에 대응하는 코드 작성을 간소화할 수 있습니다. 이를 통해 개발자는 보다 직관적이고 생산적인 코드 작성이 가능합니다.


1.2 Entity


JPA에서 엔티티(Entity)는 데이터베이스에서 관리되는 테이블과 매핑되는 자바 객체입니다. 즉, 엔티티 클래스는 데이터베이스에서 조회, 삽입, 수정, 삭제 등의 작업을 수행할 수 있도록 매핑된 자바 객체입니다.

JPA에서는 엔티티 클래스를 정의하고, 이를 매핑정보에 따라 데이터베이스 테이블과 매핑합니다. 이를 통해 객체와 관계형 데이터베이스 사이의 차이를 추상화하고, 객체 지향적인 방식으로 데이터베이스를 다룰 수 있습니다.

예를 들어, 다음과 같은 User 엔티티 클래스를 정의하고,

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    // Getters and setters
}

위의 User 클래스에서 @Entity 어노테이션은 이 클래스가 JPA에서 관리하는 엔티티임을 표시하며, @Table 어노테이션은 이 클래스가 데이터베이스의 users 테이블과 매핑됨을 나타냅니다. 또한, @Id 어노테이션과 @GeneratedValue 어노테이션은 이 클래스의 id 필드가 데이터베이스의 기본키(primary key) 역할을 수행하고, 이 필드가 자동으로 생성되도록 설정합니다.

이렇게 정의된 User 클래스를 사용하여 JPA에서 데이터를 조회하거나, 삽입, 수정, 삭제 등의 작업을 수행할 수 있습니다.

@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager entityManager;

    public List<User> findAllUsers() {
        return entityManager.createQuery("SELECT u FROM User u", 
        		User.class).getResultList();
    }

    public void saveUser(User user) {
        entityManager.persist(user);
    }

    public void updateUser(User user) {
        entityManager.merge(user);
    }

    public void deleteUser(User user) {
        entityManager.remove(user);
    }
}

위의 UserRepository 클래스에서는 @PersistenceContext 어노테이션을 사용하여 EntityManager 인터페이스를 주입받고, 이를 사용하여 데이터베이스에서 데이터를 조회하거나, 삽입, 수정, 삭제 등의 작업을 수행합니다.

이와 같이 JPA에서는 엔티티 클래스를 사용하여 데이터베이스와 매핑되는 자바 객체를 정의하고, 이를 사용하여 데이터베이스 작업을 수행합니다.

Entity는 EntityManager라는 인터페이스를 통해 DB와 동기화 됩니다.

웹 애플리케이션에서 JPA를 이용하여 DB 데이터에 접근하기위해서는 반드시 EntityManager를 이용해야 합니다.

@Configuration
@EnableJpaRepositories(basePackages = "com.example.demo.repository")
@EntityScan(basePackages = "com.example.demo.domain")
public class AppConfig {
    @Bean(name="entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(DataSource dataSource, 
    		JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setJpaVendorAdapter(jpaVendorAdapter);
        emf.setPackagesToScan("com.example.demo.domain");
        emf.afterPropertiesSet();
        return emf.getObject();
    }
}

위의 코드에서는 EntityManagerFactory 빈 객체를 생성하기 위해 LocalContainerEntityManagerFactoryBean 클래스를 사용합니다.

이 클래스는 스프링 부트에서 제공하는 JPA 설정 객체로, JPA에서 필요한 여러 가지 설정을 자동으로 처리해줍니다.

이를 위해 entityManagerFactory 메소드에서는 데이터베이스 연결을 위한 DataSource와 JpaVendorAdapter를 인자로 받아서, LocalContainerEntityManagerFactoryBean 객체를 생성하고, 이를 EntityManagerFactory 객체로 변환하여 반환합니다.

또한, @Bean 어노테이션의 name 속성을 사용하여 이 빈 객체의 이름을 "entityManagerFactory"로 지정하였습니다.

이렇게 생성된 EntityManagerFactory 빈 객체는 @Autowired 어노테이션을 사용하여 다른 스프링 빈에서 사용할 수 있습니다.

예를 들어, UserRepository에서 EntityManagerFactory를 주입받을 때 다음과 같이 사용합니다.

@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager entityManager;

    // EntityManagerFactory 주입받기
    @Autowired
    private EntityManagerFactory entityManagerFactory;

    public List<User> findAllUsers() {
        return entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();
    }

    public void saveUser(User user) {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        entityManager.persist(user);
        transaction.commit();
        entityManager.close();
    }

    // ... 이하 생략
}

위의 코드에서는 EntityManagerFactory를 주입받아 EntityManager 객체를 직접 생성하여 데이터를 삽입하는 코드를 작성하였습니다.

이를 통해 EntityManagerFactory를 사용하여 데이터베이스와 연결하고, JPA에서 제공하는 메소드를 사용하여 데이터를 조회하거나, 삽입, 수정, 삭제 등의 작업을 수행할 수 있습니다.



2. Spring Data JPA


2.1. 컨셉

JPA의 EntityManager를 이용해서 데이터베이스 트랜잭션을 처리할 때는 EntityManager를 직접 사용하여 트랜잭션을 시작하고, 커밋 또는 롤백을 수행해야 합니다.

또한 EntityManager를 통해 엔티티를 검색하고, 수정, 삭제 등의 작업을 수행합니다.

이 방식은 개발자가 직접 트랜잭션을 관리해야 하므로 코드가 복잡해지고, 실수가 발생할 가능성이 높아집니다.

반면에 Spring Data JPA를 이용해서 데이터베이스 트랜잭션을 처리할 때는 스프링의 트랜잭션 추상화 기능을 이용하여 트랜잭션을 관리합니다.

즉, @Transactional 어노테이션을 사용하여 트랜잭션을 시작하고, 메서드가 종료될 때 자동으로 커밋 또는 롤백을 수행합니다.


Spring Data JPA에서는 트랜잭션을 시작하는 메서드 내에서 JpaRepository 인터페이스에서 제공하는 메서드를 호출하여 데이터베이스 작업을 수행합니다.

이 방식은 개발자가 트랜잭션 관리에 대한 부담이 적어져 코드가 간결해지고, 트랜잭션 관리에 대한 실수를 줄일 수 있습니다.

따라서 Spring Data JPA를 이용하면 JPA의 EntityManager를 이용하는 방식보다 코드가 간결해지고, 트랜잭션 관리에 대한 부담이 적어져 개발자의 생산성을 높일 수 있습니다.

또한 Spring Data JPA는 스프링 프레임워크와 연동되어 있으므로 스프링의 다양한 기능들을 함께 사용할 수 있습니다.


Spring Data Repository는 Spring Data 프로젝트의 일부로, 다양한 데이터베이스 기술에 대해 Repository 인터페이스를 제공하여 데이터베이스 연동을 추상화합니다.

Spring Data Repository는 인터페이스 기반으로 설계되어 있으며, Repository 인터페이스는 CRUD(Create, Read, Update, Delete) 작업에 대한 메서드를 정의하고 있습니다.

Spring Data Repository는 이러한 메서드를 기반으로 자동으로 구현체를 생성합니다.

이를 통해 개발자는 보다 간단하게 데이터베이스 연동을 구현할 수 있습니다.


Spring Data JPA는 Spring Data Repository를 구현하기 위해 사용되며, Spring Data JPA에서는 JpaRepository 인터페이스를 제공하여 데이터베이스 연동을 추상화합니다.

JpaRepository 인터페이스는 Spring Data Repository에서 제공하는 CRUD 작업에 대한 메서드를 상속하면서, JPA에서 제공하는 메서드도 함께 제공합니다.

이를 통해 개발자는 Spring Data Repository의 추상화된 기능과 JPA의 ORM 기능을 모두 사용할 수 있습니다.

따라서, Spring Data JPA와 Spring Data Repository는 함께 사용하여, JPA를 쉽게 사용하고 데이터베이스 연동을 추상화할 수 있습니다.

Spring Data JPA는 JpaRepository 인터페이스를 통해 Spring Data Repository를 구현하며, Spring Data Repository는 JpaRepository 인터페이스를 상속하여 JPA의 ORM 기능과 Spring Data Repository의 추상화된 기능을 함께 사용합니다.

Repository 인터페이스는 CRUD(Create, Read, Update, Delete) 작업에 대한 메서드를 정의하고 있습니다.


Spring Data Repository는 이러한 메서드를 기반으로 자동으로 구현체를 생성합니다. 이를 통해 개발자는 보다 간단하게 데이터베이스 연동을 구현할 수 있습니다.

Spring Data Repository는 또한 쿼리 메서드(Query Method)라는 기능을 제공합니다. 쿼리 메서드는 메서드 이름을 분석하여 자동으로 SQL 또는 MongoDB 쿼리를 생성합니다.

이를 통해 개발자는 SQL 또는 MongoDB 쿼리를 직접 작성할 필요 없이, 메서드 이름만으로 쿼리를 자동으로 생성할 수 있습니다.

Spring Data Repository는 인터페이스 기반으로 설계되어 있으므로, 다른 데이터베이스 기술에 대해서도 일관성 있는 방식으로 데이터베이스 연동을 구현할 수 있습니다.

또한 Spring Data Repository는 스프링 프레임워크와 함께 사용되므로, 스프링의 다양한 기능들을 함께 사용할 수 있습니다.

Spring Data Repository는 Data Access Layer를 구현하는데에 작성되는 상용적인 쿼리 코드를 줄이는 것에 목표를 둔 프로젝트입니다.

단순 쿼리 검색이나 count 검색, 삭제와 저장과 같이 단순한 쿼리 처리를 직접적인 쿼리 작성이 아닌, 약속된 메서드 명명규칙을 따른 Query Methods를 이용하여 처리합니다.

Spring Data Repository 추상화의 맨 중심에는 Repository 인터페이스가 있습니다.

Repository를 상속한 interfaces은 PK를 이용한 단순검색이나 엔티티 인스턴스를 insert, PK를 이용한 record 삭제와 같이 빈번히 이용되는 추상메서드를 미리 선언해둔 interfaces입니다.
따라서, 이런 약속된 기능을 별도로 선언하고 싶지 않다면 필요에 따라 위의 상속 인터페이스를 이용하면 되고,

사용하고자 하는 메서드를 모두 직접 선언하고 싶다면 Repository 인터페이스를 상속해서 사용해도 무방합니다.

Repository 인터페이스든, Repository를 상속한 CrudRepository나 JpaRepository든 내부적으로는 SimpleJpaRepository 클래스를 이용하고, 이 클래스에서 Query Methods를 분석하여 동적으로 쿼리를 생성합니다.


2.2. Repository 상속 인터페이스의 메서드


Spring Data JPA Repository에 미리 생성되어있는 Repository 상속 인터페이스를 이용하면 빈번히 이용되는 주요 기능들에 대한 Query Methods가 선언되어있습니다.

rudRepository

  • CrudRepository는 Repository 인터페이스를 상속하여 다양한 CRUD(Create, Read, Update, Delete) 작업에 대한 메서드를 제공

  • CrudRepository는 다양한 데이터베이스 기술에 대해 지원하며, JpaRepository와 달리 JPA에 한정되지 않고, MongoDB, Redis 등 다양한 데이터베이스 기술을 지원합니다. CrudRepository는 다음과 같이 정의

  • 아래 코드에서 T는 Entity 클래스를 나타내며, ID는 Entity의 ID 타입을 나타냅니다. CrudRepository는 다양한 CRUD 작업에 대한 메서드를 제공합니다. 예를 들어 save 메서드는 엔티티를 저장하고, findById 메서드는 ID를 기반으로 엔티티를 검색합니다. 또한 delete 메서드는 엔티티를 삭제하고, existsById 메서드는 ID를 기반으로 해당 엔티티가 존재하는지 여부를 확인합니다.

  • Repository 인터페이스는 CRUD 작업에 대한 다양한 메서드를 제공합니다. 이 메서드들은 데이터베이스 연동에 필요한 작업을 추상화한 것으로, 개발자는 이 메서드를 상속하여 자신의 인터페이스에서 사용할 수 있습니다. Spring Data JPA에서는 Repository 인터페이스를 상속하여, 다양한 데이터베이스 기술에 대한 인터페이스를 제공합니다. 이를 통해 개발자는 보다 쉽게 데이터베이스 연동을 구현할 수 있습니다.

  • PagingAndSortingRepository

    • CrudRepository 인터페이스 상속
    • 다양한 CRUD 작업과 페이징 및 정렬 작업에 대한 메서드를 제공
  • JpaRepository

    • PagingAndSortingRepository 인터페이스 상속
    • 다양한 JPA(Java Persistence API) 관련 기능을 지원
    • 다양한 데이터베이스 기술에 대해 지원
    • JPA를 구현하는 여러 ORM 프레임워크(Hibernate, EclipseLink 등)에서 JPA를 사용할 수 있도록 표준 인터페이스를 제공
    • 개발자에게 ORM 프레임워크에 종속되지 않고 일관된 방식으로 JPA를 사용할 수 있도록 지원.

Hibernate


자세한 사항은 각 인터페이스에 선언된 메서드를 보면 명확히 알 수 있습니다.

package org.springframework.data.repository;

import org.springframework.stereotype.Indexed;

/**
 * Central repository marker interface. 
 * Captures the domain type to manage as well as the domain type's id type. General
 * purpose is to hold type information as well as being able to discover 
   interfaces that extend this one during
 * classpath scanning for easy Spring bean creation.
 * <p>
 * Domain repositories extending this interface can selectively expose 
   CRUD methods by simply declaring methods of the
 * same signature as those declared in {@link CrudRepository}.
 
 * @see CrudRepository
 * @param <T> the domain type the repository manages
 * @param <ID> the type of the id of the entity the repository manages
 * @author Oliver Gierke
 */
@Indexed
public interface Repository<T, ID> {

}
public interface CrudRepository<T, ID> extends Repository<T, ID> {

	<S extends T> S save(S entity);
	<S extends T> Iterable<S> saveAll(Iterable<S> entities);
	Optional<T> findById(ID id);
	boolean existsById(ID id);
	Iterable<T> findAll();
	Iterable<T> findAllById(Iterable<ID> ids);
	long count();
	void deleteById(ID id);
	void delete(T entity);
	void deleteAll(Iterable<? extends T> entities);
	void deleteAll();

}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	Iterable<T> findAll(Sort sort);
	Page<T> findAll(Pageable pageable);

}
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, 
					QueryByExampleExecutor<T> {

	void flush();
	<S extends T> S saveAndFlush(S entity);
	void deleteInBatch(Iterable<T> entities);
	void deleteAllInBatch();
	T getOne(ID id);

	@Override List<T> findAll();
	@Override List<T> findAll(Sort sort);
	@Override List<T> findAllById(Iterable<ID> ids);
	@Override <S extends T> List<S> saveAll(Iterable<S> entities);
	@Override <S extends T> List<S> findAll(Example<S> example);
	@Override <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

SimpleJpaRepository는 Spring Data JPA에서 JpaRepository 인터페이스의 구현체 중 하나입니다. SimpleJpaRepository는 JpaRepository에서 정의된 메서드를 구현하며, JPA의 기능을 직접 사용하여 데이터베이스 연동을 수행합니다.

SimpleJpaRepository는 다음과 같이 정의됩니다.

public class SimpleJpaRepository<T, ID> extends JpaRepositoryImpl<T, ID> 
				implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
    private final EntityManager entityManager;

    public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation,
    		EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    // ... 이하 생략
}

위 코드에서 T는 Entity 클래스를 나타내며, ID는 Entity의 ID 타입을 나타냅니다.

SimpleJpaRepository는 JpaRepositoryImpl을 상속하여 JpaRepository 인터페이스의 메서드를 구현하며, JpaSpecificationExecutor 인터페이스도 구현합니다.

JpaSpecificationExecutor 인터페이스는 동적 쿼리를 작성하는 기능을 제공합니다.

SimpleJpaRepository는 생성자에서 JpaEntityInformation와 EntityManager를 매개변수로 받습니다.

JpaEntityInformation는 엔티티 정보를 나타내는 인터페이스이며, EntityManager는 JPA의 핵심 기능을 제공하는 인터페이스입니다.

SimpleJpaRepository는 이 두 인터페이스를 이용하여 JPA의 기능을 사용하여 데이터베이스 연동을 수행합니다.

Spring Data JPA에서는 SimpleJpaRepository를 사용하여 JpaRepository 인터페이스의 메서드를 구현합니다. 이를 통해 개발자는 JpaRepository 인터페이스에서 제공하는 다양한 메서드를 사용하여 데이터베이스 연동을 구현할 수 있습니다.


2.3. Spring Data JPA의 Query methods


Spring Data JPA는 Spring과 JPA 기반의 repository의 정교한 구축을 지원합니다.

org.springframework.data.repository.Repository는 Spring Data에서 제공하는 리포지토리 인터페이스입니다. 이 인터페이스를 상속받아서 개발자는 쉽게 JPA 등의 ORM 기술을 이용해 데이터를 관리할 수 있습니다.

org.springframework.data.repository.Repository를 상속한 인터페이스에 정해진 규칙을 따른 Query Methods를 선언할 경우 내부에서 JPQL을 동적으로 생성하여 쿼리를 작동시킵니다.

Repository 인터페이스는 데이터베이스의 기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행하는 메서드를 제공합니다. 이렇게 제공되는 메서드들은 개발자가 추가로 메서드를 작성하지 않더라도 자동으로 생성됩니다.

Repository 인터페이스에서 제공하는 CRUD 메서드의 이름은 다음과 같습니다.

  • save(S entity) : 엔티티 객체를 저장하고 저장된 엔티티를 반환합니다. (CREATE 또는 UPDATE)
  • findById(ID id) : 주어진 ID 값으로 엔티티를 검색합니다. 검색된 엔티티를 반환합니다. (READ)
  • findAll() : 모든 엔티티를 검색합니다. 검색된 엔티티를 반환합니다. (READ)
  • deleteById(ID id) : 주어진 ID 값으로 엔티티를 삭제합니다. (DELETE)
  • delete(T entity) : 주어진 엔티티 객체를 삭제합니다. (DELETE)

이 외에도 다양한 메서드가 제공되며, 이들 메서드는 메서드 이름의 규칙을 따르는데, 메서드 이름에 작성된 단어들은 엔티티 필드명, AND, OR, BETWEEN 등과 같은 키워드 등으로 구성됩니다.

이를 통해 개발자는 필요한 메서드를 생성할 수 있습니다.

예를 들어, User 엔티티를 다루는 UserRepository 인터페이스가 있다고 가정해봅시다. 이 인터페이스는 Repository 인터페이스를 상속받아 구현됩니다.

import org.springframework.data.repository.Repository;

public interface UserRepository extends Repository<User, Long> {
    User save(User user);
    List<User> findAll();
    Optional<User> findById(Long id);
    void deleteById(Long id);
}

위의 예제 코드에서 UserRepository는 Repository 인터페이스를 상속받습니다. 그리고 User 엔티티와 Long 타입의 기본 키를 받아 CRUD 작업을 수행합니다.

이렇게 정의된 UserRepository를 사용하기 위해서는 해당 인터페이스를 구현한 클래스가 필요합니다. 예를 들어, UserRepository를 구현한 클래스는 다음과 같이 작성될 수 있습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User saveUser(User user) {
        return userRepository.save(user);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }

    public void deleteUserById(Long id) {
        userRepository.deleteById(id);
    }
}

위의 예제에서는 UserService에서 UserRepository를 사용합니다.

이를 위해 UserRepository 인스턴스를 생성하고, 필요한 메서드를 사용하여 데이터를 관리합니다.

profile
_serendipity

0개의 댓글