Hibernate @Version annotation
Asked Answered
P

3

4

What is the relation between hibernate @version and ManyToOne Mapping.

Assume that i am having two tables Department and Employee. Here is Deparment is the master table and Employee in the detail table. In the Employee table, departmentID is reference as foreign key.

Here is my classes

Public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long ID;
    @Version
    private Long version;

    //Getters and Setters

}

public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long ID;
    @Version
    private Long version;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "departmentID" )
    private Department department;

}

And also, Spring handles the session. So assume that, in one page, particular department is fetched and stored in the HTTP session.

Now in another page, i am trying to do the following

Employee emp = new Employee();
emp.setName('Test')
emp.setDepartment(dept) // already stored in the HTTP session variable
service.save(emp)

Now i am getting the following exception

org.springframework.dao.InvalidDataAccessApiUsageException: object references an unsaved transient instance - save the transient instance before flushing: 

And just it make one change as follow and there is errror

Employee emp = new Employee();
emp.setName('Test')
dept.setVersion(0);
emp.setDepartment(dept) // already stored in the HTTP session variable
service.save(emp)

My Spring config

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/tx
               http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- Container Configuration: The IOC container configuration xml file is 
    shown below,The container has the <context:component-scan> element and <context:annotation-config/> 
    <context:annotation-config/> used to intimate the beans of this IOC container 
    are annotation supported. By pass the base path of the beans as the value 
    of the base-package attribute of context:component-scan element, we can detect 
    the beans and registering their bean definitions automatically without lots 
    of overhead. The value of base-package attribute is fully qualified package 
    name of the bean classes. We can pass more than one package names by comma 
    separated -->

<context:annotation-config />
<context:component-scan base-package="com.product.business" />

<tx:annotation-driven transaction-manager="transactionManager" />

<!-- This will ensure that hibernate or jpa exceptions are automatically 
    translated into Spring's generic DataAccessException hierarchy for those 
    classes annotated with Repository -->

<bean
    class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

<bean id="CRUDService" class="com.product.business.service.CRUDServiceImpl" />
<bean id="AuthService" class="com.product.business.service.AuthServiceImpl" />

Service Implementation

package com.product.business.service;

import java.io.Serializable;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.product.business.dao.CRUDDao;

@Service
public class CRUDServiceImpl implements CRUDService {

    @Autowired
    private CRUDDao CRUDDao;

    @Transactional(readOnly = true)
    public <T> List<T> getAll(Class<T> klass) {
        return CRUDDao.getAll(klass);
    }

    @Transactional
    public <T> void Save(T klass) throws DataAccessException {
        CRUDDao.Save(klass);
    }

    @Transactional
    public <T> void delete(T klass) throws DataAccessException {
        CRUDDao.delete(klass);
    }

    @Transactional
    public <T> T GetUniqueEntityByNamedQuery(String query, Object... params) {
        return CRUDDao.GetUniqueEntityByNamedQuery(query, params);
    }

    @Transactional
    public <T> List<T> GetListByNamedQuery(String query, Object... params) {
        return CRUDDao.GetListByNamedQuery(query, params);
    }

    @Override
    @Transactional(readOnly = true)
    public <T> Long getQueryCount(String query, Object... params) {
        return CRUDDao.getQueryCount(query, params);
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByPrimaryKey(Class<T> klass, Serializable id) {
         return CRUDDao.findByPrimaryKey(klass, id);
    }

}
Platysma answered 24/9, 2013 at 13:25 Comment(0)
R
3

You need to first save the Department before saving the Employee.

service.save(dept);
service.save(emp);

UPDATE in response to your comment:

In order to associate an Employee with a Department you need to have a Department that exists. Remember that in your database the Employee has a FK to the Department so what Hibernate is complaining about is that you are trying to save an Employee with a Department that does not exist, so you have these options:

  1. If the Department is a new Department you must save it first before saving the Employee.
  2. Find an already stored Department through a query such as entityManager.find(id, Department.class) and use that object in your Employee object.
  3. Mark as @Cascade your relationship with Deparment in the Employee.
Remount answered 24/9, 2013 at 13:53 Comment(8)
Oh no!!!!!!!!!!. Why do i want to save department first, it is just only reference, no changes has been made. Simple assume that, you have employee screen where department object is given as drop down and while saving new employee, why we want to save department first ?Platysma
@SenthilMuthiah see my update. In your example you are trying to save an Employee with a new Department that has not been saved before.Remount
No, as you see in my question, Department is already exists in the database. I retrieved thru query and stored in the HTTP Session. After that , in other page, i am just passing that object to employee. Please help further, really this is killing me for more than one week. And also if i set version = 0 for department object, then everything is fine.Platysma
After reading lot, i came to know after session is close, then the object will be detached state. So now the department object is in the detached state. Now employee is in persistent state and here i am assigning department object to one of the setter of employee, then it is giving problemPlatysma
As you suggest, i try with casecade option, but still same error is comingPlatysma
@SenthilMuthiah there must be something wrong with your deparment object. Check what version it has. It must have a version > 0, check it before saving the employee.Remount
Oh Yes!!!!!!!!!!!!!!!!!! You are 100% right, actually it is null, i dont know how it is stored. May be i added the column and after few records are inserted. So hibernate will always insert >1 for new records. Thank you so much. you saved my week and dayPlatysma
@AlfredoOsorio, it is better to use getReference() over find(), if you don't want to change anything in the Department object, while persisting the Emploee object.Geilich
P
1

Your main problem is not so much the cascading but the fetching.

see the following post: Difference between FetchType LAZY and EAGER in Java Persistence API?

@ManyToOne(fetch = FetchType.EAGER)

Otherwise as you mentionned your session gets closed and you loose the object. Eager fetching will ensure that it stays opened

Pericynthion answered 7/12, 2013 at 14:40 Comment(0)
P
1

I know this is an old post, but maybe this will help others.

This assumes you are using Hibernate as a JPA implementation.

Since your Department is stored in the session it's safe to say that it is detached.

The best route for this case, since you are not modifying the Department instance is this:

Employee emp = new Employee();
emp.setName("Test");
emp.setDepartment(em.getReference(Department.class, dept.getID());
service.save(emp);

See Hibernate documentation here: Entity Manager - Loading an Object

If you get an EntityNotFoundException, make sure the code that originally retrieves Department calls at least one method on it within the transaction in which it is retrieved.

Patinous answered 4/12, 2014 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.