Hibernate与Spring的事务管理

时间:2023-03-09 00:58:43
Hibernate与Spring的事务管理

什么是事务

这个问题比较大,按照我的理解就是,一个事务内的n个操作,要么全部完成,一旦有一个操作有问题,那么所有的操作都全部回滚。

Jdbc的事务

首先,大家已经知道了,事务说白了就是一个词----统一,要么全部OK,要么都不做。

在jdbc中,默认情况下,一个sql就是一个事务,一个事务也仅仅只有一个sql。AutoCommit=true

那么我们正常使用的时候,肯定是想把若干个sql绑到一起,看做一个事务。

那么我们第一步就是先告诉connection,你别一个sql一个sql提交了,整体来。即AutoCommit=false

我们看下面的例子

public int delete(int sID) {
  dbc = new DataBaseConnection();
  Connection con = dbc.getConnection();
  try {
   con.setAutoCommit(false);   // 更改JDBC事务的默认提交方式
   dbc.executeUpdate("delete from xiao where ID=" + sID);
   dbc.executeUpdate("delete from xiao_content where ID=" + sID);
   dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);
   con.commit();//提交JDBC事务
   con.setAutoCommit(true);    // 恢复JDBC事务的默认提交方式
   dbc.close();
   return 1;
  }
  catch (Exception exc) {
   con.rollBack();//回滚JDBC事务
   exc.printStackTrace();
   dbc.close();
   return -1;
  }
}

jta事务

不懂,我目前没用到这个东西。

hibernate中的事务

Hibernate 是JDBC 的轻量级封装,本身并不具备事务管理能力。在事务管理层, Hibernate将其委托给底层的JDBC或者JTA,以实现事务管理和调度功能。 

Hibernate的默认事务处理机制基于JDBC Transaction。我们也可以通过配置文件设定采用JTA作为事务管理实现:

</pre><pre name="code" class="html"><hibernate-configuration>
	<session-factory>
		……
		<property name="hibernate.transaction.factory_class">
		net.sf.hibernate.transaction.JTATransactionFactory
		<!--net.sf.hibernate.transaction.JDBCTransactionFactory-->
		</property>
		……
	</session-factory>
</hibernate-configuration>  

单纯的使用hibernate,对于事务的处理是很简单的,例如

	Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		Teacher s = new Teacher();
		s.setName("zhangsan");
		s.setAge(232);
		s.setDate(sdf.format(date));

		SessionFactory sessionFactory = new AnnotationConfiguration()
				.configure().buildSessionFactory();
		Session session = sessionFactory.getCurrentSession();

		session.beginTransaction();
		session.save(s);
		session.getTransaction().commit();
		session.close();
		sessionFactory.close();

抽象的来说,

session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
……
tx.commit();  

在jdbc层面上就相当于:

Connection dbconn = getConnection();
dbconn.setAutoCommit(false);
……
dbconn.commit();  

另一方面,在我们获得session的时候,hibernate会初始化数据库连接,把AutoCommit设置为false,后面在beginTransaction会再次检查AutoCommit的值是否未false(防止用户更改)。

所以

session = sessionFactory.openSession();
session.save(user);
session.close();  

上面的代码不会对数据库产生任何影响。你没提交嘛!

使用spring替hibernate管理事务

首先,我们为什么要让spring去替hibernate管理事务?

一 如果单纯的用hibernate,每次对数据库做一次操作,我都得beginTransaction然后getTransaction.commit。你不烦呀。

二 粒度的问题,事务其实在更高的层次上看是一个逻辑概念,它是几个操作的集合。而默认情况下,hibernate只管理对数据库最低层次的操作。(这个,具体的咱们在后面再说)





OK,我们已经知道了用spring管理事务的必要性,再看看用spring管理事务的方式。

有两种。

一种是使用xml,一种是使用Annotation

/////////////////////////////////以下为9月30日的补充内容

其实更精确的说,还有一种------编程式事务管理,只不过相对应后面介绍的声明式与注解式,编程式就显得很low了#

我写个简单的例子:

public class BankServiceImpl implements BankService {
	private BankDao bankDao;
	private TransactionDefinition txDefinition;
	private PlatformTransactionManager txManager;

	public boolean transfer(Long fromId, Long toId, double amount) {
		TransactionStatus txStatus = txManager.getTransaction(txDefinition);
		boolean result = false;
		try {
				result = bankDao.transfer(fromId, toId, amount);
				txManager.commit(txStatus);
			} catch (Exception e) {
				result = false;
				txManager.rollback(txStatus);
				System.out.println("Transfer Error!");
			}
		return result;
	}
}

TransactionDefinition,PlatformTransactionManager都是spring注入的#

上面的bankDao.transfer(fromId, toId, amount)就是把tromid的amount块钱转移到toid上去#

感觉是和jdbc的每什么区别,还都得提交#,我们应该关注于业务本身,对于事务的提交与回滚应该交给系统#

既然说到了jdbc,那么大家就肯定会想到jdbctemplate#那么既然有jdbctebmplage,那为什么就不能有transactionTemplate呢?

我们看下面的例子

public class BankServiceImpl implements BankService {
	private BankDao bankDao;
	private TransactionTemplate transactionTemplate;

	public boolean transfer(final Long fromId, final Long toId, final double amount) {
	return (Boolean) transactionTemplate.execute(new TransactionCallback(){
	public Object doInTransaction(TransactionStatus status) {
		Object result;
		try {
			result = bankDao.transfer(fromId, toId, amount);
		} catch (Exception e) {
			status.setRollbackOnly();
		        result = false;
		       System.out.println("Transfer Error!");
		}
		return result;
		}
		});
	}
}

这样一来,我们就能关注单纯的业务逻辑了。(只不过一旦出错了,我们还得status.setRollbackOnly())

关于硬编码这块,大家参见

全面分析 Spring 的编程式事务管理及声明式事务管理

只不过,大家请记着,除非我们是接手了一个遗留系统,否则还是不要用编程式事务管理了。

当然,虽然不鼓励大家主动去用这个东西,但是我们至少得会用,看到这个这些代码得知道是什么意思,更进一步的,如果我们还能知道里面的实现过程,那对我们的编程技术,或者说架构能力都是有帮助的。

总结一下:

基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是 Spring 提供的最原始的方式,通常我们不会这么写,但是了解这种方式对理解 Spring
事务管理的本质有很大作用。

基于 TransactionTemplate 的编程式事务管理是对上一种方式的封装,使得编码更简单、清晰。

/////////////////////////////////以上为9月30日的补充内容

Annotation

我们先说使用Annotation。

第一步

在spring的xml里面加上

一般情况下,JDBC(iBATIS) 使用 DataSourceTransactionManager,hibernate使用HibernateTransactionManager

	<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

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

当然,如果xml里面没有tx的命名空间,还得加上

xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"

这一步没什么说的,大家记死就OK(我说的记死,不是说让大家记得上面的每一字母;只要知道有这两个配置,需要用的时候,知道去哪找就OK)。

第二步就是加事务了

这一步更简单

@Transaction

哪个方法需要spring的事务管理,就给那个方法加上 @Transaction

我给大家一个实例,向数据库里添加一个user,然后再添加一条日志记录。

说到这,先看代码。

package com.bjsxt.service;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.bjsxt.dao.LogDAO;
import com.bjsxt.dao.UserDAO;
import com.bjsxt.model.Log;
import com.bjsxt.model.User;

@Component("userService")
public class UserService {

        @Resource
	private UserDAO userDAO;
	@Resource
	private LogDAO logDAO;

	public User getUser(int id) {
		return null;
	}

	public void add(User user) {
		userDAO.save(user);
		Log log = new Log();
		log.setMsg("a user saved!");
		logDAO.save(log);
	}
        //省略getset
}

package com.bjsxt.dao.impl;

import javax.annotation.Resource;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Component;

import com.bjsxt.dao.UserDAO;
import com.bjsxt.model.User;

@Component("userDAO")
public class UserDAOImpl implements UserDAO {

	private SessionFactory sessionFactory;

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	@Resource
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public void save(User user) {
		Session s = sessionFactory.getCurrentSession();
		s.save(user);
	}
}

省略LogDAO的接口及实现。

现在的问题是我把 @Transaction加到UserDAOImpl的sava方法上(当然还有LogDAOImpl的save方法)还是加到UserService的add方法上。

我的代码都已经写出来了,大家就是用大腿想,应该也能想出来,加到整体的业务逻辑上。

这个观点提升一下就是,我们应该在service层做事务管理





OK,我们仔细看看这 @Transaction

下图是 @Transaction的doc文档

Hibernate与Spring的事务管理

我们其实只有看两个:

readOnly,我们应该能猜出来,如果某个方法的 @transaction加了readonly,那么方法内部就不能对数据库有增删改的行为。

为什么会有这个属性呢,spring为对readOnly的transaction的方法做优化。

因此,如果你肯定某个方法是不会修改数据库,那就给他加上readOnly=true吧,另一方面,从设计上来讲,readOnly也可以看做一种检查,看某个不应该出现更改数据库的地方出现了更改操作。





第二个属性是propagation,我们能看出他的选值是Propagation,而Propagation是一个枚举类。

Propagation的说明如下:

Hibernate与Spring的事务管理



我们最经常使用的,而且也是spring默认的就是REQUIRED

REQUIRED就是,如果当前方法没有事务,那就新产生一个事务,并且如果此方法有了事务,那么方法内部的方法调用也会使用这个事务。

至于别的几个参数,大家就都忘了吧。





如果把 @Transaction加到某个类上,就等于给这个类的所有方法都加上了 @Transaction

@Transaction标签不可继承。

前面我们已经说了,让spring管理事务有两种方式,第一是annotation,上面我们已经说了,下面我们说说使用xml。

XML(声明式事务管理)

在spring的xml中加入如下内容

<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<aop:config>
		<aop:pointcut id="bussinessService"
			expression="execution(public * com.bjsxt.service..*.*(..))" />
		<aop:advisor pointcut-ref="bussinessService"
			advice-ref="txAdvice" />
	</aop:config>

	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="getUser" read-only="true" />
			<tx:method name="add*" propagation="REQUIRED"/>
		</tx:attributes>
	</tx:advice>

当然,要去掉使用annotation的

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

我解释一下上面的定义。

在com.bjsxt.service包及其子包下的所有类的所有public的方法都加上事务管理

具体的事务设置是,如果方法名是getUser那么设置read-only为true,如果方法是以add开头的,那么设置propagation为REQUIRED(这个其实不用设,因为是默认的)









现在有个问题,到底是用annotation那,还是xml呢?

回答是看情况。

你觉得哪个方便用哪个。





另外,上面的代码使用的是spring3 hibernate3

如果使用spring4 hibernate4

xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.bjsxt" />

	<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<value>classpath:jdbc.properties</value>
		</property>
	</bean>

	<bean id="dataSource" destroy-method="close"
		class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName"
			value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="annotatedClasses">
			<list>
				<value>com.bjsxt.model.User</value>
				<value>com.bjsxt.model.Log</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
				</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>
		</property>
	</bean>

	<bean id="txManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<aop:config>
		<aop:pointcut id="bussinessService"
			expression="execution(public * com.bjsxt.service..*.*(..))" />
		<aop:advisor pointcut-ref="bussinessService"
			advice-ref="txAdvice" />
	</aop:config>

	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="getUser" read-only="true" />
			<tx:method name="add*" propagation="REQUIRED"/>
		</tx:attributes>
	</tx:advice>

</beans>

如果在一个项目里,我既有通过hibernate来访问数据库,也有使用jdbc访问数据库,那么transactionmanager选哪个呢?(如果不清楚transactionmanager是干什么的,请参阅 全面分析 Spring 的编程式事务管理及声明式事务管理  )

到底用哪个呢?

答案是采用HibernateTransactionManager

它既可以管hibernate,也可以管jdbc

参见: spring 同时配置hibernate and jdbc 事务

/////////////////////////////////以下为9月30日的补充内容

其实声明式事务管理,除了上面说的,我们还可以看看历史,我们看看在spring3.0之前,还有什么方式

怎么说呢,虽然之前的事务管理方式都已经很out了,而且我们也没太大的必要对之前的实现方法做多么深的理解,但是不是有那么已经老话嘛:温故而知新,可以为师矣。

我们只有知道了之前是怎么样的,才能真正的体会,为什么事务管理会是今天这个样子。之前是现在的基础。

之前的方法有这么几种基于TransactionInterceptor,基于TransactionProxyFactoryBean再之后就是我们上面讲的:基于 <tx> 和 <aop> 命名空间的声明式事务管理,和基于 @Transactional 的方式。

总结一下:

基于 TransactionInterceptor 的声明式事务是 Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 Spring 声明式事务有很大作用。

基于 TransactionProxyFactoryBean 的声明式事务是上中方式的改进版本,简化的配置文件的书写,这是 Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。

基于 <tx> 和 <aop> 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。

基于 @Transactional 的方式将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。

关于TransactionInterceptor和TransactionProxyFactoryBean 大家请参考 全面分析 Spring 的编程式事务管理及声明式事务管理

/////////////////////////////////以上为9月30日的补充内容

参考资料

北京尚学堂 马士兵 spring3讲解

http://www.iteye.com/topic/177988

http://blog.sina.com.cn/s/blog_4b5bc0110100h0wz.html

spring 同时配置hibernate and jdbc 事务

全面分析 Spring 的编程式事务管理及声明式事务管理