将Hibernate查询结果映射到自定义类?
继续我昨天发布的一个问题: 如何从自定义Hibernate查询中填充POJO类?
有人能告诉我一个如何在Hibernate中编写以下SQL的示例,并正确获得结果吗?
SQL:
select firstName, lastName from Employee
如果在Hibernate中可行,我想做的是将结果放在他们自己的基类中:
class Results { private firstName; private lastName; // getters and setters }
我相信它在JPA(使用EntityManager
)中是可能的,但我还没有弄清楚如何在Hibernate中使用它(使用SessionFactory
和Session
)。
我正在努力学习更好的Hibernate,甚至这个“简单”的查询也很容易让人知道Hibernate返回结果的forms,以及如何将结果映射到我自己的(基础)类中。 所以在DAO例程结束时,我会这样做:
List list = query.list();
返回Results
List
(我的基类)。
select firstName, lastName from Employee query.setResultTransformer(Transformers.aliasToBean(MyResults.class));
由于exception,你不能在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
。 它尝试使用这样的策略在DTO
找到名为getFIRSTNAME()
的getter和setter setFIRSTNAME()
PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl( PropertyAccessStrategyBasicImpl.INSTANCE, PropertyAccessStrategyFieldImpl.INSTANCE, PropertyAccessStrategyMapImpl.INSTANCE );
鉴于Hibernate,只有PropertyAccessStrategyMapImpl.INSTANCE
适合。 所以在那之后它尝试进行转换(Map)MyResults
。
public void set(Object target, Object value, SessionFactoryImplementor factory) { ( (Map) target ).put( propertyName, value ); }
不知道,这是一个bug或function。
怎么解决
使用带引号的别名
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 employees = session.createSQLQuery(sql).setResultTransformer( Transformers.aliasToBean(Results.class)).list();
使用自定义结果转换器
另一种解决问题的方法 – 使用忽略方法名称的结果转换器case(将getFirstName()
视为getFIRSTNAME()
)。 您可以自己编写或使用FluentHibernateResultTransformer 。 您不需要使用引号和别名(如果列名等于DTO名称)。
只需从项目页面下载库(它不需要额外的jar): fluent-hibernate 。
String sql = "select firstName, lastName from Employee"; List employees = session.createSQLQuery(sql) .setResultTransformer(new FluentHibernateResultTransformer(Results.class)) .list();
此变换器也可用于嵌套投影: 如何使用Hibernate转换平面结果集
请参阅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文档 :
13.1.5。 返回非托管实体
可以将ResultTransformer应用于本机SQL查询,从而允许它返回非托管实体。
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") .setResultTransformer(Transformers.aliasToBean(CatDTO.class))
此查询指定:
- SQL查询字符串
- 结果转换器上面的查询将返回已实例化的
CatDTO
列表,CatDTO
NAME和BIRTHNAME的值注入其相应的属性或字段中。
你需要使用构造函数并在hql中使用new。 我让你从这个问题中得到的代码示例: 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 results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();
YMMV但我发现关键因素是您必须确保使用SQL“AS”关键字为SELECT子句中的每个字段设置别名。 我从来没有在别名周围使用引号。 此外,在SELECT子句中,使用数据库中实际列的大小写和标点符号以及别名使用POJO中字段的大小写。 这在Hibernate 4和5中对我有用。
@Autowired private SessionFactory sessionFactory; ... String sqlQuery = "SELECT firstName AS firstName," + "lastName AS lastName from Employee"; List employeeList = sessionFactory .getCurrentSession() .createSQLQuery(sqlQuery) .setResultTransformer(Transformers.aliasToBean(Results.class)) .list();
如果您有多个表,则也可以在SQL中使用表别名。 这个设计的示例带有一个名为“Department”的附加表,在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 employeeList = sessionFactory .getCurrentSession() .createSQLQuery(sqlQuery) .setResultTransformer(Transformers.aliasToBean(Results.class)) .list();
如果你有一个原生查询,这里的所有答案都使用新版本的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()
java.lang.ClassCastException: "CustomClass" cannot be cast to java.util.Map.
当SQL Query中指定的列与映射类的列不匹配时,会出现此问题。
这可能是由于:
-
列名称不匹配的shell或
-
列名称不匹配或
-
列存在于查询中但在类中缺失。
写作(存在这种与hibernate一起使用的挑战)
- 自定义查询
- 具有可选参数的自定义查询
- 将Hibernate自定义查询结果映射到Custom类。
我不是说在SpringBoot上扩展JpaRepository的自定义EntityRepository接口,你可以用@Query编写自定义查询 – >这里你不能用可选参数写入查询,例如如果param为null则不在查询字符串中附加它。 你可以使用hibernate的Criteria api,但由于性能问题,不建议在他们的文档中使用它…
但存在简单易出错和性能良好的方式……
编写自己的QueryService类,方法将获取字符串(第一个和第二个问题的答案)sql,并将结果映射到Custom类(第三个问题),任意关联@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 List executeGenericHibernateQuery(String sql, Class 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 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 articles = hibernateQueryService.executeHibernateQuery(query, Article.class); /** * MapStruct [http://mapstruct.org/][1] */ return articles.stream().map(articleMapper::toDto).collect(Collectors.toList()); }