JDBC高级开发事务

1.事务的引入

1.1 转账业务
案例分析:之前给大家介绍过三层开发,相信大家对三层有了基本的了解,接下来我们就使用三层的开发模式来完成转账的案例1 首先在这里,我们web层就直接通过main方法,直接填充数据,调用service层的业务逻辑。2 而service业务逻辑层主要完成转账的核心业务逻辑操作,包括转出和转入的2个核心操作。3 很明显,转入和转出是和数据库打交道的,所以需要将转入和转出这2个操作放在dao层。大体分工如下图所示:
在这里插入图片描述

1.2 代码实现
(1)准备数据

create table account(
 id int primary key auto_increment,
 name varchar(20),
 money double
);

insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
 <default-config>
  <property name="driverClass">com.mysql.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
     <property name="user">root</property>
     <property name="password">root</property>
 </default-config>
</c3p0-config>

(2)dao层

public class AccountDao {
 
 /**
  * 在开发中,dao层的异常一般都抛,在service层抓
  * @param outUser 付款人
  * @param money 付款金额
  * @throws SQLException
  */
 public void outMoney(String outUser,int money) throws SQLException{
  //1.创建QueryRunner对象
  QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
  //2.执行sql语句
  String sql = "update account set money = money-? where name=?";
  queryRunner.update(sql, money,outUser);
 }
 
 /**
  * 收款操作
  * @param inUser 收款人的姓名
  * @param money  收款金额
  * @throws SQLException
  */
 public void inMoney(String inUser,int money) throws SQLException{
  //首先创建QueryRunner对象
  QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
  //执行sql语句
  String sql = "update account set money = money+? where name=?";
  queryRunner.update(sql, money,inUser);
 }
}

(3)service层

public class AccountService {
 /**
  * 注意:我们需要保证service层的connection对象和dao层的是同一个.
  * dao层不能关闭connection对象,connection对象的关闭放在servcie层中
  * @param outUser
  * @param inUser
  * @param money
  */
 public void transfer(String outUser,String inUser,int money){
  AccountDao accountDao = new AccountDao();
  try {
   //付款
   accountDao.outMoney(outUser, money);
   //收款
   accountDao.inMoney(inUser, money);
   con.commit();
   System.out.println("转账成功");
  } catch (SQLException e) {
   System.out.println("转账失败");
   e.printStackTrace();
  }
 }
}

(4)web层

public class WebTest {
 public static void main(String[] args) {
  String outUser = "jack";
  String inUser = "rose";
  int money = 100;
  //直接调用service层的方法完成转账的操作
  AccountService accountService = new AccountService();
  accountService.transfer(outUser, inUser, money);
 }
}

思考:在上面的案例中,在业务层里面,假设在一个人付款成功,另一个人还没有收款的时候,中间有其他业务操作的话,出现了异常,也就是在付款和收款之间加上如下图所示代码,模拟转账的异常。
在这里插入图片描述
那么这时候很明显,程序终止,而后面收款的操作并不会去做,那么另一个人就不会收到钱,而前一个人确确实实转账了。所以最终得出。上面的转账业务逻辑是存在bug的。那么针对这样的一个bug我们该如何去处理呢?

针对上面出现的问题的分析:上面的bug,因为异常的原因,导致了我们的一个完整的业务逻辑被中断了。所以导致只执行了一条sql语句,后一条sql语句没有去执行。所以,我们要保证,我们一个完整的业务逻辑要么全部跑完,如果中间出现异常的话,为了保证最终的结果符合我们实际生活的一个情况,所以我们需要撤销与当前业务逻辑有关的操作。所以这就需要学习接来下的事务来保证这样一个情况。

2.事务概述

事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
事务作用:保证在一个事务中多次操作要么全都成功,要么全都失败.

事务的出现 解决上面的问题。
1.事务是如何处理正常情况的呢?
在这里插入图片描述
2.事务是如何处理异常情况的呢?
在这里插入图片描述

2.1 mysql事务操作
在这里插入图片描述
MYSQL中可以有两种方式进行事务的管理:

  • 自动提交:Mysql默认自动提交,执行一条sql语句可提交一次事务
  • 手动提交:先开启,再提交

方式1:手动提交

-- 开启事务
start transaction;
update account set money = 100;
select * from account;
-- 提交事务
commit;
-- 事务一旦提交就不能回滚了。
rollback;
start transaction;
update account set money = 100;
-- 回滚事务
rollback;
select * from account;

方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
show variables like ‘%commit%’;
在这里插入图片描述

  • 设置自动提交的参数为OFF:set autocommit = 0; – 0:OFF 1:ON

扩展:Oracle数据库事务不自动提交

2.2 JDBC事务操作
在这里插入图片描述
注意:在jdbc事务操作用,事务的控制都是通过Connection对象完成的,当一个完整的业务操作前,我们首先使用connection.setAutoCommit(false)来开启事务,当业务操作完成之后,我们需要使用connection.commit()来提交事务。当然了,如果出现了异常,我们需要撤销所有的操作,所以出现异常,需要进行事务的回滚。

2.3 DBUtils事务操作
在这里插入图片描述
过程与jdbc操作一样,之不过需要注意的是,connection必须手动控制,不能交给DBUtils去控制。

2.4 事务管理:传递Connection
修改service和dao,service将connection传递给dao,dao不需要自己获得连接
在这里插入图片描述
很明显,我们的事务是针对整个业务逻辑的。所以事务的管理应该在业务层进行管理。并且,需要注意的是,我们在整个事务的控制过程中应该是一个connection对象。所以,在业务层我们需要拿到connection对象,用connection对象对业务层进行事务处理。而既然在业务层获取了connection对象,那么就必须要保证我们在dao层拿到的connection对象和业务层拿到的connection对象是同一个,那么这里我们可以使用传递参数的方式将connection对象传递给dao层。
所以dao层需要修改,connection对象需要从业务层传递过来。并且需要注意的是,connection对象是不能在dao层关闭的,因为我们业务层还需要继续使用connection对象。所以dao层代码的修改如下:

dao层:

public class AccountDao {
 
 public void outMoney(Connection con,String outUser,int money) throws SQLException{
  PreparedStatement prepareStatement = con.prepareStatement("update account set money=money-? where name=?");
  prepareStatement.setInt(1, money);
  prepareStatement.setString(2,outUser);
  prepareStatement.executeUpdate();
  JdbcUtils.release(null, null , prepareStatement);
 }
 
 public void inMoney(Connection con,String inUser,int money) throws SQLException{
  PreparedStatement prepareStatement = con.prepareStatement("update account set money=money-? where name=?");
  prepareStatement.setInt(1, money);
  prepareStatement.setString(2,inUser);
  prepareStatement.executeUpdate();
  JdbcUtils.release(null, null , prepareStatement);
 }
}

通过ThreadLocal来存储connection

public class JdbcUtils {
 //创建一个数据库连接池
 private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 //创建一个threadLocal对象
 public static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
 
 public static DataSource getDataSource(){
  return dataSource;
 }
 //连接的获取从数据库连接池获取
 public static Connection getConnection(){
  Connection con = null;
  try {
   //connection对象先从local中获取
   con = local.get();
   if(con == null){
    //表示第一次在当前线程中获取connection对象。第一次获取连接需要从数据库连接池拿。
    con = dataSource.getConnection();
    local.set(con);
   }
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return con;
 }
 }

猜你喜欢

转载自blog.csdn.net/qq_24099547/article/details/84928205