JPA用户和角色设计

时间:2017-11-17 17:26:57

标签: java hibernate jpa

我有一个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数据。

Ex:user.getFirstName()会导致数据库命中,并为用户加载所有数据,包括角色和历史记录。

我想不惜一切代价避免这种情况。在99.99%的用例中它只是不必要的。那么,处理这个问题的最佳方法是什么?

我最初的想法是将Set<Role>类中的Set<String>角色和User历史记录标记为@Transient,并仅在{{{{}}时查询角色和历史记录调用1}}或user.getRoles()方法。

感谢您的任何建议。

2 个答案:

答案 0 :(得分:0)

它不会仅加载与

相关的所有数据
 Person entity = (Person) this.em.find(Person.class, id);

在延迟抓取时,它将仅从表人发出一个select语句,对于protected Set<Role> roles;,它将不会被加载,而是替换为代理对象

Hibernate使用代理对象来实现延迟加载。当我们请求从数据库加载Object,并且获取的Object具有对另一个具体对象的引用时,Hibernate返回一个代理而不是具体的关联对象。

Hibernate使用字节码检测创建代理对象(由javassist提供)。 Hibernate使用代码生成库在运行时创建实体类的子类,并用新创建的代理替换实际对象。

答案 1 :(得分:0)

  

由于我理解延迟抓取,它将加载所有相应的用户   调用任何getXXX()方法时数据库中的数据。

您可以在从数据库中获取数据时强制JPA急切或懒惰,但首先它取决于JPA提供程序。如JPA 2.1规范,第11.1.6章中所述:

  

FetchType枚举定义了从中获取数据的策略   数据库:

public enum FetchType { LAZY, EAGER };
     

EAGER策略是持久性提供程序上的 要求   运行时必须急切地获取数据。 LAZY策略是一个    提示 到持久性提供程序运行时,首次访问时应该懒洋洋地获取数据。允许实施   急切地获取LAZY策略提示已经过的数据   指定。特别是,懒惰提取可能只适用于   使用基于属性的访问的Basic映射。

关于获取策略如何工作以及它们在现实生活中如何表现的精彩演示文稿,您可以找到here

  

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懒洋洋地做这件事。