Hibernate @Version注解

4

hibernate @version和ManyToOne映射之间的关系是什么?

假设我有两个表Department和Employee。在这里,Department是主表,Employee是明细表。在Employee表中,departmentID被引用为外键。

这是我的类:

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;

}

同时,Spring还处理会话。因此假设在一个页面中,特定的部门被获取并存储在HTTP会话中。

现在在另一个页面中,我要做以下操作:

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

现在我遇到了以下异常。
org.springframework.dao.InvalidDataAccessApiUsageException: object references an unsaved transient instance - save the transient instance before flushing: 

只需按照以下方式进行一次更改,就会出现错误。

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

我的Spring配置

<?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" />

服务实现

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);
    }

}
3个回答

3

在保存雇员(Employee)之前,你需要先保存部门(Department)

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

回复您的评论:

为了将员工与部门关联起来,您需要有一个已存在的部门。请记住,在您的数据库中,员工有一个外键指向部门。因此,Hibernate 报错是因为您试图保存一个具有不存在部门的员工。因此,您有以下几个选项:

  1. 如果该部门是新部门,则必须先保存该部门再保存员工。
  2. 通过查询(例如entityManager.find(id, Department.class))找到已存储的部门,并在您的员工对象中使用该部门对象。
  3. 在员工对象中使用 @Cascade 标记您与该部门的关系。

哦不!!!!!!为什么我想先保存部门,它只是一个参考,没有进行任何更改。简单地假设您有一个员工屏幕,其中部门对象以下拉列表的形式给出,那么在保存新员工时,为什么我们要先保存部门呢? - Senthil Muthiah
不,正如您在我的问题中看到的那样,部门已经存在于数据库中。我通过查询检索并将其存储在HTTP会话中。之后,在其他页面中,我只是将该对象传递给员工。请进一步帮助,这真的让我烦恼了一个多星期。而且,如果我为部门对象设置version = 0,那么一切都很好。 - Senthil Muthiah
经过阅读,我了解到如果会话关闭,则对象将处于分离状态。现在部门对象处于分离状态。现在员工处于持久状态,我将部门对象分配给员工的一个 setter,这时出现问题。 - Senthil Muthiah
1
@SenthilMuthiah,你的 department 对象肯定有问题。检查它的版本是多少。它必须有一个大于0的版本,在保存员工之前进行检查。 - Alfredo Osorio
1
哦,是的!!!!!!!!!!!你是100%正确的,实际上它是null,我不知道它是如何存储的。也许我添加了该列并插入了几条记录。因此,Hibernate将始终为新记录插入>1。非常感谢你,你救了我的一周和一天。 - Senthil Muthiah
显示剩余3条评论

1

我知道这是一篇旧文章,但也许这会对其他人有所帮助。

假设您正在使用Hibernate作为JPA实现。

由于您的Department存储在会话中,因此可以安全地说它是分离的。

在这种情况下,最好的方法是,由于您不修改Department实例,因此:

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

在此查看Hibernate文档:实体管理器 - 加载对象

如果出现EntityNotFoundException,请确保最初检索部门的代码在检索它的事务中调用了至少一个方法。


1
你的主要问题不是层叠,而是获取。
请参阅以下帖子:Java持久化API中FetchType LAZY和EAGER的区别?
@ManyToOne(fetch = FetchType.EAGER)

否则,正如您所提到的,会话将被关闭并且对象将丢失。热心获取将确保它保持打开状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接