spring事务管理
2016-09-19 17:29
357 查看
博主上篇文章讲到spring的优点,由于篇幅过长,留下了spring事务这块内容,今天我们继续把spring的事务相关内容简单讲解下。
事务的特性:
原子性:事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功。
一致性:事务的前后数据的完整性必须保持一致。
隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。
持久性:一个事务一旦提交,它对数据库的改变将是永久的。
事务的隔离级别:
讲隔离级别前,我们首先需要知道在数据库在事务上经常出现的问题:脏读,不可重复读,幻读。
脏读:一个事务读取了另一个事务没有提交的数据,如果这些数据被回滚了,这些读到的数据是无效的。
不可重复读:在同一个事务中,多次读取同一个数据返回的结果不同。
幻读:一个事务读取了几行数据后,另一个事务插入了一些数据,在后来的查询中,第一个事务发现原来没读取到的数据。
所以数据库的隔离级别是四种!mysql默认的是REPEATABLE_READ,oracle是READ_COMMITTED。
事务的传播行为:
先我们来说个简单的例子,最经典的转账操作,你给朋友转2000块钱,我们在数据库中其中是分2步完成的,第一步,你的账户减少2000,第二步,你朋友的账户增加2000。用代码来说就是你的service层的一个方法调用了dao层的两个方法,那么问题就来了第一步成功了,第二步应该如果是失败的,那么第一步的成功也是要回滚到之前的。
那么最常用的就是用红色表示出来的这三种。
创建相应的类,service和dao,我们这就简单的是java工程就不写control了。
jdbc.properties:
validationQuery=SELECT 1
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=rootspring.xml:
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, int money) {
String sql = "update account set money = money - ? where name = ?";
getJdbcTemplate().update(sql, money,out);
}
@Override
public void inMoney(String in, int money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money,in);
}
}
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TransferTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
OK,正常情况下,是没有任何问题的,接着我们让中间出现异常,显然第一个成功后,第二个是不会执行的。
接着我们用事务来做转账:
package com.julyday.spring_transaction.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl1 extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, int money) {
String sql = "update account set money = money - ? where name = ?";
getJdbcTemplate().update(sql, money,out);
}
@Override
public void inMoney(String in, int money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money,in);
}
}
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountService" class="com.julyday.spring_transaction.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 业务层 -->
<bean id="accountService1" class="com.julyday.spring_transaction.service.AccountServiceImpl1">
<property name="accountDao" ref="accountDao1" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
<!-- dao层 -->
<bean id="accountDao1" class="com.julyday.spring_transaction.dao.AccountDaoImpl1">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 简化我们队事务的操作 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
</beans> AccountDaoImpl1完全一样,只是我们在配置文件中改成了dataSource,而不是jdbcTemplate,其实这两个是一样的,看JdbcDaoSupport源码就知道了。
AccountServiceImpl1的transfer方法我们用了匿名内部类,我们的方法是没有返回值的,我们用的doInTransactionWithoutResult,当然如果有返回值的话,可以用doInTransaction。
接着我们再次测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TransferTest {
@Resource(name="accountService")
private AccountService accountService;
@Resource(name="accountService1")
private AccountService accountService1;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
@Test
public void testService1(){
System.out.println("testService1 begin");
accountService1.transfer("julyday", "zhangsan", 20000);
System.out.println("testService1 finish");
}
}
OK,之前的错误情况不再出现,编程式事务就这样了。
下面来说声明式事务:
第一种:代理模式
package com.julyday.spring_transaction.service.proxy;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_proxy.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.proxy.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 代理的目标类 -->
<property name="target" ref="accountService" />
<!-- 事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事务的属性 -->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans> 有了之前的spring入门,这个就比较简单了,如果不知道怎么配置的话,TransactionProxyFactoryBean这个源码里面也有参考的例子,这里的事务的属性里面prop我们就写了一个transfer,小伙伴可以根据自己项目的需要修改。insert* 表示的就是insert开头的。
我们测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_proxy.xml")
public class TransferProxyTest {
@Resource(name="accountServiceProxy")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
测试没有问题!
声明式事务第二种:基于xml配置的方式:
package com.julyday.spring_transaction.service.schema;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_schema.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.schema.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务增强处理Bean,指定事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- Spring aop事务管理 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="transactionPointcut"
expression="execution(* com.julyday.spring_transaction.service..*Impl.*(..))" />
<!-- 指定在txAdvice切入点应用txAdvice事务增强处理 -->
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="txAdvice" />
</aop:config>
</beans>这个对于aop比较熟悉的同学肯定就知道了,这里就不多说了。
测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_schema.xml")
public class TransferSchemaTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
ok,测试通过!
声明式事务第三种:注解方式
package com.julyday.spring_transaction.service.annotation;
import org.springframework.transaction.annotation.Transactional;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
@Transactional()
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_annotation.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.annotation.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans> 最后一行千万不要忘记<tx:annotation-driven transaction-manager="transactionManager"/>,@Transactional注解,我们可以点进去看下,这里我们用的全部都是默认值了!首先传播行为默认的是Propagation propagation() default Propagation.REQUIRED;隔离级别Isolation
isolation() default Isolation.DEFAULT;只读boolean readOnly() default false;还有异常的rollbackFor,noRollbackFor,rollbackFor就是出现什么异常就回滚,noRollbackFor就是出现什么异常就不回滚。当然一般情况下都是不需要加这些的,有需要的同学可以自己去添加看看,同样的其他的方式也是可以配置这些内容的。
文章中代码的地址:
https://github.com/Julyday/spring_transaction.git
一:事务的隔离级别和传播行为
讲到spring的事务就离不开事务的隔离级别和传播行为。事务的特性:
原子性:事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功。
一致性:事务的前后数据的完整性必须保持一致。
隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。
持久性:一个事务一旦提交,它对数据库的改变将是永久的。
事务的隔离级别:
讲隔离级别前,我们首先需要知道在数据库在事务上经常出现的问题:脏读,不可重复读,幻读。
脏读:一个事务读取了另一个事务没有提交的数据,如果这些数据被回滚了,这些读到的数据是无效的。
不可重复读:在同一个事务中,多次读取同一个数据返回的结果不同。
幻读:一个事务读取了几行数据后,另一个事务插入了一些数据,在后来的查询中,第一个事务发现原来没读取到的数据。
default | 使用数据库默认的隔离级别(spring中的一个选择项,数据库是没有的) |
READ_UNCOMMITTED | 允许读取为提交的改变了的数据。可能导致脏读,不可重复读,幻读 |
READ_COMMITTED | 允许在并发事务已经提交后读取。可防止脏读,但不可重复读,幻读仍可发生 |
REPEATABLE_READ | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏读,不可重复读,但幻读仍可发生 |
SERIALIZABLE | 序列化的,串行操作,速度也是最慢的,可防止脏读,不可重复读,幻读。 |
事务的传播行为:
先我们来说个简单的例子,最经典的转账操作,你给朋友转2000块钱,我们在数据库中其中是分2步完成的,第一步,你的账户减少2000,第二步,你朋友的账户增加2000。用代码来说就是你的service层的一个方法调用了dao层的两个方法,那么问题就来了第一步成功了,第二步应该如果是失败的,那么第一步的成功也是要回滚到之前的。
传播行为 | 说明 |
PROPAGATION_REQUIRES | 支持当前事务,如果不存在,就新建一个 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果有事务存在,就挂起当前事务,并创建一个新的事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务的方式运行,如果有事务存在,挂起当前事务 |
PROPAGATION_NEVER | 以非事务的方式运行,如果有事务存在,抛出异常 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套事务执行 |
二:编程式事务管理
mysql数据库先建表插入数据,简单的说一点,金额我们是以分为单位的,这样可以有效的避免小数的问题。-- ---------------------------- -- Table structure for account -- ---------------------------- DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` int(12) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` int(16) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of account -- ---------------------------- INSERT INTO `account` VALUES ('1', 'julyday', '100000'); INSERT INTO `account` VALUES ('2', 'zhangsan', '100000'); INSERT INTO `account` VALUES ('3', 'lisi', '100000');创建一个maven工程,导入需要的jar包,pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.julyday</groupId> <artifactId>spring_transaction</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring_transaction</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.1.4.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> </dependency> <!-- mysql连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.12</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-framework-bom</artifactId> <version>${spring.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
创建相应的类,service和dao,我们这就简单的是java工程就不写control了。
package com.julyday.spring_transaction.service; public interface AccountService { public void transfer(String in,String out,int money); }
package com.julyday.spring_transaction.service; public class AccountServiceImpl implements AccountService { @Override public void transfer( String in, String out, int money) { } }
package com.julyday.spring_transaction.dao; public interface AccountDao { public void outMoney(String out,int money); public void inMoney(String in,int money); }
package com.julyday.spring_transaction.dao; public class AccountDaoImpl implements AccountDao { @Override public void outMoney(String out, int money) { } @Override public void inMoney(String in, int money) { } }里面的业务实现,我们等下再写。
jdbc.properties:
validationQuery=SELECT 1
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=rootspring.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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据源 使用的是Druid数据源 --> <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> <!-- 用来检测有效sql --> <property name="validationQuery" value="${validationQuery}" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="testWhileIdle" value="true" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000" /> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="true" /> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="1800" /> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="true" /> <!-- 监控数据库 --> <property name="filters" value="mergeStat" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="accountService" class="com.julyday.spring_transaction.service.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> </beans>建出相应的bean后,我们来写我们的业务实现类。
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, int money) {
String sql = "update account set money = money - ? where name = ?";
getJdbcTemplate().update(sql, money,out);
}
@Override
public void inMoney(String in, int money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money,in);
}
}
public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String in, String out, int money) { accountDao.outMoney(out, money); //int i = 1/0; accountDao.inMoney(in, money); } }测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TransferTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
OK,正常情况下,是没有任何问题的,接着我们让中间出现异常,显然第一个成功后,第二个是不会执行的。
接着我们用事务来做转账:
package com.julyday.spring_transaction.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl1 extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String out, int money) {
String sql = "update account set money = money - ? where name = ?";
getJdbcTemplate().update(sql, money,out);
}
@Override
public void inMoney(String in, int money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money,in);
}
}
package com.julyday.spring_transaction.service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.julyday.spring_transaction.dao.AccountDao; public class AccountServiceImpl1 implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(final String in,final String out,final int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { accountDao.outMoney(out, money); //int i = 1/0; accountDao.inMoney(in, money); } }); } // public boolean transfer(final String in,final String out,final int money){ // return transactionTemplate.execute(new TransactionCallback<Boolean>() { // @Override // public Boolean doInTransaction(TransactionStatus transactionStatus) { // accountDao.outMoney(out, money); // //int i = 1/0; // accountDao.inMoney(in, money); // return true; // } // }); // } }spring.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountService" class="com.julyday.spring_transaction.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 业务层 -->
<bean id="accountService1" class="com.julyday.spring_transaction.service.AccountServiceImpl1">
<property name="accountDao" ref="accountDao1" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
<!-- dao层 -->
<bean id="accountDao1" class="com.julyday.spring_transaction.dao.AccountDaoImpl1">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 简化我们队事务的操作 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
</beans> AccountDaoImpl1完全一样,只是我们在配置文件中改成了dataSource,而不是jdbcTemplate,其实这两个是一样的,看JdbcDaoSupport源码就知道了。
AccountServiceImpl1的transfer方法我们用了匿名内部类,我们的方法是没有返回值的,我们用的doInTransactionWithoutResult,当然如果有返回值的话,可以用doInTransaction。
接着我们再次测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class TransferTest {
@Resource(name="accountService")
private AccountService accountService;
@Resource(name="accountService1")
private AccountService accountService1;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
@Test
public void testService1(){
System.out.println("testService1 begin");
accountService1.transfer("julyday", "zhangsan", 20000);
System.out.println("testService1 finish");
}
}
OK,之前的错误情况不再出现,编程式事务就这样了。
下面来说声明式事务:
第一种:代理模式
package com.julyday.spring_transaction.service.proxy;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_proxy.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.proxy.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 代理的目标类 -->
<property name="target" ref="accountService" />
<!-- 事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事务的属性 -->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans> 有了之前的spring入门,这个就比较简单了,如果不知道怎么配置的话,TransactionProxyFactoryBean这个源码里面也有参考的例子,这里的事务的属性里面prop我们就写了一个transfer,小伙伴可以根据自己项目的需要修改。insert* 表示的就是insert开头的。
我们测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_proxy.xml")
public class TransferProxyTest {
@Resource(name="accountServiceProxy")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
测试没有问题!
声明式事务第二种:基于xml配置的方式:
package com.julyday.spring_transaction.service.schema;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_schema.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.schema.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务增强处理Bean,指定事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- Spring aop事务管理 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="transactionPointcut"
expression="execution(* com.julyday.spring_transaction.service..*Impl.*(..))" />
<!-- 指定在txAdvice切入点应用txAdvice事务增强处理 -->
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="txAdvice" />
</aop:config>
</beans>这个对于aop比较熟悉的同学肯定就知道了,这里就不多说了。
测试下:
package com.julyday.spring_transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.julyday.spring_transaction.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_schema.xml")
public class TransferSchemaTest {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void testService(){
System.out.println("testService begin");
accountService.transfer("julyday", "zhangsan", 20000);
System.out.println("testService finish:");
}
}
ok,测试通过!
声明式事务第三种:注解方式
package com.julyday.spring_transaction.service.annotation;
import org.springframework.transaction.annotation.Transactional;
import com.julyday.spring_transaction.dao.AccountDao;
import com.julyday.spring_transaction.service.AccountService;
@Transactional()
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(final String in,final String out,final int money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
spring_annotation.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="0" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="20" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="0" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="33" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${validationQuery}" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 业务层 -->
<bean id="accountService" class="com.julyday.spring_transaction.service.annotation.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- dao层 -->
<bean id="accountDao" class="com.julyday.spring_transaction.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans> 最后一行千万不要忘记<tx:annotation-driven transaction-manager="transactionManager"/>,@Transactional注解,我们可以点进去看下,这里我们用的全部都是默认值了!首先传播行为默认的是Propagation propagation() default Propagation.REQUIRED;隔离级别Isolation
isolation() default Isolation.DEFAULT;只读boolean readOnly() default false;还有异常的rollbackFor,noRollbackFor,rollbackFor就是出现什么异常就回滚,noRollbackFor就是出现什么异常就不回滚。当然一般情况下都是不需要加这些的,有需要的同学可以自己去添加看看,同样的其他的方式也是可以配置这些内容的。
总结
我们一共介绍了四种事务编写的方式,编程式事务优点就是粒度是最小的,但是同样的他需要修改代码,一般用的不多。相对应的,声明式事务,代码修改的很少,很方便查找和修改,但是粒度比较大。代理的方式,我们没写一个事务就要写很多的bean,用的也不是很多;基于xml配置的aop方式,清晰明了,而且多个事务的话配置多个切点就好,使用的比较多;注解的方式,只需要在需要事务的地方加上注解就可以,也很方便,用的也比较多。所有推荐基于xml配置的aop方式及注解方式。文章中代码的地址:
https://github.com/Julyday/spring_transaction.git
相关文章推荐
- spring事务管理几种方式(转)
- Spring事务管理 ——》java service
- Spring事务管理
- Spring学习8-Spring事务管理(AOP/声明式式事务管理)
- Spring事务管理(注解式声明事务管理)
- Spring事务管理只对出现运行期异常进行回滚
- Spring事务管理-回滚
- Spring事务管理--高级应用难点剖析: 第 2 部分
- Spring事务管理只对出现运行期异常进行回滚
- spring事务管理
- 框架 day37 Spring事务管理,整合web,SSH整合,SSH整合注解
- Spring事务管理(2)-AOP注册解析器
- spring事务管理(声明式事务和编程式事务)
- Spring事务管理
- Spring事务管理(详解+实例)一
- Spring事务管理(详解+实例)
- 配置spring事务管理的几种方式(声明式事务)----使用tx标签配置的拦截器
- Spring事务管理(详解+实例)
- spring事务管理
- Spring事务管理(未完待续)