Hibernate batch update issue - java

For a simple batch update of a MariaDB table, properly mapped as a Hibernate entity class, a simple update via Hibernate produces the error
org.hibernate.StaleStateException: Batch update returned unexpected row count from update
Each table record is modeled by an Entity class, which is a simple POJO that needs to be updated (if it already exists) or inserted as a new object (if it does not exist in the table), with a primary id field (not auto-incremented) and some other values, all scalar. The error can be reproduced by the following method.
public static void update(Set<Long> ids) {
Session session = createSession();
Transaction t = session.beginTransaction();
try {
for (Long id : ids) {
Entity entity = session.get(Entity.class, id);
if (entity == null) {
entity = new Entity();
}
entity.setId(id);
// Other entity value settings
session.saveOrUpdate(entity);
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
}
What is the correct way of implementing the above operation in Hibernate?

You are using saveOrUpdate() in this way Hibernate decides by his own logic what is a new (Transient) and what is an old (Persisted) object and depends on this performs save() or update() method accordingly.
Hibernate assumes that an instance is an unsaved transient instance if:
The identifier property is null.
The version or timestamp property (if it exists) is null.
A new instance of the same persistent class, created by Hibernate internally, has the same database identifier values as the given instance.
You supply an unsaved-value in the mapping document for the class, and the value of the identifier property matches. The unsaved-value attribute is also available for version and timestamp mapping elements.
Entity data with the same identifier value isn't in the second-level cache.
You supply an implementation or org.hibernate.Interceptor and return Boolean.TRUE from Interceptor.isUnsaved() after checking the instance in your code.
Otherwise: entity will be determined like already saved persisted
In your example, Hibernate did not determine the new (Transient) object and as result, perform update() method for it. It produced UPDATE instead of INSERT statement. UPDATE statement for not existing record returns zero updated records, so it is the reason for your exception.
Solution: explicitly use save() method for new entities:
public void update(Set<Long> ids) {
Session session = getSessionFactory().openSession();
Transaction transaction = session.beginTransaction();
try {
for (Long id : ids) {
HibernateEntity entity = session.get(HibernateEntity.class, id);
if (entity == null) {
entity = new HibernateEntity();
}
// Other entity value settings
entity.setValue(entity.getValue() + "modification");
if (entity.getId() == null) {
entity.setId(id);
session.save(entity);
}
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
}
update() method is not required to call explicitly. Transactional persistent instances (i.e. objects loaded, saved, created or queried by the Session) can be manipulated by the application, and any changes to persistent state will be persisted when the Session is flushed. According to documentation.

Related

What will be the outcome if an entity object is accessed after transaction.commit() in Hibernate?

I am new to hibernate. I want to understand behavior once the transaction is commit. Consider below code-
Employee class is the class whose objects will be inserted/deleted to/from the database.
public static void main(String[] args) {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
long id = 2;
try {
session.beginTransaction();
Employee employee = (Employee) session.get(Employee.class, id);
session.delete(employee);
session.getTransaction().commit();
employee.getName(); /*What will happen at this line*/
}
catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}
}
It becomes "Transient". From the Session class documentation
Persistent instances may be made transient by calling delete()
From the guide:
Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. Transient instances will be destroyed by the garbage collector if the application does not hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition).
Take a look here for more info https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html

Delete not saved object doesnt invoke exception

I'm writing tests for my Dao Spring application. I found out that when I delete not saved items no exception is invoked as I'd expect, I've got no idea why.
Model:
#Entity
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String condition;
private int quantity;
public Ingredient() {
}
}
The Dao implementation:
#Override
public void delete(Object o) throws DaoException {
try {
Session session = mSessionFactory.openSession();
session.beginTransaction();
session.delete(o);
session.getTransaction().commit();
session.close();
} catch (Exception ex) {
throw new DaoException(ex, String.format("Problem deleting %s object (delete method).", o));
}
}
And my test, expecting DaoException:
#Test
public void testDeleteNotSavedThrowsDaoException() throws Exception {
Ingredient ingredient = new Ingredient("Not saved ingredient","", 1);
ingredientDao.delete(ingredient);
}
Hibernate's Javadoc for Session#delete(Object) states:
Remove a persistent instance from the datastore. The argument may be an instance associated with the receiving Session or a transient instance with an identifier associated with existing persistent state.
So it's not an error to pass in a transient entity (as you do). Also, the Session#delete method does not declare any exceptions, so it's not defined what happens when you pass in an entity with an ID that does not exist in the DB. As you can see - nothing happens - you requested the entity not to exist in the DB, it's not there to start with, so no reason to throw an exception (according to Hibernate, at least).
Compare this to the basic SQL DELETE FROM X WHERE ID = Y - this does not check if a record with ID=Y exists, it will succeed either way (updating 0 or 1 rows).
UPDATE after realizing the passed in transient entity has null ID.
I've dug into the sources of Hibernate 5.2.2 Session and it seems that if the passed in entity has no ID, no DELETE query is even performed on that entity's table.
See DefaultDeleteEventListener#onDelete(DeleteEvent, Set):
if (ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
// yes, your entity is transient according to ForeignKeys.isTransient
deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities );
return;
}
Now
protected void deleteTransientEntity(
EventSource session,
Object entity,
boolean cascadeDeleteEnabled,
EntityPersister persister,
Set transientEntities) {
LOG.handlingTransientEntity(); // Only log it
if ( transientEntities.contains( entity ) ) {
LOG.trace( "Already handled transient entity; skipping" );
return;
}
transientEntities.add( entity );
// Cascade deletion to related entities
cascadeBeforeDelete( session, persister, entity, null, transientEntities );
cascadeAfterDelete( session, persister, entity, transientEntities );
}
this will just print "HHH000114: Handling transient entity in delete processing" in the logs and do nothing with the entity (however, it will cascade the deletion to the related entities if there are any - not your case).
So again - it's OK to pass in a transient entity without an ID - it will simply not run a DELETE on the DB.
And that was an answer, Adam, there was no exception, because id of my new, not saved item was null. When I set id to value which not persist in DB exception was thrown.

Making insertion in database using hibernate without making transaction instance

I have been making a simple program to save an employee details in mysql using hibernate framework as follows...
public class Manifest {
Session session;
public static void main(String[] args) {
Employee employee = new Employee("varun");
new Manifest().addEmployee(employee);
}
/* Method to CREATE an employee in the database */
public void addEmployee(Employee employee){
Integer employeeID=null;
SessionGenerator sessionGenerator = new SessionGenerator();
try{
session = sessionGenerator.getSessionToDb();
employeeID = (Integer) session.save(employee);
System.out.println(employeeId);
}catch (HibernateException e) {
e.printStackTrace();
}finally {
session.close();
}
}
}
I am aware of the fact that I should use session.beginTransaction(); & tx.commit() but my question is that why no exception is thrown here in my case and it is printing employeeId on console but not making any entry in database.. What the reason behind that???
1) Session.save() may perform an INSERT outside transaction boundaries: save() method returns an identifier, and if an INSERT has to be executed to get the identifier (e.g. "identity" generator), this INSERT happens immediately (in order to get an identity), no matter if you are inside or outside of a transaction.
2) Commit might happen even without transaction if you set:
<property name="connection.autocommit">true</property>
that's why an exception is not raised
Check this article (Working nontransactionally with Hibernate): https://developer.jboss.org/wiki/Non-transactionalDataAccessAndTheAuto-commitMode

JPA: Refresh detached entity after rollback and re-attach it

I have a method that receives a JPA Entityand its related EntityManager as parameters. The Entity instance is not created inside the class, and it might very well be shared by other classes (like GUIs and such).
The method starts a transaction, carries out some changes on the entity, and finally commits the transaction.
In case the commit fails, EntityTransaction.rollback() is called: in accordance with JPA specifications, the entity is then detached from the manager.
In case of failure the application needs to discard the pending changes, restore the original values inside the entity e and re-attach it to the EntityManager, so that the various scattered references to the e object would remain valid. The problem raises here: what I understood is that this is not a straightforward operation using the EntityManager's APIs:
calling EntityManager.refresh(e) is not possible since e is detached.
doing e = EntityManager.merge(e) would create a new instance for e: all the other references to the original e in the program at runtime would not be updated to the new instance. This is the main issue.
moreover (actually not quite sure about this), EntityManager.merge(e) would update the new managed instance's values with the values currently held by e (i.e., the values that probably caused the commit to fail). Instead, what I need is to reset them.
Sample code:
public void method(EntityManager em, Entity e) {
EntityTransaction et = em.getTransaction();
et.begin();
...
// apply some modifications to the entity's fields
...
try {
et.commit();
} catch (Exception e) {
et.rollback();
// now that 'e' is detached from the EntityManager, how can I:
// - refresh 'e', discarding all pending changes
// - without instantiating a copy (i.e. without using merge())
// - reattach it
}
}
What is the best approach in this case?
A possible solution would be like:
public class YourClass {
private EntityManager em = ...; // local entity manager
public void method(Entity e) { // only entity given here
Entity localEntity = em.find(Entity.class, e.getId());
EntityTransaction et = em.getTransaction();
et.begin();
...
// apply some modifications to the local entity's fields
applyChanges(localEntity);
...
try {
et.commit();
// Changes were successfully commited to the database. Also apply
// the changes on the original entity, so they are visible in GUI.
applyChanges(e);
} catch (Exception ex) {
et.rollback();
// original entity e remains unchanged
}
}
private void applyChanges(Entity e) {
...
}
}

hibernate session.save() inserts different values than are at object

I have little problem. When I try insert new value to database, function save() inserts me different values ​​than are at object :(. What should I do?
Here is my function
public void updateListOfElements(List<Dokumenty> list) {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
for (Dokumenty dokument : list) {
Dokumenty dokumentToUpdate =
(Dokumenty) session.get(Dokumenty.class, dokument.getId());
dokumentToUpdate.setAktywny('N');
session.update(dokumentToUpdate);
// id z dupy wpisuje
dokument.setId(10114);
session.save(dokument);
}
transaction.commit();
} catch (HibernateException e) {
if (transaction != null) {
transaction.rollback();
}
} finally {
session.close();
}
}
You should use saveOrUpdate not save
dokument.setId(10114);
session.saveOrUpdate(dokument);
When you call saveOrUpdate() If the identifier exists, it will call update method else the save method will be called.
If you call save() method stores an object into the database. That means it insert an entry.
Before proceed have a look :What are the differences between the different saving methods in Hibernate?
My suggetion:Always use saveOrUpdate //if record exists update otherwise new
Use session.merge(). Becuase, NonUniqueObjectException may be thrown when using Session.saveOrUpdate() in Hibernate - See more at: http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/#sthash.WJEbdSaG.dpuf

Categories