Saving or persisting an object is actually not very complicated. The following code will do it.
Transaction tx = session.beginTransaction(); Concert c = new Concert(); c.setName("Peter"); session.save(c); tx.commit();
If you debug the code of the first example, you will see that session.save does not create an insert statement immediately but just selects the next sequence value. This is at least true, if you use a sequence as id generator.
It is the tx.commit() of the transaction, which leeds to sending the SQL insert statement to the database. Calling save makes an object persistent. If the id is configured to be generated then Hibernate will ensure that it is generated and set the value of the id property. In addition the entity is added to the persistence context of the session.
Simplified: the persistence context contains a map for each entity. The key of the map is the id and the value is a composite of the entity instance itself and the original values of all fields when the object was added to the persistence context. The latter allows Hibernate to determine if a persistent object needs to be updated in the database.
When the persistence context is flushed, Hibernate will send all insert statements to the database, determines all required updates by comparing the entities to the original field values and sends all updates to the database and finally all deletes are sent.
The call to commit will cause a flush of the persistence context before the commit is sent to the database.
There are multiple reasons for this behavior:
Less update statements
If you change multiple fields, add a relation while an entity is in persistent state, Hibernate will only send one update statement at the end.
JDBC batching
Hibernate can group updates and make use of JDBC batching. This is more efficient as compared to send statements one by one.
Reduced duration of locks
Sending an insert or update statement will cause a row or page lock depending on the database. Sending such statement at the end just before the transaction commit, will cause the lock to exist only for a short timespan. It reduces the risk of concurrent users waiting for locks or dead lock situations.
Therefor, Hibernate will do only the minimum required to determine the id of the entity and add the entity to the persistence context of the session. If the id is a generated value and the generator is a sequence or a table, Hibernate will just select the value. But if the id is generated on inserting - for example increment column in MS SQL server - then Hibernate needs to send the SQL insert statement to get an id generated.
Saving becomes more complex, when you start to use relations.
We are having the following tables mapped as 1:n relation. We will define that developer_id must not be null, an idea cannot exist without a developer. The database will create a not null constraint for the column developer_id.
Developer d = new Developer(); Idea idea = new Idea(); d.getIdeas().add(idea); idea.setDeveloper(d);
You must take care that the idea is not saved before the developer_id is defined. If you save the idea first, then the developer_id is not yet defined and you will receive a constraint violation exception.
Use case: no cascading defined
You must call save in the following order:
session.save(developer); session.save(idea);
Use case: cascading from developer to idea
You only need to call
session.save(developer);
Considerations
We talked in chapter Hibernate States Chapter 2, Hibernate Concepts - State of Objects about the states of an object. Below you can find the figure we used in that chapter.
Updating an entity requires that you get your object in persistent state. One approach is to fetch an object from the database within a transaction, apply your changes and commit the transaction.
session.beginTransaction(); Visitor visitor = session.get(Visitor.class, aVisitorId); visitor.setName("Edgar"); session.getTransaction().commit();
In the code above there is no session.update. Working with Hibernate means working with objects and states. This is a big difference compared to JDBC and sending insert and update SQL statements.
Another approach to change an entity, is to reattach a detached object.
session.beginTransaction(); session.buildLockRequest(LockOptions.NONE).lock(visitor); // reattach visitor visitor.setName("Edgar"); session.getTransaction().commit();
What is Reattaching?
Reattaching changes the state of an object from detached to persistent.
When you load an object within a session and close the session, your object is detached from the session and any changes to the object have no influence to the session or the database.
This happens normally if you use the recommended approach of session handling. Read more about session handling in chapter Session Handling Chapter 13, Session and Transaction Handling.
If you want to apply any updates you have to reattach the object to a session.
Reattaching
You have different options to reattach an object to a session. The main difference between these approaches are three criteria:
Session.lock
Use session.lock when you expect that the object is unchanged and a new instance is not loaded within the same session.
If you apply any changes before you lock the instance, your session will end in an inconsistent state. To be more precise the changes will exist in the session but they will not be written to the database. Hibernate thinks that it is unchanged.
contact1.setFirstName("Peter"); tx = session.beginTransaction(); session.buildLockRequest(LockOptions.NONE).lock(contact1, LockMode.None); tx.commit();
lock(..) throws an org.hibernate.NonUniqueObjectException when the instance is already in the session.
This problem can happen in the following situations:
You have methods retrieving new object instances without using the existing detached object.
Your object has a relation to another object X. When X and its relation are loaded then you have two instances as well. For example when you load a Tree class that itself fetches the leafs and then you try to reattach a leaf.
You have different options for the LockOptions:
LockOptions.NONE Tries to get object from cache, if not present read it from the database. It does not create a database lock. LockOptions.READ Bypasses cache and reads object from the database. LockOptions.UPGRADE Bypasses cache and creates a lock using select for update statement. This might wait for a database timeout. By default the lock request will wait for ever. If supported by your database and your JDBC driver, you can set a timeout. A timeout value of 0 is nowait.
session.getTransaction().begin(); Session.LockRequest lockRequest = session.buildLockRequest(LockOptions.UPGRADE); lockRequest.setTimeOut(10); lockRequest.lock(john); session.getTransaction().commit(); session.close();
If Hibernate does not find the object in the database it will throw an exception.
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [de.laliluna.hibernate.Concert#1]
You should handle this Exception as it can happen when multiple users access your application concurrently. You can find an example in the Hibernate Struts chapter.
If the object was changed, use session.update to save the changes in the database.
tx = session.beginTransaction(); contact1.setFirstName("Peter"); session.update(contact1); tx.commit();
The method throws an org.hibernate.NonUniqueObjectException if an instance is already in the session. I explained in the last paragraph, in which situation this can happen.
If you do not want to distinguish between inserting or updating an object, you can use session.saveOrUpdate. This is a combination of save and update.
The following code will insert the object in the first statement and will automatically update it in the second part.
log.debug("saveOrUpdate to insert"); Session session = HibernateSessionFactory.currentSession(); session.beginTransaction(); Concert c = new Concert(); c.setName("Peter"); session.saveOrUpdate(c); session.getTransaction().commit(); log.debug("saveOrUpdate to reattach and update"); session = HibernateSessionFactory.currentSession(); session.beginTransaction(); session.saveOrUpdate(c); session.getTransaction().commit();
Like lock and update, saveOrUpdate does not like a second instance of an object in the same session. Please, have a look at the explanation in the session.lock chapter.
Use session.merge, if you consider that a new instance could exist in this session. Hibernate loads the object from the session or the cache or if it is not there from the database. Then it copies the values of your old object to the new object and returns the loaded object. This object is in persistent state. The old object is not attached to the session.
Pit fall using merge
Imagine you have created an object contact.
Contact contact = new Contact(); contact.setFirstName("Peter"); Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); session.save(contact); tx.commit(); session.close();
Later you want to reattach your object to a Hibernate session and change the name to Peter. Do you think you have successfully changed the name?
session = sessionFactory.openSession(); tx = session.beginTransaction(); session.merge(contact); contact.setFirstName("Peter4"); tx.commit(); session.close();
You have not! The method merge looks up the object in the session. If it is not found, a new instance is loaded. Then the attributes of contact are copied to the found/created object. If you change contact it has no influence on the object in the session. You must apply your changes to the new object.
The solution is to assign the found/created object to the newInstance variable. Than you can access the variable directly.
Contact newInstance =(Contact) session.merge(contact); newInstance.setFirstName("Peter4");
We have already seen how to delete an object. A call to session.delete deletes an object.
session = sessionFactory.openSession(); session.beginTransaction(); session.delete(concert); session.getTransaction().commit(); session.close();
The object to delete does not have to be in persistent state, it can be detached.
When the object was already deleted from the database before, you will encounter an exception.
org.hibernate.StaleStateException: Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
To prevent this you might consider to validate that the object exists before deleting it. You might even consider to lock the object with a UPGRADE{}-lock.
Concert freshObject = (Concert) session.get(Concert.class, concert.getId()); if (freshObject != null) session.delete(freshObject);
Further attention is needed when relations are used. Let us reuse the example we took above for the update problem.
We will define that an idea can only exist with a developer. This means that developer_id must not be null.
In this case you cannot delete a developer without deleting the ideas first. We have two options to delete the objects.
Deletion without cascading
Reattach your developer, delete all the ideas first, and then delete the developer.
session.buildLockRequest(LockOptions.None).lock(developer); for (Iterator iter = developer.getIdeas().iterator(); iter.hasNext();) { Idea element = (Idea) iter.next(); session.delete(element); } session.delete(developer);
Have a look at the test class of the relation mapping examples for further examples.
Cascading is explained in detailed in chapter Cascading the section called “Cascading”. Here, I present only a short description. If you have set cascading to delete or all, then you just need to delete the developer and Hibernate will delete the ideas for you.
The Hibernate session provides more commands. I will explain the most important in this chapter.
If a column of a table is calculated by a database trigger, it might be required to reload the object. A call to refresh will reread the object from the database.
session.refresh(developer);
An object in persistent state is stored in the persistence context and cannot be garbage collected even if it is no longer referenced from your code. Sometimes you want to limit the size of the session for example to keep memory consumption low. The command evict removes one object from the session. If the object was changed, the SQL update statement is most likely not yet send to the database. If you do not want to loose your changes, then you should call flush to send all open changes to your database.
session.flush(); session.evict(developer);
The manual call to flush is only required in use cases as the one just described. By default flush is called when you commit a transaction.