准备数据
CREATE TABLE `t_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into `t_user`(`user_id`,`username`,`balance`) values (1,'张三',100);
insert into `t_user`(`user_id`,`username`,`balance`) values (2,'李四',100);
基于注解是声明式事务
事务配置
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"
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">
<!--引入外部属性文件,创建数据源对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--创建jdbcTemplate对象,注入数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
配置类配置
//用于启用注解驱动的事务管理,这相当于XML配置中的<tx:annotation-driven />元素。
@EnableTransactionManagement
@Configuration
@PropertySource("classpath:jdbc.properties")
public class AppConfig {
@Value("${jdbc.url}")
private String jdbcUrl;
@Value("${jdbc.driver}")
private String jdbcDriver;
@Value("${jdbc.user}")
private String jdbcUser;
@Value("${jdbc.password}")
private String jdbcPassword;
// 配置数据源
@Bean
public DataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(jdbcUrl);
dataSource.setDriverClassName(jdbcDriver);
dataSource.setUsername(jdbcUser);
dataSource.setPassword(jdbcPassword);
return dataSource;
}
// 配置 JdbcTemplate
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 配置事务管理器
* @param dataSource 就是上面配置的数据源
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
使用@Transactional
@SpringJUnitConfig(classes = AppConfig.class)
public class TestTX {
@Autowired
private JdbcTemplate jdbcTemplate;
// @Transactional
@Test
public void test2(){
// 张三转给李四50元
transfer();
}
@Transactional
public String transfer(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
return "success";
}
}
在JUnit的
@Test
函数上使用事务注解@Transactional
时,函数无论抛异常与否,事务都会自动回滚,这是Spring测试框架为了确保测试的独立性和可重复性而设计的默认行为。这种设计使得每个测试方法都可以在一个干净的环境中运行,不会受到前一个测试方法的影响。所以
@Test
和@Transactional
不能一起使用
@Transactional的位置
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
基于XML的声明式事务
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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.lin.xmltx"></context:component-scan>
<!--数据源对象-->
<!--引入外部属性文件,创建数据源对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--配置事务通知切面(增强)-->
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="trans*"></tx:method>
<tx:method name="get*" read-only="true"/>
<tx:method name="update*" read-only="false" propagation="REQUIRED"></tx:method>
<tx:method name="buy*" read-only="false" propagation="REQUIRED"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置切入点和通知使用的方法-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.lin.xmltx.service.*.*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.lin.xmltx.TestBookTx.*(..))"/>
<aop:pointcut id="pt3" expression="execution(* com.lin.xmltx.TestMethod.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"></aop:advisor>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt3"></aop:advisor>
</aop:config>
</beans>
注意:基于xml实现的声明式事务,必须引入aspectJ的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency>
测试
事务的切面点
@Component
public class TestMethod {
@Autowired
private JdbcTemplate jdbcTemplate;
public String updateBalance(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
int i =1/0; // 模拟错误
return "success";
}
}
测试类
@SpringJUnitConfig(locations = "classpath:beans-xml.xml")
public class TestBookTx {
@Autowired
private TestMethod testMethod;
@Test
public void test2(){
// 张三转给李四50元
testMethod.updateBalance();
}
}
注意:切面点无法放在测试类下,否则基于xml的声明式事务会无效
@SpringJUnitConfig(locations = "classpath:beans-xml.xml") public class TestBookTx { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private TestMethod testMethod; @Test public void test2(){ // 张三转给李四50元 testMethod.updateBalance(); } // 想要验证updateBalance,事务无效 public String updateBalance(){ String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?"; int r1 = jdbcTemplate.update(sql1,1,"张三"); System.out.println("r1="+r1); String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?"; int r2 = jdbcTemplate.update(sql2,2,"李四"); System.out.println("r2="+r2); int i =1/0; return "success"; } }
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com