一.Spring对编程式事务的支持
Spring中的事务分为物理事务和逻辑事务;
物理事务:就是底层数据库提供的事务支持,如JDBC或JTA提供的事务;
逻辑事务:是Spring管理的事务,不同于物理事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务;
逻辑事务即支持非常低级别的控制,也有高级别解决方案:
低级别解决方案:使用工具类获取连接(会话)和释放连接(会话),如Spring中使用DataSourceUtils ,Hibernate中使用SessionFactoryUtils,JPA中使用EntityManagerFactoryUtils
高级别解决方案:使用Spring提供的模板类,如JdbcTemplate、HibernateTemplate和JpaTemplate模板类等,而这些模板类内部其实是使用了低级别解决方案中的工具类来管理连接或会话
二.使用PlatformTransactionManager
- applicationContext.xml
<!-- 加载jdbc.property -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Connection Pooling Info -->
<property name="maxActive" value="3"/>
<property name="defaultAutoCommit" value="false"/>
<!-- 连接Idle一个小时后超时 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<property name="minEvictableIdleTimeMillis" value="3600000"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 使用低级别解决方案来进行事务管理器测试:
@Test
public void testPlatformTransactionManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
Connection connection = DataSourceUtils.getConnection(dataSource);
try{
connection.prepareStatement(CREATE_TABLE_SQL).execute();
PreparedStatement pstmt = connection.prepareStatement(INSERT_SQL);
pstmt.setString(1, "test");
pstmt.execute();
connection.prepareStatement(DROP_TABLE_SQL).execute();
txManager.commit(status);
}catch(Exception ex){
status.setRollbackOnly();
txManager.rollback(status);
}finally{
DataSourceUtils.releaseConnection(connection, dataSource);
}
}
- 使用高级别方案JdbcTemplate****来进行事务管理器测试
@Test
public void testPlatformTransactionManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try{
jdbcTemplate.execute(CREATE_TABLE_SQL);
jdbcTemplate.update(INSERT_SQL, "test");
}catch(Exception ex){
txManager.rollback(status);
}
txManager.commit(status);
}
三.使用TransactionTemplate
TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate模板类使用的回调接口:
TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
- TransactionTemplate 模板类使用
@Test
public void testTransactionTemplate(){
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
jdbcTemplate.execute(CREATE_TABLE_SQL);
jdbcTemplate.update(INSERT_SQL, "test");
}
});
String COUNT_ALL = "select count(*) from test";
Number count = jdbcTemplate.queryForInt(COUNT_ALL);
Assert.assertEquals(1, count.intValue());
jdbcTemplate.execute(DROP_TABLE_SQL);
}
三.实际应用
- 模型对象
public class User implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private Address address;
private Integer id;
private String name;
//省略get,set
}
public class Address implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String city;
private Integer id;
private String province;
private String street;
private Integer userId;
//省略get,set
}
- Dao层接口及实现
public interface UserDao {
public void save(User user);
public int countAll();
}
package transaction.dao;
import java.util.HashMap;
import java.util.Map;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import transaction.bean.User;
public class UserDaoImp extends NamedParameterJdbcDaoSupport implements UserDao{
private final String INSERT_SQL = "insert into user(name) values(:name)";
private final String COUNT_ALL_SQL = "select count(*) from user";
@Override
public void save(final User user) {
Map<String,String> para = new HashMap<String,String>();
para.put("name", user.getName());
getNamedParameterJdbcTemplate().update(INSERT_SQL, para);
}
@Override
public int countAll() {
return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
}
}
public interface AddressDao {
public void save(Address address);
public int countAll();
}
public class AddressDaoImp extends NamedParameterJdbcDaoSupport implements AddressDao{
private final String INSERT_SQL = "insert into address(province, city, street, user_id)" + "values(:province, :city, :street, :userId)";
private final String COUNT_ALL_SQL = "select count(*) from address";
@Override
public void save(Address address){
KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
/*SqlParameterSource source = new BeanPropertySqlParameterSource(address);
getNamedParameterJdbcTemplate().update(INSERT_SQL, source, generatedKeyHolder);*/
Map<String,Object> para = new HashMap<String,Object>();
para.put("province", address.getProvince());
para.put("city", address.getCity());
para.put("street", address.getStreet());
para.put("userId", address.getUserId());
getNamedParameterJdbcTemplate().update(INSERT_SQL, para);
}
@Override
public int countAll() {
return getJdbcTemplate().queryForInt(COUNT_ALL_SQL);
}
}
- Service****层接口及实现
public interface UserService {
public void save(User user);
public int countAll();
}
@Override
public void save(final User user){
TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
userDao.save(user);
user.getAddress().setUserId(user.getId());
try {
addressService.save(user.getAddress());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
@Override
public int countAll() {
return userDao.countAll();
}
public interface AddressService {
public void save(Address address) throws Exception;
public int countAll();
}
public class AddressServiceImp implements AddressService{
private AddressDao addressDao;
private PlatformTransactionManager txManager;
public void setAddressDao(AddressDao addressDao) {
this.addressDao = addressDao;
}
public void setTxManager(PlatformTransactionManager txManager) {
this.txManager = txManager;
}
@Override
public void save(final Address address){
TransactionTemplate transactionTemplate = TransactionTemplateUtils.getDefaultTransactionTemplate(txManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
addressDao.save(address);
}
});
}
@Override
public int countAll() {
return addressDao.countAll();
}
}
- applicationContext.xml
<!-- 加载jdbc.property -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 数据源配置, 使用DBCP数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Connection Pooling Info -->
<property name="maxActive" value="3"/>
<property name="defaultAutoCommit" value="false"/>
<!-- 连接Idle一个小时后超时 -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<property name="minEvictableIdleTimeMillis" value="3600000"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="abstractDao" abstract="true">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDao" class="transaction.dao.UserDaoImp" parent="abstractDao"/>
<bean id="addressDao" class="transaction.dao.AddressDaoImp" parent="abstractDao"/>
<bean id="userService" class="transaction.service.UserServiceImp">
<property name="addressService" ref="addressService"/>
<property name="userDao" ref="userDao"/>
<property name="txManager" ref="transactionManager"/>
</bean>
<bean id="addressService" class="transaction.service.AddressServiceImp">
<property name="addressDao" ref="addressDao"/>
<property name="txManager" ref="transactionManager"/>
</bean>
- TransactionTemplateUtils类
public class TransactionTemplateUtils {
private static TransactionTemplate getTransactionTemplate(
PlatformTransactionManager txManager, int propagationBehavior,
int isolationLevel) {
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.setPropagationBehavior(propagationBehavior);
transactionTemplate.setIsolationLevel(isolationLevel);
return transactionTemplate;
}
public static TransactionTemplate getDefaultTransactionTemplate(PlatformTransactionManager txManager) {
return getTransactionTemplate(txManager,
TransactionDefinition.PROPAGATION_REQUIRED,
TransactionDefinition.ISOLATION_READ_COMMITTED);
}
}
三.事务属性
事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。
Spring提供TransactionDefinition接口默认实现DefaultTransactionDefinition,可以通过该实现类指定这些事务属性
事务隔离级别:用来解决并发事务时出现的问题,其使用TransactionDefinition中的静态变量来指定:
<b>ISOLATION_DEFAULT:</b>默认隔离级别,即使用底层数据库默认的隔离级别;
<b> ISOLATION_READ_UNCOMMITTED:</b>未提交读;
<b>ISOLATION_READ_COMMITTED:</b>提交读,一般情况下我们使用这个;
<b>ISOLATION_REPEATABLE_READ:</b>可重复读;
<b>ISOLATION_SERIALIZABLE:</b>序列化事务传播行为:Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为
Required:必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务
RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)
Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行
NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行
Mandatory:必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务
Never:不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)
Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚
Nested和RequiresNew的区别:
1、 RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
2、 Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
3、 Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。
使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要我们来开启
- 事务超时:设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;
- 事务只读:将事务标识为只读,只读事务不修改任何数据;