Spring 学习笔记 Part05
1. 前置知识 —— 线程与连接绑定来方便实现事务
默认情况下,每一个数据访问语句都会建立一个连接,各个连接间相互独立,其中某个连接出错就会导致错误的数据修改结果。
解决思路:
一个事务都使用同一个 Connection 的话,要么一起成功,要么一起失败,可以实现事务。在 Spring 中,使用 TheadLocal 对象把 Connection 和当前线程绑定,从而使一个线程中只能有一个能控制事务的对象。
1.1 工具类的编写(连接管理、事务管理)
//连接工具类
@Component("connectionUtil")
public class ConnectionUtil {
private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public Connection getConnection(){
Connection conn = connectionThreadLocal.get();
try {
if (conn == null){
conn = dataSource.getConnection();
connectionThreadLocal.set(conn);
}
return conn;
} catch (Exception e){
throw new RuntimeException(e);
}
}
public void removeConnection(){
connectionThreadLocal.remove(); //与当前线程解绑
}
}
//事务管理类
@Component("transactionManager")
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void begin(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void release(){
try {
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection(); //与当前线程解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
需要注意的是关闭连接时需要和线程进行解绑操作。连接 close( ) 关闭时会还回连接池中,线程关闭时也会还回线程池中,但它们间的绑定关系还是存在的。当我们第二次使用这个线程时,get( ) 方法会拿出来之前绑定的那个连接,但是这个连接之前被关闭了(加回到池里去了),使用不了。所以我们需要线程在使用好连接后调用一个 remove( ) 方法来解绑连接,以便下次获取的连接是新的、能用的。
1.2 业务层的编写
Service层需要加入事务管理,分为 6 个步骤:
@Service("service")
public class AccountServiceImpl implements AccountService {
@Override
public boolean transfer(String sourceName, String targetName, double money) {
try {
transactionManager.begin(); //第1步:开启事务
Account source = dao.findByName(sourceName); //第2步:执行操作
Account target = dao.findByName(targetName);
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
dao.update(source);
//double a = 4/0; 有了事务管理,出错了将会回滚,不会造成数据错误了~!
dao.update(target);
transactionManager.commit(); //第3步:提交事务
return true; //第4步:返回结果
} catch (Exception e) {
transactionManager.rollback(); //第5步:回滚操作
throw new RuntimeException(e);
} finally {
transactionManager.release(); //第6步:释放连接
}
}
}
1.3 持久层的编写
Dao层需要注入ConnectionUtils对象,用ConnectionUtils获得当前线程对应的连接,并作为QueryRunner的query( )方法中的第一个参数传入,为本次数据操作指定一个特定的连接。
@Repository("dao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public Account findByName(String username) {
List<Account> accounts = null;
try {
accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?",
new BeanListHandler<Account>(Account.class), username);
} catch (Exception e) {
throw new RuntimeException(e);
}
if(accounts==null||accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}
@Override
public void update(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),
"update account set money = ? where id = ?",account.getMoney(),account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84500.html