开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
前言
不推荐使用@Transactional声明式注解:
- 声明式事务的粒度问题
- 事务有可能被开发者忽略:容易在被事务嵌套的方法中加入一些如 RPC 远程调用、消息发送、缓存更新、文件写入、内存操作、跨库操作
I 通过编程的方式来进行事务管理
public void test() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 事务操作
// 事务提交
transactionManager.commit(status);
} catch (DataAccessException e) {
// 事务提交
transactionManager.rollback(status);
throw e;
}
}
1.1 创建一个事务管理对象,该对象将连接对象绑定到当前线程
package bank.util;
import java.sql.Connection;
import java.sql.SQLException;
public class TransactionManager {
private static ThreadLocal tl=new ThreadLocal();
private Connection conn;
public TransactionManager(){
try {
conn=ConnectionUtil.getConn();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
//将获得的连接对象绑定到线程
tl.set(conn);
}
public static Connection getConn(){
//从当前线程中获取绑定的连接对象
return (Connection) tl.get();
}
//将连接对象的事务设置手动提交
public void openTransaction(){
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//事务提交
public void commitTransaction(){
try {
conn.commit();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//事务回滚
public void rollBackTransaction(){
try {
conn.rollback();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
1.2 dao层的代码示例
package bank.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import bank.dao.UserContextDao;
import bank.entity.User;
import bank.exception.DAOException;
import bank.util.ConnectionUtil;
import bank.util.TransactionManager;
public class UserContextDaoImpl implements UserContextDao {
/**
* 根据sql获取单个用户
*/
public User getUserBySql(String sql) {
//获取连接对象
Connection conn=null;
PreparedStatement pstmt=null;
ResultSet rs=null;
User u=new User();
try {
conn=ConnectionUtil.getConn();
pstmt=conn.prepareStatement(sql);
rs=pstmt.executeQuery();
while(rs.next()){
u.setId(rs.getInt(1));
u.setName(rs.getString(2));
u.setPwd(rs.getString(3));
u.setMoney(rs.getDouble(4));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new DAOException("连接数据库失败");
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException("执行sql失败");
}finally{
try {
ConnectionUtil.close(conn,pstmt,rs);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException("关闭连接资源失败");
}
}
return u;
}
@Override
public int updateUserBySql(String sql) {
//获取连接对象
Connection conn=null;
PreparedStatement pstmt=null;
int num=0;
try {
conn=TransactionManager.getConn();
pstmt=conn.prepareStatement(sql);
num=pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
try {
conn.rollback();
conn.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
throw new DAOException("执行sql失败");
}
throw new DAOException("执行sql失败");
}finally{
try {
ConnectionUtil.close(null,pstmt,null);
} catch (SQLException e) {
e.printStackTrace();
try {
conn.rollback();
conn.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
throw new DAOException("执行sql失败");
}
throw new DAOException("关闭连接资源失败");
}
}
return num;
}
}
1.3 biz层处理数据库的事务
package bank.biz.impl;
import bank.biz.UserContextBiz;
import bank.dao.UserContextDao;
import bank.entity.User;
import bank.exception.AimUserNotExistException;
import bank.exception.ChangeFailException;
import bank.exception.MoneyEnoughException;
import bank.exception.PasswordNotCorrectException;
import bank.exception.UserNotExistException;
import bank.util.Factory;
import bank.util.TransactionManager;
public class UserContextBizImpl implements UserContextBiz {
//校验登录
private UserContextDao dao=(UserContextDao) Factory.getImpl("UserContextDaoImpl");
public User login(User u) {
String sql="select * from t_user where name='"+u.getName()+"'";
User user=dao.getUserBySql(sql);
if(user.getPwd()==null){
throw new UserNotExistException("用户不存在");
}
if(!user.getPwd().equals(u.getPwd())){
throw new PasswordNotCorrectException("密码错误");
}
return user;
}
@Override
public void change(int countId, double m,int id) {
//转账
//判断当前用户的余额
String sql="select * from t_user where id="+id;
User user=dao.getUserBySql(sql);
if(user.getMoney()
II 声明式事务
Transactional注解是添加在实现类或者接口实现方法上,只对public方法生效。
2.1 注解属性
2.2 声明式事务的粒度问题
- 声明式事务有一个局限,那就是他的最小粒度要作用在方法上。
如果想要给一部分代码块增加事务的话,那就需要把这个部分代码块单独独立出来作为一个方法。
- 可能导致声明式事务失效: a. @Transactional 应用在非 public 修饰的方法上 b. @Transactional 注解属性 propagation 设置错误 c. @Transactional 注解属性 rollbackFor 设置错误 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚,即Exception的子类中,除了RuntimeException及其子类,其他的类默认不回滚。如果代码中要抛出自定义异常,并且要求把事务进行回滚,那么就需要指定 rollbackFor=自定义异常。 d. 异常被 catch 捕获导致 @Transactional 失效 e. 同一个类中方法调用,导致 @Transactional 失效。 同一个业务类里面 , 即使声明为 Propagation.REQUIRES_NEW也不会新启一个事务。必须调用另一个类的Propagation.REQUIRES_NEW方法才行。
f. 数据库引擎不支持事务
- 事务有可能被开发者忽略:容易在被事务嵌套的方法中加入一些如 RPC 远程调用、消息发送、缓存更新、文件写入、内存操作、跨库操作
这些操作自身是无法回滚的,这就会导致数据的不一致。在事务中有远程调用,就会拉长整个事务。