JAVA——事务,事务面试题,mysql事务,jdbc事务,DButils事务,使用事务完成转账功能。

一、事务的特性和隔离级别 ——(面试题)

  1. 事务的特性ACID
  • 原子性(Atomicity):指事务是一个不可分割的工作单位,事务中的操作。要么都发生,要么都不发生。
  • 一致性(Consistency):一个事务中,事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation):是指多个用户并发访问数据库时,一个用户的 事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
  • 持久性(Durability):指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
  1. 并发访问问题——由隔离性引起
    如果不考虑隔离性,事务存在3中并发访问问题。
  • 脏读:B事务读取到了A事务尚未提交的数据 ------ 要求B事务要读取A事 务提交的数据
  • 不可重复读:一个事务中 两次读取的数据的内容不一致 ----- 要求的是一个事 务中多次读取时数据是一致的 — unpdate
  • 幻读/虚读:一个事务中 两次读取的数据的数量不一致 ----- 要求在一个事务多 次读取的数据的数量是一致的 --insert delete

3.事务的隔离级别

  • read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决
  • read committed:读取已经提交的数据 :可以解决脏读 ---- oracle默认的
  • repeatable read:重读读取:可以解决脏读 和 不可重复读 —mysql默认的
  • serializable:串行化:可以解决 脏读 不可重复读 和 虚读—相当于锁表

隔离级别的性能
read uncommitted>read committed>repeatable read>serialazable
安全性:
read uncommitted<read committed<repeatable read<serialazable

二、 MySQL事务

1.什么是事务

一件事情有n个组成单元 要不这n个组成单元同时成功 要不n个单元就同时失败。就是将n个组成单元放到一个事务中

2.mysql的事务
默认的事务

一条sql语句就是一个事务 默认就开启事务并提交事务

手动事务

  • 显示的开启一个事务:start transaction
  • 事务提交:commit代表从开启事务到事务提交 中间的所有的sql都认为有效 真正的更新数据库。如果忘记写commit 就会默认将事务回滚
  • 事务的回滚:rollback 代表事务的回滚 从开启事务到事务回滚 中间的所有的 sql操作都认为无效数据库没有被更新

案例:
1.我们首先创建一个数据库,表名为account

START TRANSACTION;
INSERT INTO account VALUES(NULL,'jack',5000);
COMMIT;

以上sql语句执行,并且事务执行成功。

2.如果我们 没有写commit

START TRANSACTION;
INSERT INTO account VALUES(NULL,'lucy',5000);

那么在cmd窗口下进行查询,可以查询到数据,但是该数据并不存在于磁盘中而是打印在日志文件中。

3.事务的回滚

START TRANSACTION;
INSERT INTO account VALUES(NULL,'amy',5000);
ROLLBACK;

该sql语句无法进行插入,因为进行了事务的回滚

三、JDBC事务操作

默认是自动事务

执行sql语句:executeUpdate() ---- 每执行一次executeUpdate方法就代表事务自动提交。

通过jdbc的API手动事务

开启事务:conn.setAutoComnmit(false);
提交事务:conn.commit();
回滚事务:conn.rollback();
注意:控制事务的connnection必须是同一个
执行sql的connection与开启事务的connnection必须是同一个才能对事务进行控制

//通过jdbc去控制事务
		Connection connection=null;
		//1.注册驱动
		try {
    
    Class.forName("com.mysql.jdbc.Driver");
		//2.获取连接
		connection= DriverManager.getConnection("jdbc:mysql:///web19","root","123");
		//手动开启事务
		connection.setAutoCommit(false);
		//3.获取执行平台
		Statement statement =connection.createStatement();
		//4.操作sql
		statement.executeUpdate("insert into account values(null,'zhangsan',6000)");
		
		//statement.executeUpdate("insert into account values(null,'lisi',7000)");
		
		//提交事务
		connection.commit();
		//关闭
		statement.close();
		connection.close();
		}catch (Exception e) {
    
    
			// TODO: handle exception
			try {
    
    
				connection.rollback();
			} catch (SQLException e1) {
    
    
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

四、 DBUtils事务操作

QueryRunner
有参构造:QueryRunner runner = new QueryRunner(DataSource dataSource);
有参构造将数据源(连接池)作为参数传入QueryRunner,QueryRunner会从连接池中获得一个数据库连接资源操作数据库,所以直接使用无Connection参数的update方法即可操作数据库

无参构造:QueryRunner runner = new QueryRunner();
无参的构造没有将数据源(连接池)作为参数传入QueryRunner,那么我们在使用QueryRunner对象操作数据库时要使用有Connection参数的方法

简单来说,当我们的sql需要进行事务控制使用无参构造,不需要事务控制使用有参构造。

QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());
		Connection conn=null;
		try {
    
    
			//获得一个Connection
			conn =DataSourceUtils.getConnection();
			//开启事务
			conn.setAutoCommit(false);
			//runner.update("update account set money=1000 where name='tom");
			runner.update(conn, "update account set money=1000 where name='tom");
			//提交或回滚事务
			conn.commit();
		} catch (SQLException e) {
    
    
			try {
    
    
				conn.rollback();
			} catch (SQLException e1) {
    
    
				
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}

五、完成一个转账功能的案例

先分析MVC模式下的,web转账功能
在这里插入图片描述

第一步:先写一个jsp

<form action="${pageContext.request.contextPath}/transfer" method="post">
			转出账户:<input type="text" name="out"><br/>
			转入账户:<input type="text" name="in" ><br/>
			转账金额:<input type="text" name="money"><br/>
			<input type="submit" value="确认转账"><br/>
</form>

第二步:书写web层,负责获取页面的数据,调用service层的数据
web层是一个servlet,需要自行建一个servlet

@WebServlet("/transfer")
public class TransferServlet extends HttpServlet {
    
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
		//接受转账的参数
		String out = request.getParameter("out");
		String in =request.getParameter("in");
		String moneyStr =request.getParameter("money");
		double money =Double.parseDouble(moneyStr);
		//调用业务层的转账方法
		TransferService service =new TransferService();
		boolean isTransferSuccess= service.tranfer(out,in,money);
		response.setContentType("text/html;charset=UTF-8");
		if (isTransferSuccess) {
    
    
			response.getWriter().write("转账成功!!!");
		}else {
    
    
			response.getWriter().write("转账失败!!");
		}
	}

	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

第三步:
书写dao层,处理数据库的数据,并反馈给service层

public void out(Connection conn,String out, double money) throws SQLException {
    
    
		QueryRunner queryRunner =new QueryRunner();
		//Connection conn =DataSourceUtils.getConnection();
		String sql ="update account set money =money-? where name=?";
		queryRunner.update(conn,sql,money,out);
		
	}

	public void in(Connection conn,String in, double money) throws SQLException {
    
    
		QueryRunner queryRunner =new QueryRunner();
		//Connection conn =DataSourceUtils.getConnection();
		String sql ="update account set money =money+? where name=?";
		queryRunner.update(conn,sql,money,in);
		
	}

第四步:
书写service层

public boolean tranfer(String out, String in, double money) {
    
    
		//创建dao对象
		TransferDao dao =new TransferDao();
		
		boolean isTranferSuccess =true;
		Connection conn=null;
		try {
    
    
			conn =DataSourceUtils.getConnection();
			conn.setAutoCommit(false);
			
			//转出方法
			dao.out(conn,out,money);
			//转入方法
			dao.in(conn,in,money);
		} catch (Exception e) {
    
    		
			isTranferSuccess =false;	
			//回滚事务
			try {
    
    
				conn.rollback();
			} catch (SQLException e1) {
    
    
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}finally {
    
    
			try {
    
    
				conn.commit();
			} catch (SQLException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		return isTranferSuccess;
	}

六、 升级版:使用ThreadLocal绑定连接资源

因为我们要使用事务,不符合MVC模式。在我们上面的代码中,Service层中,使用了Connection。但是Connection,,是出现在数据库中的。应该使用在dao层。所以我们应该推荐使用ThreadLocal方法。
ThreadLocal方法,运用的是线程的知识,我们将转账这一条线,放在一条线程上完成。

第一步:
书写工具类

public class MyDataSourceUtils {
    
    
	//获取Connection------从连接池中获取
	private static ComboPooledDataSource dataSource =new ComboPooledDataSource();	
	//创建ThreadLocal-------相当于一个map集合
	private static ThreadLocal<Connection> tl =new ThreadLocal<Connection>();
	
	//开启事务
	public static void startTransaction() throws SQLException {
    
    
		Connection connection= getCurrentConnection();
		connection.setAutoCommit(false);
	}
	
	//获得当前线程上绑定的conn
	public static Connection getCurrentConnection() throws SQLException {
    
    
		//从ThreadLocal寻找当前线程是否有对应Connection
		Connection conn =tl.get();
		if (conn ==null) {
    
    
			//获得新的connection
			conn=getConnection();
			//将conn资源绑定到ThreadLocal(map)上
			tl.set(conn);
		}
		return conn;
	}
	
	public static Connection getConnection() throws SQLException {
    
    
		return dataSource.getConnection();
	}
	
	//事务回滚
	public static void rollback() throws SQLException {
    
    
		getCurrentConnection().rollback();
	}
	//事务提交
	public static void commit() throws SQLException {
    
    
		getCurrentConnection().commit();
		//将Connection从ThreadLocal中移除
		tl.remove();
		getCurrentConnection().close();
	}
}

第二步:
修改service层

public class TransferService {
    
    

	public boolean tranfer(String out, String in, double money) {
    
    
		//创建dao对象
		TransferDao dao =new TransferDao();
		
		boolean isTranferSuccess =true;
		Connection conn=null;
		try {
    
    
			//开启事务
			//conn =DataSourceUtils.getConnection();
			//conn.setAutoCommit(false);
			//使用ThreadLocal存储Connection
			MyDataSourceUtils.startTransaction();
			
			//转出方法
			dao.out(out,money);
			//转入方法
			//int i=1/0;
			dao.in(in,money);
		} catch (Exception e) {
    
    
			
			isTranferSuccess =false;	
			//回滚事务
			try {
    
    
				MyDataSourceUtils.rollback();
			} catch (SQLException e1) {
    
    
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}finally {
    
    
			try {
    
    
				MyDataSourceUtils.commit();
			} catch (SQLException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		return isTranferSuccess;
	}

}

第三步:
修改dao层

public class TransferDao {
    
    

	public void out(String out, double money) throws SQLException {
    
    
		QueryRunner queryRunner =new QueryRunner();
		Connection conn =MyDataSourceUtils.getCurrentConnection();
		String sql ="update account set money =money-? where name=?";
		queryRunner.update(conn,sql,money,out);
		
	}

	public void in(String in, double money) throws SQLException {
    
    
		QueryRunner queryRunner =new QueryRunner();
		Connection conn =MyDataSourceUtils.getCurrentConnection();
		String sql ="update account set money =money+? where name=?";
		queryRunner.update(conn,sql,money,in);
		
	}

}

注意ThreadLocal是一个map集合,它的键主要放线程名,值只需要放我们的connection就行了

猜你喜欢

转载自blog.csdn.net/Mr_GYF/article/details/109125561