JDBC事务

一、事务的概念:

    事务指的是在逻辑上的一个整体,在这整体中,所有相关的操作要么全部执行成功,要么全部失败!

        转账:在你老爸的卡上减掉一部分金额(就是一条SQL语句);还得在你的卡上增强一部分金额(也是一条SQL语句) 当你老爸自己卡上的钱扣掉了,突然ATM机断电了,但是你卡上的每增加!

            如果使用了事务,一旦你老爸卡上的钱被扣,然后你卡上的钱没有发生变化,这整个操作应该都是失败的!

    事务的作用:能够保证在同一个事务中,数据的完整性(操作前和操作后)

        转账:操作前:你卡上的金额(0)+你老爸卡上的金额(100000)=100000 转账失败!

            操作后:你卡上的金额(10000)+你老爸卡上的金额(90000)=100000 转账成功!

 

二、MySQL的事务操作

    开启事务:start transaction;

        执行删除操作: delete from product; //一条记录一条记录的删除,如果主键是自动增长的,那么主键记录不会清零。

        

    回滚事务:rollback; //一旦执行这个操作,说明我们希望整个执行操作全部失败 ,事务可以保证数据的完整性。

 

    提交事务:commit;//一旦执行这个操作,说明我们希望整个操作全部成功,调用commit可以让之前的操作生效。

 

    truncate table:即使使用了事务,删除的数据也无法找回;先把整张表摧毁,然后创建一张结构一模一样的表,那么它的主键记录清零。

三、JDBC的事务操作

    JDBC的事务操作全都依靠Connection对象!

    他

    开启事务:

        JDBC默认的是自动提交事务,所有我们需要设置为手动提交;connection.setAutoCommit(false);//设置手动提交

        

        执行相关操作:

            你老爸卡上的金额-10000;

            你卡上的金额+10000;

        然后才提交事务:connection.commit();

        

    一旦中间出现问题,那么整个操作应该都算失败的!

        回滚操作:connection.rollback();

        

    在JDBC代码中应该如何体现呢?

        

        @Test

        public void test(){

            Connection conn = JDBCUtils.getConnection();

            //1.设置手动提交

            conn.setAutoCommit(false);

            //2.编写SQL语句

            String sql = "update account set money=money-10000";

            //3.获得执行SQL语句对象

            PreparedStatement pstmt = conn.prepareStatement(sql);

            //4.执行SQL语句

            try{

                int rows = pstmt.executeUpdate();

                ……

                //5.提交事务

                conn.commit();

            }catch(){

                ……

                //6.回滚

                conn.rollback();

            }finally{

                //7.关闭资源

            }

        }

    

四、使用JDBC的事务操作完成转账功能

    第一步:导入原型(account.jsp)

    第二步:创建数据库(表)并初始化数据

    第三步:寻找程序的入口!(account.jsp的表单,书写action的请求路径)

    第四步:书写AccountServlet(获得表单提交的三个参数,调用service层转账方法)

    第五步:书写service层(获得Connection对象,完成JDBC事务操作)

    第六步:书写dao层(2个方法,付款和收款)

代码    

    

    倒入原型和程序的入口

    <%@ page language="java" contentType="text/html; charset=utf-8"

pageEncoding="utf-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>转账案例</title>

</head>

<body>

    <!-- 客户端路径:使用的是相对路径,带有/,相对当前主机。 /day19_JDBC_245/AccountServlet -->

    <form action="${pageContext.request.contextPath}/AccountServlet" method="post">

        <table border="1" width="50%" align="center">

            <tr>

                <td>收款人</td>

                <td><input type="text" name="to"/></td>

            </tr>

            <tr>

                <td>付款人</td>

                <td><input type="text" name="from"/></td>

            </tr>

            <tr>

                <td>付款金额</td>

                <td><input type="text" name="money"/></td>

            </tr>

            <tr>

                <td></td>

                <td><input type="submit" value="转账"/></td>

            </tr>

        </table>

    </form>

</body>

</html>

    WEB层

    public class AccountServlet extends HttpServlet {

 

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");

        request.setCharacterEncoding("utf-8");

        

        String form = request.getParameter("from");

        String to = request.getParameter("to");

        double money = Double.parseDouble(request.getParameter("money"));

        

        AccountService service = new AccountService();

        try {

            service.transfer(form,to,money);

        } catch (Exception e) {

            // TODO: handle exception

        }

        

        

    }

 

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        doGet(request, response);

    }

 

}

    Service

public void transfer1(String form, String to, double money) {

        Connection conn = C3P0Utils.getConnnection();

        try {

            conn.setAutoCommit(false);

            AccountDao dao = new AccountDao();

            dao.transferFrom(form,money,conn);

            

            dao.transferTo(to,money,conn);

            conn.commit();

            

            

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            try {

                conn.rollback();

            } catch (SQLException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            }

        }

    }

    Dao

/**

* 付款方法:JDBC方式

* @param from 付款人

* @param money 付款金额

* @param conn 连接对象

*/

public void transferFrom(String from, double money, Connection conn) {

//1.编写SQL语句

String sql = "update tbl_account set money=money-? where username=?";

try {

//2.获得执行SQL语句对象

PreparedStatement pstmt = conn.prepareStatement(sql);

//3.设置实际参数

pstmt.setDouble(1, money);

pstmt.setString(2, from);

//4.执行更新操作

pstmt.executeUpdate();

} catch (Exception e) {

throw new RuntimeException(e);

}

}

 

/**

* 收款方法:JDBC方式

* @param to 收款人

* @param money 收款金额

* @param conn 连接对象

*/

public void transferTo(String to, double money, Connection conn) {

//1.编写SQL语句

String sql = "update tbl_account set money=money+? where username=?";

try {

//2.获得执行SQL语句对象

PreparedStatement pstmt = conn.prepareStatement(sql);

//3.设置实际参数

pstmt.setDouble(1, money);

pstmt.setString(2, to);

//4.执行更新操作

pstmt.executeUpdate();

} catch (Exception e) {

throw new RuntimeException(e);

}

}

 

}

    效果演示:(展示的不美观? 美观是美工的事情)

    数据库结果:

                    

五、DBUtils的事务操作

    DBUtils是对JDBC的一个封装,主要是简化JDBC的代码书写。这意味DBUtils操作事务也是依赖Connection对象。

 

    回顾DBUtils的操作

        //1.获得QueryRunner核心对象

        QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); //C3P0Utils.getDataSource()主要是获得Connection

        //2.编写SQL语句

        String sql = "update tbl_account set money=money-? where username=?";

        //3.设置实际参数

        Object[] params = {money,from};

        //4.执行更新操作

        qr.update(sql,params);    

//现在要使用事务进行操作,那么必须保证service层得到的Connection与Dao里面的Connection是同一个!!!!我们使用之前的方式以方法的参数传递进来!那么代码可以这么改写:

        //1.获得QueryRunner核心对象

        QueryRunner qr = new QueryRunner();//Connection对象是service层传递过来的!

        //2.编写SQL语句

        String sql = "update tbl_account set money=money-? where username=?";

        //3.设置实际参数

        Object[] params = {money,from};

        //4.执行更新操作

        qr.update(conn,sql,params);    

 

    代码:

        和JDBC的区别只有Dao层

        /**

* 付款方法:DBUtils方式

* @param from 付款人

* @param money 付款金额

* @param conn 连接对象

*/

public void transferFrom(String from, double money, Connection conn) {

//1.获得QueryRunner核心对象(不能使用数据源获得Connection对象,否则与service层不是同一个)

QueryRunner qr = new QueryRunner();

//2.编写SQL语句

String sql = "update tbl_account set money=money-? where username=?";

//3.设置实际参数

Object[] params = {money,from};

//4.执行更新操作

try {

qr.update(conn, sql, params);

} catch (Exception e) {

throw new RuntimeException(e);

}

 

}

 

/**

* 收款方法:DBUtils方式

* @param to 收款人

* @param money 收款金额

* @param conn 连接对象

*/

public void transferTo(String to, double money, Connection conn) {

//1.获得QueryRunner核心对象(不能使用数据源获得Connection对象,否则与service层不是同一个)

QueryRunner qr = new QueryRunner();

//2.编写SQL语句

String sql = "update tbl_account set money=money+? where username=?";

//3.设置实际参数

Object[] params = {money,to};

//4.执行更新操作

try {

qr.update(conn, sql, params);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

 

      

    

六、使用ThearLocal改善方法传递Connection对象

    从之前的实现,我们发现每次要想保证service层与dao层的connection是同一个对象,就必须在service层调用dao层方法的时候以方法参数的形式进行传递!这种处理方式不太好,我们想到了一个对象ThreadLocal!

    

    之前我们学习了一个对象Map(key,value);TheadLocal对于Map内部的存储结构一样!

        Map设置值:map.put(key,value);

        Map获取值:map.get(key);

      

        ThrealLocal设置值:local.set(key,value); //key就是当前线程对象,不需要我们去维护,有ThreadLocal自己维护

        ThrealLocal获取值:local.get(key);

但是他们之间有点区别:

        Map的key需要我们去维护,而ThreadLocal的key不用我们维护,这个对象自己负责维护,而且能够保证key是唯一的!那意味着调用对应的方法是:

            ThrealLocal设置值:local.set(value);

            ThrealLocal获取值:local.get();

        

        ThreadLocal的key是什么呢?它就是当前线程对象!这个ThreadLocal就好比是咱们进入超市存放东西的柜子!

            现在有A线程 类似这个条形码 张三去存包 买完东西取包

            现在有B线程 类似这个条形码 班长去存包 买完东西取包

 

    代码:主要service层 dao层 C3P0Utils不一样

    改写C3P0Utils

        public class C3P0Utils {

private static DataSource dataSource;

static{

    dataSource=new ComboPooledDataSource("oracle");

}

private static ThreadLocal<Connection> local = new ThreadLocal<>();

 

public static Connection getConnectionForThreadLocal(){

    Connection conn = local.get();

    if (conn==null) {

            try {

                conn = dataSource.getConnection();

                local.set(conn);

                return local.get();

            } catch (SQLException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

        return conn;

      

}

 

 

}

Service

 

    public void transfer(String form, String to, double money) {

        Connection conn = C3P0Utils.getConnectionForThreadLocal();

        try {

            conn.setAutoCommit(false);

            AccountDao dao = new AccountDao();

            dao.transferFrom(form,money);

            

            dao.transferTo(to,money);

            conn.commit();

            

            

        } catch (SQLException e) {

            // TODO Auto-generated catch block

            try {

                conn.rollback();

            } catch (SQLException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            }

        }

    }

Dao

public void transferFrom(String form, double money) {

        // TODO Auto-generated method stub

        Connection conn = C3P0Utils.getConnectionForThreadLocal();

        QueryRunner qr = new QueryRunner();

        // 2.编写SQL语句

        String sql = "update transfer set money=money-? where name=?";

        // 3.设置实际参数

        Object[] params = { money, form };

        // 4.执行更新操作

        try {

            qr.update(conn, sql, params);

        } catch (Exception e) {

            throw new RuntimeException(e);

        }

    }

 

    public void transferTo(String to, double money) {

        // TODO Auto-generated method stub

        Connection conn = C3P0Utils.getConnectionForThreadLocal();

        QueryRunner qr = new QueryRunner();

        // 2.编写SQL语句

        String sql = "update transfer set money=money+? where name=?";

        // 3.设置实际参数

        Object[] params = { money, to };

        // 4.执行更新操作

        try {

            qr.update(conn, sql, params);

        } catch (Exception e) {

            throw new RuntimeException(e);

        }

    }

}

 

    

七、验证事务和同一个connnection的必要

    a.验证事务

    public void transfer(String to, String from, double money) {

/**

* 整个转账功能就是要执行Dao层的2SQL语句(减金额和加金额),我们要使用事务将这2SQL语句绑定在一起

*/

//1.获得Connection对象

Connection conn = C3P0Utils.getConnectionForThreadLocal();

try {

//2.手动开启事务

conn.setAutoCommit(false);

AccountDao dao = new AccountDao();

//3.调用dao付钱方法

dao.transferFrom(from,money);

 

//模拟断电让其后收钱代码失效

int i = 1/0;

 

//4.调用dao收钱方法

dao.transferTo(to,money);

//5.提交事务

conn.commit();

} catch (Exception e) {

//6.回滚事务

try {

conn.rollback();

} catch (Exception e2) {

throw new RuntimeException(e);

}

throw new RuntimeException(e);

}

}

虽然模拟断电其后的收钱方法失效 但是运行后转账人的钱没有减少 验证了事务的要么全成功要么全失败

b.同一个connection的必要性

如果service层不是一个connection对象 那么就不能保证实务操作 如果断电操作后 实务不会全部失败 转账人钱减少 收款人钱没增加

八、事务的特性(笔试) ACID

     * A 原子性:事务中的多条语句是不可再分隔的!要么完全成功,要么完全失败!

     * C 一致性(核心):在事务执行前后,要保证业务数据前后一致!

     * I 隔离性:多个事务并发执行,需要隔离开来,保存不相互影响!

     * D 持久性:数据只要保存到了数据库中,数据就是持久的!就算数据库马上崩溃,也要在重启后有能力恢复数据。

    

九、事务并发访问问题:

    脏读:读到了另外一个事务未提交的数据

    不可重复读:读到了另外一个事务修改了数据

    幻读:读到了另外一个事务添加了的数据    

    

    解决办法:设置事务隔离级别:

        串行化:单线程,性能低,可以解决所有的并发访问问题。

        可重复读:能够解决脏读和不可重复读

        读已提交:能够解决脏读问题。

        读未提交:啥都没干,没卵用

posted @ 2017-02-16 08:08  小彭快跑  阅读(277)  评论(0编辑  收藏  举报