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.
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.
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.
Related
I have been practicing one-to-one mapping in hibernate and don't understand this particular case. I have to say, the program is working fine and as I intended, but apparently I can omit a perist() call and it still works smoothly. The fact that it's working is good, but I want to know exactly why the call is optional. Let me write some details:
This is the user class, which is supposed to be the owning side of the mapping:
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToOne
private Ticket ticket;
public User() {}
public User(String name) {
this.name=name;
}
}
And this is the ticket class that's supposed to be the dependent one:
#Data
#Entity
public class Ticket {
#Id
#GeneratedValue(generator="foreignGenerator")
#GenericGenerator(name="foreignGenerator", strategy="foreign",
parameters=#org.hibernate.annotations.Parameter(name="property", value="user"))
private Long id;
#OneToOne(optional = false, mappedBy="ticket")
#PrimaryKeyJoinColumn
private User user;
public Ticket() {
}
public Ticket(User user) {
this.user=user;
}
}
I am trying to test the "shared primary key" strategy in one-to-one mapping. As you can see I have set up the generator with foreign strategy, which is supposed to make Ticket's id the same as it's corresponding User's id.
#Bean
CommandLineRunner loadData() {
return args->{
EntityManager em=emf.createEntityManager();
em.getTransaction().begin();
User user=new User("Test User");
Ticket ticket=new Ticket(user);
//em.persist(user);
user.setTicket(ticket);
em.persist(ticket);
em.getTransaction().commit();
em.close();
//We don't have to call persist on user
};
}
}
This program runs perfectly. Uncommenting the line which calls persist on user makes no difference. I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well. Therefore, the reason it makes no difference is that no matter if user is getting saved or not, it will get persisted when we call ticket.
I want to know if my assumption is correct and any additional links to articles/documentation would be greatly appreaciated. Especially I am wondering about this part that I said above-"I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well." I couldn't find anything that would confirm or deny this. I know that the "shared primary key" approach in one-to-one mapping is the only use case of "foreign" generation strategy, so there are not a lot of posts about it, and whatever posts are there are getting overshadowed by "foreign key" during the search.
Any help regarding this or any other issue that might be wrong with the code provided above would be appreciated. Thanks for taking your time to read this
The JPA specification states this behavior is wrong:
Looking at the 3.0 release:
section "3.2.2. Persisting an Entity Instance" implies user is unmanaged after your persist (you can check with the em.contains method).
Section "3.2.4. Synchronization to the Database" covers the flush/commit which states:
• If X is a managed entity, it is synchronized to the database.
..
◦ For any entity Y referenced by a relationship from X, where the relationship to Y has not been annotated with the cascade element value cascade=PERSIST or cascade=ALL:
▪ If Y is new or removed, an IllegalStateException will be thrown by the flush operation (and the transaction marked for rollback) or the transaction commit will fail.
User is new, so this should be resulting in an exception. That it works might be a glitch in how Hibernate is handling the #PrimaryKeyJoinColumn annotation (speculation on my part) and custom "foreignGenerator".
This is not a pattern I'd suggest you rely on, and should instead call persist to avoid inconsistencies with the behavior on other mapping setups.
I'm newbie in Hibernate and I'm trying to learn about JPA and Hibernate.
I want to know that what is the reason that Hibernate does not allow to save the object which references an unsaved transient instance? I want to know WHY this is a problem?
I asked someone and some of them answer me like this:
How could we possibly map the customer to the address, if there is no
adress record in the DB yet?
and
you are assigning particular Address to Customer. But Address does
not have any ID
but honestly I can't understand them.
(I know that an exception will be thrown and the solution is Cascade but I want to the reason of the problem inside the database)
now, let's assume we have all of these code:
(I use Bidirectional One-To-One relationship for my example)
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer")
private Address address;
}
#Entity
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String zipCode;
#OneToOne
private Customer customer;
}
public static void main(String[] args) {
EntityManager entityManager = emf.createEntityManager();
entityManager.getTransaction().begin(); // Begin Transaction
Customer c1 = new Customer("Mi", "S");
Address addrss1 = new Address("5412 S 5th", "212524");
c1.setAddress(addrss1);
addrss1.setCustomer(c1);
entityManager.persist(c1);
entityManager.getTransaction().commit(); // Commit
entityManager.close();
}
and let's assume that the exception is not thrown and java and hibernate have allowed us to run our code and this is our customer table.
id firstName lastName
---------------------------------
1 Mi S
and this is our address table:
id street zipCode customer_id
---------------------------------------------
- - - -
now, what is the problem? everything in these Bidirectional One-To-One relationship seems right.
then what is the problem?
PS:
if it is possible, please explain and show me code.
I can understand better with code. thank you.
I want to see for example if we are allowed to save the object which references an unsaved transient instance, what problems will we face in our code and in our tables (for example do we have any problem when we want to retrieve a customer and etc)
Because your adress entity have the primay key of customer as a foreign key ,(since mappedby is in Customer entity) ,and the customer referenced by the adress has no id ,which tells hibernate that that entity was never persisted in the database (which literally means transient) ,and hibernate needs a persisted/managed entity to make sure it exists in the database so that the adress object can be associated with an existing customer.
Customer is new, and it is clear from the persist call you want to insert it, but it isn't clear what you want to happen to any of customer's references. To make it clear, you define what you want the JPA provider (Hibernate) to do in the mappings under any/all circumstances - this is what the cascade operations refer to. In this case, JPA will look at the customer.address OneToOne mapping and find nothing defined; Address is NOT managed in this EntityManager context, so it doesn't know what to do to handle this relationship, so it signals you've made a mistake by throwing an error.
If it let it through, your Customer instance references something that does not exist, and its state does not match what is in the database. What you pass into persist should be what you would get back on reads, so it should reflect the state that is in the database.
The issue isn't directly with your persist call, as the spec does allow providers to ignore references to detached/new instances that don't have cascade settings - what happens is just undefined. Where you go wrong in this situation is on flush/commit, which is when the persistence unit is synchronized to the database (section 3.2.4 of JPA 3.0), which requires providers to go through managed entities and then determine any changes. Adding a new address pre persist will result in the same issue as if you did it post persist, and requires providers to throw an IllegalStateException if it discovers new or removed entities and rollback the transaction.
Why this is a problem: JPA is very big on entity Identity, as this enables caching of these entities in multiple levels of caches, and this entity might go into those caches as it is. It has to know what to do with references to entities that do not exist, and the spec decided to require an exception. Even to your app this is and should be a problem, as the EntityManager context is a unit of work, and the state within that unit of work is based on something that is wrong. Your Customer doesn't really have an address when this is said and done, yet your application business logic thinks it assigned one, with state that just isn't going to be there afterward.
You already know the solutions:
correct the customer to have a valid, managed address by calling persist on it directly in this same EntityManager context.
set the cascade options on the mapping to cascade persist to address for you
don't set addresses on a new customer in the same operation.
I have an old database where there are two tables with implicit association between them:
booking
- id
- name
... some other fields...
and
booking_info
- id
- booking_id
... some other fields...
Due to the current database design there no any constraints between these two tables, which means that one booking entry may exist without any booking_info entries and vice versa. booking_id in booking_info table is an implicit foreign key which refers to booking table (column id), but it also may refer to the absent booking.
I have created the following JPA mapping for these tables:
#Entity
public class Booking {
#Id
private Long id;
private String name;
// getters & setters
}
and
#Entity
public class BookingInfo {
#Id
private Long id;
#OneToOne
#JoinColumn(name = "booking_id")
private Booking booking
// getters & setters
}
Now I need to be able to persist a BookingInfo entity even if there's no related Booking entry in the database.
BookingInfo bookingInfo = new BookingInfo();
bookingInfo.setId(1);
Booking booking = new Booking();
booking.setId(182); // let's say that there's no booking with id 182 in my database, but I still need to persist this bookingInfo
bookingInfo.setBooking(booking);
bookingInfoRepository.save(bookingInfo); // using Spring Data JPA
If I run this code then I get javax.persistence.EntityNotFoundException since booking with id 182 is absent.
What would be the proper workaround for my case using JPA or Hibernate.
Btw, I also tried to use Hibernate's #NotFound annotation. As a result, save method doesn't throw javax.persistence.EntityNotFoundException and entity gets persisted int the database, but the problem is that booking_id in the database always null.
Any help would be appreciated.
I am not sure my answer will help you or not, but the result you are getting perfectly make sense. As you are setting a JPA object, and that object is not present, hence the null value is saved.
If you want to save 182 as an integer, you don't do JPA relationship. Instead, you just use booking-id as an integer field in booking-info. And that makes sense because you actually do not have the relationship between those tables which the JPA is trying to achieve.
But I am sure you just want to save 182 and as well as maintain the JPA relationship. And I am sure you already know it, but DB integrity is not being maintained with the approach you are taking. I am sure there is enough reason behind that. But my recommendation would be applying proper constraints in the DB and then in JPA.
Hibernate persists modified entities at the of transactional methods, I can avoid by using session#evict(entity).
If I detach it from the persistence context, the entities whithin it will also be detached?
For instance, I have this classes:
#Entity
public class User extends BaseEntity{
#Column(name = "email")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Address> addresses;
// getters and setters
}
#Entity
public class Address extends BaseEntity{
#Column(name = "email")
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
// getters and setters
}
If I detach a user object, but change the address object in it, will the address be persisted at the end of transaction? Like this:
User user = userDAO.getById(id);
session.evict(user);
Address address = user.getAddresses().get(0);
address.setNumber(number);
addressDAO.saveOrUpdate(address); //will this work?
Entities that are updated or deleted using a EntityManager.createQuery() are not loaded into the Persistence Context, this only happens for select queries, and when you use find()or merge().
After you do an update or delete query your persistence context may actually be out-of-sync with the database, because the query doesn't update the entities which has already been loaded into the persistence context (you need to call refresh() to see the changes).
If you load a number of user (into the persistence context), and later doUpdate User set status='active' where id IN (:ids), then you have not modified any of the users in the persistence context, you have only modified the database. To modify a user, you must modify the actually managed Entity by calling `aUser.setStatus('active'), when the transaction commits, JPA will check all managed entities against a copy created when it was loaded, and if anything has changed it will do an Update.
If you are loading 5000 objects into the Persistence it may take some time for JPA to run though the entity graph, and detect the changes when the transaction commits. If you didn't modify anything, and would like to speed up the change-detection, there are two ways to do this. Load your entities using a read-only query, this tells JPA that it does not need to keep a copy of the loaded entity. The other option is to call EntityManager.clear() to throw away all managed entities. However, if you are interested in performance, the best solution is probably to avoid loading the entities into the persistence context. As I understand you problem, you need to do a Update User set ... where id IN (:ids)and for that you only need the user's id so you don't need to load the user, you just need the ids, and therefore you can do List<Long> ids = em.createQuery("select u.id from User u where ...", Long.class).getResultList();
Hope this clarifies things for you :)
EDIT: this is written from a JPA perspective, but for hibernate EntityManager just forwards directly to SessionImpl, so the behavior is exactly as described, except for find() being called get()in native Hibernate.
Since JPA 2.0
given an EntityManager you can call detach with the entity you want to be detached as parameter
void detach(Object entity)
more here
if you use injection then you can inject an EntityManger in the service where you want to detach the required entity.
I have 2 classes: Driver and Car. Cars table updated in separate process. What I need is to have property in Driver that allows me to read full car description and write only Id pointing to existing Car. Here is example:
#Entity(name = "DRIVER")
public class Driver {
... ID and other properties for Driver goes here .....
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CAR_ID")
private Car car;
#JsonView({Views.Full.class})
public Car getCar() {
return car;
}
#JsonView({Views.Short.class})
public long getCarId() {
return car.getId();
}
public void setCarId(long carId) {
this.car = new Car (carId);
}
}
Car object is just typical JPA object with no back reference to the Driver.
So what I was trying to achieve by this is:
I can read full Car description using detailed JSON View
or I can read only Id of the Car in Short JsonView
and most important, when creating new Driver I just want to pass in JSON ID of the car.
This way I dont need to do unnesessery reads for the Car during persist but just update Id.
Im getting following error:
object references an unsaved transient instance - save the transient instance before flushing : com.Driver.car -> com.Car
I dont want to update instance of the Car in DB but rather just reference to it from Driver. Any idea how to achieve what I want?
Thank you.
UPDATE:
Forgot to mention that the ID of the Car that I pass during creation of the Driver is valid Id of the existing Car in DB.
You can do this via getReference call in EntityManager:
EntityManager em = ...;
Car car = em.getReference(Car.class, carId);
Driver driver = ...;
driver.setCar(car);
em.persist(driver);
This will not execute SELECT statement from the database.
As an answer to okutane, please see snippet:
#JoinColumn(name = "car_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = Car.class, fetch = FetchType.EAGER)
private Car car;
#Column(name = "car_id")
private Long carId;
So what happens here is that when you want to do an insert/update, you only populate the carId field and perform the insert/update. Since the car field is non-insertable and non-updatable Hibernate will not complain about this and since in your database model you would only populate your car_id as a foreign key anyway this is enough at this point (and your foreign key relationship on the database will ensure your data integrity). Now when you fetch your entity the car field will be populated by Hibernate giving you the flexibility where only your parent gets fetched when it needs to.
You can work only with the car ID like this:
#JoinColumn(name = "car")
#ManyToOne(targetEntity = Car.class, fetch = FetchType.LAZY)
#NotNull(message = "Car not set")
#JsonIgnore
private Car car;
#Column(name = "car", insertable = false, updatable = false)
private Long carId;
That error message means that you have have a transient instance in your object graph that is not explicitly persisted. Short recap of the statuses an object can have in JPA:
Transient: A new object that has not yet been stored in the database (and is thus unknown to the entitymanager.) Does not have an id set.
Managed: An object that the entitymanager keeps track of. Managed objects are what you work with within the scope of a transaction, and all changes done to a managed object will automatically be stored once the transaction is commited.
Detached: A previously managed object that is still reachable after the transction commits. (A managed object outside a transaction.) Has an id set.
What the error message is telling you is that the (managed/detached) Driver-object you are working with holds a reference to a Car-object that is unknown to Hibernate (it is transient). In order to make Hibernate understand that any unsaved instances of Car being referenced from a Driver about be saved should also be saved you can call the persist-method of the EntityManager.
Alternatively, you can add a cascade on persist (I think, just from the top of my head, haven't tested it), which will execute a persist on the Car prior to persisting the Driver.
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinColumn(name = "CAR_ID")
private Car car;
If you use the merge-method of the entitymanager to store the Driver, you should add CascadeType.MERGE instead, or both:
#ManyToOne(fetch=FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "CAR_ID")
private Car car;
public void setCarId(long carId) {
this.car = new Car (carId);
}
It is actually not saved version of a car. So it is a transient object because it hasn't id. JPA demands that you should take care about relations. If entity is new (doesn't managed by context) it should be saved before it can relate with other managed/detached objects (actually the MASTER entity can maintain it's children by using cascades).
Two ways: cascades or save&retrieval from db.
Also you should avoid set entity ID by hand. If you do not want to update/persist car by it's MASTER entity, you should get the CAR from database and maintain your driver with it's instance. So, if you do that, Car will be detached from persistence context, BUT still it will have and ID and can be related with any Entity without affects.
Add optional field equal false like following
#ManyToOne(optional = false) // Telling hibernate trust me (As a trusted developer in this project) when building the query that the id provided to this entity is exists in database thus build the insert/update query right away without pre-checks
private Car car;
That way you can set just car's id as
driver.setCar(new Car(1));
and then persist driver normal
driverRepo.save(driver);
You will see that car with id 1 is assigned perfectly to driver in database
Description:
So what make this tiny optional=false makes may be this would help more https://stackoverflow.com/a/17987718
Here's the missing article that Adi Sutanto linked.
Item 11: Populating a Child-Side Parent Association Via Proxy
Executing more SQL statements than needed is always a performance penalty. It is important to strive to reduce their number as much as possible, and relying on references is one of the easy to use optimization.
Description: A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent ( #ManyToOne or #OneToOne lazy association). In such cases, fetching the parent entity from the database (execute the SELECT statement) is a performance penalty and a pointless action. Hibernate can set the underlying foreign key value for an uninitialized proxy.
Key points:
Rely on EntityManager#getReference() In Spring
use JpaRepository#getOne() Used in this example,
in Hibernate, use load()
Assume two entities, Author and Book, involved in a unidirectional #ManyToOne association (Author is the parent-side) We fetch the author via a proxy (this will not trigger a SELECT), we create a new book
we set the proxy as the author for this book and we save the book (this will trigger an INSERT in the book table)
Output sample:
The console output will reveal that only an INSERT is triggered, and no SELECT
Source code can be found here.
If you want to see the whole article put https://dzone.com/articles/50-best-performance-practices-for-hibernate-5-amp into the wayback machine. I'm not finding a live version of the article.
PS. I'm currently on a way to handle this well when using Jackson object mapper to deserialize Entities from the frontend. If you're interested in how that plays into all this leave a comment.
Use cascade in manytoone annotation
#manytoone(cascade=CascadeType.Remove)