什么是事务
这个问题比较大,按照我的理解就是,一个事务内的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())
关于硬编码这块,大家参见
只不过,大家请记着,除非我们是接手了一个遗留系统,否则还是不要用编程式事务管理了。
当然,虽然不鼓励大家主动去用这个东西,但是我们至少得会用,看到这个这些代码得知道是什么意思,更进一步的,如果我们还能知道里面的实现过程,那对我们的编程技术,或者说架构能力都是有帮助的。
总结一下:
基于 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文档
readOnly,我们应该能猜出来,如果某个方法的 @transaction加了readonly,那么方法内部就不能对数据库有增删改的行为。
为什么会有这个属性呢,spring为对readOnly的transaction的方法做优化。
因此,如果你肯定某个方法是不会修改数据库,那就给他加上readOnly=true吧,另一方面,从设计上来讲,readOnly也可以看做一种检查,看某个不应该出现更改数据库的地方出现了更改操作。
第二个属性是propagation,我们能看出他的选值是Propagation,而Propagation是一个枚举类。
Propagation的说明如下:
我们最经常使用的,而且也是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 事务