JDBC5 - 事务

事务

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功

数据库开启事务命令

  • start transaction 开启事务
  • Rollback 回滚事务
  • Commit 提交事务


    使用事务
//方式一
//创建表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
//创建事务
start transaction;
update account money=money-500 where id=1;
update account money=money+500 where id=1;
update account money=money+500 where id=1;
rollback;//事务回滚,回滚到最开始的样子
commit;//只有commit后,以上才会操作

//方式二 
//可以查看当前autocommit值,默认“ON”打开状态,任意一条语句都是一个事务
show variable like '%commit%';
//关闭自动事务,需要手动commit
set autocommit = off;
//JDBC方式
public class TransactionTest1 {
    public static void main(String[] args) throws Exception {
        //修改id=2的money
        String sql = "update account set money=100 where id=2";
        Connection con = jdbcUtils.getConnection();
        con.setAutoCommit(false);//开启事务
        
        Statement st = con.createStatement();
        st.executeUpdate(sql);
        con.rollback();//事务回滚
        
        con.commit();//事务提交
        
        st.close();
        con.close();
    }
}

事务特性

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

事务隔离性的设置语句

设置事务隔离性
  • 脏读 dirty read 指一个事务读取了另一个事务未提交的数据
  • 不可重复读 指在一个事务内读取表中的某一行数据,多次读取结果不同(update)
  • 虚读 指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致( insert )
  • 丢失更新 lost update 后提交的事务把先提交的事务给覆盖掉

在MySql中设置事务隔离级别

select @@tx_isolation 查询当前事务隔离几倍
默认级别是 Repeatable read.


查看事务隔离级别

set session transaction isolation level 设置事务隔离级别
比如:set session transaction isolation level read uncommitted;

在jdbc中设置事务隔离级别

调用java.sql.Connection中的方法
void setTransactionIsolation(int level);

  • level取值可以看API,设置的有常量

演示

//演示脏读dirty read,一个事务读到另一个事务未提交的数据

set session transaction isolation level read uncommitted; //设置隔离级别
//在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb'; //先不提交事务

//在B事务中
start transaction;
select * from account; //此时,B事务读取到变化后的数据,就出现了脏读
//当A事务提交前,执行rollback,commit. B事务再查询就会发现,数据恢复原样。
//出现两次查询结果不一致问题,出现了不可重复读。

//解决脏读问题,将事务的隔离级别设置为read committed;
set session transaction isolation level read committed; //设置隔离级别
//此时解决了脏读,但还存在不可重复读,两次读取的数据不一样。A事务commit前和commit后

//解决不可重复读,设置隔离级别为repeatable read;
set session transaction isolation level repeatable read; //设置隔离级别
//当A事务提交后,B事务查询的与上次提交前的结果相同。

//Serializable可以解决所有问题
//该设置可以锁表,A事务未提交前,其它事务无法对此表进行操作。性能较差
转账汇款操作

代码

//jsp

转入账户:
转出账户:
金额:

//ServletAccount.java public class ServletAccount extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); //得到请求参数 String accountIn = request.getParameter("accountin"); String accountOut = request.getParameter("accountout"); double money = Double.parseDouble(request.getParameter("money")); //调用service AccountService service = new AccountService(); try { service.account(accountIn,accountOut,money); response.getWriter().write("转账成功"); } catch (Exception e) { e.printStackTrace(); response.getWriter().write("转账失败"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } //AccountService.java public class AccountService { //汇款方法 public void account(String accountIn, String accountOut, double money) throws Exception { //调用AccountDao中的两个方法 AccountDao dao = new AccountDao(); Connection con = null; try { con = jdbcUtils.getConnection(); //开启事务 con.setAutoCommit(false); dao.accountIn(con,accountIn,money); dao.accountOut(con,accountOut,money); } catch (Exception e) { e.printStackTrace(); //出现问题,进行事务回滚 if(con!=null){ try { con.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } throw e; }finally{ //事务提交 if(con!=null){ try { con.commit(); con.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } //AccountDao.java public class AccountDao { //转入 public void accountIn(Connection con, String accountIn, double money) throws SQLException, AccountException { String sql = "update account set money=money+? where name=?"; PreparedStatement pst = con.prepareStatement(sql); pst.setDouble(1, money); pst.setString(2, accountIn); int row = pst.executeUpdate(); if(row == 0){ throw new AccountException("转入失败"); } pst.close(); } //转出 public void accountOut(Connection con, String accountOut, double money) throws SQLException, AccountException { String sql = "update account set money=money-? where name=?"; PreparedStatement pst = con.prepareStatement(sql); pst.setDouble(1, money); pst.setString(2, accountOut); int row = pst.executeUpdate(); if(row == 0){ throw new AccountException("转出失败"); } pst.close(); } } //自定义异常 AccountException.java public class AccountException extends Exception{ public AccountException() { super(); } public AccountException(String message, Throwable cause) { super(message, cause); } public AccountException(String message) { super(message); } public AccountException(Throwable cause) { super(cause); } }

用ThreadLocal解决问题

如何在另一个类中的两个方法中共享另一个类中的Connection,比如

public interface AccountDao{
//这两个方法都需要使用同一个Connection对象,需要从调用方法的地方传递,
//但没有传参,此时就要用ThreadLocal来解决
  public void accountOut(String accountOut, double money) throws Exception;
  public void accountIn(String accountInt, double monye) throws Exception;
}

丢失更新

丢失更新问题

解决方案详解:

  • 悲观锁(假设丢失更新一定会发生)
    • 利用数据库内部锁机制,管理事务
    • 允许一张数据表添加多个共享锁,只能添加一个排他锁
    • 事务在修改记录过程中,锁定记录,别的事务无法并发修改
    • update 语句默认添加排他锁
  • 乐观锁(假设丢失更新不会发生)
    • 采用程序中添加版本字段解决丢失更新问题
    • timestamp 时间戳自动更新
create table product(
      id int,
      name varchar(20),
      updatetime timestamp; //时间戳
);
// timastamp 在插入和修改时,都会自动更新当前时间
//如果读取时版本字段与修改时版本字段不一致,说明别人进行修改过数据

你可能感兴趣的:(JDBC5 - 事务)