minlog
article thumbnail

 

JPA 란?

  • ORM 기술 표준으로 사용되는 인터페이스의 모음이다. 

실제적으로 구현된것이 아니라 구현된 클래스와 매핑을 해주기 위해 사용되는 프레임워크이다. JPA를 구현한 대표적인 오픈소스로는 Hibernate가 있다.  JPA를 사용하면 개발자가 직접 JDBC API를 쓰는 것이 아니다.

 

  • ORM  (Object-Relational Mapping) : 객체 관계 매핑

자바객체(Class)와 데이터베이스 레코드관의 연결관계를 맺어주는 것

최종적으로 동작하는 것들은 sql 쿼리  

 

1. JPA 세팅과 DB객체 설정

1) 의존성 주입

📑build.gradle

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

 

2) 데이터베이스 연결

인텔리제이 

 

3) 객체를 @Entity로 설정

자바 객체를 @Entity로 설정해준다. 이때 Entity는 PK가 반듯이 필요하다.

필드 중 PK에 @ Id를 선언해준다. 추가로 순차적으로 생성하려면 @GeneratedValue를 선언해준다.

@Data
@Builder
@Entity // pk가 반드시 필요.
public class User {

    @Id //pk
    @GeneratedValue //자동으로 증가
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;
    private LocalDateTime createdAt;
    private LocalDateTime updateAt;

    public User() {
    }
    public User(Long id, @NonNull String name, @NonNull String email, LocalDateTime createdAt, LocalDateTime updateAt) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.createdAt = createdAt;
        this.updateAt = updateAt;
    }


}

 

 

 

2. JPA 사용방법

DB객체를 생성하고 조회하는 방법 > 레파지토리 생성

1) JpaRepository 인터페이스를 상속받는 레파지토리 인터페이스 생성

public interface UserRepository extends JpaRepository<Users,Long> {
}

 

💡 JpaRepository 인터페이스 기능 정의

JpaRepository는 데이터를 검색하는 메소드를 정의 하고 있다. 해당 인터페이스를 상속하면 메서드를 선언하지 않아도 다양한 기능을 외부로 개방할 수 있다. 

JpaRepository 인퍼페이스 내부를 살펴보면, 페이징 기능의 인터페이스 (PagingAndSortingRepository)검색 기능을 가지는 인터페이스(QueryByExampleExecutor)를 상속 받고 있다. 다시 PagingAndSortingRepository의 내부를 살펴보면 CrudRepository 인터페이스를 상속 받고 있는데 여기서 주요한 Crud( creat,read,update,delete )의 기능을 하는 메서드들을 가지고 있다.

가장 최상위의 인터페이스는 Repository 인데, JPA에서 사용하는 도메인 레파지토리 타입이라는것을 알려주기 위한 인터페이스이고 실제 메서드 정의는 없다. 

 

 

 

2) JpaRepository 내부 메서드 확인 

📑 JpaRepository 에서 정의 하는 메서드

메서드 의미
findAll() 전체 리스트를 가져오는 메서드, 실제로는 리스트가 많을 경우 성능 부분 때문에 잘 사용하지 않는다.
findAll(Sort sort) 정렬 값을 가진 전체 리스트
findAll(Example<S> example)  
findAll(Example<S> example,Sort sort)  
findAllById(Iterable<ID> ids) id(PK) 값을 리스트 타입으로 받아서 조회하여 해당 entity들을 조회
saveAll(Iterable<s> entitys) entity를 리스트 형식으로 받아서 한번에 저장 
flush() JPA 컨텍스트에서 가지고 있는 DB값을 DB에 저장
saveAndFlush(S entitys) 저장한 데이터를 JPA 컨텍스트에서 가지고 있지 말고 바로 DB에 저장
deleteInBatch(Iterable<T> entitys) entity를 리스트 형식으로 받아서 한번에 지우는 메서드
deleteAllInBatch() 조건 없이 해당 테이블 전체를 지우는 메서드
getOne(ID id) id값을 가지고 하나만 가져오는 메서드 (레이지 페치를 지원...)

 

📑 CrudRepository 에서 정의 하는 메서드 ⭐⭐

메서드 의미
S save(S entity) entity에 대한 저장
Iterable<S> saveAll(Iterable<s> entitys) entity를 리스트 형식으로 받아서 한번에 저장
Optional<T> findById(ID id) 옵셔널 객체로 맵핑을해서 리턴해주는 메서드 ( * getOne과 동작이 다르다 )
<S extends T> boolean exists(Example<S> example);
실제 존재하는지 확인하는 True, False로 값이 반환됨.
boolean existBy(ID id) 해당 객체가 존재하는지 확인해주는 메서드
Iterable<T> findAll() 전체 리스트를 가져오는 메서드, 실제로는 리스트가 많을 경우 성능 부분 때문에 잘 사용하지 않는다.
findAllById(Iterable<ID> ids) id 값이 리스트 타입으로 받아서 in 구문으로 조회하여 여러 entity들을 조회
void deleteById(ID id) id 값이 일치 하는 객체 제거
void delete(T entity) 객체를 인자로 받아서 제거
void deleteAll() 리스트 전체 를 제거 
long count() 전체 entity의 갯수를 가져온다.

 

 

3) 페이징 객체로 메서드 사용.

기본적인 조회와 페이징 처리를 제공하고 있다.

import org.springframework.data.domain.Page;
Page<User> users = userRepository.findAll(PageRequest.of(1,3));//pagerble의 구현체 PageRequest
System.out.println("page : "+users);
System.out.println("page : "+users.getTotalElements()); //전체 개수
System.out.println("page : "+users.getTotalPages());//전체 페이지
System.out.println("NumberOfElements : "+users.getNumberOfElements()); //현제 페이지의 레코드수
System.out.println("Sort : "+users.getSort());//전체 페이지의 정렬 기준
System.out.println("size : "+users.getSize());//페이지 할때 나누는 크기
users.getContent(); //현제페이지의 갯수

 

 

3) QueryByExampleExecutor 사용 방법.

검색이 필요한 인자를 matcher로 생성을해서 DB에 존재하는지 찾을 수 있다.

ExampleMatcher matcher = ExampleMatcher.matching()
        .withIgnorePaths("name") //이름은 매칭하지 않는다.
        .withMatcher("email",endsWith());
Example<User> example = Example.of(new User("a","a@gamil.com"),matcher);
userRepository.findAll(example).forEach(System.out::print);

 

 

 

 

 

🍫메서드 실행시 실제 진행되는 SQL 로그를 볼 수 있는 방법

 

📑application.yml

spring:
  h2:
    console:
      enabled: true
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

 

 

 

 

※ 기타 메서드

.orElse("null") // 값이 없으면 null 을 출력

 

 

※ SimpleJpaRepository 에서 구현체를 확인 가능하다.

📑 SimpleJpaRepository

/*
 * Copyright 2008-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.jpa.repository.support;

import static org.springframework.data.jpa.repository.query.QueryUtils.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.QueryHints.NoHints;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.data.util.ProxyUtils;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
 * you a more sophisticated interface than the plain {@link EntityManager} .
 *
 * @param <T> the type of the entity to handle
 * @param <ID> the type of the entity's identifier
 * @author Oliver Gierke
 * @author Eberhard Wolff
 * @author Thomas Darimont
 * @author Mark Paluch
 * @author Christoph Strobl
 * @author Stefan Fussenegger
 * @author Jens Schauder
 * @author David Madden
 * @author Moritz Becker
 * @author Sander Krabbenborg
 * @author Jesse Wouters
 * @author Greg Turnquist
 * @author Yanming Zhou
 * @author Ernst-Jan van der Laan
 * @author Diego Krupitza
 */
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

   private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";

   private final JpaEntityInformation<T, ?> entityInformation;
   private final EntityManager em;
   private final PersistenceProvider provider;

   private @Nullable CrudMethodMetadata metadata;
   private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;

   /**
    * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}.
    *
    * @param entityInformation must not be {@literal null}.
    * @param entityManager must not be {@literal null}.
    */
   public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {

      Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
      Assert.notNull(entityManager, "EntityManager must not be null!");

      this.entityInformation = entityInformation;
      this.em = entityManager;
      this.provider = PersistenceProvider.fromEntityManager(entityManager);
   }

   /**
    * Creates a new {@link SimpleJpaRepository} to manage objects of the given domain type.
    *
    * @param domainClass must not be {@literal null}.
    * @param em must not be {@literal null}.
    */
   public SimpleJpaRepository(Class<T> domainClass, EntityManager em) {
      this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
   }

   /**
    * Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be
    * applied to queries.
    *
    * @param crudMethodMetadata
    */
   @Override
   public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
      this.metadata = crudMethodMetadata;
   }

   @Override
   public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
      this.escapeCharacter = escapeCharacter;
   }

   @Nullable
   protected CrudMethodMetadata getRepositoryMethodMetadata() {
      return metadata;
   }

   protected Class<T> getDomainClass() {
      return entityInformation.getJavaType();
   }

   private String getDeleteAllQueryString() {
      return getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName());
   }

   private String getCountQueryString() {

      String countQuery = String.format(COUNT_QUERY_STRING, provider.getCountQueryPlaceholder(), "%s");
      return getQueryString(countQuery, entityInformation.getEntityName());
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
    */
   @Transactional
   @Override
   public void deleteById(ID id) {

      Assert.notNull(id, ID_MUST_NOT_BE_NULL);

      delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
            String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
    */
   @Override
   @Transactional
   @SuppressWarnings("unchecked")
   public void delete(T entity) {

      Assert.notNull(entity, "Entity must not be null!");

      if (entityInformation.isNew(entity)) {
         return;
      }

      Class<?> type = ProxyUtils.getUserClass(entity);

      T existing = (T) em.find(type, entityInformation.getId(entity));

      // if the entity to be deleted doesn't exist, delete is a NOOP
      if (existing == null) {
         return;
      }

      em.remove(em.contains(entity) ? entity : em.merge(entity));
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#deleteAllById(java.lang.Iterable)
    */
   @Override
   @Transactional
   public void deleteAllById(Iterable<? extends ID> ids) {

      Assert.notNull(ids, "Ids must not be null!");

      for (ID id : ids) {
         deleteById(id);
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#deleteAllByIdInBatch(java.lang.Iterable)
    */
   @Override
   @Transactional
   public void deleteAllByIdInBatch(Iterable<ID> ids) {

      Assert.notNull(ids, "Ids must not be null!");

      if (!ids.iterator().hasNext()) {
         return;
      }

      if (entityInformation.hasCompositeId()) {

         List<T> entities = new ArrayList<>();
         // generate entity (proxies) without accessing the database.
         ids.forEach(id -> entities.add(getReferenceById(id)));
         deleteAllInBatch(entities);
      } else {

         String queryString = String.format(DELETE_ALL_QUERY_BY_ID_STRING, entityInformation.getEntityName(),
               entityInformation.getIdAttribute().getName());

         Query query = em.createQuery(queryString);
         /**
          * Some JPA providers require {@code ids} to be a {@link Collection} so we must convert if it's not already.
          */
         if (Collection.class.isInstance(ids)) {
            query.setParameter("ids", ids);
         } else {
            Collection<ID> idsCollection = StreamSupport.stream(ids.spliterator(), false)
                  .collect(Collectors.toCollection(ArrayList::new));
            query.setParameter("ids", idsCollection);
         }

         query.executeUpdate();
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
    */
   @Override
   @Transactional
   public void deleteAll(Iterable<? extends T> entities) {

      Assert.notNull(entities, "Entities must not be null!");

      for (T entity : entities) {
         delete(entity);
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable)
    */
   @Override
   @Transactional
   public void deleteAllInBatch(Iterable<T> entities) {

      Assert.notNull(entities, "Entities must not be null!");

      if (!entities.iterator().hasNext()) {
         return;
      }

      applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em)
            .executeUpdate();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.Repository#deleteAll()
    */
   @Override
   @Transactional
   public void deleteAll() {

      for (T element : findAll()) {
         delete(element);
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch()
    */
   @Override
   @Transactional
   public void deleteAllInBatch() {
      em.createQuery(getDeleteAllQueryString()).executeUpdate();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable)
    */
   @Override
   public Optional<T> findById(ID id) {

      Assert.notNull(id, ID_MUST_NOT_BE_NULL);

      Class<T> domainType = getDomainClass();

      if (metadata == null) {
         return Optional.ofNullable(em.find(domainType, id));
      }

      LockModeType type = metadata.getLockModeType();

      Map<String, Object> hints = new HashMap<>();
      getQueryHints().withFetchGraphs(em).forEach(hints::put);

      return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
   }

   /**
    * Returns {@link QueryHints} with the query hints based on the current {@link CrudMethodMetadata} and potential
    * {@link EntityGraph} information.
    */
   protected QueryHints getQueryHints() {
      return metadata == null ? NoHints.INSTANCE : DefaultQueryHints.of(entityInformation, metadata);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable)
    */
   @Deprecated
   @Override
   public T getOne(ID id) {
      return getReferenceById(id);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#getById(java.io.Serializable)
    */
   @Deprecated
   @Override
   public T getById(ID id) {
      return getReferenceById(id);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#getReferenceById(java.io.Serializable)
    */
   @Override
   public T getReferenceById(ID id) {

      Assert.notNull(id, ID_MUST_NOT_BE_NULL);
      return em.getReference(getDomainClass(), id);
   }

   /*
   * (non-Javadoc)
   * @see org.springframework.data.repository.CrudRepository#existsById(java.io.Serializable)
   */
   @Override
   public boolean existsById(ID id) {

      Assert.notNull(id, ID_MUST_NOT_BE_NULL);

      if (entityInformation.getIdAttribute() == null) {
         return findById(id).isPresent();
      }

      String placeholder = provider.getCountQueryPlaceholder();
      String entityName = entityInformation.getEntityName();
      Iterable<String> idAttributeNames = entityInformation.getIdAttributeNames();
      String existsQuery = QueryUtils.getExistsQueryString(entityName, placeholder, idAttributeNames);

      TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);

      if (!entityInformation.hasCompositeId()) {
         query.setParameter(idAttributeNames.iterator().next(), id);
         return query.getSingleResult() == 1L;
      }

      for (String idAttributeName : idAttributeNames) {

         Object idAttributeValue = entityInformation.getCompositeIdAttributeValue(id, idAttributeName);

         boolean complexIdParameterValueDiscovered = idAttributeValue != null
               && !query.getParameter(idAttributeName).getParameterType().isAssignableFrom(idAttributeValue.getClass());

         if (complexIdParameterValueDiscovered) {

            // fall-back to findById(id) which does the proper mapping for the parameter.
            return findById(id).isPresent();
         }

         query.setParameter(idAttributeName, idAttributeValue);
      }

      return query.getSingleResult() == 1L;
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#findAll()
    */
   @Override
   public List<T> findAll() {
      return getQuery(null, Sort.unsorted()).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
    */
   @Override
   public List<T> findAllById(Iterable<ID> ids) {

      Assert.notNull(ids, "Ids must not be null!");

      if (!ids.iterator().hasNext()) {
         return Collections.emptyList();
      }

      if (entityInformation.hasCompositeId()) {

         List<T> results = new ArrayList<>();

         for (ID id : ids) {
            findById(id).ifPresent(results::add);
         }

         return results;
      }

      Collection<ID> idCollection = Streamable.of(ids).toList();

      ByIdsSpecification<T> specification = new ByIdsSpecification<>(entityInformation);
      TypedQuery<T> query = getQuery(specification, Sort.unsorted());

      return query.setParameter(specification.parameter, idCollection).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#findAll(org.springframework.data.domain.Sort)
    */
   @Override
   public List<T> findAll(Sort sort) {
      return getQuery(null, sort).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
    */
   @Override
   public Page<T> findAll(Pageable pageable) {

      if (isUnpaged(pageable)) {
         return new PageImpl<>(findAll());
      }

      return findAll((Specification<T>) null, pageable);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne(org.springframework.data.jpa.domain.Specification)
    */
   @Override
   public Optional<T> findOne(@Nullable Specification<T> spec) {

      try {
         return Optional.of(getQuery(spec, Sort.unsorted()).setMaxResults(2).getSingleResult());
      } catch (NoResultException e) {
         return Optional.empty();
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification)
    */
   @Override
   public List<T> findAll(@Nullable Specification<T> spec) {
      return getQuery(spec, Sort.unsorted()).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification, org.springframework.data.domain.Pageable)
    */
   @Override
   public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {

      TypedQuery<T> query = getQuery(spec, pageable);
      return isUnpaged(pageable) ? new PageImpl<>(query.getResultList())
            : readPage(query, getDomainClass(), pageable, spec);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification, org.springframework.data.domain.Sort)
    */
   @Override
   public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
      return getQuery(spec, sort).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example)
    */
   @Override
   public <S extends T> Optional<S> findOne(Example<S> example) {

      try {
         return Optional
               .of(getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())
                     .setMaxResults(2).getSingleResult());
      } catch (NoResultException e) {
         return Optional.empty();
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example)
    */
   @Override
   public <S extends T> long count(Example<S> example) {
      return executeCountQuery(
            getCountQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType()));
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example)
    */
   @Override
   public <S extends T> boolean exists(Example<S> example) {

      Specification<S> spec = new ExampleSpecification<>(example, this.escapeCharacter);
      CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
      cq.select(this.em.getCriteriaBuilder().literal(1));
      applySpecificationToCriteria(spec, example.getProbeType(), cq);
      TypedQuery<Integer> query = applyRepositoryMethodMetadata(this.em.createQuery(cq));
      return query.setMaxResults(1).getResultList().size() == 1;
   }

   @Override
   public boolean exists(Specification<T> spec) {

      CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
      cq.select(this.em.getCriteriaBuilder().literal(1));
      applySpecificationToCriteria(spec, getDomainClass(), cq);
      TypedQuery<Integer> query = applyRepositoryMethodMetadata(this.em.createQuery(cq));
      return query.setMaxResults(1).getResultList().size() == 1;
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
    */
   @Override
   public <S extends T> List<S> findAll(Example<S> example) {
      return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())
            .getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
    */
   @Override
   public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
      return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), sort).getResultList();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
    */
   @Override
   public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {

      ExampleSpecification<S> spec = new ExampleSpecification<>(example, escapeCharacter);
      Class<S> probeType = example.getProbeType();
      TypedQuery<S> query = getQuery(new ExampleSpecification<>(example, escapeCharacter), probeType, pageable);

      return isUnpaged(pageable) ? new PageImpl<>(query.getResultList()) : readPage(query, probeType, pageable, spec);
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.query.QueryByExampleExecutor#findBy(org.springframework.data.domain.Example, java.util.function.Function)
    */
   @Override
   public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQuery<S>, R> queryFunction) {

      Assert.notNull(example, "Sample must not be null!");
      Assert.notNull(queryFunction, "Query function must not be null!");

      Function<Sort, TypedQuery<S>> finder = sort -> {

         ExampleSpecification<S> spec = new ExampleSpecification<>(example, escapeCharacter);
         Class<S> probeType = example.getProbeType();

         return getQuery(spec, probeType, sort);
      };

      FetchableFluentQuery<S> fluentQuery = new FetchableFluentQueryByExample<>(example, finder, this::count,
            this::exists, this.em, this.escapeCharacter);

      return queryFunction.apply(fluentQuery);
   }

   /*
   * (non-Javadoc)
   * @see org.springframework.data.repository.CrudRepository#count()
   */
   @Override
   public long count() {
      return em.createQuery(getCountQueryString(), Long.class).getSingleResult();
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
    */
   @Override
   public long count(@Nullable Specification<T> spec) {
      return executeCountQuery(getCountQuery(spec, getDomainClass()));
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
    */
   @Transactional
   @Override
   public <S extends T> S save(S entity) {

      Assert.notNull(entity, "Entity must not be null.");

      if (entityInformation.isNew(entity)) {
         em.persist(entity);
         return entity;
      } else {
         return em.merge(entity);
      }
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java.lang.Object)
    */
   @Transactional
   @Override
   public <S extends T> S saveAndFlush(S entity) {

      S result = save(entity);
      flush();

      return result;
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#save(java.lang.Iterable)
    */
   @Transactional
   @Override
   public <S extends T> List<S> saveAll(Iterable<S> entities) {

      Assert.notNull(entities, "Entities must not be null!");

      List<S> result = new ArrayList<>();

      for (S entity : entities) {
         result.add(save(entity));
      }

      return result;
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#saveAllAndFlush(java.lang.Iterable)
    */
   @Transactional
   @Override
   public <S extends T> List<S> saveAllAndFlush(Iterable<S> entities) {

      List<S> result = saveAll(entities);
      flush();

      return result;
   }

   /*
    * (non-Javadoc)
    * @see org.springframework.data.jpa.repository.JpaRepository#flush()
    */
   @Transactional
   @Override
   public void flush() {
      em.flush();
   }

   /**
    * Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and
    * {@link Specification}.
    *
    * @param query must not be {@literal null}.
    * @param spec can be {@literal null}.
    * @param pageable must not be {@literal null}.
    * @deprecated use {@link #readPage(TypedQuery, Class, Pageable, Specification)} instead
    */
   @Deprecated
   protected Page<T> readPage(TypedQuery<T> query, Pageable pageable, @Nullable Specification<T> spec) {
      return readPage(query, getDomainClass(), pageable, spec);
   }

   /**
    * Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and
    * {@link Specification}.
    *
    * @param query must not be {@literal null}.
    * @param domainClass must not be {@literal null}.
    * @param spec can be {@literal null}.
    * @param pageable can be {@literal null}.
    */
   protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,
         @Nullable Specification<S> spec) {

      if (pageable.isPaged()) {
         query.setFirstResult((int) pageable.getOffset());
         query.setMaxResults(pageable.getPageSize());
      }

      return PageableExecutionUtils.getPage(query.getResultList(), pageable,
            () -> executeCountQuery(getCountQuery(spec, domainClass)));
   }

   /**
    * Creates a new {@link TypedQuery} from the given {@link Specification}.
    *
    * @param spec can be {@literal null}.
    * @param pageable must not be {@literal null}.
    */
   protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Pageable pageable) {

      Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
      return getQuery(spec, getDomainClass(), sort);
   }

   /**
    * Creates a new {@link TypedQuery} from the given {@link Specification}.
    *
    * @param spec can be {@literal null}.
    * @param domainClass must not be {@literal null}.
    * @param pageable must not be {@literal null}.
    */
   protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass,
         Pageable pageable) {

      Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
      return getQuery(spec, domainClass, sort);
   }

   /**
    * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
    *
    * @param spec can be {@literal null}.
    * @param sort must not be {@literal null}.
    */
   protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Sort sort) {
      return getQuery(spec, getDomainClass(), sort);
   }

   /**
    * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
    *
    * @param spec can be {@literal null}.
    * @param domainClass must not be {@literal null}.
    * @param sort must not be {@literal null}.
    */
   protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {

      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<S> query = builder.createQuery(domainClass);

      Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
      query.select(root);

      if (sort.isSorted()) {
         query.orderBy(toOrders(sort, root, builder));
      }

      return applyRepositoryMethodMetadata(em.createQuery(query));
   }

   /**
    * Creates a new count query for the given {@link Specification}.
    *
    * @param spec can be {@literal null}.
    * @deprecated override {@link #getCountQuery(Specification, Class)} instead
    */
   @Deprecated
   protected TypedQuery<Long> getCountQuery(@Nullable Specification<T> spec) {
      return getCountQuery(spec, getDomainClass());
   }

   /**
    * Creates a new count query for the given {@link Specification}.
    *
    * @param spec can be {@literal null}.
    * @param domainClass must not be {@literal null}.
    */
   protected <S extends T> TypedQuery<Long> getCountQuery(@Nullable Specification<S> spec, Class<S> domainClass) {

      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Long> query = builder.createQuery(Long.class);

      Root<S> root = applySpecificationToCriteria(spec, domainClass, query);

      if (query.isDistinct()) {
         query.select(builder.countDistinct(root));
      } else {
         query.select(builder.count(root));
      }

      // Remove all Orders the Specifications might have applied
      query.orderBy(Collections.emptyList());

      return em.createQuery(query);
   }

   /**
    * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
    *
    * @param spec can be {@literal null}.
    * @param domainClass must not be {@literal null}.
    * @param query must not be {@literal null}.
    */
   private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass,
         CriteriaQuery<S> query) {

      Assert.notNull(domainClass, "Domain class must not be null!");
      Assert.notNull(query, "CriteriaQuery must not be null!");

      Root<U> root = query.from(domainClass);

      if (spec == null) {
         return root;
      }

      CriteriaBuilder builder = em.getCriteriaBuilder();
      Predicate predicate = spec.toPredicate(root, query, builder);

      if (predicate != null) {
         query.where(predicate);
      }

      return root;
   }

   private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {

      if (metadata == null) {
         return query;
      }

      LockModeType type = metadata.getLockModeType();
      TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);

      applyQueryHints(toReturn);

      return toReturn;
   }

   private void applyQueryHints(Query query) {
      getQueryHints().withFetchGraphs(em).forEach(query::setHint);
   }

   /**
    * Executes a count query and transparently sums up all values returned.
    *
    * @param query must not be {@literal null}.
    */
   private static long executeCountQuery(TypedQuery<Long> query) {

      Assert.notNull(query, "TypedQuery must not be null!");

      List<Long> totals = query.getResultList();
      long total = 0L;

      for (Long element : totals) {
         total += element == null ? 0 : element;
      }

      return total;
   }

   private static boolean isUnpaged(Pageable pageable) {
      return pageable.isUnpaged();
   }

   /**
    * Specification that gives access to the {@link Parameter} instance used to bind the ids for
    * {@link SimpleJpaRepository#findAllById(Iterable)}. Workaround for OpenJPA not binding collections to in-clauses
    * correctly when using by-name binding.
    *
    * @author Oliver Gierke
    * @see OPENJPA-2018</a>
    */
   @SuppressWarnings("rawtypes")
   private static final class ByIdsSpecification<T> implements Specification<T> {

      private static final long serialVersionUID = 1L;

      private final JpaEntityInformation<T, ?> entityInformation;

      @Nullable ParameterExpression<Collection<?>> parameter;

      ByIdsSpecification(JpaEntityInformation<T, ?> entityInformation) {
         this.entityInformation = entityInformation;
      }

      /*
       * (non-Javadoc)
       * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
       */
      @Override
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

         Path<?> path = root.get(entityInformation.getIdAttribute());
         parameter = (ParameterExpression<Collection<?>>) (ParameterExpression) cb.parameter(Collection.class);
         return path.in(parameter);
      }
   }

   /**
    * {@link Specification} that gives access to the {@link Predicate} instance representing the values contained in the
    * {@link Example}.
    *
    * @param <T>
    * @author Christoph Strobl
    * @since 1.10
    */
   private static class ExampleSpecification<T> implements Specification<T> {

      private static final long serialVersionUID = 1L;

      private final Example<T> example;
      private final EscapeCharacter escapeCharacter;

      /**
       * Creates new {@link ExampleSpecification}.
       *
       * @param example the example to base the specification of. Must not be {@literal null}.
       * @param escapeCharacter the escape character to use for like expressions. Must not be {@literal null}.
       */
      ExampleSpecification(Example<T> example, EscapeCharacter escapeCharacter) {

         Assert.notNull(example, "Example must not be null!");
         Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!");

         this.example = example;
         this.escapeCharacter = escapeCharacter;
      }

      /*
       * (non-Javadoc)
       * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
       */
      @Override
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
         return QueryByExamplePredicateBuilder.getPredicate(root, cb, example, escapeCharacter);
      }
   }
}
profile

minlog

@jimin-log

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!