事务

1什么是事务

一件事情有n个组成单元,要么这n个组成单元同时成功,要么n个单元就同时失败,

就是将n个组成单元放到一个事务中。

银行转账例子:

表名account

现在,test1test2借钱4000

银行转账:

第一步: update  account  set 存款=存款-4000  where id=2

第一步做完,出现异常,则下面代码不执行:

第二步: update  account  set 存款=存款+4000  where id=1

如果没有事务,就会发生test2减少了4000块,test1也没有收到4000块。4000块不翼而飞了。

2 mysql的事务

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

手动事务:

1)显示开启事务:start transaction

2)事务提交:commit代表从开启事务到事务提交,中间的所有的sql都认为有效,真正的更新数据库

3)事务回滚:rollback 代表事务的回滚,从开启事务到事务回滚中间的所有的sql操作都认为无效,数据库没有被更新

Tips:MySQL存储引擎:

主要存储引擎:MyISAM、InnoDB、MEMORY和MERGE

MySQL5.5以后默认使用InnoDB存储引擎,其中InnoDB和BDB提供事务安全表,其它存储引擎都是非事务安全表。
若要修改默认引擎,可以修改配置文件中的default-storage-engine。

命令show variables like 'default_storage_engine'; 查看当前数据库的默认引擎。

命令show engines和show variables like 'have%'; 可以列出当前数据库所支持的引擎。

所以,MySQL要安装5.5以后的版本才能使用事务。

例:

第一次查出的结果,没有真正进入数据库,只是在当前事务的缓存中。

3 JDBC事务操作

默认是自动事务:

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

通过jdbcAPI手动事务:

  开启事务:conn.setAutoComnmit(false);

  提交事务:conn.commit();

  回滚事务:conn.rollback();

注意:控制事务的connnection必须是同一个

  执行sqlconnection与开启事务的connnection必须是同一个才能对事务进行控制

例:

数据库中数据:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCdemo {    
    public static void main(String[] args){
        Connection conn=null;
        try{        
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获得连接对象
        String url="jdbc:mysql://localhost:3306/market0929?useUnicode=true&characterEncoding=UTF-8";
        String username="root";
        String password="123456";
        conn=DriverManager.getConnection(url,username,password);
        //3.获得语句执行平台
        Statement sta=conn.createStatement();
        //4.开启事务
        conn.setAutoCommit(false);
        //5.Sql语句
        String sql="UPDATE account SET money=money-2000 WHERE aid=1";
        sta.executeUpdate(sql);            
        }catch(Exception ex){
            //7.回滚事务
            try {
                conn.rollback();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ex.printStackTrace();
        }finally{
            //6.提交事务
            try {
                conn.commit();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }    
        }
    }
}

加一条错误代码:

数据没有被继续减少:

 

4 DBUtils事务操作

有参构造:QueryRunner runner = new QueryRunner(DataSource dataSource);

使用无Connection参数的方法操作数据库

无参构造:QueryRunner runner = new QueryRunner();

使用有Connection参数的方法操作数据库

5 ThreadLocal

ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

Tips

因为是单线程程序,可以让线程携带着连接对象,

这样就可以使service层和dao层用的是一个Connection对象了,

在这里,ThreadLocal 类似一个mapkey是线程名,值是Connection对象。 

6实例:建一个web项目Transfer实现转帐功能

transfer.jsp

<form action="${pageContext.request.contextPath}/TransferServlet" 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>

TransferServlet

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        //获取转出、转入账户以及金额
        String out=request.getParameter("out");
        String in=request.getParameter("in");
        String moneyStr=request.getParameter("money");
        double money=Double.parseDouble(moneyStr); //字符串转换成数字
        //调用Service层方法
        boolean flag=accountService.transfer(out, in, money);
        response.setContentType("text/html;charset=utf-8");
        if(flag){
            response.getWriter().write("转账成功!");
        }else{
            response.getWriter().write("转账失败!");    
        }
    }

DBUtils工具类:

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;

public class DBUtils {
    public static final String DRIVER = "com.mysql.jdbc.Driver";
    public static final String URL = "jdbc:mysql://127.0.0.1:3306/market0929?useUnicode=true&characterEncoding=UTF-8";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "123456";
    /*
     * 创建连接池BasicDataSource
     */
    public static BasicDataSource dataSource = new BasicDataSource();
    //创建ThreadLocal对象
    private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
    
    //静态代码块
    static {
        //对连接池对象 进行基本的配置
        dataSource.setDriverClassName(DRIVER); // 这是要连接的数据库的驱动
        dataSource.setUrl(URL); //指定要连接的数据库地址
        dataSource.setUsername(USERNAME); //指定要连接数据的用户名
        dataSource.setPassword(PASSWORD); //指定要连接数据的密码
    }
    /*
     * 返回连接池对象
     */
    public static DataSource getDataSource(){
        return dataSource;
    }
    //从连接池中取一个连接对象
    public static Connection getConn(){
        Connection conn=null;
        try {
            conn=dataSource.getConnection();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn;
    }
    
    //获取当前线程上绑定的Connection对象
    public static Connection getCurrentConn(){
        //获取线程绑定的conn对象
        Connection conn=tl.get();
        //判断线程是否有绑定的对象
        if(conn==null){
            conn=getConn();
            tl.set(conn);
        }
        return conn;
    }
    
    //开启事务
    public static void startTranscation(){
        //获取当前线程绑定的conn对象
        Connection conn=getCurrentConn();
        //开启事务
        try {
            conn.setAutoCommit(false);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    //提交事务
    public static void commit(){
        //获取当前线程绑定的conn对象
        Connection conn=getCurrentConn();
        //提交事务
        try {
            conn.commit();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    //回滚事务
    public static void rollback(){
        //获取当前线程绑定的conn对象
        Connection conn=getCurrentConn();
        //回滚事务
        try {
            conn.rollback();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }    
}

AccountDao

import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import com.oracle.tools.DBUtils;

public class AccountDao {
    //转出
    public void substractMoney(String out,double money) throws SQLException{
        Connection conn=DBUtils.getCurrentConn();
        QueryRunner qr=new QueryRunner();
        String sql="update account set money=money-? where aname=?";
        qr.update(conn,sql,money,out);
    }
    //转入
    public void addMoney(String in,double money) throws SQLException{
        Connection conn=DBUtils.getCurrentConn();
        QueryRunner qr=new QueryRunner();
        String sql="update account set money=money+? where aname=?";
        qr.update(conn,sql,money,in);
    }
}

AccountService

import com.oracle.dao.AccountDao;
import com.oracle.tools.DBUtils;

public class AccountService {
    private AccountDao accountDao=new AccountDao();
    //转账
    public boolean transfer(String out,String in,double money){        
        boolean flag=true;    
        try {
            //开启事务
            DBUtils.startTranscation();
            accountDao.substractMoney(out, money);
            accountDao.addMoney(in, money);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            flag=false;
            //事务回滚
            DBUtils.rollback();
            e.printStackTrace();
        }finally{
            //事务提交
            DBUtils.commit();
        }        
        return flag;
    }
}

加一条错误代码:

注意:

1)Dao层只写事务的组成单元

2Service层不能出现Connection,因为与数据库的相关操作都要放在dao

3)把事务开启、提交、回滚全部封装在工具类中,在Service层直接调用方法,就不会出现Connection相关内容了。

4)写好工具类后,daoQueryRunner runner的就要用无参构造了,Connection参数从工具类中获取当前线程上的Connection对象

6事务的特性

原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 

一致性(Consistency):一个事务中,事务前后数据的完整性必须保持一致。

隔离性(Isolation):多个事务,事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。(用同步代码块实现)

持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

原文地址:https://www.cnblogs.com/qq1312583369/p/11187000.html