休眠删除查询

Posted

技术标签:

【中文标题】休眠删除查询【英文标题】:Hibernate Delete query 【发布时间】:2012-11-03 15:35:37 【问题描述】:

当我尝试从数据库中删除一个条目时,使用

session.delete(object) 

那么我可以:

1) 如果该行存在于 DB 中,则执行两个 SQL 查询:一个选择,然后一个删除

2) 如果数据库中不存在该行,则仅执行选择查询

但这又不是更新的情况。无论是否存在 DB 行,都只会执行更新查询。

请告诉我为什么删除操作会出现这种行为。这不是性能问题,因为两个查询被命中而不是一个?

编辑:

我使用的是休眠 3.2.5

示例代码:

SessionFactory sessionFactory = new Configuration().configure("student.cfg.xml").buildSessionFactory();
    Session session = sessionFactory.openSession();
    Student student = new Student();
    student.setFirstName("AAA");
    student.setLastName("BBB");
    student.setCity("CCC");
    student.setState("DDD");
    student.setCountry("EEE");
    student.setId("FFF");
    session.delete(student);
    session.flush();
            session.close();

cfg.xml

<property name="hibernate.connection.username">system</property>
    <property name="hibernate.connection.password">XXX</property>
    <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
    <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521/orcl</property>      
    <property name="hibernate.jdbc.batch_size">30</property>
    <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
    <property name="hibernate.cache.use_query_cache">false</property>
    <property name="hibernate.cache.use_second_level_cache">false</property>
    <property name="hibernate.connection.release_mode">after_transaction</property>
    <property name="hibernate.connection.autocommit">true</property>
    <property name="hibernate.connection.pool_size">0</property>
    <property name="hibernate.current_session_context_class">thread</property>    
    <property name="hibernate.show_sql">true</property>
    <property name="hibernate.hbm2ddl.auto">update</property>        

hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.infy.model.Student" table="STUDENT">
    <id name="id" column="ID">
        <generator class="assigned"></generator>
    </id>
    <property name="firstName" type="string" column="FIRSTNAME"></property>
    <property name="lastName" type="string" column="LASTNAME"></property>
    <property name="city" type="string" column="CITY"></property>
    <property name="state" type="string" column="STATE"></property>
    <property name="country" type="string" column="COUNTRY"></property>        
</class>

【问题讨论】:

Hibernate 有时会在更新之前进行选择。这取决于对象的状态(持久、瞬态或分离)。要回答您的具体问题,请查看您的示例代码。 【参考方案1】:

原因是为了删除一个对象,Hibernate 要求该对象处于持久状态。因此,Hibernate 首先获取对象(SELECT),然后将其删除(DELETE)。

为什么 Hibernate 需要先获取对象?原因是可能启用了 Hibernate 拦截器(http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/events.html),并且对象必须通过这些拦截器才能完成其生命周期。如果直接在数据库中删除行,则拦截器不会运行。

另一方面,可以使用批量操作在单个 SQL DELETE 语句中删除实体:

Query q = session.createQuery("delete Entity where id = X");
q.executeUpdate();

【讨论】:

不,要删除的对象可以处于瞬态、分离或持久状态。我认为您的代码的重点是您正在创建一个新对象(瞬态状态),然后将其删除。您为对象设置 id 的事实不会导致对象从 Transient 状态变为 Persistent 状态(它仍然是 Transient 对象)。出于这个原因,一旦 session.delete() 被执行,它首先需要获取对象,将其存储在持久化上下文(一级缓存)中,然后将其删除。 如果您想进行测试,请尝试使用数据库中不存在的 id 设置您的对象。然后在删除之前保存它,然后进行删除。您将看到删除不需要执行 SELECT,因为该对象已经在一级缓存中(它是一个持久对象)。 瞬态对象和分离对象之间的区别不仅在于 id 值的存在,更重要的是数据库中是否存在代表该对象的行(不,在瞬态的情况下;是的在分离的情况下)。关于另一个问题,我需要在另一条评论中写出来:)(相当长) 第一件事是删除瞬态对象没有多大意义(数据库中没有代表该对象的行)。在策略“已分配”的情况下,您为对象分配了一个 ID。执行删除时,Hibernate 认为该对象曾经存储在 DB 中(因为它有一个 ID),因此它检索该对象(因为我之前解释过)然后将其删除。在策略“native”的情况下,您不需要为对象设置 id(由 Hibernate 处理)。如果 Hibernate 试图检索它会发生什么?执行的 SELECT 将是... ...类似:SELECT * FROM X WHERE id = null,它总是返回空。这个查询可以被跳过,Hibernate 只执行 DELETE。我相信(尽管我需要根据一些文档检查它),对于具有“已分配”策略的瞬态对象,Hibernate 认为该对象已分离,并在删除它之前尝试获取。我坚持认为,如果您想要删除一个对象,那么创建一个新实例并设置一个 id 没有多大意义。该对象在删除之前必须存在于数据库中,您应该使用 session.get(id) 获取然后将其删除【参考方案2】:

要理解hibernate的这种特殊行为,理解几个hibernate概念很重要——

休眠对象状态

Transient - 一个对象处于瞬态状态,如果它已经 已实例化,但仍未与 Hibernate 会话关联。

持久化 - 持久化实例在 数据库和标识符值。它可能刚刚被保存或 但是,根据定义,它是在 Session 范围内加载的。

Detached - 一个分离的实例是一个持久化的对象, 但其 Session 已关闭。

http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html#objectstate-overview

事务后写

接下来要理解的是“事务写入落后”。当附加到休眠会话的对象被修改时,它们不会立即传播到数据库。 Hibernate 这样做至少有两个不同的原因。

执行批量插入和更新。 仅传播最后的更改。如果一个对象被多次更新,它仍然只触发一个更新语句。

http://learningviacode.blogspot.com/2012/02/write-behind-technique-in-hibernate.html

一级缓存

Hibernate 有一个叫做“一级缓存”的东西。每当您将对象传递给save()update()saveOrUpdate(),以及每当您使用load()get()list()iterate()scroll() 检索对象时,该对象都会添加到Session 的内部缓存。这是它跟踪各种对象变化的地方。

Hibernate 拦截器和对象生命周期监听器 -

从会话到应用程序的拦截器接口和侦听器回调允许应用程序在保存、更新、删除或加载持久对象之前检查和/或操作它的属性。 http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html#d0e3069


此部分已更新

级联

Hibernate 允许应用程序定义关联之间的级联关系。例如,'cascade-delete' 父子关联将导致删除父时所有子删除。

那么,为什么这些很重要。

为了能够进行事务后写,能够跟踪对对象(对象图)的多个更改并能够执行生命周期回调,hibernate 需要知道对象是否为transient/detached,并且它需要具有在对底层对象和关联关系进行任何更改之前,对象在其一级缓存中。

这就是为什么 hibernate (有时) 发出 'SELECT' 语句以在对对象进行更改之前将对象(如果尚未加载)加载到其第一级缓存中。

为什么 hibernate 有时只发出 'SELECT' 语句?

Hibernate 发出'SELECT' 语句来确定对象处于什么状态。如果select 语句返回对象,则对象处于detached 状态,如果不返回对象,则对象处于@987654337 @ 状态。

来到你​​的场景 -

Delete - 'Delete' 发出 SELECT 语句,因为 hibernate 需要知道对象是否存在于数据库中。如果该对象存在于数据库中,hibernate 将其视为detached,然后将其重新附加到会话并处理删除生命周期。

更新 - 由于您显式调用'Update' 而不是'SaveOrUpdate',hibernate 盲目地假定对象处于detached 状态,将给定对象重新附加到会话第一级缓存并处理更新生命周期。如果与hibernate的假设相反,发现该对象在数据库中不存在,则在会话刷新时抛出异常。

SaveOrUpdate - 如果调用'SaveOrUpdate',hibernate 必须确定对象的状态,因此它使用SELECT 语句来确定对象是否处于Transient/Detached 状态。如果对象处于transient 状态,则处理'insert' 生命周期,如果对象处于detached 状态,则处理'Update' 生命周期。

【讨论】:

我贴了一个示例代码,对象是分离状态吗? 是的,它处于分离状态。最初的想法是它处于“瞬态”状态,但是由于您已为 Student 分配了一个 id,因此 hibernate 会认为它是分离的。 当我使用generator = assigned 执行上述代码时,会执行两个查询:选择后跟删除,但如果我将生成器更改为native,那么只有删除会被执行。那么当生成器为native 时,即使我分配了 Id,对象是否也没有处于分离状态? 如果你使用'generator = assignment'并且你没有指定'unsaved-value',hibernate必须总是去数据库来确定一个对象是暂时的还是分离的。因此,它必须发出“SELECT”语句。我不确定为什么当“generator = native”时它没有发出“SELECT”语句。唯一的解释是hibernate认为对象处于“瞬态”而不是“分离”状态。你能发布你的“student.cfg.xml”文件的代码吗? 再说一次,更新不也是一样吗?如果我将上面发布的相同代码delete 更改为update,那么也只有一个查询被触发。那为什么 update 处于持久状态而 delete 处于分离状态?【参考方案3】:

我不确定,但是:

如果您使用非瞬态对象调用删除方法,这意味着首先从数据库中获取对象。所以看到select语句是正常的。也许最后你会看到 2 select + 1 delete?

1234563需要。

编辑: 使用瞬态实例调用 delete() 意味着执行以下操作:

MyEntity entity = new MyEntity();
entity.setId(1234);
session.delete(entity);

这将删除 id 为 1234 的行,即使该对象是 Hibernate 未检索到的简单 pojo,不存在于其会话缓存中,也完全不受 Hibernate 管理。

如果你有一个实体关联,Hibernate 可能必须获取完整的实体,以便知道删除是否应该级联到关联的实体。

【讨论】:

所有内容都在 javadoc 中。在使用 Hibernate 时,了解实体的生命周期很重要。 docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/…【参考方案4】:

而不是使用

session.delete(对象)

使用

getHibernateTemplate().delete(object)

select 查询和delete 都使用getHibernateTemplate()

select 查询中,您必须使用DetachedCriteriaCriteria

选择查询示例

List<foo> fooList = new ArrayList<foo>();
DetachedCriteria queryCriteria = DetachedCriteria.forClass(foo.class);
queryCriteria.add(Restrictions.eq("Column_name",restriction));
fooList = getHibernateTemplate().findByCriteria(queryCriteria);

在休眠中避免使用会话,这里我不确定,但问题只是因为会话使用而发生

【讨论】:

为什么要避免使用会话?由于您的解释不清楚,请您提供一些进一步的意见 我也在我的所有应用程序中使用休眠,过去我也面临使用同一个表的问题,超过 2 个查询背靠背。经过几次研究,我得到了避免使用会话的解决方案。它可能不会像我们需要或我们想的那样留下/删除/修改任何值。在会话中,有时您的数据将在没有更新/删除查询的情况下更改/删除(只是因为会话使用)。这是我过去 2 年使用 hibernate 的经验。 你在db中检查父子关系,它也可能会给你一个错误。作为“开放赏金”问题,我想你已经检查过了。 在哪里可以找到getHibernateTemplate() 方法?你能把jar的名字贴出来吗? 它是 Spring 框架的一部分 - download jar

以上是关于休眠删除查询的主要内容,如果未能解决你的问题,请参考以下文章

带有子查询的休眠标准

MySQL 交叉联接查询上的休眠异常

休眠oracle rownum问题

系统休眠文件可以删除吗 系统休眠文件怎么删除

事务在休眠中不回滚

Windows 7关闭睡眠(休眠)模式和删除休眠文件