Hibernate Transaction Choices

I take part in a project using Spring MVC and Hibernate technologies. During this project, we had to make some transaction choices. The transaction management could be handled by the HibernateTransactionManager. With the use of annotations, the manager could recognize what kind of transaction should be opened.

A transaction could run in different kind of modes. The default flushing mode is auto. In this case the database session is sometimes flushed before query execution in order to ensure that queries never return stale state. The side effect of unexpected flushes caused us some problems in the execution of DML statements within business logic methods.

What if you have to update some relevant objects before the edited object may be saved to the database? Or you want to do some validation on the unsaved object and you need other persistent objects to do this validation. We consider an object being persistent when the state of the object is the same as its state in the database.

On most forums, hibernate users propose to override the auto flush mode by the manual flush mode to prevent unwanted flushes. A database session in manual flushmode is only flushed when Session.flush() is explicitly called by the application. My team members and I do not favor this kind of solution. It’s like overruling the normal transaction behavior.

We prefer to split up the transaction methods. Like I said in Spring MVC it is possible to indicate which kind of transaction should be opened by the HibernateTransationManager. This is done with annotations set in classes and class methods. If the method only retrieves data from the database, the transaction should not be flushed and will be a read-only method.

We did some trials on our application. Within our model, managers are containing business logic interacting with the Dao’s setting up statements to save and retrieve data information. An important decision is at which level a new transaction should be created. As the Dao’s setting up statements, it might be a good solution to open and close transactions as Dao methods are called.

For example:
We try to update the salary of an employee in a company. When the employee’s salary is more than his superior, the superior’s salary is increased with the same value.

public EmployeeManager{
private EmployeeDAO employeeDAO;

public Employee update(EmployeeDto employeeDto){

// retrieve the object as currently known in the DB
Employee employee = employeeDAO.getById(employeeDto.getId());
Employee superior = employee.getSuperior();

// compare the salary update
if(employeeDto.getSalary() > superior.getSalary()){
int difference = employeeDto.getSalary() -employee.getSalary();

// increase the superior’s salary and update
superior.setSalary(superior.getSalary() + difference);
employeeDAO.update(superior);
}

// update the employee
return employeeDAO.merge(employeeDto);
}
}

The business logic to update the employee’s salary is maintained by an EmployeeManager.
As entities need to be saved, updated or deleted, this is done through different Dao methods.
In this case three transactions are created.

1) employeeDAO.getById(employeeDto.getId()) – type: read-only
2) employeeDAO.update(superior) – type: normal (auto flush)
3) employeeDAO.merge(employeeDto) – type: normal (auto flush)

What if something went wrong in the last transaction method? Only the last transaction will be rolled back. The superior’s salary could be increased when the employee’s salary is still the same. If the user retries to increase the employee’s salary, the superior’s salary will receive twice the wage storage.

For information:
At the end of the transaction, a merge is done instead of an update to prevent a Hibernate TransientObjectException. The transient object – employeeDto – could not be updated when it is not yet persistent with the database. With the use of merge; the changes between the transient and the persistent object are merged to the database.

It’s clear this is not the right transaction choice. We moved the transaction management to the manager level. A better choice, cause now the whole manager method will end in one transaction and also the child methods executed within take over the same transaction.

Another example:
The wage storage rules remain but the superior will be updated by cascade. This means no update action should be called, the update is done automatically with the merge of the employee.

We also update the last name of the employee and check if the name is not already used by another employee.

public EmployeeManager {
private EmployeeDAO employeeDAO;

public Employee update(EmployeeDto employeeDto) throws EmployeeException {

// retrieve the object as currently known in the DB
Employee employee = employeeDAO.getById(employeeDto.getId());
Employee superior = employee.getSuperior();

// compare the salary update
if(employeeDto.getSalary() > superior.getSalary()){
int difference = employeeDto.getSalary() -employee.getSalary();

// increase the superior’s salary and update
superior.setSalary(superior.getSalary() + difference);
}

if(!employeeDto.getLastName().equals(employee.getLastName()) &&
lastNameIsUsedByOtherEmployees(employeeDto.getLastName()){
throw new EmployeeException(“Last name is already used by another employee”);
}

// update the employee
return employeeDAO.merge(employeeDto);
}

private Boolean lastNameIsUsedByOtherEmployees(String lastName){
List employees = employeeDAO.getEmployeesByLastName(lastName);

if(employees.size() > 0){
return true;
}

return false;
}
}

The whole method results in one transaction. Data is read from the database and at the end of the method the employee is updated. Propose the new salary of the employee is higher than his superior and also the person’s name is changed. While the persistent superior object is changed, employees are fetched from the database via the employee’s last name validation. That moment the object state is synchronized with the database and the superior is updated directly before the employee might have been updated. This is caused by the auto flush mode the transaction is running on.

The transaction is flushed whenever changes to a persistent object are made. As soon as a new read is done, Hibernate updates the object state of the employee object. We didn’t aim to flush (commit) at this point of our transaction. We still have to do validation and maybe an exception will to be thrown.

Another solution is to split manager methods into read-only and not read-only transactions. Validation of objects is done with read-only methods and the real database changes are done in another method.

This could look like this:

public class EmployeeManager{
private EmployeeDAO employeeDAO;

public void validate (EmployeeDto employeeDto) throws EmployeeException {

// retrieve the object as currently known in the DB
Employee employee = employeeDAO.getById(employeeDto.getId());

if(!employeeDto.getLastName().equals(employee.getLastName()) &&
lastNameIsUsedByOtherEmployees(employeeDto.getLastName()){
throw new EmployeeException(“Last name is already used by another employee”);
}

return employee;
}

public Employee update(EmployeeDto employeeDto) {
// retrieve the object as currently known in the DB
Employee employee = employeeDAO.getById(employeeDto.getId());
Employee superior = employee.getSuperior();

// compare the salary update
if(employeeDto.getSalary() > superior.getSalary()){
int difference = employeeDto.getSalary() -employee.getSalary();

// increase the superior’s salary and update
superior.setSalary(superior.getSalary() + difference);
}

// update the employee
return employeeDAO.merge(employeeDto);
}
}

public class EmployeeController {
private EmployeeManager employeeManager;

public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors){
EmployeeDto employeeDto = (EmployeeDto) command;

try {
employeeManager.validate (employeeDto);
employeeManager.update(employeeDto);

} catch (EmployeeException e){
errors.rejectValue(“lastName”,”lastName.alreadyUsed”);
return showForm(request, errors, this.getFormView());
}

return new ModelAndView(……);
}

}

The EmployeeController implements two method calls to the manager. The first method is used for validation and second for updating the database.

Do you have other experiences in taking Hibernate transaction choices or do you know a better solution to solve this issue? Please let me know…