事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
甲乙两个人转钱,正常情况下:甲输入乙的账号,输入金额,然后甲的账户扣除相应的钱,乙的账户扣除相应的钱;
但是就是有特殊情况,甲在输入账号的时候输错了,就会发生一个情况,甲的钱扣掉了,但是乙没有收到钱,肯定就会出现问题;这就是事务相关的问题:
在进行转账的时候,我们可以进行事务判断,当其中有一方出现异常时,及时的进行回滚(rollback()),在成功后再进行提交(commit())
练习一:不使用事物进行转账出现的数据不一致现象!
建立数据库表
CREATE TABLE `userinfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `birthday` datetime DEFAULT NULL, `sex` varchar(1) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, `money` int(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of userinfo -- ---------------------------- BEGIN; INSERT INTO `userinfo` VALUES (1, ‘旺财‘, ‘2020-09-29 00:00:00‘, ‘女‘, ‘五一广场‘, 2100); INSERT INTO `userinfo` VALUES (2, ‘来福‘, ‘2020-09-16 00:00:00‘, ‘男‘, ‘五一新干线‘, 700); COMMIT; SET FOREIGN_KEY_CHECKS = 1;
导入jar包
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>net.sf.ezmorph</groupId>
<artifactId>ezmorph</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.3</version>
<classifier>jdk15</classifier><!-- 指定jdk版本 -->
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.3</version>
</dependency>
创建Userinfo实体类---与表字段类型一致
public class Userinfo {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private Integer money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex == null ? null : sex.trim();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address == null ? null : address.trim();
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
@Override
public String toString() {
return "Userinfo{" +
"id=" + id +
", username=‘" + username + ‘\‘‘ +
", birthday=" + birthday +
", sex=‘" + sex + ‘\‘‘ +
", address=‘" + address + ‘\‘‘ +
", money=" + money +
‘}‘;
}
}
创建转账接口
public interface Changmoney {
boolean giveMoney(int on,int to,int moeny);
}
使用generator.xml创建接口的数据库语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/t142" userId="root"
password="123456">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="pojo"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cc.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cc.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!--指定数据库表-->
<table tableName="userinfo"
domainObjectName="Userinfo"
enableCountByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
enableUpdateByExample="false"></table>
<!--<table tableName="test" domainObjectName="Test" enableCountByExample="false" enableDeleteByExample="false"-->
<!--enableSelectByExample="false" enableUpdateByExample="false"></table>-->
</context>
</generatorConfiguration>
创建运行generator.xml的Generator
public class Generator {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("D:\\IDe\\SpringzhuanzTest\\src\\config\\generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
}
将创建的UserinfoMapper.xml放在config/mapper里,将里面的pojo替换成entity
创建Sqlsession链接数据库的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--默认都是false-->
<!--<setting name="lazyLoadingEnabled" value="true"/>-->
<!--<setting name="aggressiveLazyLoading" value="false"/>-->
<!-- 开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<package name="pojo"/>
</typeAliases>
<!-- 配置mybatis的环境信息 -->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,采用dbcp连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/t142?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 加载mapper -->
<mappers>
<package name="cc.mapper"/>
</mappers>
</configuration>
创建impl实现接口方法
public class ChangeMoneyImpl implements Changmoney {
public static SqlSession getSqlSession(){
String resource = "SqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 2、根据配置文件创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession(true);
}
@Override
public boolean giveMoney(int on, int to, int moeny) {
SqlSession sqlSession = getSqlSession();
UserinfoMapper mapper = sqlSession.getMapper(UserinfoMapper.class);
//转钱人信息
Userinfo userinfo = mapper.selectByPrimaryKey(on);
int i=0;
if (userinfo!=null){
//设置转钱人的鱼儿
userinfo.setMoney(userinfo.getMoney()-moeny);
i = mapper.updateByPrimaryKey(userinfo);
}
//设置被转钱的信息
Userinfo userinfo1 = mapper.selectByPrimaryKey(to);
int j=0;
if (userinfo1!=null){
//设置被转钱人的鱼儿
userinfo1.setMoney(userinfo1.getMoney()+moeny);
//受影响的行数
j = mapper.updateByPrimaryKey(userinfo1);
}
if (i>0&&j>0){
System.out.println("转钱成功");
return true;
}else {
System.out.println("转钱失败");
return false;
}
}
}
编写测试Sqlsession类
public class TestSqlSession {
public static void main(String[] args) {
SqlSession sqlSession = ChangeMoneyImpl.getSqlSession();
UserinfoMapper mapper = sqlSession.getMapper(UserinfoMapper.class);
Userinfo userinfo = mapper.selectByPrimaryKey(1);
System.out.println(userinfo.toString());
}
}
OK,数据能显示出来
Userinfo{id=1, username=‘旺财‘, birthday=Tue Sep 29 00:00:00 CST 2020, sex=‘女‘, address=‘五一广场‘, money=200}
Process finished with exit code 0
编辑转账测试
public class TestZhuangz {
public static void main(String[] args) {
Changmoney changmoney = new ChangeMoneyImpl();
boolean b = changmoney.giveMoney(1, 2, 200);
System.out.println(b);
}
}
转账成功
转钱成功 true Process finished with exit code 0
练习二:手工的方式加入mybatis的事物控制,解决数据不一致的问题,但是这种代码入侵性很强,通过AOP思想进行优化
在上面的代码上进行修改
加入输入错误进行回滚
public class ChangeMoneyImpl implements Changmoney {
public static SqlSession getSqlSession(){
String resource = "SqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 2、根据配置文件创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory.openSession(true);
}
@Override
public boolean giveMoney(int on, int to, int moeny) {
SqlSession sqlSession = getSqlSession();
UserinfoMapper mapper = sqlSession.getMapper(UserinfoMapper.class);
//转钱人信息
Userinfo userinfo = mapper.selectByPrimaryKey(on);
int i=0;
if (userinfo!=null){
//设置转钱人的鱼儿
userinfo.setMoney(userinfo.getMoney()-moeny);
i = mapper.updateByPrimaryKey(userinfo);
}
//设置被转钱的信息
Userinfo userinfo1 = mapper.selectByPrimaryKey(to);
int j=0;
if (userinfo1!=null){
//设置被转钱人的鱼儿
userinfo1.setMoney(userinfo1.getMoney()+moeny);
//受影响的行数
j = mapper.updateByPrimaryKey(userinfo1);
}
if (i>0&&j>0){
System.out.println("转钱成功");
sqlSession.commit();
return true;
}else {
System.out.println("转钱失败");
sqlSession.rollback();
return false;
}
}
}
测试
public class TestZhuangz {
public static void main(String[] args) {
Changmoney changmoney = new ChangeMoneyImpl();
boolean b = changmoney.giveMoney(2, 3, 200);
System.out.println(b);
}
}
结果返回转钱失败,并且数据库没有扣2号账户的钱,这就进行了事物的回滚操作
练习三:自己手动写一个事物黑客(事物管理器),通过springHk的配置来对我们方法进行增强。但还是不够好,入侵目标的所有方法,如果想要只入侵目标对象的部分方法,可以通过方法名来进行判断,这种方式比较粗鲁?
在上面的代码上进行修改
加入util油条,把Sqlsession单独拿出来
public class SqlSessionUtil {
private static SqlSession sqlSession= null;
static {
//1、根据mybaits的配置文件构建SqlSessionFactory
//需要改成我们自己的SqlMapConfig.xml的路径
String resource = "SqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2、创建SqlSession
sqlSession= sqlSessionFactory.openSession();
}
public static SqlSession openSession() {
return sqlSession;
}
public static void main(String[] args) {
System.out.println(SqlSessionUtil.openSession());
}
}
加入Spring自带的事物管理器
public class TransactionManager implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation method) throws Throwable {
SqlSession sqlSession = SqlSessionUtil.openSession();
//调用目标方法
boolean result = (boolean) method.proceed();
if( method.getMethod().getName().equals("giveMoney")){
if(result) {
sqlSession.commit();
System.out.println("====提交事务===");
}else {
sqlSession.rollback();
System.out.println("====回滚事务===");
}
}
return result;
}
}
加入beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 1、启动注解扫描-->
<!-- <context:annotation-config/> -->
<context:component-scan base-package="cc"></context:component-scan>
<!-- 1)目标 -->
<bean id="target" class="cc.change.ChangeMoneyImpl"></bean>
<!-- 2)黑客 -->
<bean id="transactionManager" class="cc.proxy.TransactionManager"></bean>
<!--3)代理 -->
<bean id="ChangeMoneyImpProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="cc.change.Changmoney"></property>
<!-- 1)注入目标对象 -->
<property name="target" ref="target"/>
<!-- 2)黑客对象 -->
<property name="interceptorNames">
<array>
<value>transactionManager</value>
</array>
</property>
</bean>
</beans>
修改ChangeMoneyImpl 使用Sql油条
public class ChangeMoneyImpl implements Changmoney {
@Override
public boolean giveMoney(int on, int to, int moeny) {
SqlSession sqlSession = SqlSessionUtil.openSession();
UserinfoMapper mapper = sqlSession.getMapper(UserinfoMapper.class);
//转钱人信息
Userinfo userinfo = mapper.selectByPrimaryKey(on);
int i=0;
if (userinfo!=null){
//设置转钱人的鱼儿
userinfo.setMoney(userinfo.getMoney()-moeny);
i = mapper.updateByPrimaryKey(userinfo);
}
//设置被转钱的信息
Userinfo userinfo1 = mapper.selectByPrimaryKey(to);
int j=0;
if (userinfo1!=null){
//设置被转钱人的鱼儿
userinfo1.setMoney(userinfo1.getMoney()+moeny);
//受影响的行数
j = mapper.updateByPrimaryKey(userinfo1);
}
if (i>0&&j>0){
System.out.println("转钱成功");
return true;
}else {
System.out.println("转钱失败");
return false;
}
}
}
添加测试方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:beans.xml"})
public class TestHKzhuanz {
@Autowired
@Qualifier("ChangeMoneyImpProxy")
private Changmoney change;
@Test
public void test1(){
boolean b = change.giveMoney(1, 2, 300);
System.out.println(b);
}
}
测试结果
转钱成功 ====提交事务=== true
package cc.change;
public interface Changmoney {
boolean giveMoney(int on,int to,int moeny);
}
原文:https://www.cnblogs.com/qxhIndex/p/14151737.html