2016-05-24 1 views
9

Facendo seguito a una domanda che ho postato ieri: How to populate POJO class from custom Hibernate query?mappatura Hibernate risultati delle query alla classe personalizzata?

Qualcuno mi può mostrare un esempio di come codificare il seguente SQL in Hibernate, e ottenere i risultati in modo corretto?

SQL:

select firstName, lastName 
from Employee 

Quello che mi piacerebbe fare, se è possibile in Hibernate, è quello di mettere i risultati nella propria classe di base:

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

Credo che sia possibile in JPA (usando EntityManager), ma non ho capito come farlo in Hibernate (usando SessionFactory e Session).

Sto cercando di imparare Hibernate meglio, e anche questa "semplice" query si sta rivelando fuorviante per sapere quale forma Hibernate restituisce i risultati e come mappare i risultati nella mia (base) classe. Così, alla fine della routine DAO, farei:

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

restituzione di un List di Results (la mia classe di base).

risposta

7

Vedi AliasToBeanResultTransformer:

trasformatore Risultato che permette di trasformare un risultato a un classe specificata utente che verrà popolata tramite metodi setter o campi che corrispondono ai nomi di 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); 

tuo codice modificato:

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

per le query SQL nativo vedere Hibernate documentation:

13.1.5. Restituzione di entità non gestite

È possibile applicare un ResultTransformer a query SQL native, consentendo di restituire entità non gestite.

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

Questa interrogazione specificato:

  • la stringa di query SQL
  • un trasformatore risultato La query precedente restituirà un elenco di CatDTO che è stata istanziata e iniettato i valori di NOME e nome di battesimo in le sue proprietà o campi corrispondenti.
+1

divertente, posso riprodurre il problema con Hibernate 5.1. Il trasformatore 'Transformers.aliasToBean (CatDTO.class)' non funziona. Ho appena provato con il mio trasformatore prima :) Cerca di capire perché. –

+1

È molto più divertente, avevo ragione con gli alias. Vedi la mia risposta: http://stackoverflow.com/a/37423885/3405171 –

3

È necessario utilizzare un costruttore e in hql utilizzare nuovo.ti lascio l'esempio di codice preso da questa domanda: hibernate HQL createQuery() list() type cast to model directly

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

allora il vostro HQL

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

e la tua Java (usando Hibernate)

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

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

Non è possibile utilizzare sopra il codice con Hibernate 5 e Hibernate 4 (almeno Hibernate 4.3.6.Final), a causa di un'eccezione

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) 

Il problema è che Hibernate converte alias per i nomi di colonna in maiuscolo - firstName diventa FIRSTNAME. E cercare di trovare un getter con il nome getFIRSTNAME(), e Setter setFIRSTNAME() nel DTO utilizzando tali strategie

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

Solo PropertyAccessStrategyMapImpl.INSTANCE abiti, a parere di Hibernate, bene. Quindi dopo prova a fare la conversione (Map)MyResults.

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

Non so, si tratta di un bug o di una funzionalità.

Come risolvere

Utilizzando gli alias con le citazioni

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

Utilizzando un risultato personalizzato trasformatore

Un altro modo per risolvere il problema - utilizzando un trasformatore risultato che ignora nome dei metodi caso (trattare getFirstName() come getFIRSTNAME()). Puoi scrivere il tuo o usare FluentHibernateResultTransformer. Non è necessario utilizzare virgolette e alias (se i nomi delle colonne sono uguali ai nomi DTO).

Basta scaricare la libreria dalla pagina del progetto (non necessita di ulteriori giare): fluent-hibernate.

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

Questo trasformatore può essere utilizzato per le proiezioni nidificati troppo: How to transform a flat result set using Hibernate

0

misura con le pinze, ma ho trovato che il fattore chiave è che deve fare in modo di alias ogni campo nel clausola SELECT con SQL "AS " parola chiave. Non ho mai dovuto usare le virgolette sui nomi degli alias. Inoltre, nella clausola SELECT utilizzare il caso e la punteggiatura delle colonne effettive nel database e negli alias utilizzare il caso dei campi nel POJO. Questo ha funzionato per me in Hibernate 4 e 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(); 

Se si dispone di più tabelle è possibile utilizzare alias di tabella nella SQL pure. Questo esempio forzato con una tabella aggiuntiva denominata "Reparto" utilizza più caratteri minuscoli e caratteri di sottolineatura meno evidenti nei nomi dei campi del database con caso cammello nei nomi dei campi 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(); 
0

Nel caso in cui si dispone di una query nativo, tutte le risposte qui usare metodi obsoleti per le versioni più recenti di Hibernate, quindi se si utilizza 5.1+ Questa è la strada da percorrere:

// 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

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

Questo problema si verifica quando le colonne specificate in SQL Query non corrispondono alle colonne della classe di mapping.

Può essere dovuta a:

  • non-matching carcassa del nome di colonna o

  • I nomi di colonna non sono corrispondenti o

  • colonna esiste nella query, ma manca in classe .

0

scrittura (esiste questo tipo di sfide lavorare con Hibernate)

  1. personalizzato Query
  2. query personalizzate con i parametri facoltativi
  3. Mapping Hibernate personalizzati i risultati delle query per classe personalizzata.

Non sto dicendo su un'interfaccia personalizzata EntityRepository che si estende su JpaRepository SpringBoot cui è possibile scrivere personalizzato query con @ query -> qui non si può scrivere query con params opzionali esempio se param è nullo non aggiungerlo nella stringa di query. Ed è possibile utilizzare criteri di API di Hibernate, ma non è raccomandato nella loro documentazione a causa del problema di prestazioni ...

Ma esiste semplice e soggetto a errori e le prestazioni buon modo ...

Scrivi la tua classe QueryService che sono i metodi otterrà stringa (risposta per primo e il secondo problema) sql e mapperà provocare a classe personalizzata (terzo problema) con la sua qualunque forma di associazione @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(); 
} 


} 

Caso d'uso - è possibile scrivere qualsiasi condizione tipo di query su params

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

    } 
Problemi correlati