八、事务

事务,一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元。这些单元要么全都成功,要么全都不成功

事务在开发中的作用
下面我们来举例说明什么是事务,如下所示:
现实生活中的银行转账业务,张三要给李四转账1000元,而在程序员眼中两条SQL语句就可以搞定,如下:
给张三的账户减去1000元;
给李四的账户加上1000元;
如果在转账的业务中,成功的将张三的账户减去1000元,而在给李四的账户加1000元的时候,程序出现了问题,李四的账户没有加上1000元,而张三的账户却减掉了1000元,在现实生活中,这种情况肯定是不允许存在的。当我们将这个转账业务放在一个事务中,就不会出现以上情况了。
事务中有多个操作,这些操作要么全部成功,要么全部失败,也就是说给张三的账户减去1000元如果成功了,那么给李四的账户加上1000元的操作也必须是成功的,否则给张三减去1000元,以及给李四加上1000元都必须是失败的。

一、 mysql中的事务

mysql中默认事务处理
在mysql登录的情况下执行以下命令
show variables like '%commit%'; ---- autocommint 值是 on,说明开启自动提交

mysql中默认事务处理.png

mysql数据库默认是开启事务的,一条sql一个事务.
oracle它默认情况下autocommit是off,就需要手动提交事务.

关闭mysql的自动事务处理命令
set autocommit = off;( set autocommit = 0)
如果设置autocommit 为 off,意味着以后每条SQL 都会处于同一个事务中,相当于第一条SQL执行前执行了 start transaction

mysql中手动事务处理
start transaction:-- 开启事务 一旦手动开启了事务,事务自动提交失效.
commit;-- 提交事务
rollback;-- 事务回滚

二、jdbc中的事务

jdbc中事务处理api
java.sql.Connection接口中提供了关于事务操作的API
setAutoCommit(boolean flag);`参数为false相当于start transaction
commit(); 事务提交
rollback(); 事务回滚
回滚(Rollback)指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为

回滚点介绍
Savepoint setSavepoint(String name)
在当前事务中创建一个具有给定名称的保存点,并返回表示它的新 Savepoint 对象。
例如:
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);

三、事务的特性

事务的四大特性是面试官经常问的问题,简称ACID(Atomicity Consistency Isolation Durability),分别是:
原子性:原子性对应的英文是Atomicity,即表示事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败;
一致性:一致性对应的英文是Consistency,事务执行后,数据库状态与其它业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的;
隔离性:隔离性对应的英文是Isolation,是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰;
持久性:持久性对应的英文是Durability,指的是一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

不同的事务,其一致性的表现形式是不同的,事务的其他三大特性其实都是为了事务的一致性服务的。

四、事务的隔离级别与问题

不考虑隔离性产生的问题介绍

  • 脏读 一个事务读取到了另一个事务未提交数据.
  • 不可重复读 一个事务内,两次读取到的数据不一致.(update)
  • 虚读(幻读) 两次读取的数据不一致(insert)

事务的4种隔离级别介绍
数据库内部定义了四种隔离级别,用于解决三种隔离问题

  1. Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
  2. Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
  3. Read committed:可避免脏读情况发生(读已提交)
  4. Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
    mysql数据库默认的事务隔离级别-----repeatable read级别.
    oracle数据默认的事务隔离级别 ----read committed

设置事务隔离级别

  • mysql中设置
    数据库默认有事务的隔离级别,mysql 中查看与修改事务的隔离级别
    set session transaction isolation level 隔离级别;设置事务隔离级别
    select @@tx_isolation; 查询当前事务隔离级别

  • jdbc中设置事务隔离级别
    在java.sql.Connection接口中提供
    setTransactionIsolation(int level) ;
    参数可以取 Connection 常量之一:
    Connection.TRANSACTION_READ_UNCOMMITTED、
    Connection.TRANSACTION_READ_COMMITTED、
    Connection.TRANSACTION_REPEATABLE_READ
    Connection.TRANSACTION_SERIALIZABLE。
    (注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)

安全性:serializable > repeatable read > read committed > read uncommitted
效率 :serializable< repeatable read < read committed < read uncommitted

在开发中,一般使用 read committed、repeatable read两种。
MYSQL :repeatable read
Oracle :read committed

脏读分析与解决
脏读:一个事务读取到另一个事务未提交数据.
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
select @@tx_isolation
步骤三:设置A窗口的隔离级别为read uncommitted
set session transaction isolation level read uncommitted;
步骤四:在两个窗口中分别开启事务:
start transaction;
步骤五:在B窗口中转账操作:
update account set money = money - 1000 where name='守义';
update account set money = money + 1000 where name='凤儿';
在B窗口中没有提交事务的!!!
步骤六:在A窗口中进行查询:
已经转账成功!!!(脏读:一个事务中读到了另一个事务未提交的数据)

解决脏读 设置事务的隔离级别为 read committed

不可重复读分析与解决
步骤一:分别开启两个窗口A,B:
步骤二:分别查询两个窗口的隔离级别:
select @@tx_isolation
步骤三:设置A窗口的隔离级别为read committed;
set session transaction isolation level read committed;
步骤四:在两个窗口中分别开启事务:
start transaction;
步骤五:在B窗口中完成转账的操作:
update account set money = money - 1000 where name='守义';
update account set money = money + 1000 where name='凤儿';
在B窗口中没有提交事务!!!
步骤六:在A窗口中进行查询:
没有转账的结果!!!(已经避免了脏读)
步骤七:在B窗口中提交事务!!!
commit;
步骤八:在A窗口中进行查询:
转账成功!!!(不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致一次事务中多次查询结果不一致.)

解决不可重复读 设置事务的隔离级别为 Repeatable read

虚读分析
它主要强调的是多次查询的结果的条不一样,而不可重复读强调的是结果不一样。

串行化
设置隔离级别为 Serializable

五、JDBC的隔离级别的设置

Connection中的方法:


Connection中的方法.png

Connection中提供了隔离级别的常量:


Connection中提供了隔离级别的常量.png

使用DBUtils的进行事务的管理:

QueryRunner:
    * 构造:
    QueryRunner();
    QueryRunner(DataSource ds);

    * 方法:
    T query(String sql,ResultSetHanlder<T> rsh,Object... params);
    T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
    int update(String sql,Object... params);
    int update(Connection conn,String sql,Object... params);

方法分类:
    * 没有事务:
    QueryRunner(DataSource ds);
    T query(String sql,ResultSetHanlder<T> rsh,Object... params);
    int update(String sql,Object... params);

    * 有事务:
    QueryRunner();
    T query(Connection conn,String sql,ResultSetHanlder<T> rsh,Object... params);
    int update(Connection conn,String sql,Object... params);

六、ThreadLocal

public class ThreadLocal<T>extends Object
该类提供了线程局部 (thread-local) 变量。
这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。
ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
它的底层是使用了一个Map集合
Map<Thread,Object>
它的key就是当前的线程对象.
set(Object obj) 它就相当于 map.put(Thread.currentThread(),obj);
get()它就相当于 map.get(Thread.currentThread()));

七、转账案例

设计转账的页面:

<h1>转账的页面</h1>
    <form action="" method="post">
        <table border="1" width="400">
            <tr>
                <td>付款人:</td>
                <td><input type="text" name="from"/></td>
            </tr>
            <tr>
                <td>收款人:</td>
                <td><input type="text" name="to"/></td>
            </tr>
            <tr>
                <td>转账金额:</td>
                <td><input type="text" name="money"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="转账"/></td>
            </tr>
        </table>
    </form>

Servlet:

public class AccountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /**
         * 1.接收数据:
         * 2.封装数据:
         * 3.调用业务层:
         * 4.页面跳转作出响应:
         */
        request.setCharacterEncoding("UTF-8");
        // 接收数据:
        String from = request.getParameter("from");
        String to = request.getParameter("to");
        double money = Double.parseDouble(request.getParameter("money"));
        // 调用业务层:
        AccountService accountService = new AccountService();
        accountService.transfer(from,to,money);
        
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }


}

Service:

public class AccountService {

    /**
     * 业务层转账的方法:
     * @param from  :付款人
     * @param to    :收款人
     * @param money :转账金额
     */
    public void transfer(String from, String to, double money) {
        // 调用DAO:
        AccountDao accountDao = new AccountDao();
        try {
            accountDao.outMoney(from, money);
            // int d = 1/0;
            accountDao.inMoney(to, money);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
    }
    
}

DAO:

public class AccountDao {

    /**
     * 付款的方法
     * @param name
     * @param money
     * @throws SQLException 
     */
    public void outMoney(String name,double money) throws SQLException{
        Connection conn = null;
        PreparedStatement pstmt = null;
        try{
            // 获得连接:
            conn = JDBCUtils.getConnection();
            // 编写一个SQL:
            String sql = "update account set money = money-? where name=?";
            // 预编译SQL:
            pstmt = conn.prepareStatement(sql);
            // 设置参数:
            pstmt.setDouble(1, money);
            pstmt.setString(2, name);
            // 执行SQL:
            pstmt.executeUpdate();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            pstmt.close();
            conn.close();
        }
    }
    
    /**
     * 收款的方法
     * @param name
     * @param money
     * @throws SQLException 
     */
    public void inMoney(String name,double money) throws SQLException{
        Connection conn = null;
        PreparedStatement pstmt = null;
        try{
            // 获得连接:
            conn = JDBCUtils.getConnection();
            // 编写一个SQL:
            String sql = "update account set money = money+? where name=?";
            // 预编译SQL:
            pstmt = conn.prepareStatement(sql);
            // 设置参数:
            pstmt.setDouble(1, money);
            pstmt.setString(2, name);
            // 执行SQL:
            pstmt.executeUpdate();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            pstmt.close();
            conn.close();
        }       
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 一、事务 1、事务四要素:ACID 对于事务,我之前的理解是很粗糙的,不就是为了保证操作的原子性么?一般订单系统或...
    张伟科阅读 1,230评论 0 5
  • 一、什么是事务 逻辑上的一组操作, ,要不全部失败,要不全部成功。 MySql的事务管理 在事务管理中执行sql,...
    明天你好向前奔跑阅读 770评论 0 4
  • 人生中有很多第一次,我的也是。 以前,每当面临第一次的时候,会有些许慌张失措,或害羞,或不知所云。 这一次,似乎有...
    Leohunter阅读 198评论 0 0
  • 程少不再抱怨了,他说生活不是原来那样的,拼搏奋斗也好,及时行乐也罢,关键是要对得起自己。偶尔佛系一下,未必是坏事,...
    云水烟波阅读 125评论 0 1
  • 言午木要结婚了。 在准备去参加婚礼的路上,林希不止一遍在想,为什么要去言午木的婚礼呢?明明她最美好的时间全部都耗在...
    爱吃肉的木头阅读 157评论 0 0