qiuxuhui

导航

Spring-事务管理练习,转账(三种)(maven空间)

1、事务

1.1 、事务是什么

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。

1.2、事务的四大特性

事务应该具有4个属性:原子性一致性隔离性持久性。这四个属性通常称为ACID特性

  1. 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  2. 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  3. 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

1.3、例子

甲乙两个人转钱,正常情况下:甲输入乙的账号,输入金额,然后甲的账户扣除相应的钱,乙的账户扣除相应的钱;
但是就是有特殊情况,甲在输入账号的时候输错了,就会发生一个情况,甲的钱扣掉了,但是乙没有收到钱,肯定就会出现问题;这就是事务相关的问题:

 

1.4、解决办法:

在进行转账的时候,我们可以进行事务判断,当其中有一方出现异常时,及时的进行回滚(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);

}

posted on 2020-12-17 20:23  qiuxuhui  阅读(130)  评论(0编辑  收藏  举报