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 出现问题的示例代码


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


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


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


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 示例代码
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();
}
}
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);
}
}
}
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();
}
}
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();
}
}
}
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);
}
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;
}
}
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();
}
}
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 {
}