Сопоставление результатов запроса 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;
}
}