java小技能:biz层处理dao层的事务

244 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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 声明式事务的粒度问题

  1. 声明式事务有一个局限,那就是他的最小粒度要作用在方法上。

如果想要给一部分代码块增加事务的话,那就需要把这个部分代码块单独独立出来作为一个方法。

  1. 可能导致声明式事务失效: 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. 数据库引擎不支持事务

  1. 事务有可能被开发者忽略:容易在被事务嵌套的方法中加入一些如 RPC 远程调用、消息发送、缓存更新、文件写入、内存操作、跨库操作

这些操作自身是无法回滚的,这就会导致数据的不一致。在事务中有远程调用,就会拉长整个事务。

see also

Java开发的深入浅出:blog.csdn.net/z929118967/…