休眠删除查询
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
查询中,您必须使用DetachedCriteria
或Criteria
选择查询示例
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以上是关于休眠删除查询的主要内容,如果未能解决你的问题,请参考以下文章