web-dev-qa-db-fra.com

mapper les résultats d'une requête Hibernate à une classe personnalisée?

Pour faire suite à une question que j’ai posée hier: Comment renseigner la classe POJO à partir d’une requête Hibernate personnalisée?

Quelqu'un peut-il me montrer un exemple de la manière de coder le code SQL suivant dans Hibernate et d’obtenir les résultats correctement?

SQL:

select firstName, lastName
from Employee

Ce que j'aimerais faire, si cela est possible dans Hibernate, est de placer les résultats dans leur propre classe de base:

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

Je pense que c'est possible dans JPA (en utilisant EntityManager), mais je n'ai pas trouvé comment le faire dans Hibernate (en utilisant SessionFactory et Session).

J'essaie de mieux apprendre à utiliser Hibernate, et même cette requête "simple" s'avère déconcertante de savoir quelle forme Hibernate renvoie les résultats et comment mapper les résultats dans ma propre classe (de base). Donc, à la fin de la routine DAO, je ferais:

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

retournant une List de Results (ma classe de base).

13
Chris
select firstName, lastName from Employee

query.setResultTransformer(Transformers.aliasToBean(MyResults.class));

Vous ne pouvez pas utiliser le code ci-dessus avec Hibernate 5 et Hibernate 4 (au moins Hibernate 4.3.6.Final), à cause d'une exception

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)

Le problème est que Hibernate convertit les alias des noms de colonnes en majuscules - firstName devient FIRSTNAME. Et il essaie de trouver un getter avec le nom getFIRSTNAME() et le setter setFIRSTNAME() dans la DTO en utilisant de telles stratégies

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

Seul PropertyAccessStrategyMapImpl.INSTANCE convient, selon Hibernate, eh bien. Donc, après cela, il essaie de faire la conversion (Map)MyResults.

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

Je ne sais pas, c'est un bug ou une fonctionnalité.

Comment résoudre

Utilisation d'alias avec des guillemets

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(); 

Utilisation d'un transformateur de résultat personnalisé

Une autre façon de résoudre le problème - en utilisant un transformateur de résultat qui ignore la casse des noms de méthodes (traitez getFirstName() comme getFIRSTNAME()). Vous pouvez écrire le vôtre ou utiliser FluentHibernateResultTransformer . Vous n’avez pas besoin d’utiliser des guillemets et des alias (si vous avez un nom de colonne égal à un nom DTO). 

Il suffit de télécharger la bibliothèque à partir de la page du projet (elle n’a pas besoin de bocaux supplémentaires): fluent-hibernate .

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

Ce transformateur peut également être utilisé pour les projections imbriquées: Comment transformer un ensemble de résultats plat avec Hibernate

13
v.ladynev

Voir AliasToBeanResultTransformer :

Transformateur de résultat qui permet de transformer un résultat en une classe spécifiée par l'utilisateur qui sera renseignée via des méthodes de définition ou des champs correspondant aux noms d'alias.

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

Votre code modifié:

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

Pour les requêtes SQL natives, voir Documentation Hibernate :

13.1.5. Renvoi des entités non gérées

Il est possible d'appliquer un ResultTransformer à des requêtes SQL natives, ce qui lui permet de renvoyer des entités non gérées.

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

Cette requête a spécifié:

  • la chaîne de requête SQL
  • un transformateur de résultat La requête ci-dessus renverra une liste de CatDTO qui a été instanciée et a injecté les valeurs de NAME et BIRTHNAME dans les propriétés ou champs correspondants.
10
dur

Vous devez utiliser un constructeur et utiliser dans hql new. Je vous laisse l’exemple de code tiré de cette question: hibernate HQL createQuery () list () tapez le type à modeler directement

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

alors votre hql

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

et votre Java (avec Hibernate)

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

YMMV, mais j’ai constaté que le facteur clé est que vous devez vous assurer d’aliaser tous les champs de votre clause SELECT avec le mot-clé SQL "AS". Je n'ai jamais eu à utiliser de guillemets autour des noms d'alias. De plus, dans votre clause SELECT, utilisez la casse et la ponctuation des colonnes réelles de votre base de données et dans les alias, utilisez la casse des champs de votre POJO. Cela a fonctionné pour moi dans Hibernate 4 et 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();

Si vous avez plusieurs tables, vous pouvez également utiliser des alias de table dans le code SQL. Cet exemple artificiel avec une table supplémentaire nommée "Department" utilise des minuscules plus traditionnelles et des traits de soulignement dans les noms de champ de base de données avec une casse de chameau dans les noms de champ 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();
1
Night Owl

Écriture (existe ce type de défis travaillant avec hibernation)

  1. Requêtes Personnalisées
  2. Requêtes personnalisées avec paramètres facultatifs
  3. Mappage des résultats de requête Hibernate Custom sur une classe personnalisée.

Je ne dis pas que l'interface EntityRepository personnalisée étend JpaRepository sur SpringBoot et que vous pouvez écrire une requête personnalisée avec @Query -> vous ne pouvez pas écrire ici de requête avec les paramètres facultatifs. si param est null, ne l'ajoutez pas à la chaîne de requête. Et vous pouvez utiliser les critères api de hibernate mais cela n’est pas recommandé dans leur documentation en raison de problèmes de performances ...

Mais il existe des solutions simples et sujettes aux erreurs et des performances optimales ...

Ecrivez votre propre classe QueryService avec les méthodes suivantes: string (réponse pour les premier et second problèmes) sql et mappera le résultat à Classe personnalisée (troisième problème) avec n'importe quelle association @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();
}


}

Cas d'utilisation - vous pouvez écrire n'importe quelle condition de type sur les paramètres de requête

 @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());

    }
0
Musa

Si vous avez une requête native, toutes les réponses ici utilisent des méthodes déconseillées pour les nouvelles versions d'Hibernate. Par conséquent, si vous utilisez la version 5.1+, voici la solution:

// 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()
0
Lisandro

Ci-dessous un résultat de transformation qui ignore la casse:

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;
    }
}
0
Stephen Gray

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

Ce problème survient lorsque les colonnes spécifiées dans SQL Query ne correspondent pas aux colonnes de la classe de mappage. 

Cela peut être dû à:

  • Mise en majuscule du nom de la colonne ou 

  • Les noms de colonne ne correspondent pas ou 

  • la colonne existe dans la requête mais manque dans la classe.

0
Pankaj Shinde