我有一个User
类和一个Role
类。这两个类都是JPA实体,因此存储在Person
表和Role
表中,以及用于联接的对应链接表Person_Role
中,因为关联是多对多的。一个用户可能有许多角色,并且一个角色可能被分配给许多用户。
@Entity
@Table(name="role")
public class Role implements Comparable<Role>
{
// data members
@Id @GeneratedValue
private int id; // primary key
private String name; // name of the role
private String description; // description of the role
...
}
@Entity
@Table(name="person")
public class Person implements Comparable<Person>
{
// data members
@Id @GeneratedValue
protected int id; // the primary key
protected String username; // the user's unique user name
protected String firstName; // the user's first name
protected String lastName; // the user's last name
protected String email; // the user's work e-mail address
@Transient
protected String history; // chronological list of changes to the person
// don't want to load this unless an explicit call to getHistory() is made
@Transient
protected Set<Role> roles; // list of roles assigned to the user
// don't want to load this unless an explicit call to getRoles() is made
...
}
User实体在整个应用程序中被广泛使用,因为它是许多对象的共享引用,并在许多搜索中使用。99.99%的情况下,不需要用户的角色和历史记录。我是JPA的新手,为了学习,我一直在阅读"Java Persistence with Hibernate"一书。正如我所理解的惰性获取,当调用任何getXXX()方法时,它将从数据库加载所有相应的User
数据。
例如:user.getFirstName()
会导致数据库命中并加载用户的所有数据,包括角色和历史记录。
我想不惜一切代价避免这种情况。在99.99%的用例中,这是不必要的。那么,处理这个问题的最佳方法是什么?
我最初的想法是将User
类中的Set<Role>
角色和Set<String>
历史标记为@Transient
,并且只有在调用user.getRoles()
或user.getHistory()
方法时才能手动查询角色和历史。
谢谢你的建议。
正如我所理解的惰性抓取,它将加载所有相应的User当调用任何getXXX()方法时,数据库中的数据。
您可以强制JPA在从数据库中获取数据时表现出急切或懒散,但这首先取决于JPA提供者。如JPA 2.1规范第11.1.6章:所述
FetchType
枚举定义了从数据库:public enum FetchType { LAZY, EAGER };
EAGER
策略是持久性提供程序的要求运行时,必须急切地提取数据。LAZY
策略是向持久性提供程序运行时提示首次访问数据时应延迟获取数据。允许实施急切地获取LAZY
策略提示已被指定。特别是,延迟获取可能仅适用于使用基于属性的访问的Basic
映射。
关于抓取策略如何工作以及它们在现实场景中的性能的精彩演示,您可以在这里找到。
Ex:user.getFirstName()会导致数据库命中并加载所有用户的数据,包括角色和历史记录。
数据可以直接从持久性上下文中检索(通常使用寿命较短),也可以间接从底层数据库中检索(当在事务/共享缓存中找不到数据时)。如果实体管理器被请求获取您的实体对象,而它在持久性上下文中不存在,那么它需要更深入——在最坏的情况下进入数据库。
我想不惜一切代价避免这种情况。99.99%的用例。那么,处理这个问题的最佳方法是什么?
一个示例方法:
@Entity
@NamedQuery(name="Person.getNameById",
query="SELECT p.name FROM Person p WHERE p.id = :id")
public class Person
{
@Id @GeneratedValue
protected int id;
private String name; //the sole attribute to be requested
@ManyToMany //fetch type is lazy by default
@JoinTable
protected Set<Role> roles; //not loaded until referenced or accessed
...
}
通常最好的方法是find
方法。当你想一次检索所有非关系属性时,这是完美的:
Person p = em.find(Person.class, id)
另一种选择是使用命名查询。当你需要一个属性或一小部分属性时,它很有用:
String name = em.createNamedQuery("Person.getNameById", String.class)
.setParameter("id", id)
.getSingleResult()
我最初的想法是在用户类为@Transient,并手动查询角色和历史记录仅当调用user.getRoles()或user.getHistory()方法时。
瞬态属性不会持久化在数据库中。无论你将如何设置这些属性,它都只会留在内存中。我更喜欢JPA懒散地做这件事。
它不会加载所有数据,只加载相对于的数据
Person entity = (Person) this.em.find(Person.class, id);
在惰性获取中,它将只从表人员发出select语句,因为对于protected Set<Role> roles;
,它不会被加载,而是被代理对象替换
Hibernate使用代理对象来实现延迟加载。当我们请求从数据库加载Object,并且提取的Object引用了另一个具体对象时,Hibernate会返回一个代理,而不是具体的关联对象。
Hibernate使用字节码插入(由javassist提供)创建一个代理对象。Hibernate在运行时使用代码生成库创建实体类的子类,并用新创建的代理替换实际对象。