如何使用Hibernate Criteria懒惰地获取字段

应阅读表格Person (具有namefirstnameage )的每一行。

 EntityManager em = emf.createEntityManager(); Session s = (Session) em.getDelegate(); Criteria criteria = s.createCriteria(Person.class); criteria.setFetchMode("age", FetchMode.SELECT); 

但SQL显示

 Hibernate: select person0_.name, person0_.firstname, person0_.age from SCOPE.PERSON person0_ 

如何让年龄为标准而懒惰?

我认为懒惰模式只对协会有意义。 如果您正在访问普通表,它将加载所有字段。

如果您希望age字段不出现在SQL中,因此不会加载到内存中,那么使用投影:

 Criteria crit = session.createCriteria(Person.class); ProjectionList projList = Projections.projectionList(); projList.add(Projections.property("name")); projList.add(Projections.property("firstname")); crit.setProjection(projList); 

在条件上设置“age”属性的FetchMode没有任何效果,因为此时的提取策略仅适用于关联对象,但不适用于属性。 见第20.1节。 获取 hibernate文档的策略 。

如果应用程序需要导航关联,Hibernate使用提取策略来检索关联对象 。 获取策略可以在O / R映射元数据中声明,也可以由特定的HQL或Criteria查询覆盖。

延迟加载属性的唯一方法是将FetchType.LAZY注释设置为FetchType.LAZY 。 请参阅此处 ,或者如果使用.hbm.xml文件进行映射,请使用lazy=true ,请参阅hibernate docs的这一部分。

@Basic注释允许您声明属性提取策略 。 如果设置为LAZY,则指定在首次访问实例变量时应该懒惰地获取此属性。 它需要构建时字节码检测,如果您的类没有检测,则会默默忽略属性级延迟加载。

延迟加载属性也使用构建时字节码实例 (hibernate在编译后更改实体类以允许延迟加载属性)。 阅读20.1.8。 使用延迟属性获取

您的问题的另一个可能的解决方案(除了所有其他解决方案)是创建一个更简单的Person类并使用构造函数查询,如:

 public class PersonDTO { private String name; private String firstname; private Person(String name, String firstname) { this.name = name; this.firstname = firstname; } // getters & setters } Query q = session.createQuery("select new your.package.name.PersonDTO(" + "p.name, p.firstname) from Person p"); q.list(); 

您甚至可以使用现有的Person类,只需使用适当的构造函数扩展它,但我更喜欢显式。

但是这里提出的所有解决方案都没有实现延迟加载age属性。 唯一的方法是@Basic注释,或者你必须实现自己的延迟加载。

如果你的年龄是像@Dragan的PersonAge这样的对象,你可以将fecth模式与标准相关联,而不是像你那样将实体关联起来。

所以,我认为你有三个选择:

  1. 像@Paco这样的原始和投影的年龄(Person.age将为null而不是代理,你会失去你想要的懒惰)
  2. 没有投影的原始年龄(电线中的字节数更多)
  3. age作为PersonAge + criteria.setFetchMode(您将以额外的对象/表/映射为代价获得所需的延迟)

对于Projection,您可以使用ResultTransformer

 Criteria crit = session.createCriteria(Person.class); ProjectionList projList = Projections.projectionList(); projList.add(Projections.property("name")); projList.add(Projections.property("firstname")); crit.setProjection(projList); crit.setResultTransformer(new ResultTransformer() { @Override public Object transformTuple(Object[] tuple, String[] aliases) { String name = (Long) tuple[0]; String firstName = (String) tuple[1]; return new Person(name , firstName); } @Override public List transformList(List collection) { return collection; } }); 

我认为您可以自己创建一个PersonProxy来触发查询来检索年龄,但这有点糟糕。

  @Override public Object transformTuple(Object[] tuple, String[] aliases) { String name = (Long) tuple[0]; String firstName = (String) tuple[1]; return new PersonProxy(name , firstName); } class PersonProxy { Person realPerson; public getAge(){ // create a query with realPerson.id for retrieve the age. } } 

你的推理是有效的(一般来说,我们可以争论age领域的具体例子),但不幸的是,没有直接的解决方案。 实际上,Hibernate具有获取配置文件的概念,但它目前非常有限(您只能使用连接样式的获取配置文件覆盖默认的获取计划/策略)。

因此,您的问题可能的解决方法如下。

1)将age移至单独的实体,并将Person实体与其关联,并使其具有懒惰的一对一关系:

 @Entity class PersonAge { private Integer age; } @Entity class Person { @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false) @JoinColumn(name = "PERSON_AGE_ID") private PersonAge personAge; public Integer getAge() { return personAge.getAge(); } public void setAge(Integer age) { personAge.setAge(age); } } 

2)定义一个覆盖默认值的获取配置文件:

 @FetchProfile(name = "person-with-age", fetchOverrides = { @FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN) }) 

3)为应用程序中的每个会话启用此配置文件:

 session.enableFetchProfile("person-with-age"); 

根据您使用的框架,应该有一个简单的钩子/拦截器,您将使用它来为每个会话(事务)启用配置文件。 例如,Spring中的方法可能是覆盖正在使用的事务管理器的AbstractPlatformTransactionManager.doBegin 。

这样,除非明确禁用了获取配置文件,否则personAge将被急切地加载到应用程序中的所有会话中。

4)在使用所需Criteria查询的会话中禁用获取配置文件:

 session.disableFetchProfile("person-with-age"); 

这样就使用了默认的获取计划/策略(在实体映射中指定),这是PersonAge的延迟加载。

您可以简单地定义一个新实体SimplePerson映射到同一persons数据库表,该表只包含以下属性:

  • ID
  • 名称
  • 名字

这样,当选择具有Criteria和HQL的SimplePerson时,将不会检索年龄列。

另一种方法是对基本属性使用延迟加载 ,但将多个子实例映射到同一数据库表要灵活得多。