【问题标题】:Hibernate @Version annotation休眠@Version注解
【发布时间】:2013-09-29 17:57:23
【问题描述】:

hibernate @version 和 ManyToOne Mapping 有什么关系。

假设我有两张表 Department 和 Employee。这是部门是主表 和 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);
    }

}

【问题讨论】:

    标签: java hibernate hibernate-mapping spring-orm


    【解决方案1】:

    您需要先保存Department,然后再保存Employee。

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

    更新回应您的评论:

    为了将员工与部门相关联,您需要有一个存在的部门。请记住,在您的数据库中,Employee 对 Department 有一个 FK,所以 Hibernate 抱怨的是您试图用一个不存在的 Department 保存一个 Employee,所以您有以下选项:

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

    【讨论】:

    • 哦不!!!!!!!!!!为什么我要先保存部门,它只是参考,没有做任何更改。简单假设,您有员工屏幕,其中部门对象作为下拉列表给出,并且在保存新员工时,为什么我们要先保存部门?
    • @SenthilMuthiah 看到我的更新。在您的示例中,您尝试使用以前未保存的新部门保存员工。
    • 不,正如您在我的问题中看到的那样,部门已经存在于数据库中。我通过查询检索并存储在 HTTP 会话中。之后,在其他页面中,我只是将该对象传递给员工。请进一步帮助,这真的让我死了一个多星期。而且,如果我为部门对象设置 version = 0,那么一切都很好。
    • @SenthilMuthiah 您的部门对象一定有问题。检查它有什么版本。它的版本必须 > 0,在保存员工之前检查它。
    • 哦是的!!!!!!!!!!!!!!!!!!你是 100% 正确的,实际上它是空的,我不知道它是如何存储的。可能是我添加了列并在插入了几条记录之后。所以hibernate总是会为新记录插入>1。太感谢了。你拯救了我的一周和一天
    【解决方案2】:

    您的主要问题不是级联,而是获取。

    请参阅以下帖子: Difference between FetchType LAZY and EAGER in Java Persistence API?

    @ManyToOne(fetch = FetchType.EAGER)
    

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

    【讨论】:

      【解决方案3】:

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

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

      由于您的部门存储在会话中,因此可以肯定地说它是分离的。

      由于您没有修改 Department 实例,因此这种情况的最佳途径是:

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

      在此处查看 Hibernate 文档:Entity Manager - Loading an Object

      如果您收到 EntityNotFoundException,请确保最初检索 Department 的代码在检索它的事务中至少调用了一个方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-02
        • 1970-01-01
        • 1970-01-01
        • 2019-09-27
        • 2011-03-14
        • 2015-11-06
        • 1970-01-01
        相关资源
        最近更新 更多