创建Maven项目 test0703
。
修改 pom.xml
,添加依赖以及设置静态资源:
......
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.21</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
......
创建POJO MyObject.java
:
package pojo;
public class MyObject {
private int c1;
private int c2;
public int getC1() {
return c1;
}
public void setC1(int c1) {
this.c1 = c1;
}
public int getC2() {
return c2;
}
public void setC2(int c2) {
this.c2 = c2;
}
@Override
public String toString() {
return "MyObject{" +
"c1=" + c1 +
", c2=" + c2 +
'}';
}
}
创建Mapper MyMapper.java
和 MyMapper.xml
:
package dao;
import pojo.MyObject;
import java.util.List;
public interface MyMapper {
public List<MyObject> read();
public void add(int c1, int value);
public void reduce(int c1, int value);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.MyMapper">
<select id = "read" resultType="pojo.MyObject">
select c1, c2 from t1
</select>
<update id="add">
update t1 set c2 = c2 + #{param2} where c1 = #{param1}
</update>
<update id="reduce">
update t1 set c2 = c2 - #{param2} where c1 = #{param1}
</update>
</mapper>
创建Service MyService.java
(接口)和 MyServiceImpl1.java
(实现类):
package service;
public interface MyService {
public void show();
public void change();
}
package service;
import dao.MyMapper;
import pojo.MyObject;
import java.util.List;
public class MyServiceImpl1 implements MyService{
private MyMapper mapper;
public void setMyMapper(MyMapper mapper) {
this.mapper = mapper;
}
@Override
public void show() {
List<MyObject> list = mapper.read();
list.forEach(System.out::println);
}
@Override
public void change() {
int value = 100;
try {
mapper.reduce(1, value);
mapper.add(2, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- datasource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/repo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
<property name="mapperLocations" value="classpath:dao/*.xml"/>
</bean>
<bean id="myMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="dao.MyMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="myService1" class="service.MyServiceImpl1">
<property name="myMapper" ref="myMapper"/>
</bean>
</beans>
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.MyService;
import java.io.IOException;
public class MyTest {
@Test
public void test1() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService service = context.getBean("myService1", MyService.class);
System.out.println("==================== Before: ====================");
service.show();
service.change();
System.out.println("==================== After: ====================");
service.show();
}
}
运行测试,如下:
==================== Before: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
==================== After: ====================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}
本例模拟了一个简单的转账操作, c1
账户转出100元,同时 c2
账户转入100元。
假定转入操作失败了。本例中,为了模拟操作失败,我们故意把SQL写错:
......
<update id="add">
updateaaa t1 set c2 = c2 + #{param2} where c1 = #{param1}
</update>
......
再次运行测试:
==================== Before: ====================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......
==================== After: ====================
MyObject{c1=1, c2=9800}
MyObject{c1=2, c2=10100}
可见,这样就会出现问题, c1
账户已经转出100元,而 c2
账户转入100元的操作失败了。
转账是一个典型的事务处理操作,应该使用事务管理,如果某一个操作失败,应该把整个事务回滚。
Spring的声明式事务基于AOP实现,它的优点是只需修改Spring配置文件,无需修改Service组件的代码逻辑。
spring-webmvc
已经包含了对Spring AOP的依赖,但是我们还需要添加对 aspectjweaver
的依赖:
......
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
<scope>runtime</scope>
</dependency>
......
修改 applicationContext.xml
配置文件:
......
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
......
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>
......
transactionManager
,格式是固定的,照抄就行(对于所需的 dataSource
,本例中使用的是构造器注入,如果换成属性注入也行);txAdvice
的格式基本也是固定的:
method
属性,本例中使用了 name="*"
,表示所有方法都适用,可以改成比如 name="get*"
,则表示适用于 get
开头的方法;propagation="REQUIRED"
是默认属性,不写也可以,表示如果当前没有事务,就新建一个事务,这是最常见的选择;read-only
, tmeout
等属性,基本上顾名思义;myPointcut
:expression="execution(* service.*.*(..))"
表示 service
包下的所有类的所有方法,参数不限(本例中其实只有 change()
方法需要做事务管理);现在就可以运行测试了,但有一点需要注意,虽然说声明式事务是非侵入式的,不用修改任何代码就能直接运行,但是在本例中,service里使用了 try
块:
......
try {
mapper.reduce(1, value);
mapper.add(2, value);
} catch (Exception e) {
e.printStackTrace();
}
......
这会对事务失败的判断产生影响。具体说来,因为代码中捕获并处理了异常,事务管理器就不会认为事务失败并回滚事务了,就跟没有事务管理的效果一样,所以这里一定要把 try
去掉:
// try {
mapper.reduce(1, value);
mapper.add(2, value);
// } catch (Exception e) {
// e.printStackTrace();
// }
为了使得异常之后的代码能继续运行,可以把 try
块放到测试里:
......
try {
service.change();
} catch (Exception e) {
e.printStackTrace();
}
......
把两条记录的 c2
值都还原为 10000
,重新运行测试,如下:
==================== Before: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......
==================== After: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
可见,出现异常后,事务回滚了。具体来说,因为转入操作失败,则之前转出的操作回滚了。
修改 applicationContext.xml
文件:
......
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
......
transactionManager
:和声明式事务一样,需要配置 transactionManager
;创建 MyServiceImpl2.java
:
package service;
import dao.MyMapper;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import pojo.MyObject;
import java.util.List;
public class MyServiceImpl2 implements MyService{
private final PlatformTransactionManager transactionManager;
private MyMapper mapper;
public MyServiceImpl2(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setMyMapper(MyMapper mapper) {
this.mapper = mapper;
}
@Override
public void show() {
List<MyObject> list = mapper.read();
list.forEach(System.out::println);
}
@Override
public void change() {
TransactionStatus txStatus =
transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
int value = 100;
mapper.reduce(1, value);
System.out.println("===================================");
mapper.add(2, value);
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
}
}
注:为了防止事务一开始就报错引起混淆,代码里加了一行输出,如果看到这行输出,说明转出操作已经做了。
注意:该方法需要显式 try
,捕获异常,并回滚事务。而前面介绍的声明式事务则不能捕获异常,要留给事务管理器来判断异常并回滚事务。
在 applicationContext.xml
中注册 MyServiceImpl2
:
......
<bean id="myService2" class="service.MyServiceImpl2">
<constructor-arg ref="transactionManager"/>
<property name="myMapper" ref="myMapper"/>
</bean>
......
创建测试如下:
@Test
public void test2() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService service = context.getBean("myService2", MyService.class);
service.show();
try {
service.change();
} catch (Exception e) {
e.printStackTrace();
}
service.show();
}
把SQL改成正确写法,把两条记录的 c2
值都还原为 10000
,运行test2,如下:
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
===================================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}
把SQL改成错误写法,把两条记录的 c2
值都还原为 10000
,运行test2,如下:
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
===================================
org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
可见,转入操作失败,事务回滚了。
编程式事务有一个变种的用法:
......
@Override
public void change() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
int value = 100;
mapper.reduce(1, value);
System.out.println("===================================");
mapper.add(2, value);
return null;
});
}
......
效果和前面一样,不再赘述。
注意:这里跟声明式事务类似,千万不要 try
并且捕获异常,否则事务管理器就不会认为事务失败了。
还有一点需要注意的是,在配置 SqlSessionFactory
时,不要添加如下属性:
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
</property>
否则会报错: org.springframework.dao.TransientDataAccessResourceException: SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization
该属性值表示由容器管理事务,而不使用Spring的事务管理。
try
并且捕获异常,否则事务管理器就不会认为事务失败了。当然,可以在外层捕获异常;因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- ovod.cn 版权所有 湘ICP备2023023988号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务