应阅读表格Person
(有name
,firstname
和age
)的每一行。
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没有任何效果,因为此时的提取策略仅适用于关联对象,但不适用于属性。请参阅hibernate docs的20.1. Fetching strategies部分。
如果应用程序需要导航关联,Hibernate使用提取策略来检索关联对象。获取策略可以在O / R映射元数据中声明,也可以由特定的HQL或Criteria查询覆盖。
延迟加载属性的唯一方法是将@Basic
注释设置为FetchType.LAZY
。请参阅here,或者如果使用.hbm.xml文件进行映射,请使用lazy=true
,请参阅hibernate docs的this部分。
@Basic注释允许您声明属性的提取策略。如果设置为LAZY,则指定在首次访问实例变量时应该懒惰地获取此属性。它需要构建时字节码检测,如果没有检测类,则会默默忽略属性级延迟加载。
延迟加载属性也使用构建时字节码实例(hibernate在编译后更改实体类以允许延迟加载属性)。阅读20.1.8. Using lazy property fetching
你的问题的另一个可能的解决方案(除了所有其他解决方案)是制作一个更简单的Person类并使用constructor query,如:
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
annotation,或者你必须实现自己的延迟加载。
如果你的年龄是像@Dragan的PersonAge这样的对象,你可以将fecth模式与标准相关联,而不是像你那样将实体关联起来。
所以,我认为你有三个选择:
对于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<Reference> 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具有fetch profiles的概念,但它目前非常有限(您只能使用连接样式的获取配置文件覆盖默认的获取计划/策略)。
因此,您的问题可能的解决方法如下。
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
,该数据库表仅包含以下属性:
这样,当选择具有Criteria和HQL的SimplePerson
时,将不会检索年龄列。
另一种方法是使用lazy loading for basic attributes,但将多个子实体映射到同一个数据库表更加灵活。