下面的示例需要的jar包
<!--spring 核心--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.17.RELEASE</version> </dependency> <!--Servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.8.RELEASE</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--junit 测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13-beta-2</version> </dependency> <!--测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.7.RELEASE</version> </dependency> <!--c3p0连接池--> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency> <!--dpcp 连接池--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.6.0</version> </dependency> <!--dpcp 连接池需要依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.2</version> </dependency> <!--AOP:全自动配置代理--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
事物场景当中,抛出异常被catch之后,如果要回滚事务,一定要手动回滚
java异常包括checked exception
(检查异常) 和 unchecked exception
(非检查异常),其中未检查异常又叫RuntimeException
(运行时异常),对于运行时异常,java
编译器不要求对异常进行处理(捕获或者抛出),但是对于checked exception
异常,必须要在方法里面捕获或者继续抛出。
上面一句话的意思就很明显了,spring默认对于运行时的异常进行捕获的,如果你在方法中添加了try{}catch{}
捕获了异常,则spring
是不会对事务进行回滚的,因为他默认你已经对异常进行了回滚,如果想要事务回滚,则必须在catch
中继续抛出异常。
注意
这里抛出的异常需要是RuntimeException
的子类,如果需要设置spring
也识别其他的异常并进行回滚,则需要设置作用在该方法上的@Transactional
注解的rollbackFor
属性。例如:
@Transaction(rollbackFor = MyException.class)
这里如果在方法中抛出的异常是MyExcepiton
的子类,事务也会进行回滚。
事务回滚操作
SpringBoot提供了非常方便的事务操作,通过注解就可以实现事务的回滚,非常方便快捷,下面我就说一下如何进行事务操作。
@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
如果我们需要捕获异常后,同时进行回滚,通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();进行手动回滚操作。
使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
设置回滚点,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint
事务:一系类的业务操作,要么全部成功,要么全部失败,最常见的例子就是转账。
事务有四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。(数据的完整)
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。(并发)
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。(结果)
隔离问题:
脏读:一个事务读到了另一个事务没有提交的数据
不可重复读:一个事务读到了另一个事务已提交的数据(update)
虚读(幻读):一个事务读到了另一个事务已经提交的数据(insert)
事务的四种隔离级别
- 读未提交:一个事务可以读取到,另外一个事务尚未提交的变更。
- 读已提交:一个事务提交后,其变更才会被另一个事务读取到。
- 可重复读:在一个事务执行的过程中所读取到的数据,和事务启动时所看到的一致。
- 串行化:当操作一行数据时,读写分别都会加锁。当出现读写锁互斥时,会排队串行执行。
参考:https://blog.csdn.net/taylor_tao/article/details/7063639
参考:http://blog.itpub.net/26736162/viewspace-2638951/
事务隔离主要是为了解决并发的问题
- DEFAULT:采用 DB 默认的事务隔离级别。MySql 默认为 REPEATABLE_READ;Oracle 默认为:READ_COMMITTED;
- READ_UNCOMMITTED:读未提交。未解决任何并发问题。存在3个问题
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。存在2个问题
- REPEATABLE_READ:可重复读。解决脏读、不可重复读。存在幻读。存在1个问题
- SERIALIZABLE:串行化。不存在并发问题(效率低)。不存在任何问题
事务原本是数据库中的概念,用于数据访问层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
Mysql 简单的事务操作
需求:ABCD必须完成
Connection connection = null; try { //1 获得连接connection[可以从连接池中获得] //2 开启事务 connection.setAutoCommit(false); //3 完成ABCD(一系类操作) //A //B //C //D //4 提交事务 connection.commit(); } catch (Exception e) { //5 如果ABCD任何一个操作出现错误,事务回滚 try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } }finally { //释放资源; try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
Mysql 事务操作 Savepoint(保存点)
AB必须完成,CD可以不用完成(例如银行转账之后,给我们手机发送消息)
Connection connection = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置 Savepoint savepoint = null; try { //1 获得连接connection[可以从连接池中获得] //2 开启事务 connection.setAutoCommit(false); //3 完成ABCD(一系类操作) //A //B savepoint = connection.setSavepoint();//添加保存点 //C //D //4 提交事务 connection.commit(); } catch (Exception e) { if (savepoint!=null){ try { //表示CD发生了异常 connection.rollback(savepoint); //将AB提交 connection.commit(); } catch (SQLException ex) { ex.printStackTrace(); } }else{ //AB发送异常 try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } }finally { //释放资源; try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }
一、Spring 事务管理 API
1.1 三个顶级接口
PlatformTransactionManager:事务管理器接口(spring管理事务必须使用事务管理器),其主要用于完成事务的提交、回滚,及获取事务的状态信息。
TransactionDefinition :事务定义接口,定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
TransactionStatus :事务状态,spring用于记录当前事务的运行状态,例如是否有保存点,事务是否完成。spring底层根据状态进行相应的操作(该接口知道即可)
1.1.1 PlatformTransactionManager
两个实现类,事务管理器(必须配置)
DataSourceTransactionManager
:使用 JDBC 或 MyBatis 进行持久化数据时使用。HibernateTransactionManager
:使用 Hibernate 进行持久化数据时使用。
该接口定义了 3 个事务方法:
void commit(TransactionStatus status)
:事务的提交TransactionStatus getTransaction(TransactionDefinition definition)
:获取事务的状态void rollback(TranscationStatus status)
:事务的回滚
1.1.2 TransactionDefinition
TIMEOUT_DEFAULT:int 默认超时时间。默认值:1。使用款据库底层的时时间。 getPropagationBehavior0:int 传播行为 getlsolationLevel0:int 隔离级别 getTimeout0:int 获得超时时间 isReadOnly0:boolean 是否只读(增删;读写;查询;只读) getName0:String 配置务详情名称。一般方法名称。如:save、add*等
1.1.3 TransactionStatus(了解)
isNewTransaction0:boolean 是否是新的事务 hasSavepoint0:boolean 是否有保存点 setRollbackOnly0:void 设置回滚 isRollbackOnly0:boolean 是否回滚 flush0:void 刷新 isCompleted0:boolean 是否完成
1.2 事务的七种传播行为
参考:https://blog.csdn.net/weixin_39625809/article/details/80707695
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 a()
调用 B 事务中的方法 b()
,在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
不同业务之间共享事务
- REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
- SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
- MANDATORY:指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。
- REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
- NOT_SUPPORTED:指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。指定方法以非事务执行,如果当前不存在事务,指定方法还是以非事务执行。
- NEVER:指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。如果当前没有事务,指定方法以非事务执行
- NESTED:指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。
如何指定传播类型:可以在注解或者XML中指定传播类型, 如 “@Transactional(Propagation=xxx)”
spring管理事务回滚的时候,自己不可以在代码中try 。。。catch。。。
二、综合示例
使用技术 JdbcTemplate、 c3p0(连接池) 、
重要的jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.8.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
2.1 搭建环境
创建数据表
CREATE TABLE acount( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(10), money INT ); INSERT INTO acount(username,money) VALUES("渣渣辉",100); INSERT INTO acount(username,money) VALUES("古天乐",100);
2.2手动管理事务(了解)
spring 底层 通过TransactionTemplate 事务模板来进行操作
使用 TransactionTemplate,需要手动给他注入事务管理器,给事务管理器注入连接池(datasource) [事务管理器就行一个没有车的马达,而连接池就是马达,而马达可以更换性能更高的马达]
UserAcountDaoImpl
public class UserAcountDaoImpl extends JdbcDaoSupport{ //用户账号转入 public void in(String innerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money+? WHERE username=?;",money,innerUsername); } //用户账号转出 public void out(String outerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money-? WHERE username=?;",money,outerUsername); } }
UserAcountServiceImpl
public class UserAcountServiceImpl { @Autowired private UserAcountDaoImpl userAcountDao; private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } //转账(核心) public void transfer(final String outerUsername,final String innerUsername,final int money){ transactionTemplate.execute(new TransactionCallbackWithoutResult(){ protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { userAcountDao.out(outerUsername,money); //测试报错 int a=1/0; userAcountDao.in(innerUsername,money); } }); } }
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.test"></context:component-scan> <!-- 加载配置文件--> <context:property-placeholder location="jdbcinfo.properties"></context:property-placeholder> <!-- 如果配置文件没有放在资源目录,下面这种写法就可以找到,classpath:表示定位到src目录下--> <!-- <context:property-placeholder location="classpath:com.test.xx.properties"></context:property-placeholder>--> <bean id="basicDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="basicDataSource"></property> </bean> <bean id="userDaoImpl" class="com.test.UserAcountDaoImpl"> <property name="jdbcTemplate" ref="JdbcTemplate"></property> </bean> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="basicDataSource"></property> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="dataSourceTransactionManager"></property> </bean> <bean id="userAcountServiceImpl" class="com.test.UserAcountServiceImpl"> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> </beans>
测试
public class DemoTest { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserAcountServiceImpl userAcountServiceImpl = (UserAcountServiceImpl) classPathXmlApplicationContext.getBean("userAcountServiceImpl"); userAcountServiceImpl.transfer("渣渣辉","古天乐",10); } }
2.3 工厂Bean生成代理,管理事务(了解)
xml 不需要配置 TransactionTemplate,任然需要配置事务管理器,连接池,加上一个 TransactionProxyFactoryBean 工厂Bean
UserAcountDao
public class UserAcountDaoImpl extends JdbcDaoSupport{ //用户账号转入 public void in(String innerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money+? WHERE username=?;",money,innerUsername); } //用户账号转出 public void out(String outerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money-? WHERE username=?;",money,outerUsername); } }
UserAcountService(需要有接口)
public interface UserAcountService { public void transfer(final String outerUsername,final String innerUsername,final int money); }
UserAcountServiceImpl
public class UserAcountServiceImpl implements UserAcountService{ @Autowired private UserAcountDaoImpl userAcountDao; //转账 public void transfer(final String outerUsername,final String innerUsername,final int money){ userAcountDao.out(outerUsername,money); //测试报错 int a=1/0; userAcountDao.in(innerUsername,money); } }
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.test"></context:component-scan> <!-- 加载配置文件--> <context:property-placeholder location="jdbcinfo.properties"></context:property-placeholder> <!-- 如果配置文件没有放在资源目录,下面这种写法就可以找到,classpath:表示定位到src目录下--> <!-- <context:property-placeholder location="classpath:com.test.xx.properties"></context:property-placeholder>--> <bean id="basicDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="basicDataSource"></property> </bean> <bean id="userDaoImpl" class="com.test.UserAcountDaoImpl"> <property name="jdbcTemplate" ref="JdbcTemplate"></property> </bean> <!-- 配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="basicDataSource"></property> </bean> <bean id="userAcountServiceImpl" class="com.test.UserAcountServiceImpl"></bean> <!-- 配置代理类--> <bean id="transactionProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="proxyInterfaces" value="com.test.UserAcountService"></property><!--如果ServiceImpl没有接口,可以不写--> <property name="target" ref="userAcountServiceImpl"></property> <property name="transactionManager" ref="dataSourceTransactionManager"></property> <!-- 事务属性--> <property name="transactionAttributes"> <!-- prop.key:确定哪些方法使用当前事务配置 prop.text:用于配置事务详情 格式 : PROPAGATION,ISOLATION,readOnly,-Exception,+Exception 传播行为 隔离级别 是否只读 异常回滚 异常提交 --> <props> <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-Exception</prop><!--配置了默认的传播行为和默认隔离级别--> </props> </property> </bean> </beans>
测试
public class DemoTest { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserAcountService userAcountServiceImpl = (UserAcountService) classPathXmlApplicationContext.getBean("transactionProxyFactoryBean"); userAcountServiceImpl.transfer("渣渣辉","古天乐",10); } }
2.4 AOP配置基于xml(掌握)
在spring中配置aop自动生成代理,进行事务管理
1、配置事务管理器
2、配置事务详情
3、配置aop
UserServiceDaoImpl
public class UserAcountDaoImpl extends JdbcDaoSupport{ //用户账号转入 public void in(String innerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money+? WHERE username=?;",money,innerUsername); } //用户账号转出 public void out(String outerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money-? WHERE username=?;",money,outerUsername); } }
UserService
public interface UserAcountService { public void transfer(final String outerUsername,final String innerUsername,final int money); }
UserServiceImpl
@Service("userAcountServiceImpl") public class UserAcountServiceImpl implements UserAcountService{ @Autowired private UserAcountDaoImpl userAcountDao; //转账 public void transfer(final String outerUsername,final String innerUsername,final int money){ userAcountDao.out(outerUsername,money); //测试报错 int a=1/0; userAcountDao.in(innerUsername,money); } }
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.test"></context:component-scan> <!-- 加载配置文件--> <context:property-placeholder location="jdbcinfo.properties"></context:property-placeholder> <!-- 如果配置文件没有放在资源目录,下面这种写法就可以找到,classpath:表示定位到src目录下--> <!-- <context:property-placeholder location="classpath:com.test.xx.properties"></context:property-placeholder>--> <bean id="basicDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="basicDataSource"></property> </bean> <bean id="userDaoImpl" class="com.test.UserAcountDaoImpl"> <property name="jdbcTemplate" ref="JdbcTemplate"></property> </bean> <!-- 配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="basicDataSource"></property> </bean> <!-- 事务通知(详细|定义) <tx:attributes> :配置事务详情 <tx:method:详情的具体配置 name:切入点的某一个方法 propagation:传播行为 isolation:隔离级别 read-only:只读(用于查询语句) --> <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- 配置AOP--> <aop:config> <aop:advisor advice-ref="txadvice" pointcut="execution(* com.test.UserAcountServiceImpl.*(..))"></aop:advisor> </aop:config> </beans>
测试
public class DemoTest { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserAcountService userAcountServiceImpl = (UserAcountService) classPathXmlApplicationContext.getBean("userAcountServiceImpl"); userAcountServiceImpl.transfer("渣渣辉","古天乐",10); } }
2.4 AOP配置基于注解(掌握)
1、配置事务管理器,将事务管理器交给spring
2、在目标类或目标方法添加注解即可,@Transactional
UserAcountDaoImpl
public class UserAcountDaoImpl extends JdbcDaoSupport{ //用户账号转入 public void in(String innerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money+? WHERE username=?;",money,innerUsername); } //用户账号转出 public void out(String outerUsername,int money){ this.getJdbcTemplate().update("UPDATE acount SET money=money-? WHERE username=?;",money,outerUsername); } }
UserAcountService
public interface UserAcountService { public void transfer(final String outerUsername,final String innerUsername,final int money); }
UserAcountServiceImpl(添加注解@Transactional)
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false) //如果注解加载类上,表示该类的所有的方法都用事务,如果加载方法上,表示这一个方法有事务[readOnly=true:表示只读,只可以加在查询上,可以不写] @Service("userAcountServiceImpl") public class UserAcountServiceImpl implements UserAcountService{ @Autowired private UserAcountDaoImpl userAcountDao; //转账 public void transfer(final String outerUsername,final String innerUsername,final int money){ userAcountDao.out(outerUsername,money); //测试报错 // int a=1/0; userAcountDao.in(innerUsername,money); } }
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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.test"></context:component-scan> <!-- 加载配置文件--> <context:property-placeholder location="jdbcinfo.properties"></context:property-placeholder> <!-- 如果配置文件没有放在资源目录,下面这种写法就可以找到,classpath:表示定位到src目录下--> <!-- <context:property-placeholder location="classpath:com.test.xx.properties"></context:property-placeholder>--> <bean id="basicDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">--> <!-- <property name="dataSource" ref="basicDataSource"></property>--> <!-- </bean>--> <!-- <bean id="userDaoImpl" class="com.test.UserAcountDaoImpl">--> <!-- <property name="jdbcTemplate" ref="JdbcTemplate"></property>--> <!-- </bean>--> <!-- 下面这 相当上面的代码,setDataSource会自动SetJdgcTemplate,并将dataSource传递进去,可以自己查看源码--> <bean id="xx" class="com.test.UserAcountDaoImpl"> <property name="dataSource" ref="basicDataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="basicDataSource"></property> </bean> <!-- 将事务管理器交给spring proxy-target-class=true :表示底层强制使用cglib代理 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"></tx:annotation-driven> </beans>
测试
public class DemoTest { public static void main(String[] args) { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserAcountService userAcountServiceImpl = (UserAcountService) classPathXmlApplicationContext.getBean("userAcountServiceImpl"); userAcountServiceImpl.transfer("渣渣辉","古天乐",10); } }
三、补充(可以忽略,基本和上面一样,不全)
3.1 使用 AspectJ 的 AOP 配置管理事务(即XML配置)
AspectJ 主要是使用 XML 配置顾问方式自动为每个符合切入点表达式的类生成事务代理。创建测试操作步骤如下:
创建一个名为 aspectj-aop
项目,pom.xml
文件如下:
<?xml version="1.0" encoding="UTF-8"?> <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>hello-spring-transaction</groupId> <artifactId>aspectj-aop</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <!-- 环境配置 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <!-- 统一的依赖管理 --> <log4j.version>1.2.17</log4j.version> <slf4j.version>1.7.25</slf4j.version> <spring.version>4.3.17.RELEASE</spring.version> <alibaba-druid.version>1.1.6</alibaba-druid.version> <mysql.version>5.1.46</mysql.version> <mybatis.version>3.2.8</mybatis.version> <mybaits-spring.version>1.3.1</mybaits-spring.version> <junit.version>4.12</junit.version> <lombok.version>1.16.18</lombok.version> </properties> <dependencies> <!-- Test Begin --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <!-- Test Begin --> <!-- Spring Begin --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring End --> <!-- Log Begin --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <!-- Log End --> <!-- Database Begin --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${alibaba-druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybaits-spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- Database End --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> </dependencies> </project>
主要是增加了 org.springframework:spring-aspects
依赖
配置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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config/> <context:component-scan base-package="com.hello.spring.transaction.aspectsj.aop"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> //选择MyBatis,则使用DataSourceTransactionManager <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务通知 --> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> //表示以save开头的数据库操作,使用REQUIRED事务,可以自己根据需求增加 <tx:method name="get*" propagation="NEVER"/> //表示以get开头的数据库操作,不使用任何事务 </tx:attributes> </tx:advice> <!-- 配置顾问和切入点 --> <aop:config> <aop:pointcut id="myPointcut" expression="execution(* com.hello.spring.transaction.aspectsj.aop.service.*.*(..))" /> //表示在service目录下的所有的类的所有的方法 <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut" /> </aop:config> </beans>
3.2 使用 Spring 注解管理事务
实际开发里用的较多,开启事务注解驱动
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:annotation-config/> <context:component-scan base-package="com.hello.spring.transaction.aspectsj.aop"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> //选择MyBatis,则使用DataSourceTransactionManager <property name="dataSource" ref="dataSource"/> </bean> <!-- 开启事务注解驱动 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
@Transactional
注解简介
@Transactional
的所有可选属性:
propagation
:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED
。isolation
:用于设置事务的隔离级别。该属性类型为 Isolation 枚举 ,默认值为Isolation.DEFAULT
。readOnly
:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为false
。timeout
:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。rollbackFor
:指定需要回滚的异常类。类型为Class[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。rollbackForClassName
:指定需要回滚的异常类类名。类型为String[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。noRollbackFor
:指定不需要回滚的异常类。类型为Class[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。noRollbackForClassName
: 指定不需要回滚的异常类类名。类型为String[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional
若用在方法上,只能用于 public
方法上。对于其他非 public
方法,如果加上了注解 @Transactional
,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public
方法上的 @Transaction
注解。
若 @Transaction
注解在类上,则表示该类上所有的方法均将在执行时织入事务。
使用 @Transaction
注解
使用起来很简单,只需要在需要增加事务的业务类上增加 @Transaction
注解即可
@Transactional @Service(value = "tbContentCategoryService") public class TbContentCategoryServiceImpl implements TbContentCategoryService { 。。。。。 }
实际业务中:
spring Boot事务
使用@Transaction
注解,没有任何配置