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. TheLAZY
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 theLAZY
strategy hint has been specified. In particular, lazy fetching might only be available forBasic
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.