您的位置:首页 > 其它

hibernate 10 事务和并发 | hibernate 实战(第二版) 第10章事务和并发 | 笔记

2011-11-02 11:35 381 查看
1事务本质

事务本质:


哪怕只有一个步骤失败,则整个工作单元都必定失败。这就是大家所知的原子性(atomicity),则所有操作都作为一个原子单元来执行。

ACID,指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求.

  原子性

  整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  一致性

  在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  隔离性

  两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。

  持久性

  在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

数据库和系统事务

为了在事务内部执行所有的数据库操作,必须给这个工作单元的范围做标记。必须启动事务,并在某个时间点提交变化。如果出现错误(在

执行操作或者提交事务的时候),就必须回滚事务,把数据留着一致的状态中。这就是大家所知的事务划分,取决于你所使用的技术,它

设计更多或更少的手工干涉。

1、编程式的事务划分

通过JDBC Connection中调用setAutoCommit(false)启动事务,并通过调用commit()终止他。

在系统事务(system transaction)中处理几个资源的事务管理器(transaction manager)。这样的事务处理系统公开了与

开发人员进行交互的Java Transaction API(JTA)。JTA中的主API是UserTransaction接口,包含begin()和commit()系统事务方法。

常用接口:

java.sql.Connection--利用setAuto(false)、commit()和rollback()进行简单的JDBC事务划分。它可以但不应该被用在Hibernate

应用程序中,因为它把应用程序绑定到了一个简单的JDBC环境。

org.hibernate.Transaction--hibernate应用程序中统一的事务划分。它适用于非托管的简单JDBC环境,也适用于以JTA作为底层系统

事务俯卧ide应用程序服务器。但是,他最主要的好处在于与持久化上下文管理的紧密整合--例如,你提交时Session被自动清除。持久化上下文

也可以拥有这个事务的范围。如果你无法具备JTA兼容的事务服务,就使用Java SE中的这个API。

javax.transaction.UserTransaction--Java中编程式事务控制的标准接口,他是JTA的一部分。每当你具备JTA兼容的事务服务,并想

编程式的控制事务时,他就是应该成为你的首选。

java.persistence.EntityTransaction--在是欧诺个Java Persistence的java SE应用程序中,编程式事务控制的标准接口。

2、声明式事务划分

在应用程序中,当你希望在一个事物内部进行工作的时候要进行声明。然后处理这个关注点就是应用部署程序和运行时环境的责任了。Java

中提供声明式事务服务的标准容器是EJB容器,这项服务也称为容器托管事务。

Hibernate应用程序中的事务:

1、Java SE中的编程式事务

迟写行为,当Session的持久化上下文被清除时,大批量的SQL语句被尽可能迟的执行。默认情况下,发生在Transaction上调用Commit()

的时候。

2、异常处理

tx.rollback()回滚事务。

hibernate抛出类型(typed)异常:

最常见的HibernateException是个一般的错误。你必须检查异常信息,或者在通过异常中调用getCause()找出更多原因。

JDBCException是被Hibernate内部JDBC层抛出的任何异常。这种异常总是由一个特定的SQL语句产生,可以用getSQL()获得这个

引起麻烦的语句。JDBC连接(实际上是JDBC驱动器)抛出的内部异常可以通过getSQLException()或者getCause()获得,并且

通过getErrorCode()可以得到特定于数据库和特定于供应商的错误代码。

Hibernate包括JDBCException的子类型和一个内部转换器,该转换器试图把数据库驱动抛出器的特定于供应商的错误代码变成一些

更有意义的东西。内建的转换器可以给Hibernate支持的最重要的数据库方言生成。。。。

Hibernate抛出的其他RuntimeException也应该终止事务。应该始终确保捕捉RuntimeException,无论你计划利用任何细粒度的异常

处理策略去做什么。

使用JTA的编程式事务:

容器托管事务:

2控制并发访问

在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同:

◆未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

◆授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

◆可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

◆序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

如果一个事务读取由另一个还没有被提交的事务进行的改变,就发生脏读取(dirty read)。这很危险,因为由其他事务进行的改变随后

可能回滚,并且低一个事务可能编写无效的数据。

如果一个事物读取一个行两次,并且每次读取不同的状态,就会发生不可重复读取(unrepeatable read)。例如,另一个事务可能已经

写到这个行,并已经在两次读取之间提交。

不可重复读取的一个特殊案例是二次丢失更新问题(second lost updates problem)。想象两个并发事务都读取一个行:一个写到行并

提交,然后第二个也写到行并提交。有第一个事务所做的改变丢失了。

幻读(phantom read)发生在一个事务执行一个查询两次,并且第二个结果集包括第一个结果集中不可见的行,或者包括已经删除的行时。

这种情况是由另一个事务在两次查询执行之间插入或者删除行照成的。

ANSI事务隔离性级别:

允许脏读取但不允许丢失更新的系统,要在读取未提交(read uncommitted)的隔离性中操作。如果一个为提交事务已经写到一个行,

另一个事务就不可能再写到这个行。但任何事务都可以读取任何行。这个隔离性级别可以在数据库管理系统中通过专门的写锁来实现。

允许不可重复读取但不允许脏读取系统,要实现读取提交(read committed)的事务隔离性。这可以用共享的读锁和专门的写锁来实现。

读取事务不会阻塞其他事务访问行。但是为提交的鞋事务隔赛所有其他的事务访问改行。

在可重复读(repeatable read)隔离性模式中操作的系统既不允许不可重复读取,也不允许脏读取。幻读可能发生。

可序列化 (serializable)提供最严格的事务隔离性。这个隔离性级别模拟连续的事务执行,好像事务是连续的一个接一个的执行,而不是

并发的执行。

选择隔离性级别:

首先,消除读取为提交隔离性级别。在不同的事务中使用一个为提交的事务变化是很危险的。一个事务的回滚或者失败将影响其他的并发事务。

甚至由一个终止回滚的事务所做的改变也可能在任何地方被提交,因为他们可能读取,然后由另一个成功的事务传播!

其次,大多数应用程序不需要可序列化的隔离级别(幻读通常不成问题),并且这个隔离性往往难以伸缩。

这样就把选择读取提交还是可重复读取交给你了。

Hibernate配置选项:

hibernate.connection.isolation = 4

1-读取为提交隔离性

2-读取提交隔离性

3-可重复读取隔离性

4-可序列化隔离性

乐观并发控制:

乐观的方法始终假设一切都会很好,并且很少有冲突的数据修改。在编写数据时,乐观并发控制值在工作单元结束时才出现错误。多用户

的应用程序通常默认为使用读取提交隔离性级别的乐观并发控制和数据库连接。只有适当的时候才获得额外的隔离性保证;这种方法保证

了最佳的性能和可伸缩性。

对于如何处理对话中这些第二个事务中的丢失更新,你有3种选择:

最晚提交生效(last commit wins)--两个事物提交都成功,第二个提交覆盖第一个变化。没有显示错误消息。

最早提交生效(fist commit wins)--对话A的事务被提交,并且在对话B中提交事务的用户得到一条错误信息。用户必须获取新数据来

重启会话,并在此利用没有失效的数据完成对话的所有步骤。

合并冲突更新(merge conflicting updates)--第一个修改被提交,并且对话B中的事务在提交时终止,带有一条错误消息。但是失败

的对话B用户可以选择性的应用变化,而不是再次在对话中完成所有工作。

在hibernate中启用版本控制:

hibernate提供自动的版本控制。每个实体实例都有一个版本,它可以是一个数字或者是一个时间戳。当对象修改时,hibernate就增加

他的版本号,自动比较版本,如果侦测到冲突就抛出异常。因此,你给所有持久化的实体类都添加这个版本属性,来启动乐观锁:

version:

view
plaincopy
to clipboardprint?

public class Item implements Serializable
{

private Integer
itemId;

private String
itemName;

/**
版本控制 */

private int version;

配置:

view
plaincopy
to clipboardprint?

<class name="Item" table="ITEM">

<id
name="itemId" column="ITEM_ID" type="integer">

<generator class="native"/>

</id>

<!--
XML格式的version属性映射必须立即放在标示符属性映射之后 -->

<version
name="version" access="field" column="OBJ_VERSION" />

<property
name="itemName" type="string" column="ITEM_NAME"/>

</class>

代码:

view
plaincopy
to clipboardprint?

/**

* 报错:

* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

* @param itemName

*/

private static void update1(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

// 一样会报错

// item1.setItemName(itemName);

item1.setItemName(itemName+"1");

tr.commit();

session.close();

//org.hibernate.StaleObjectStateException:

//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

tr1.commit();

session1.close();

sessionFactory.close();

}

/**

* 提交成功

* @param itemName

*/

private static void update2(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

tr.commit();

session.close();

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

item1.setItemName(itemName+"1");

tr1.commit();

session1.close();

sessionFactory.close();

}

timestamp(理论上讲,时间戳更不安全。且在集群环境中更不安全):

view
plaincopy
to clipboardprint?

public class Item implements Serializable
{

private Integer
itemId;

private String
itemName;

/**
版本控制 */

private Date
lastUpdated;

配置:

view
plaincopy
to clipboardprint?

<class name="Item" table="ITEM">

<id
name="itemId" column="ITEM_ID" type="integer">

<generator class="native"/>

</id>

<timestamp
name="lastUpdated" access="field" column="LAST_UPDATED"></timestamp>

<property
name="itemName" type="string" column="ITEM_NAME"/>

</class>

代码:

view
plaincopy
to clipboardprint?

/**

* 报错:

* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

* @param itemName

*/

private static void update1(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

// 一样会报错

// item1.setItemName(itemName);

item1.setItemName(itemName+"1");

tr.commit();

session.close();

//org.hibernate.StaleObjectStateException:

//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

tr1.commit();

session1.close();

sessionFactory.close();

}

/**

* 提交成功

* @param itemName

*/

private static void update2(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

tr.commit();

session.close();

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

item1.setItemName(itemName+"1");

tr1.commit();

session1.close();

sessionFactory.close();

}

hibernate检查机制:

org.hibernate.persister.entity.AbstractEntityPersister.check:

view
plaincopy
to clipboardprint?

protected boolean check(int rows,
Serializable id, int tableNumber,
Expectation expectation, PreparedStatement statement) throws HibernateException
{

try {

expectation.verifyOutcome(
rows, statement, -1 );

}

catch(
StaleStateException e ) {

if (
!isNullableTable( tableNumber ) ) {

if (
getFactory().getStatistics().isStatisticsEnabled() ) {

getFactory().getStatisticsImplementor()

.optimisticFailure(
getEntityName() );

}

throw new StaleObjectStateException(
getEntityName(), id );

}

return false;

}

catch(
TooManyRowsAffectedException e ) {

throw new HibernateException(

"Duplicate
identifier in table for: " +

MessageHelper.infoString( this,
id, getFactory() )

);

}

catch (
Throwable t ) {

return false;

}

return true;

}

org.hibernate.jdbc.Expectation:

view
plaincopy
to clipboardprint?

/**

*
Perform verification of the outcome of the RDBMS operation based on

*
the type of expectation defined.

*

*
@param rowCount The RDBMS reported "number of rows affected".

*
@param statement The statement representing the operation

*
@param batchPosition The position in the batch (if batching)

*
@throws SQLException Exception from the JDBC driver

*
@throws HibernateException Problem processing the outcome.

*/

public void verifyOutcome(int rowCount,
PreparedStatement statement, int batchPosition) throws SQLException,
HibernateException;

view
plaincopy
to clipboardprint?

/**

*
我用了两个factory,也是可以提交成功的

*
你想想,这证明了什么

*
@param itemName

*/

private static void update3(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

tr.commit();

session.close();

sessionFactory.close();

Configuration
configuration1 = new Configuration().configure();

SessionFactory
sessionFactory1 = configuration1.buildSessionFactory();

Session
session1 = sessionFactory1.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

item1.setItemName(itemName+"11");

tr1.commit();

session1.close();

sessionFactory1.close();

}

版本控制的自动管理:

涉及目前被版本控制的Item对象的每一个DML操作都包括版本检查。例如,假设在一个工作单元中,你从版本为1的数据库中加载一个Item。然后修改它的其中一个值类型属性。当持久化

上下文被清除时,hibernate侦测到修改,并把Item的版本增加到2。然后执行SQL UPDATE使这一修改在数据库中永久化:

update item set intial_price = '11.23',obj_version=2 where item_id = 123 and obj_version=1

如果另一个并发的工作单元更新和提交了同一个行,obj_version列就不包含值1,行也不会被更新。hibernate检查由jdbc驱动器返回这个语句所更新

的行数---在这个例子中,被更新的行数为0--并抛出StaleObjectStateException。加载Item时呈现的状态,清除时不再在数据库中呈现;

因而,你真正使用失效的数据,必须通知应用程序的用户。可以捕捉这个异常,并显示一条错误消息,或者显示帮助用户给应用程序重启会话

的一个提示框。

如果你想要禁用对特定值类型属性或者集合的自动增加,就用optimistic-lock="false"属性映射他。

没有版本号或者时间戳的版本控制(如果你用的遗留表,不方便再添加字段):

view
plaincopy
to clipboardprint?

<!--
org.hibernate.MappingException: optimistic-lock=all|dirty requires dynamic-update="true" -->

<!--
dirty的区别在于,只有当某个属性共同修改时,才报错;all是只要有修改就报错 -->

<class name="Item" table="ITEM" optimistic-lock="all" dynamic-update="true">

<id name="itemId" column="ITEM_ID" type="integer">

<generator class="native"/>

</id>

<property name="itemName" type="string" column="ITEM_NAME"/>

</class>

用Java Persistence版本控制:

version:


实体:

view
plaincopy
to clipboardprint?

@Entity

public class Item implements Serializable
{

@Id

@GeneratedValue

@Column(name="ITEM_ID")

private Integer
itemId;

@Column(name="ITEM_NAME")

private String
itemName;

/**
版本控制 */

@Version

@Column(name="OBJ_VERSION")

private int version;

更新:

view
plaincopy
to clipboardprint?

private static void update(String
itemName) {

EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");

EntityManager
em = factory.createEntityManager();

EntityTransaction
tr = em.getTransaction();

tr.begin();

Item
item = em.find(Item.class, 1);

item.setItemName(itemName);

EntityManager
em1 = factory.createEntityManager();

EntityTransaction
tr1 = em1.getTransaction();

tr1.begin();

Item
item1 = em1.find(Item.class, 1);

item1.setItemName(itemName+"he");

tr.commit();

em.close();

//Caused
by: javax.persistence.OptimisticLockException

tr1.commit();

em1.close();

factory.close();

}

报错:

Caused by: javax.persistence.OptimisticLockException

optimisticLock:

实体:

view
plaincopy
to clipboardprint?

@Entity

//optimistic-lock=all|dirty
requires dynamic-update="true"

@org.hibernate.annotations.Entity(

optimisticLock=OptimisticLockType.ALL,

dynamicUpdate=true)

public class Item implements Serializable
{

@Id

@GeneratedValue

@Column(name="ITEM_ID")

private Integer
itemId;

@Column(name="ITEM_NAME")

private String
itemName;

//版本检测排除下面的属性

@OptimisticLock(excluded=true)

private Double
price;

public Item()
{

super();

}

调用:

view
plaincopy
to clipboardprint?

/**

*
报错

*
Caused by: javax.persistence.OptimisticLockException

*
@param itemName

*/

private static void update1(String
itemName) {

EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");

EntityManager
em = factory.createEntityManager();

EntityTransaction
tr = em.getTransaction();

tr.begin();

Item
item = em.find(Item.class, 1);

item.setItemName(itemName);

EntityManager
em1 = factory.createEntityManager();

EntityTransaction
tr1 = em1.getTransaction();

tr1.begin();

Item
item1 = em1.find(Item.class, 1);

item1.setItemName(itemName+"he");

tr.commit();

em.close();

//Caused
by: javax.persistence.OptimisticLockException

tr1.commit();

em1.close();

factory.close();

}

/**

*
不会报错

*
@param price

*/

private static void update2(Double
price) {

EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");

EntityManager
em = factory.createEntityManager();

EntityTransaction
tr = em.getTransaction();

tr.begin();

Item
item = em.find(Item.class, 1);

item.setPrice(price);

EntityManager
em1 = factory.createEntityManager();

EntityTransaction
tr1 = em1.getTransaction();

tr1.begin();

Item
item1 = em1.find(Item.class, 1);

item1.setPrice(price+1D);

tr.commit();

em.close();

tr1.commit();

em1.close();

factory.close();

}

获得额外的隔离性保证:

1、显示的悲观锁

当我们关注应用程序的课伸缩性时。例如,对标量查询可能需要可重复读取。

不是把所有的数据库事务转换为一个更高的、不可伸缩的隔离性级别,而是在必要时,在Hibernate Session中使用lock()方法获得

更高的隔离性:

view
plaincopy
to clipboardprint?

/**

*
session.lock(item, LockMode.UPGRADE);

*
@param itemName

*/

private static void update1(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

//指定锁级别

session.lock(item,
LockMode.UPGRADE);

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 1);

item1.setItemName(itemName+"1");

//停在这步,因为碰到for
update的锁了,他要等到tr提交了之后才会执行,但是,我们这个demo当然无法执行到这步

tr1.commit();

session1.close();

tr.commit();

session.close();

sessionFactory.close();

}

/**

*
只锁定指定列的数据,不会锁定表

*
但是,以前这种方式是悲观锁,会锁定整个表,如果指定锁定某一行,如下:

*
Item item = (Item) session.get(Item.class, 1, LockMode.UPGRADE);

*
@param itemName

*/

private static void update2(String
itemName) {

Configuration
configuration = new Configuration().configure();

SessionFactory
sessionFactory = configuration.buildSessionFactory();

Session
session = sessionFactory.openSession();

Transaction
tr = session.beginTransaction();

Item
item = (Item) session.get(Item.class, 1);

item.setItemName(itemName);

//指定锁级别

session.lock(item,
LockMode.UPGRADE);

Session
session1 = sessionFactory.openSession();

Transaction
tr1 = session1.beginTransaction();

Item
item1 = (Item) session1.get(Item.class, 2);

item1.setItemName(itemName+"1");

//执行通过

tr1.commit();

session1.close();

tr.commit();

session.close();

sessionFactory.close();

}

Java Persistence 的hibernate也实现了这个技术,但是规范并没有要求实现这个。

hibernate锁模式:

LockMode.NONE--别到数据库中去,除非对象不处于任何高速缓存中。

LockMode.READ--绕过所有高速缓存,并执行版本检查,来验证内存中的对象是否与当前数据中村中的版本相同。

LockMode.UPGRADE--绕过所有高速缓存,做一个版本检查,如果支持的话,就获得数据库级的悲观升级锁。相当于Java Persistence中的LockModeType.READ。如果数据库方言不支持

select...for update选项,这个模式就透明的退回到LockMode.READ。

LockMode.UPGRADE_NOWAIT--与LockMode.UPGRADE相同,但如果支持的话,就使用select...for update nowait。他禁用了等待并发所释放,因为如果无法获得锁,就立即抛出锁异常。

如果数据库SQL方言不支持nowait选择,这个模式就透明的退回到LockMode.UPGRADE。

LockMode.FORCE--在数据库中强制增加对象的版本,来表明他已经被当前事务修改。相当于Java Persistence中的LockModeType.WRITE。

LockMode.WRITE--当hibernate已经在当前事务中写到一个行时,就自动获得他。(内部模式)

默认情况下load()和get()使用NONE。

3非事务数据访问

许多数据库管理员在每个新的数据库连接上默认启用所谓的自动提交模式。(唯一的保证是单个SQL语句具有原子性)

术语非事物的数据访问意味着没有显式的事务范围,没有系统事务,并且数据访问的行为处于自动提交模式。这并不意味着没有涉及实质性

的数据库事务。

get()可以

connection.autocommit = true

当没有事务在处理时,Session默认FlushMode改变了。默认的行为FlushMode.AUTO,导致在每个HQL、SQL或者Criteria之前同步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: