Im Anschluss an eine Frage, die ich gestern gepostet habe: Wie fülle ich die POJO-Klasse aus einer benutzerdefinierten Hibernate-Abfrage aus?
Kann mir jemand ein Beispiel zeigen, wie die folgende SQL in Hibernate codiert wird, und die Ergebnisse korrekt abrufen?
SQL:
select firstName, lastName
from Employee
Was ich gerne tun würde, wenn es in Hibernate möglich ist, ist, die Ergebnisse in eine eigene Basisklasse zu stecken:
class Results {
private firstName;
private lastName;
// getters and setters
}
Ich glaube, dass dies in JPA (mit EntityManager
) möglich ist, aber ich habe nicht herausgefunden, wie es in Hibernate (mit SessionFactory
und Session
) funktioniert.
Ich versuche, Hibernate besser zu lernen, und selbst diese "einfache" Abfrage erweist sich als verwirrend, zu wissen, welche Form Hibernate die Ergebnisse zurückgibt und wie die Ergebnisse in meiner eigenen (Basis-) Klasse zugeordnet werden. Am Ende der DAO-Routine würde ich Folgendes tun:
List<Results> list = query.list();
rückgabe einer List
von Results
(meiner Basisklasse).
select firstName, lastName from Employee
query.setResultTransformer(Transformers.aliasToBean(MyResults.class));
Mit Hibernate 5 und Hibernate 4 (zumindest Hibernate 4.3.6.Final) können Sie den obigen Code aufgrund einer Ausnahme nicht verwenden
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)
Das Problem ist, dass Hibernate Aliase für Spaltennamen in Großbuchstaben konvertiert - firstName
wird FIRSTNAME
. Und es wird versucht, einen Getter mit dem Namen getFIRSTNAME()
und dem Setter setFIRSTNAME()
in der DTO
zu finden, der solche Strategien verwendet
PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
PropertyAccessStrategyBasicImpl.INSTANCE,
PropertyAccessStrategyFieldImpl.INSTANCE,
PropertyAccessStrategyMapImpl.INSTANCE
);
Nur PropertyAccessStrategyMapImpl.INSTANCE
passt, nach Ansicht von Hibernate, gut. Danach versucht es, die Konvertierung (Map)MyResults
durchzuführen.
public void set(Object target, Object value, SessionFactoryImplementor factory) {
( (Map) target ).put( propertyName, value );
}
Weiß nicht, es ist ein Fehler oder eine Funktion.
Wie löst man
Aliase mit Anführungszeichen verwenden
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();
Verwenden eines benutzerdefinierten Ergebniswandlers
Eine andere Möglichkeit, das Problem zu lösen, ist die Verwendung eines Ergebnistransformators, bei dem die Methodennamen ignoriert werden (behandeln Sie getFirstName()
als getFIRSTNAME()
). Sie können selbst schreiben oder FluentHibernateResultTransformer verwenden. Sie müssen keine Anführungszeichen und Aliase verwenden (wenn Spaltennamen den DTO-Namen entsprechen).
Laden Sie einfach die Bibliothek von der Projektseite herunter (es werden keine zusätzlichen Gläser benötigt): fluent-hibernate .
String sql = "select firstName, lastName from Employee";
List<Results> employees = session.createSQLQuery(sql)
.setResultTransformer(new FluentHibernateResultTransformer(Results.class))
.list();
Dieser Transformator kann auch für verschachtelte Projektionen verwendet werden: So transformieren Sie eine flache Ergebnismenge mit Hibernate
Siehe AliasToBeanResultTransformer :
Ergebnistransformator, der die Umwandlung eines Ergebnisses in eine vom Benutzer angegebene Klasse ermöglicht, die über Setter-Methoden oder Felder gefüllt wird, die mit den Aliasnamen übereinstimmen.
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);
Ihr geänderter Code:
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);
Für native SQL-Abfragen siehe Hibernate-Dokumentation :
13.1.5. Nicht verwaltete Entitäten zurückgeben
Es ist möglich, einen ResultTransformer auf native SQL-Abfragen anzuwenden, sodass nicht verwaltete Entitäten zurückgegeben werden können.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") .setResultTransformer(Transformers.aliasToBean(CatDTO.class))
Diese Abfrage spezifizierte:
- die SQL-Abfragezeichenfolge
- ein result transformator Die obige Abfrage gibt eine Liste von
CatDTO
zurück, die instanziiert wurde und die Werte von NAME und BIRTHNAME in die entsprechenden Eigenschaften oder Felder eingefügt hat.
Sie müssen einen Konstruktor verwenden und in der Hql new verwenden. Ich ließ Ihnen das Codebeispiel aus dieser Frage entnehmen: hibernate HQL createQuery () list () Typ direkt in Modell umwandeln
class Result {
private firstName;
private lastName;
public Result (String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
dann deine Hql
select new com.yourpackage.Result(employee.firstName,employee.lastName)
from Employee
und Ihr Java (mit Ruhezustand)
List<Result> results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();
YMMV, aber ich habe festgestellt, dass der Schlüsselfaktor ist, dass Sie jedes Feld in Ihrer SELECT-Klausel mit dem SQL-Schlüsselwort "AS" als Aliasnamen angeben müssen. Ich musste noch nie Anführungszeichen um die Aliasnamen verwenden. Verwenden Sie in Ihrer SELECT-Klausel außerdem die Groß- und Kleinschreibung der tatsächlichen Spalten in Ihrer Datenbank und in den Aliasnamen die Groß- und Kleinschreibung der Felder in Ihrem POJO. Das hat in Hibernate 4 und 5 für mich funktioniert.
@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();
Wenn Sie über mehrere Tabellen verfügen, können Sie auch Tabellenaliasnamen in SQL verwenden. Dieses Beispiel mit einer zusätzlichen Tabelle mit dem Namen "Department" verwendet traditionellere Kleinbuchstaben und Unterstriche in Datenbankfeldnamen mit Kamelbuchstaben in den POJO-Feldnamen.
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();
Schreiben (existiert diese Art von Herausforderungen, die mit dem Ruhezustand arbeiten)
Ich sage nicht über eine benutzerdefinierte EntityRepository-Schnittstelle, die JpaRepository auf SpringBoot erweitert, mit der Sie eine benutzerdefinierte Abfrage mit @Query schreiben können. Wenn param null ist, hängt es nicht in der Abfragezeichenfolge an. Sie können Criteria api of hibernate verwenden, dies wird jedoch in der Dokumentation aufgrund von Leistungsproblemen nicht empfohlen.
Aber es gibt einen einfachen und fehleranfälligen und leistungsfähigen Weg ...
Schreiben Sie Ihre eigene QueryService-Klasse, die als Methoden erhalten wird string (Antwort auf das erste und zweite Problem) sql und ordnet das Ergebnis auf Benutzerdefinierte Klasse (drittes Problem) mit jeder Assoziation @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();
}
}
Anwendungsfall - Sie können jede Typbedingung zu Abfrageparametern schreiben
@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());
}
Falls Sie eine native Abfrage haben, verwenden alle Antworten hier veraltete Methoden für neuere Versionen von Hibernate. Wenn Sie also 5.1 oder höher verwenden, ist dies der Weg, den Sie verwenden müssen:
// 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()
Unten ist ein Ergebnistransformator, der den Fall ignoriert:
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;
}
}
Java.lang.ClassCastException: "CustomClass" cannot be cast to Java.util.Map.
Dieses Problem tritt auf, wenn die in SQL Query angegebenen Spalten nicht mit den Spalten der Mapping-Klasse übereinstimmen.
Es kann sein, dass:
Nicht übereinstimmendes Gehäuse des Spaltennamens oder
Die Spaltennamen stimmen nicht mit oder überein
spalte existiert in der Abfrage, fehlt aber in der Klasse.