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
<body>
<form action="${ pageContext.request.contextPath }/account" method="POST">
    转入账户:<input type="text" name="accountin"><br>
    转出账户:<input type="text" name="accountout"><br>
    金额:<input type="text" name="money"><br>
    <input type="submit" name="提交"><br>
</form>
</body>

//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 在插入和修改时,都会自动更新当前时间
//如果读取时版本字段与修改时版本字段不一致,说明别人进行修改过数据

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 10,245评论 2 59
  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    初来的雨天阅读 8,967评论 0 22
  • 问题:事务是什么,有什么用? 事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不...
    yeller阅读 4,073评论 0 0
  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    JackFrost_fuzhu阅读 12,352评论 4 83
  • 事务的定义 事务由单独单元的一个或多个SQL语句组成,在这个单元中,每个MySQL语句是相互依赖的。而整个单独单元...
    诸葛坚强阅读 4,809评论 0 3

友情链接更多精彩内容