美文网首页Java框架Spring
【Spring】 07 - Spring account tra

【Spring】 07 - Spring account tra

作者: itlu | 来源:发表于2020-10-17 12:57 被阅读0次

1. 初始化项目

1.1 新建一个项目

  1. 使用maven创建一个不使用骨架的项目。

1.2 导入依赖

  1. 打包方式使用jar ,导入需要使用的依赖。属性maven项目,导入依赖。
刷新maven项目
    <packaging>jar</packaging>

    <dependencies>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <!-- spring 框架 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!--dbUtils -->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

        <!-- Spring整合Junit单元测试jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

    </dependencies>

1.3 创建account实体类

  public class Account {

    private Integer id;
    private String name;
    private float money;
    // 省略setter 和 getter 方法
}

1.4 创建持久层接口以及实现类

  1. 持久层接口 AccountDao
public interface AccountDao {

      /**
       * 更新账户信息
       * @param account
       */
      void updateAccount(Account account);

     /**
      * 根据账户名称查询账户
      * @param accountName
      * @return
      */
       Account findByName(String accountName);
}
  1. 创建持久层实现类AccountDaoImpl
  public class AccountDaoImpl implements AccountDao {
   public void updateAccount(Account account) {
       try {
           queryRunner.update("update account set name = ? , money = ?   where id = ? ", account.getName(), ac
       } catch (SQLException e) {
           e.printStackTrace();
       }
   }

    /**
     * 根据账户名称查询账户信息
     *
   * @param accountName
   * @return
   */
  public Account findByName(String accountName) {
      try {
          List<Account> accounts = queryRunner.query("select * from account where name = ? ", new
          // 判断查询的结果集
          if (accounts == null || accounts.size() == 0) {
            return null;
         }
          if (accounts.size() > 1) {
            throw new RuntimeException("查询结果集不唯一 ,数据有问题");
          }
          return accounts.get(0);
      } catch (SQLException e) {
        throw new RuntimeException(e);
      }
    }
}

1.5 创建业务层接口及实现类

  1. 创建业务层接口 AccountService
 public interface AccountService {
 /**
  * 更新账户信息
  * @param account
  */
 void updateAccount(Account account);
 /**
  * 转账案例
  * @param sourceName 转出账户
  * @param targetName 转入账户
  * @param money 转出金额
  */
 void transfer(String sourceName , String targetName, Float money);
}
  1. 创建持久层实现类AccountServiceImpl
public class AccountServiceImpl implements AccountService {
   private AccountDao accountDao;

public void updateAccount(Account account) {
    accountDao.updateAccount(account);
}
 /**
  * @param sourceName 转出账户
  * @param targetName 转入账户
  * @param money      转出金额
  */
 public void transfer(String sourceName, String targetName, Float money) {
     // 1. 根据名称查询转出账户 名称
     Account source = accountDao.findByName(sourceName);
     // 2. 根据名称查询转入账户名称
     Account target = accountDao.findByName(targetName);
     // 3. 转出账户减钱
     source.setMoney(source.getMoney() - money);
     // 4. 转入账户加钱
     target.setMoney(target.getMoney() + money);
     // 5. 更新装出账户
     accountDao.updateAccount(source);
     // 6. 更新转入账户
     accountDao.updateAccount(target);
 }
}

1.6 编写 bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.lyp.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <bean id="accountDao" class="com.lyp.dao.impl.AccountDaoImpl">
        <property name="queryRunner" ref="queryRunner"/>
    </bean>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="ds"/>
    </bean>

    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="user" value="root"/>
        <property name="password" value="xxxx"/>
    </bean>
</beans>

1.7 测试 transfer方法

  1. 在测试类中编写代码 对 transfer 方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class SpringCrudTest {

   @Autowired
    private AccountService as;

   @Test
   public void testTransfer() {
       as.transfer("aaa", "bbb", 100.0f);
   }
}
  1. 此时测试是没有任何问题的,但是如果其中出现任何的异常,将会违背数据库的一致性原则。例如在 两个 account 更新数据之间加入一个造成异常信息的代码,此时这种情况就会出现。转出 account 减 ,但是转入 account 没有加。
添加出现异常的代码+

2. 案例分析

  1. 问题 : 在transfer中方法中,每个与数据库的操作都分被获取了一个连接对象。这样每个对象都会进行一次提交。但是如果某个连接提交之前出现异常,该异常之后的连接事务将不会被提交。这样就会导致一致性出现问题。

  2. 解决 : 需要使用ThreadLocal对象将Connection和当前线程绑定,从而是一个线程中只有一个能控制事务的对象。多次操作使用同一个事务,要发生就同时发生。不发生就统一不发生。

  3. 事务的控制都应该是在业务层的。

3. 解决办法

3.1 新建一个获取连接的工具类

  1. 连接的工具类:用于在数据源中获取一个连接,并且实现和线程的绑定。
    public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    /**
     * 使用注入的方式注入数据源
     *
     * @param dataSource
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Connection getTreadLocalConnection() {
        try {
            // 1. 首先,从ThreadLocal上获取
            Connection conn = tl.get();

            // 2. 判断当前线程上是否有连接
            if (conn == null) {
                // 3. 如果没有连接将在数据源中获取一个连接存入到当前线程中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            // 4. 返回conn
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将线程与连接进行解绑
     */
    public void removeConn() {
        tl.remove();
    }

}

3.2 创建一个事务管理的工具类 TransactionManager

  1. 和事务管理相关的工具类:包含开启事务、提交事务、回滚事务、释放连接。
    public class TransactionManager {

    private ConnectionUtils cu;

    /**
     * 用于注入 ConnectionUtils 对象
     *
     * @param cu
     */
    public void setCu(ConnectionUtils cu) {
        this.cu = cu;
    }

    /**
     * 开启事务
     */
    public void beginTransaction() {
        try {
            /* 设置事务提交方式为手动提交 */
            cu.getTreadLocalConnection().setAutoCommit(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollbackTransaction() {
        try {
            /* 设置事务提交方式为手动提交 */
            cu.getTreadLocalConnection().rollback();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commitTransaction() {
        try {
            /* 设置事务提交方式为手动提交 */
            cu.getTreadLocalConnection().commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void closeTransaction() {
        try {
            /* 设置事务提交方式为手动提交 */
            cu.getTreadLocalConnection().close();
            cu.removeConn();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 细节:我们使用的连接都使用了连接池,连接池消耗时间和获取连接的部分放到应用加载的一开始。在web工程中,当我们启动tomcat时加载应用是创建一些连接,当我们在后续中获取连接时不再与数据库获取连接,保证在使用Connection的执行效率。

3.3 在连接管理工具类中创建一个方法用于将线程和连接进行解绑操作

  1. 创建一个方法用于将线程与已经关闭的连接进行解绑。
    /**
     * 将线程与连接进行解绑
     */
    public void removeConn() {
        tl.remove();
    }

3.4 编写业务层和持久层事务控制代码

  1. 在业务层加入事务管理对象对事务进行管理。并提供依赖注入的 setter 方法
     private TransactionManager txManager;

    /**
     * 使用注入的方式进行注入
     *
     * @param txManager
     */
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }
  1. 进行事务管理之后的transfer 方法
    /**
     * @param sourceName 转出账户
     * @param targetName 转入账户
     * @param money      转出金额
     */
    public void transfer(String sourceName, String targetName, Float money) {

        try {
            // 1. 开启事务
            txManager.beginTransaction();
            // 2. 执行操作
            // 2.1. 根据名称查询转出账户 名称
            Account source = accountDao.findByName(sourceName);
            // 2.2. 根据名称查询转入账户名称
            Account target = accountDao.findByName(targetName);
            // 2.3. 转出账户减钱
            source.setMoney(source.getMoney() - money);
            // 2.4. 转入账户加钱
            target.setMoney(target.getMoney() + money);
            // 2.5. 更新装出账户
            accountDao.updateAccount(source);
            // 添加一个异常
            int i = 1 / 0;
            // 2.6. 更新转入账户
            accountDao.updateAccount(target);
            // 3. 提交事务
            txManager.commitTransaction();
            // 4. 返回结果集
        } catch (Exception e) {
            // 5. 回滚事务
            throw new RuntimeException(e);
        } finally {
            // 6. 释放连接
            txManager.closeTransaction();
        }
    }
  1. 修改持久层的代码 ,持久层获取连接的方式不再是使用QueryRunner对象获取,而是通过ConnectionUtils对象获取。
     /* 由于使用了事务控制QueryRunner对象中的连接不能再自个获取自个的了 而是从ConnectionUtils中取 */

    private ConnectionUtils connectionUtils;

    /**
     * 为 注入 ConnectionUtils 对象 添加set方法
     *
     * @param connectionUtils
     */
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
  1. 在持久层中QueryRunner的方法中加入该参数用于获取连接


    在持久层中QueryRunner的方法中加入该参数用于获取连接
  2. 修改bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.lyp.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="txManager" ref="txManager"/>
    </bean>

    <bean id="accountDao" class="com.lyp.dao.impl.AccountDaoImpl">
        <property name="queryRunner" ref="queryRunner"/>
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"/>

    <bean id="connectionUtils" class="com.lyp.utils.ConnectionUtils">
        <property name="dataSource" ref="ds"/>
    </bean>

    <bean id="txManager" class="com.lyp.utils.TransactionManager">
        <property name="cu" ref="connectionUtils"/>
    </bean>

    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="user" value="root"/>
        <property name="password" value="****"/>
    </bean>


</beans>

3.5 测试代码并分析当前代码中存在的问题

  1. 测试成功!
  2. 存在问题:代码重复,且存在相互依赖,方法之间的依赖严重。
  3. 解决办法:动态代理。

4. demo 地址

  1. demo地址 : spring修改数据库操作,实现事务简单控制.

相关文章

网友评论

    本文标题:【Spring】 07 - Spring account tra

    本文链接:https://www.haomeiwen.com/subject/ypvxmktx.html