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;
}
}