Сопоставление результатов запроса Hibernate с пользовательским классом?

Следуя за вопросом, который я опубликовал вчера: Как заполнить класс POJO из пользовательского запроса Hibernate?

Может ли кто-нибудь показать мне пример того, как закодировать следующий SQL в Hibernate и правильно ли получить результаты?

SQL:

select firstName, lastName
from Employee

Что бы я хотел сделать, если это возможно в Hibernate, это поставить результаты в свой собственный базовый класс:

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

Я полагаю, что это возможно в JPA (используя EntityManager), но я не понял, как это сделать в Hibernate (используя SessionFactory и Session).

Я пытаюсь узнать Hibernate лучше, и даже этот "простой" запрос вызывает недоумение узнать, какая форма Hibernate возвращает результаты, и как сопоставить результаты в моем собственном (базовом) классе. Поэтому в конце процедуры DAO я бы сделал:

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

возвращает List из Results (мой базовый класс).

Ответ 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() и setter setFIRSTNAME() в DTO, используя такие стратегии

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

Только PropertyAccessStrategyMapImpl.INSTANCE подходит, по мнению Hibernate, хорошо. Поэтому после этого он пытается выполнить преобразование (Map)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(); 

Использование настраиваемого трансформатора результатов

Другой способ решить проблему - использовать трансформатор результатов, который игнорирует регистр имен методов (лечить getFIRSTNAME() как getFIRSTNAME()). Вы можете написать свой собственный или использовать FluentHibernateResultTransformer. Вам не нужно использовать кавычки и псевдонимы (если имена столбцов равны именам DTO).

Просто загрузите библиотеку с страницы проекта (ей не нужны дополнительные банки): fluent-hibernate.

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

Этот трансформатор может также использоваться для вложенных проекций: Как преобразовать плоский результирующий набор с использованием Hibernate

Ответ 2

См. AliasToBeanResultTransformer:

Трансформатор результатов, который позволяет преобразовать результат в указанный пользователем класс, который будет заполняться с помощью методов setter или полей, соответствующих именам псевдонимов.

List resultWithAliasedBean = s.createCriteria(Enrolment.class)
            .createAlias("student", "st")
            .createAlias("course", "co")
            .setProjection( Projections.projectionList()
                    .add( Projections.property("co.description"), "courseDescription" )
            )
            .setResultTransformer( new AliasToBeanResultTransformer(StudentDTO.class) )
            .list();

StudentDTO dto = (StudentDTO)resultWithAliasedBean.get(0);

Ваш измененный код:

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 documentation:

13.1.5. Возврат не управляемых объектов

Можно применить ResultTransformer к собственным SQL-запросам, позволяя ему возвращать не управляемые объекты.

sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
    .setResultTransformer(Transformers.aliasToBean(CatDTO.class))

Указанный запрос:

  • строка запроса SQL
  • трансформатор результата Вышеприведенный запрос вернет список CatDTO, который был создан, и ввел значения NAME и BIRTHNAME в соответствующие им свойства или поля.

Ответ 3

Вам нужно использовать конструктор, а в hql - новый. Я даю вам пример кода, взятый из этого вопроса: 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  

и ваш java (с помощью Hibernate)

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

Ответ 4

YMMV, но я обнаружил, что ключевым фактором является то, что вы должны иметь псевдоним каждого поля в предложении SELECT с ключевым словом SQL "AS". Мне никогда не приходилось использовать кавычки вокруг имен псевдонимов. Кроме того, в вашем предложении SELECT используйте случай и пунктуацию фактических столбцов в вашей базе данных и в псевдонимах используйте поля полей вашего POJO. Это сработало для меня в Hibernate 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. Этот надуманный пример с дополнительной таблицей с именем "Департамент" использует более традиционные строчные буквы и подчеркивания в именах полей базы данных с верблюжьим флагом в именах полей 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

Если у вас есть собственный запрос, все ответы здесь используют устаревшие методы для более новых версий 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

java.lang.ClassCastException: "CustomClass" cannot be cast to java.util.Map.

Эта проблема возникает, когда столбцы, указанные в SQL Query, не соответствуют столбцам класса сопоставления.

Это может быть связано с:

  • Неподходящая оболочка с именем столбца или

  • Имена столбцов не соответствуют или

  • столбец существует в запросе, но отсутствует в классе.

Ответ 7

Написание (существует этот тип проблем, работающих с спящим)

  • Пользовательские запросы
  • Пользовательские запросы с дополнительными параметрами
  • Отображение Hibernate Пользовательские результаты запроса в пользовательский класс.

Я не говорю о пользовательском интерфейсе EntityRepository, который расширяет JpaRepository на SpringBoot, который вы можете написать пользовательский запрос с помощью @Query → здесь вы не можете писать запрос с необязательными параметрами например Если параметр равен null, не добавляйте его в строку запроса. И вы можете использовать Criteria api в спящем режиме, но не рекомендуется в своей документации из-за проблемы с производительностью...

Но существуют простые и подверженные ошибкам и эффективные результаты...

Напишите свой собственный класс QueryService, который будет получать методы строка (ответ для первой и второй проблемы) sql и будет отображать результат в Пользовательский класс (третья проблема) с ним любой ассоциации @OneToMany, @ManyToOne....

@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

Ниже приведен результат трансформатора, который игнорирует случай:

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;
    }
}