简体   繁体   中英

JPA Design of User and Roles

I have a User class and a Role class. Both of these classes are JPA entities, and hence stored in a Person table and a Role table, as well as a corresponding link table Person_Role used for joins, since the association is many to many. A user may have many roles, and a role may be assigned to many users.

@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

   ...
}

The User entity is used extensively throughout the application, as it is a shared reference for many objects, and is used in many, many searches. 99.99% of the time, the user's roles and history are not needed. I'm new to JPA, and have been reading the "Java Persistence with Hibernate" book in order to learn. As I understand lazy fetching, it will load all the corresponding User data from the database when any getXXX() method is called.

Ex: user.getFirstName() would cause a database hit and load all the data, including roles and history, for the user.

I want to avoid this at all costs. Its just needless in 99.99% of the use cases. So, what's the best way to handle this?

My initial thought is to mark the Set<Role> roles and Set<String> history in the User class as @Transient and manually query for the roles and history only when the user.getRoles() or user.getHistory() method is called.

Thanks for any suggestions.

It will not Load all the data just the the relative to

 Person entity = (Person) this.em.find(Person.class, id);

in lazy fetching it will issue a select statement from only the table person, as for protected Set<Role> roles; it will not be loaded but replaced with a proxy object

Hibernate uses a proxy object to implement lazy loading. When we request to load the Object from the database, and the fetched Object has a reference to another concrete object, Hibernate returns a proxy instead of the concrete associated object.

Hibernate creates a proxy object using bytecode instrumentation (provided by javassist). Hibernate creates a subclass of our entity class at runtime using the code generation library and replaces the actual object with the newly created proxy.

As I understand lazy fetching, it will load all the corresponding User data from the database when any getXXX() method is called.

You can force JPA to be eager or lazy while fetching data from the database but first and foremost it depends on JPA provider. As described in JPA 2.1 specification, chapter 11.1.6:

The FetchType enum defines strategies for fetching data from the database:

 public enum FetchType { LAZY, EAGER }; 

The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified. In particular, lazy fetching might only be available for Basic mappings for which property-based access is used.

A nice presentation on how fetching strategies work and how performant they are in real-life scenarios you can find here .

Ex: user.getFirstName() would cause a database hit and load all the data, including roles and history, for the user.

Data are retrieved either directly from the persistence context (usually it has a short lifespan) or indirectly from the underlying database (when it's not found in the transactional/shared caches). If entity manager is requested to get your entity object and it does not exist in the persistence context it needs to go deeper - into the database in the worst scenario.

I want to avoid this at all costs. Its just needless in 99.99% of the use cases. So, what's the best way to handle this?

An example approach:

@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
   ...
}

Usually the best way to go is the find method. It's perfect when you want to retrieve all non-relationship attributes at once:

Person p = em.find(Person.class, id)

An alternative for you would be to use named query. It's useful when you need a single attribute or a small subset of attributes:

String name = em.createNamedQuery("Person.getNameById", String.class)
                 .setParameter("id", id)
                 .getSingleResult() 

My initial thought is to mark the Set roles and Set history in the User class as @Transient and manually query for the roles and history only when the user.getRoles() or user.getHistory() method is called.

Transient attributes are not persisted in a database. Whatever you will set to these attributes it will stay in memory only. I would prefer JPA doing it lazily.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM