银行转账案例

1 案例中的问题

1.1 示例代码

  • 示例:
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.domain.Account;
import com.sunxiaping.spring5.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}
View Code

1.2 问题

  • 事务被自动控制了。换言之,我们使用的就是Connection对象的setAutoCommit(true)。
  • 这种方式控制事务,如果我们每次都执行一条SQL语句,没有什么问题,但是如果业务方法一次要执行多条SQL语句,这种方式就无法实现功能了。

1.3 出现问题的示例代码

  • 示例:
  • IAccountService.java
package com.sunxiaping.spring5.service;

import com.sunxiaping.spring5.domain.Account;

import java.util.List;

public interface IAccountService {

    /**
     * 保存账户
     *
     * @param account
     */
    void save(Account account);

    /**
     * 更新账户
     *
     * @param account
     */
    void update(Account account);

    /**
     * 删除账户
     *
     * @param id
     */
    void delete(Integer id);

    /**
     * 根据主键查询账户信息
     *
     * @param id
     * @return
     */
    Account findById(Integer id);

    /**
     * 查询所有
     *
     * @return
     */
    List<Account> findAll();

    /**
     * 转账
     * @param sourceName
     * @param targetName
     * @param money
     */
    void transfer(String sourceName, String targetName, Double money);


}
View Code
  • AccountServiceImpl.java
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.domain.Account;
import com.sunxiaping.spring5.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public void transfer(String sourceName, String targetName, Double money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);

        accountDao.update(source);
        int i = 1 / 0;//模拟转账异常
        accountDao.update(target);
    }
}
View Code
  • IAccountDao.java
package com.sunxiaping.spring5.dao;

import com.sunxiaping.spring5.domain.Account;

import java.util.List;

/**
 * 账户的持久层接口
 */
public interface IAccountDao {

    /**
     * 保存账户
     *
     * @param account
     */
    void save(Account account);

    /**
     * 更新账户
     *
     * @param account
     */
    void update(Account account);

    /**
     * 删除账户
     *
     * @param id
     */
    void delete(Integer id);

    /**
     * 根据主键查询账户信息
     *
     * @param id
     * @return
     */
    Account findById(Integer id);

    /**
     * 查询所有
     *
     * @return
     */
    List<Account> findAll();

    /**
     * 根据名称获取账户信息
     *
     * @param name
     * @return
     */
    Account findByName(String name);
}
View Code
  • AcccountDaoImpl.java
package com.sunxiaping.spring5.dao.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;
import java.util.List;

@Repository
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void save(Account account) {
        try {
            queryRunner.update("insert into account(name,money) values (?,?)", account.getName(), account.getMoney());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void update(Account account) {
        try {
            queryRunner.update("update account set name=?,money=? where id = ?", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Integer id) {
        try {
            queryRunner.update("delete from account where id = ?", id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Account findById(Integer id) {
        try {
            return queryRunner.query("select * from account where id = ?", new BeanHandler<>(Account.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Account> findAll() {
        try {
            return queryRunner.query("select * from account", new BeanListHandler<>(Account.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public Account findByName(String name) {
        try {
            List<Account> accountList = queryRunner.query("select * from account where name = ? ", new BeanListHandler<>(Account.class), name);
            if (null == accountList || accountList.size() == 0) {
                return null;
            } else {
                return accountList.get(0);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;

    }
}
View Code
  • 测试:
package com.sunxiaping;

import com.sunxiaping.spring5.config.SpringConfiguration;
import com.sunxiaping.spring5.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class spring5Test {

    @Autowired
    private IAccountService accountService;


    @Test
    public void test() {

        accountService.transfer("aaa","bbb",100d);
    }

}
View Code
  • 业务层实现类中的转账方法:
    @Override
    public void transfer(String sourceName, String targetName, Double money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);

        accountDao.update(source);
        int i = 1 / 0;//模拟转账异常
        accountDao.update(target);
    }
  • 当我们执行的时候,由于执行有异常,转账失败。但是因为我们是每次执行持久层的方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)。

2 问题的解决

2.1 思路

  • 让业务层来控制事务的提交和回滚。 

2.2 示例代码

  • Connection.java
package com.sunxiaping.spring5.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * Connection的工具类
 */
@Component
public class ConnectionUtils {

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

    @Autowired
    private DataSource dataSource;

    /**
     * 获取当前线程上的连接
     *
     * @return
     */
    public Connection getThreadConnection() {
        //先从ThreadLocal中获取连接
        Connection connection = threadLocal.get();
        //如果连接不存在
        if (connection == null) {
            try {
                //从连接池中获取连接
                connection = dataSource.getConnection();
                //将连接存放到ThreadLocal中
                threadLocal.set(connection);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return connection;
    }

    /**
     * 从ThreadLocal中将连接移除
     */
    public void removeConnection() {
        threadLocal.remove();
    }


}
  • TransactionManager.java
package com.sunxiaping.spring5.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * 事务控制器:事务管理的工具类。包含了开启事务、提交事务、回滚事务和释放连接。
 */
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 开始事务
     */
    public void beginTransaction() {
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 提交事务
     */
    public void commit() {
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 回滚事务
     */
    public void rooback() {
        Connection connection = connectionUtils.getThreadConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放连接
     */
    public void close() {
        Connection connection = connectionUtils.getThreadConnection();
        try {
            //释放连接后,需要将此连接和当前线程解绑
            connection.close();
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }


}
  • IAccountService.java
package com.sunxiaping.spring5.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {

    @Value("${jdbc.driverClass}")
    private String driverClass;
    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        try {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setDriverClass(driverClass);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public QueryRunner queryRunner() {
        return new QueryRunner();
    }
}
  • AccountServiceImpl.java
package com.sunxiaping.spring5.service.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.domain.Account;
import com.sunxiaping.spring5.service.IAccountService;
import com.sunxiaping.spring5.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private TransactionManager transactionManager;

    @Autowired
    private IAccountDao accountDao;

    @Override
    public void save(Account account) {
        try {
            transactionManager.beginTransaction();
            accountDao.save(account);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }
    }

    @Override
    public void update(Account account) {
        try {
            transactionManager.beginTransaction();
            accountDao.update(account);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }

    }

    @Override
    public void delete(Integer id) {

        try {
            transactionManager.beginTransaction();
            accountDao.delete(id);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }

    }

    @Override
    public Account findById(Integer id) {
        try {
            transactionManager.beginTransaction();
            Account account = accountDao.findById(id);
            transactionManager.commit();
            return account;
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }


    }

    @Override
    public List<Account> findAll() {
        try {
            transactionManager.beginTransaction();
            List<Account> accountList = accountDao.findAll();
            transactionManager.commit();
            return accountList;
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Double money) {
        try {
            transactionManager.beginTransaction();
            Account source = accountDao.findByName(sourceName);
            Account target = accountDao.findByName(targetName);
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);

            accountDao.update(source);
            int i = 1 / 0;//模拟转账异常
            accountDao.update(target);

            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rooback();
            throw new RuntimeException(e);
        } finally {
            transactionManager.close();
        }
    }
}
  • IAccountDao.java
package com.sunxiaping.spring5.dao;

import com.sunxiaping.spring5.domain.Account;

import java.util.List;

/**
 * 账户的持久层接口
 */
public interface IAccountDao {

    /**
     * 保存账户
     *
     * @param account
     */
    void save(Account account);

    /**
     * 更新账户
     *
     * @param account
     */
    void update(Account account);

    /**
     * 删除账户
     *
     * @param id
     */
    void delete(Integer id);

    /**
     * 根据主键查询账户信息
     *
     * @param id
     * @return
     */
    Account findById(Integer id);

    /**
     * 查询所有
     *
     * @return
     */
    List<Account> findAll();

    /**
     * 根据名称获取账户信息
     *
     * @param name
     * @return
     */
    Account findByName(String name);
}
  • AccountDaoImpl.java
package com.sunxiaping.spring5.dao.impl;

import com.sunxiaping.spring5.dao.IAccountDao;
import com.sunxiaping.spring5.domain.Account;
import com.sunxiaping.spring5.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;
import java.util.List;

@Repository
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private ConnectionUtils connectionUtils;


    @Override
    public void save(Account account) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), "insert into account(name,money) values (?,?)", account.getName(), account.getMoney());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void update(Account account) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), "update account set name=?,money=? where id = ?", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Integer id) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(), "delete from account where id = ?", id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Account findById(Integer id) {
        try {
            return queryRunner.query(connectionUtils.getThreadConnection(), "select * from account where id = ?", new BeanHandler<>(Account.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Account> findAll() {
        try {
            return queryRunner.query(connectionUtils.getThreadConnection(), "select * from account", new BeanListHandler<>(Account.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public Account findByName(String name) {
        try {
            List<Account> accountList = queryRunner.query(connectionUtils.getThreadConnection(), "select * from account where name = ? ", new BeanListHandler<>(Account.class), name);
            if (null == accountList || accountList.size() == 0) {
                return null;
            } else {
                return accountList.get(0);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;

    }
}
  • JdbcConfig.java
package com.sunxiaping.spring5.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {

    @Value("${jdbc.driverClass}")
    private String driverClass;
    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        try {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setDriverClass(driverClass);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public QueryRunner queryRunner() {
        return new QueryRunner();
    }
}
  • SpringConfiguration.java
package com.sunxiaping.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan("com.sunxiaping.spring5")
@Import(JdbcConfig.class)
public class SpringConfiguration {


}

猜你喜欢

转载自www.cnblogs.com/xuweiweiwoaini/p/11741920.html