복붙노트

[SPRING] Hibernate 질의 결과를 커스텀 클래스에 매핑?

SPRING

Hibernate 질의 결과를 커스텀 클래스에 매핑?

어제 게시 한 질문에 따라 : 어떻게 사용자 정의 최대 절전 모드 쿼리에서 POJO 클래스를 채우는가?

누군가 나에게 Hibernate에서 다음 SQL을 코딩하는 방법의 예제를 보여줄 수 있고 결과를 정확하게 얻을 수 있습니까?

SQL :

select firstName, lastName
from Employee

만약 내가 Hibernate에서 가능하다면 결과는 그들 자신의 기본 클래스에 넣는 것이다.

class Results {
    private firstName;
    private lastName;
    // getters and setters
}

JPA (EntityManager 사용)에서 가능하다고 생각하지만, SessionFactory 및 Session을 사용하여 Hibernate에서 수행하는 방법을 알지 못했습니다.

나는 Hibernate를 더 잘 배우려고 노력하고 있는데,이 "간단한"질의조차도 Hibernate가 결과를 리턴하는 형태와, 결과를 내 (기본) 클래스로 매핑하는 방법을 아는 것을 혼란스럽게하고있다. 그래서 DAO 루틴의 끝에서, 나는 할 것입니다 :

List<Results> list = query.list();

결과 목록 (내 기본 클래스)을 반환합니다.

해결법

  1. ==============================

    1.

    select firstName, lastName from Employee
    
    query.setResultTransformer(Transformers.aliasToBean(MyResults.class));
    

    예외 때문에 위의 코드를 Hibernate 5와 Hibernate 4 (적어도 Hibernate 4.3.6.Final)에서 사용할 수 없다.

    java.lang.ClassCastException: com.github.fluent.hibernate.request.persistent.UserDto cannot be cast to java.util.Map
        at org.hibernate.property.access.internal.PropertyAccessMapImpl$SetterImpl.set(PropertyAccessMapImpl.java:102)
    

    문제는 Hibernate가 열 이름의 별칭을 대문자로 변환한다는 것입니다. firstName은 FIRSTNAME이됩니다. 그리고 getFIRSTNAME ()이라는 이름을 가진 getter를 찾고, 그런 전략을 사용하여 DTO에 setter setFIRSTNAME ()을 시도합니다.

        PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
                PropertyAccessStrategyBasicImpl.INSTANCE,
                PropertyAccessStrategyFieldImpl.INSTANCE,
                PropertyAccessStrategyMapImpl.INSTANCE
        );
    

    Hibernate의 견해로는 PropertyAccessStrategyMapImpl.INSTANCE만이 적합합니다. 그래서 그 변환 (지도) MyResults하려고합니다.

    public void set(Object target, Object value, SessionFactoryImplementor factory) {
        ( (Map) target ).put( propertyName, value );
    }
    

    모름, 버그 또는 기능입니다.

    해결 방법

    따옴표가있는 별칭 사용

    public class Results {
    
        private String firstName;
    
        private String lastName;
    
        public String getFirstName() {
            return firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
    }
    
    String sql = "select firstName as \"firstName\", 
        lastName as \"lastName\" from Employee";
    
    List<Results> employees = session.createSQLQuery(sql).setResultTransformer(
        Transformers.aliasToBean(Results.class)).list(); 
    

    사용자 정의 결과 변환기 사용

    문제를 해결하는 또 다른 방법 - 메서드 이름을 무시하는 결과 변환기 사용 case (getFirstName ()를 getFIRSTNAME ()으로 처리). 직접 작성하거나 FluentHibernateResultTransformer를 사용할 수 있습니다. 따옴표와 별칭을 사용할 필요가 없습니다 (열 이름이 DTO 이름과 동일한 경우).

    프로젝트 페이지에서 라이브러리를 다운로드하면됩니다 (추가 병은 필요하지 않습니다) : 유창 - 최대 절전 모드.

    String sql = "select firstName, lastName from Employee";
    List<Results> employees = session.createSQLQuery(sql)
            .setResultTransformer(new FluentHibernateResultTransformer(Results.class))
            .list();
    

    이 변환기는 중첩 프로젝션에도 사용할 수 있습니다. Hibernate를 사용하여 플랫 결과 집합을 변환하는 방법

  2. ==============================

    2.AliasToBeanResultTransformer를 참조하십시오.

    AliasToBeanResultTransformer를 참조하십시오.

    수정 된 코드 :

    List resultWithAliasedBean = s.createCriteria(Employee.class, "e")
        .setProjection(Projections.projectionList()
            .add(Projections.property("e.firstName"), "firstName")
            .add(Projections.property("e.lastName"), "lastName")
        )
        .setResultTransformer(new AliasToBeanResultTransformer(Results.class))
        .list();
    
    Results dto = (Results) resultWithAliasedBean.get(0);
    

    네이티브 SQL 쿼리의 경우 Hibernate 문서를 참조하십시오.

  3. ==============================

    3.생성자를 사용해야하고 hql에서는 new를 사용해야합니다. hibernate HQL createQuery () list () 타입을 직접 모델링하여 캐스트한다.

    생성자를 사용해야하고 hql에서는 new를 사용해야합니다. hibernate HQL createQuery () list () 타입을 직접 모델링하여 캐스트한다.

    class Result {
        private firstName;
        private lastName;
        public Result (String firstName, String lastName){
          this.firstName = firstName;
          this.lastName = lastName;
       }
    }
    

    다음 hql

    select new com.yourpackage.Result(employee.firstName,employee.lastName) 
    from Employee  
    

    와 당신의 자바 (Hibernate 사용)

    List<Result> results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();
    
  4. ==============================

    4.YMMV하지만 핵심 요소는 SQL "AS"키워드를 사용하여 SELECT 절의 모든 필드에 별칭을 지정해야한다는 것입니다. 별칭 이름을 따옴표로 묶을 필요가 없었습니다. 또한 SELECT 절에서 데이터베이스의 실제 열의 대소 문자 및 구두점을 사용하고 별칭에서는 POJO의 필드 대 / 소문자를 사용합니다. 이것은 최대 절전 모드 4와 5에서 저에게 효과적입니다.

    YMMV하지만 핵심 요소는 SQL "AS"키워드를 사용하여 SELECT 절의 모든 필드에 별칭을 지정해야한다는 것입니다. 별칭 이름을 따옴표로 묶을 필요가 없었습니다. 또한 SELECT 절에서 데이터베이스의 실제 열의 대소 문자 및 구두점을 사용하고 별칭에서는 POJO의 필드 대 / 소문자를 사용합니다. 이것은 최대 절전 모드 4와 5에서 저에게 효과적입니다.

    @Autowired
    private SessionFactory sessionFactory;
    
    ...
    
    String sqlQuery = "SELECT firstName AS firstName," +
            "lastName AS lastName from Employee";
    
    List<Results> employeeList = sessionFactory
            .getCurrentSession()
            .createSQLQuery(sqlQuery)
            .setResultTransformer(Transformers.aliasToBean(Results.class))
            .list();
    

    테이블이 여러 개인 경우 SQL에서도 테이블 별칭을 사용할 수 있습니다. "Department"라는 이름의 추가 표를 사용한이 인위적인 예제는 POJO 필드 이름에 낙타의 경우와 함께 데이터베이스 필드 이름에서보다 전통적인 소문자와 밑줄을 사용합니다.

    String sqlQuery = "SELECT e.first_name AS firstName, " +
            "e.last_name AS lastName, d.name as departmentName" +
            "from Employee e, Department d" +
            "WHERE e.department_id - d.id";
    
    List<Results> employeeList = sessionFactory
            .getCurrentSession()
            .createSQLQuery(sqlQuery)
            .setResultTransformer(Transformers.aliasToBean(Results.class))
            .list();
    
  5. ==============================

    5.네이티브 질의가있는 경우 여기에서 모든 응답은 새로운 버전의 Hibernate에 대해 더 이상 사용되지 않는 메소드를 사용하므로 5.1+를 사용하고 있다면 이것은 갈 방법입니다 :

    네이티브 질의가있는 경우 여기에서 모든 응답은 새로운 버전의 Hibernate에 대해 더 이상 사용되지 않는 메소드를 사용하므로 5.1+를 사용하고 있다면 이것은 갈 방법입니다 :

    // Note this is a org.hibernate.query.NativeQuery NOT Query.
    NativeQuery query = getCurrentSession()
                    .createNativeQuery(
                            "SELECT {y.*} , {x.*} from TableY y left join TableX x on x.id = y.id");
    
    
    // This maps the results to entities. 
    query.addEntity("x", TableXEntity.class);
    query.addEntity("y", TableYEntity.class);
    
    query.list()
    
  6. ==============================

    6.java.lang.ClassCastException : "CustomClass"를 java.util.Map으로 형변환 할 수 없습니다.

    java.lang.ClassCastException : "CustomClass"를 java.util.Map으로 형변환 할 수 없습니다.

    이 문제는 SQL 쿼리에 지정된 열이 매핑 클래스의 열과 일치하지 않을 때 나타납니다.

    그 원인은 다음과 같습니다.

  7. ==============================

    7.글쓰기 (최대 절전 모드로 작업하는이 유형의 문제 존재)

    글쓰기 (최대 절전 모드로 작업하는이 유형의 문제 존재)

    @Query로 커스텀 질의를 작성할 수있는 SpringBoot에서 JpaRepository를 확장하는 커스텀 EntityRepository 인터페이스에 대해서는 언급하지 않았다. -> 여기에서는 선택적 params로 질의를 작성할 수 없다. 예 : param가 null의 경우, 쿼리 캐릭터 라인에 추가하지 않는다 그리고 당신은 hibernate의 Criteria API를 사용할 수 있지만 성능 문제 때문에 그들의 문서에서 추천하지 않습니다 ...

    그러나 단순하고 오류가 발생하기 쉽고 성능이 좋습니다 ...

    @Service
    @Transactional
    public class HibernateQueryService {
    
        private final Logger log = LoggerFactory.getLogger(HibernateQueryService.class);
        private JpaContext jpaContext;
    
        public HibernateQueryService(JpaContext jpaContext) {
            this.jpaContext = jpaContext;
        }
    
        public List executeJPANativeQuery(String sql, Class entity){
            log.debug("JPANativeQuery executing: "+sql);
            EntityManager entityManager = jpaContext.getEntityManagerByManagedType(Article.class);
            return entityManager.createNativeQuery(sql, entity).getResultList();
        }
    
    /**
     * as annotation @Query -> we can construct here hibernate dialect 
     * supported query and fetch any type of data
     * with any association @OneToMany and @ManyToOne.....
     */
        public List executeHibernateQuery(String sql, Class entity){
            log.debug("HibernateNativeQuery executing: "+sql);
            Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
            return session.createQuery(sql, entity).getResultList();
        }
    
    public <T> List<T> executeGenericHibernateQuery(String sql, Class<T> entity){
        log.debug("HibernateNativeQuery executing: "+sql);
        Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
        return session.createQuery(sql, entity).getResultList();
    }
    
    
    }
    
     @Transactional(readOnly = true)
        public List<ArticleDTO> findWithHibernateWay(SearchFiltersVM filter){
    
            Long[] stores = filter.getStores();
            Long[] categories = filter.getCategories();
            Long[] brands = filter.getBrands();
            Long[] articles = filter.getArticles();
            Long[] colors = filter.getColors();
    
            String query = "select article from Article article " +
                "left join fetch article.attributeOptions " +
                "left join fetch article.brand " +
                "left join fetch article.stocks stock " +
                "left join fetch stock.color " +
                "left join fetch stock.images ";
    
    boolean isFirst = true;
    
            if(!isArrayEmptyOrNull(stores)){
                query += isFirst ? "where " : "and ";
                query += "stock.store.id in ("+ Arrays.stream(stores).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(brands)){
                query += isFirst ? "where " : "and ";
                query += "article.brand.id in ("+ Arrays.stream(brands).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(articles)){
                query += isFirst ? "where " : "and ";
                query += "article.id in ("+ Arrays.stream(articles).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
                isFirst = false;
            }
    
            if(!isArrayEmptyOrNull(colors)){
                query += isFirst ? "where " : "and ";
                query += "stock.color.id in ("+ Arrays.stream(colors).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
            }
    
            List<Article> articles = hibernateQueryService.executeHibernateQuery(query, Article.class);
    
    
          /**
            *  MapStruct [http://mapstruct.org/][1]
            */
            return articles.stream().map(articleMapper::toDto).collect(Collectors.toList());
    
        }
    
  8. ==============================

    8.다음은 대소 문자를 무시하는 결과 변환기입니다.

    다음은 대소 문자를 무시하는 결과 변환기입니다.

    package org.apec.abtc.dao.hibernate.transform;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.List;
    
    import org.hibernate.HibernateException;
    import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
    import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
    import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
    import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
    import org.hibernate.property.access.spi.Setter;
    import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
    
    /**
     * IgnoreCaseAlias to BeanResult Transformer
     * 
     * @author Stephen Gray
     */
    public class IgnoreCaseAliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer
    {
    
        /** The serialVersionUID field. */
        private static final long serialVersionUID = -3779317531110592988L;
    
        /** The resultClass field. */
        @SuppressWarnings("rawtypes")
        private final Class resultClass;
        /** The setters field. */
        private Setter[] setters;
        /** The fields field. */
        private Field[] fields;
        private String[] aliases;
    
        /**
         * @param resultClass
         */
        @SuppressWarnings("rawtypes")
        public IgnoreCaseAliasToBeanResultTransformer(final Class resultClass)
        {
            if (resultClass == null)
            {
                throw new IllegalArgumentException("resultClass cannot be null");
            }
            this.resultClass = resultClass;
            this.fields = this.resultClass.getDeclaredFields();
        }
    
        @Override
        public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
            return false;
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public Object transformTuple(final Object[] tuple, final String[] aliases)
        {
            Object result;
    
            try
            {
                if (this.setters == null)
                {
                    this.aliases = aliases;
    
                    setSetters(aliases);
                }
                result = this.resultClass.newInstance();
    
                for (int i = 0; i < aliases.length; i++)
                {
                    if (this.setters[i] != null)
                    {
                        this.setters[i].set(result, tuple[i], null);
                    }
                }
            }
            catch (final InstantiationException | IllegalAccessException e)
            {
                throw new HibernateException("Could not instantiate resultclass: " + this.resultClass.getName(), e);
            }
    
            return result;
        }
    
        private void setSetters(final String[] aliases)
        {
            PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
                                                                                                             PropertyAccessStrategyBasicImpl.INSTANCE,
                                                                                                             PropertyAccessStrategyFieldImpl.INSTANCE,
                                                                                                             PropertyAccessStrategyMapImpl.INSTANCE
                                                                            );
    
            this.setters = new Setter[aliases.length];
            for (int i = 0; i < aliases.length; i++)
            {
                String alias = aliases[i];
                if (alias != null)
                {
                    for (final Field field : this.fields)
                    {
                        final String fieldName = field.getName();
                        if (fieldName.equalsIgnoreCase(alias))
                        {
                            alias = fieldName;
                            break;
                        }
                    }
                    setters[i] = propertyAccessStrategy.buildPropertyAccess( resultClass, alias ).getSetter();
                }
            }
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        @SuppressWarnings("rawtypes")
        public List transformList(final List collection)
        {
            return collection;
        }
    
        @Override
        public boolean equals(Object o) {
            if ( this == o ) {
                return true;
            }
            if ( o == null || getClass() != o.getClass() ) {
                return false;
            }
    
            IgnoreCaseAliasToBeanResultTransformer that = ( IgnoreCaseAliasToBeanResultTransformer ) o;
    
            if ( ! resultClass.equals( that.resultClass ) ) {
                return false;
            }
            if ( ! Arrays.equals( aliases, that.aliases ) ) {
                return false;
            }
    
            return true;
        }
    
        @Override
        public int hashCode() {
            int result = resultClass.hashCode();
            result = 31 * result + ( aliases != null ? Arrays.hashCode( aliases ) : 0 );
            return result;
        }
    }
    
  9. from https://stackoverflow.com/questions/37420401/mapping-hibernate-query-results-to-custom-class by cc-by-sa and MIT license