hibernate_事务

1) 概念

事务是指一个单元的工作,这些工作要么全做,要么全不做。

事务处理可以确保所有操作都成功完成,否则不会永久更新面向数据的资源。

 

2) 事务特性

事务有ACID属性(Atomicity原子性,Consistency一致性,Isolation隔离性,Durability持久性。)

(1)原子性:事务是不可分割的工作单元,事务中所有操作执行成功事务才算成功

(2)一致性:事务不能破坏数据的完整性和一致性(正确性)

(3)隔离性:在并发环境中,事务是独立的,它不依赖其他事务也能完成任务

(4)持久性:只要事务成功执行,数据永久保存下来

 

3) 并发

并发问题的解决方案就是对事务进行加锁,有共享锁(为读访问锁住数据,防止其他事务更改),排他锁(用于每一次数据的更新会阻止其他事务访问数据)等锁。

Hibernate中的事务隔离级别有如下四种:

1级读操作未提交,允许脏读,不允许更新丢失。如果一个事务已经开始写数据,则不允许另外一个事务同时进行写操作,但允许其他事物进行读操作。

2级读操作已提交,允许不可重复读取,但不允许脏读取。读取数据的事务允许其他事物继续反问该行数据,但是未提交的写事务将会禁止其他事务访问该数据。

4级可重复读,禁止不可重复读取和脏读取,但是有时肯恩改行粗线幻想读取数据,读取数据的事务将会禁止写事务,但允许读事务,写事务则禁止其他事务。

8级可串行化,提供严格的事务隔离,要求事务序列化执行,事务职能一个接着一个执行,不能并发执行。

配置代码如下:<property name="hibernate.connection.isolation"> 4 </property>

 

4) 悲观锁和乐观锁。

    悲观锁假定任何时刻存取数据时都可能有另一个事务也正在存取同一数据,因而采取了对数据库层次的多顶状态。在锁定的实践内其他事务不能对资料进行存取,这样很有可能造成长时间等待。在Hibernate中可以通过设定锁定模式实现。

session.lock(student, LockMode.UPGRADE);

乐观锁则认为当前事务操作数据库资源,很少或不会有其他事务同时访问该数据资源,因此不用数据库级别上的锁定,完全依靠数据库的隔离界别来自动管理所的工作。主要在乐观锁定中对应用程序采用版本控制手段来避免可能出现的并发问题。在Hibernate中有两种元素来实现版本控制功能。

<version>元素利用一个递增的证书来跟踪数据库表中记录的版本;

<timestamp>元素则用时间戳来跟踪数据库表中的记录的版本。

 

5) Hibernate中使用事务

Hibernate中使用事务的步骤如下:

1、事务开始

Transaction t = getSession().beginTransaction();  

 

2、提交事务,若是新的JDBC事务或者JTA事务则会先调用flush()方法清理缓存,然后向数据库提交事务,在受管理环境中,若加入JTA事务,则不会向数据库提交数据,只是flush()清理缓存而已。

t.commit();  

 

3、当事务在执行过程中出现问题时,为了保证数据完整性,需要回滚事务。

t.rollback();

 

如:

Transaction tx = session.beginTransaction();

User user = new User();

    user.setName("lisi");  

    user.setAge(28);

user.setBirth(new Date());

session.save(user);

tx.commit();

 

6) Spring中使用事务

    有了spring之后,他对事务进行了很好的封装。有了面向切面的AOP,实现事务变得越来越方便。我们不用再每个需要事务的方法中去开启关闭事务,把这些直接交给配置文件,代码中看不到事务的影子,悄无声息的就把事务给实现了。

下面介绍使用注解添加事务,分两步,1.配置事务,2.po添加注解

1.配置

<!-- 配置事务管理器 -->  

<bean id="UserTransactionManager"  class="org.springframework.orm.hibernate4.HibernateTransactionManager">

    <!-- 为事务管理器注入sessionFactory" -->

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

2.启用注解

如果使用注解方式配置事务,那么需要添加配置:

<!-- 注解实现事务 -->

<tx:annotation-driven transaction-manager="UserTransactionManager"/>

 

3.添加注解

事务注解为:@Transactional

@Transactional(noRollbackFor=RuntimeException.class)
@TRacsactional(RollbackFor=Exception.class)
@Tracsactional(readOnly=true);
@Tracsactional(timeout=100)
@Tracsactional(isolation)//数据库的隔离级别

可以添加到类上,也可以添加到函数上。如:

如:

@Transactional(propagation=Propagation.REQUIRED)

public void save(User user){

userdao.save(user);

}

当有配置文件中配置的有多个事务时,可以通过id去指定使用哪个,如:

@Transactional(value="UserTransactionManager",propagation=Propagation.REQUIRED)

public void TestTransctionSave(User user) throws Exception{

userdao.save(user);

throw new Exception();

}

 

7) 测试事务

倒底事务如何使用呢,看一个例子:

Controller层:

@ResponseBody

@RequestMapping("/doadduser")

public String doAddUser(User user){

userService.TestTransctionSave(user);

return "success";

}

Service层:

@Service

@Transactional(value="UserTransactionManager",rollbackFor=Exception.class)

public class UserService {

@Autowired

UserDao userdao;

public void TestTransctionSave(User user){

userdao.save(user);

user.setName(user.getName()+"-ak47");

userdao.save(user);

throw new RuntimeException();

}

}

Dao层:

@Repository

public class UserDao {

@Resource(name="sessionFactory")

private SessionFactory sessionFactory;

public void save(User user){

Session session = sessionFactory.openSession();//getCurrentSession

session.save(user);

}

}

通过页面调用上面的方法。

正确情况应该是保存被回滚了,但经测试dao调用save一结束数据就被提交了,并没有回滚。

 

不能回滚有很多原因,

1.看配置是否正确

2.看注解是否正确

3.是否为RuntimeException

4.Mysql自身的问题

执行sql语句:show engines

 hibernate_事务

当前数据库的默认存储引擎是InnoDB(注意观察Support这一列的值),不过有的数据库却不是,它们默认的是MyISAM

InnoDB支持事务,而MyISAM不支持!

其实MyISAMMySQL默认的存储引擎,在安装MySQL时如果没有指定存储引擎,那么MySQL会默认使用MyISAM,因为它不支持事务,也许效率会比InnoDB高一些。

如果使用的是MyISAM,那么需要将其改为InnoDB,具体方法如下:

(因为本机是InnoDB,因此下面的方法没有测试过)

1、打开MySQL_HOME/my.ini”,找到[mysqld],在它的下面有一句话是“skip-innodb”,将其注释掉。

2、[mysqld]下加上一句“default_table_type=INNODB”(如果写成default-storage-engine=INNODB也可以)

3、重启MySQL服务(注意:是重启服务,不是光退出数据库就完事了!)

4、再次执行mysql> show engines;可以看见默认存储引擎已经变成InnoDB了。

5、原来已经存在的表,如果想使用事务,要修改它的存储引擎,在默认是InnoDB的情况下删除重建。当不想删除的时候,执行alter table 表名 ENGINE = InnoDB;之后执行show table status from 数据库名 where name='表名';可以查看表状态,看存储引擎是否被修改了。

本例中不能回滚,不是上面的三原因,经过一天艰辛的排查,最后发现是因为dao层代码的问题,使用的是sessionFactory.openSession();Open出来新的seccion事务影响。因此修改dao层代码:sessionFactory.getCurrentSession();

步运行到userdao.save(user);查看数据库,并没有添加新记录,F8运行结束,库中也没有新记录,说明回滚成功。

openSessiongetCurrentSessionservice类的每个操作数据库的函数包括get)都需要上事务,否则调用getCurrentSession方法会出错直接将事务注解添加到类上即可

 

8) 关于回滚

Transactional的异常控制,默认是:

Check Exception 不回滚,这类异常都是Exception的子类

unCheck Exception回滚,这类异常都是RuntimeException的子类

 

如果要对checked异常进行回滚,则必须在此方法上加:

@Transactional(rollbackFor=Exception.class)

同理,如果不想在某种异常下回滚,则配置noRollbackFor

如果配置了rollbackFor noRollbackFor 且两个都是用同样的异常,那么遇到该异常,还是回滚。

 

 

9) 事务的几种传播特性

//如果有事务,那么加入事务,没有的话新建一个

@Transactional(propagation=Propagation.REQUIRED)

//容器不为这个方法开启事务

@Transactional(propagation=Propagation.NOT_SUPPORTED)

//不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

@Transactional(propagation=Propagation.REQUIRES_NEW)

//必须在一个已有的事务中执行,否则抛出异常

@Transactional(propagation=Propagation.MANDATORY)

//必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

@Transactional(propagation=Propagation.NEVER)

//如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

@Transactional(propagation=Propagation.NESTED)

//如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

@Transactional(propagation=Propagation.SUPPORTS)

/*public void methodName(){

update();//本类的修改方法 1

    otherBean.update();//调用其他类的修改方法

update();//本类的修改方法 2

}

other失败了不会影响 本类的修改提交成功

本类update的失败,other也失败

*/

 

@Transactional(propagation = Propagation.REQUIRED,readOnly=true) //readOnly=true只读,不能更新,删除

@Transactional(propagation = Propagation.REQUIRED,timeout=30)//设置超时时间

@Transactional(propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)//设置数据库隔离级别

 

10) Spring事务的隔离级别

@Transactional(propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)//设置数据库隔离级别

1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.

另外四个与JDBC的隔离级别相对应

2.  ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

3.  ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据

4.  ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

5.  ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。