WeLi的java学习Java学习笔记程序员

JDBC_事务管理

2017-06-03  本文已影响44人  明天你好向前奔跑

一、什么是事务

逻辑上的一组操作, ,要不全部失败,要不全部成功。

对数据库的操作会先在一张临时表上进行,一旦发生错误,就回滚撤销所有操作。全部成功才存盘修改数据库数据。

MySql的事务管理

1. 启动事务(对数据的操作在临时表中进行)
    startTransaction

2. 提交,存盘
    commit

3. 回滚,撤销
    rollBack

JDBC的事务

1. 启动事务管理
    //首先获取连接。(以C3P0为基础准备一个JDBCUtils工具类,提供获取连接池和连接的方法)
    Connection connection = JDBCUtils.getConnection();
    //启动事务
    connection.setAutoCommit(false);

2. 提交,存盘
    connection.commit();

3. 回滚,撤销
    connection.rollBack();

DBUtils的事务管理

1. 启动事务
    //创建QuueryRunner对象,使用它的无参构造方式,自己给连接。从而可以通过连接控制事务
    QueryRunner runner = new QueryRunner();
    Connection connection = JDBCUtils.getConnection();
    connection.setAutoCommit(false);

2. 提交并释放资源,异常由框架处理(其实没处理)
    //connection.commitAndCloseQuietly();

3. 回滚,撤销并释放资源,异常由框架处理(其实没处理)
    //connection.rollbackAndCloseQuietly();

事务回滚点 SavePoint(类似与游戏中的存档)

当事务特别复杂,有些情况不会回滚到事务的最开始状态,需要将事务回滚到指定位置

* 核心API
    * connection.setSavepoint();// 设置回滚点
    * connection.rollback(savepoint);//事务回滚到指定回滚点

    Connection connection = null;
    PreparedStatement prepareStatement = null;
    Savepoint savepoint = null;
    try {
        connection = JDBCUtils.getConnection();
        // 开启事务
        connection.setAutoCommit(false);
        // 设置初始回滚点
        savepoint = connection.setSavepoint();
        String sql = "insert into person values (?,?)";
        prepareStatement = connection.prepareStatement(sql);
        for (int i = 1; i <= 5000; i++) {
            prepareStatement.setInt(1, i);
            prepareStatement.setString(2, "name" + i);
            prepareStatement.addBatch();
            // 模拟错误
            if (i == 3201) {
                int x = 11 / 0;
            }
            // 每隔200条数据执行一次批处理
            if (i % 200 == 0) {
                prepareStatement.executeBatch();
                prepareStatement.clearBatch();
            }
            if (i % 1000 == 0) {
                // 每一千条数据,创建一个回滚点
                savepoint = connection.setSavepoint();
            }
        }
        prepareStatement.executeBatch();
        prepareStatement.clearBatch();
        // 没有异常,提交事务
        connection.commit();
    } catch (Exception e) {
        try {
            // 发生异常,事务回滚到指定回滚点
            connection.rollback(savepoint);
            // 提交事务
            connection.commit();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    } finally {
        JDBCUtils.release(connection, prepareStatement);
    }

二、事务案例:简易转账案例

1、分析

img01.png

2、搭建环境

1. 数据库及用户表单的创建。

    CREATE DATABASE day20;
    USE day20;
    CREATE TABLE USER(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(40),
    money DOUBLE
    );
    INSERT INTO USER VALUES(NULL,'tom',2000);
    INSERT INTO USER VALUES(NULL,'jerry',2000);

2. 数据库驱动,c3p0的jar包,c3p0.xml配置文件,dbutils的jar包,

3. 转账页面
    <form action="${pageContext.request.contextPath}/transfer" method="post">
        转账人:<input type="text" name="sender" /><br/>
        被转账人:<input type="text" name="receiver" /><br/>
        转账金额:<input type="text" name="acount" /><br/>
        <input type="submit" value="转账">    <br/>
    </form>

3、分包实现

3.1、创建web层TransactServlet

Servlet要做的三件事:

  1. 获取表单提交的参数
  2. 调用业务逻辑
  3. 分发转向

    public class TransferServlet extends HttpServlet {
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            // 设置编码集
            response.setContentType("text/html;charset=UTF-8");
    
            try {
                // 获取参数
                String sender = request.getParameter("sender");
                String receiver = request.getParameter("receiver");
                String amount = request.getParameter("amount");
                
                // 调用业务层对象,执行转账
                TransferService service = new TransferService();
                service.transfer(sender, receiver, amount);
                // 写出响应
                response.getWriter().write("转账成功");
            } catch (Exception e) {
                // 写出响应
                response.getWriter().write("转账失败:" + e.getMessage());
            }
    
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
}

3.2、创建service层TransferService

与数据库交互的操作交到dao层完成,service层只处理业务逻辑


    public class TransferService {
    
        public void transfer(String sender, String receiver, String amount) throws Exception {
            Connection connection = null;
    
            try {
                // 与数据库交互的事情交给dao层去操作 ,创建dao对象
                TransferDao dao = new TransferDao();
    
                // 开启事务。要想控制事务dao层的connection连接对象和这里的连接对象应该一致.因此将这个connection对象作为参数传递下去。
                //connection = JDBCUtils.getConnection();
                //connection.setAutoCommit(false);
                //为了不打破分层结构,不应该将连接Connection的操作放在Service层.因此抽取出来
                JDBCUtils.startTransaction();
    
                // 改变的行数
                int outDao = dao.outDao(sender, amount);
                
                //一旦数据库操作失败,影响行数即为0,抛出异常带上对应的信息。
                if(outDao != 1) {
                    throw new RuntimeException("转出失败");
                }
                
                int inDao = dao.inDao(receiver, amount);
                
                if(inDao != 1) {
                    throw new RuntimeException("转入失败");
                }
    
                // 没有异常,提交事务
                JDBCUtils.commitAndRelease();

            //这里是用try-catch是为了保证有异常与无异常时的两种处理方式
            //这里抓住了上面抛出的转账信息的异常但是为了告诉Servlet这里发生了异常,将该异常继续向上抛
            } catch (Exception e) { 

                // 发生异常,回滚撤销操作
                JDBCUtils.rollbackAndRelease();
                throw e;
            }
    
        }
    }

3.3、创建utils工具类JDBCUtils

提供与数据库操作相关的工具:获取连接池与获取连接的方法

因为DBUtils框架的QueryRunner对象会自行释放资源,因此不提供释放Connection等资源的方法。

private static DataSource dataSource = new ComboPooledDataSource();

    // 获取连接池对象
    public static DataSource getDataSource() {
        return dataSource;
    }

    // 提供连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

3.4、创建dao层TransferDao

进行操作数据库的动作。


    public class TransferDao {
    
        public int outDao(String sender, String amount) throws SQLException {
            //对数据库数据进行更改
            //1.创建QueryRunner对象
            QueryRunner runner = new QueryRunner();
            Connection connection = JDBCUtils.getConnectionTL();
            //2.执行sql语句
            String sql = "update user set money=money-? where name=?";

            //为了告知service层sql语句是否执行成功,返回被影响的行数
            int i = runner.update(connection, sql, amount,sender);
            return i;
        }
    
        public int inDao(String receiver, String amount) throws SQLException {
            //对数据库数据进行更改
            //1.创建QueryRunner对象
            QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
            Connection connection = JDBCUtils.getConnectionTL();
            //2.执行sql语句
            String sql = "update user set money=money+? where name=?";
            int i = runner.update(connection, sql, amount,receiver);
            return i;
        }
    }

3.5、优化代码,完善utils工具类

  1. 因为第一版中service层执行了属于dao层与数据库交互的操作,这打破了三层结构,为了避免这种情况,应当考虑将与数据库交互的相关操作抽取出来,于是我将控制事务的代码抽取到工具类中。提供开启事务,提交事务和回滚事务的方法。

  2. 但是为了控制事务,service层和dao层的connection应当是同一个connection,因此使用了一个类似于map集合的ThreadLocal类,它的内部存在<Thread.currentThread,Connection>的键值对,通过它保证获取同一个connection连接。


    public class JDBCUtils {
        private static DataSource dataSource = new ComboPooledDataSource();
        private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
    
        // 获取连接池对象
        public static DataSource getDataSource() {
            return dataSource;
        }
    
        // 提供连接
        public static Connection getConnection() throws SQLException {
            return dataSource.getConnection();
        }
    
        // 提供连接,从ThreadLocal中获取的
        public static Connection getConnectionTL() throws SQLException {
            // 从ThreadLocal中获取连接
            Connection connection = threadLocal.get();
            // 如果连接对象为空
            if (connection == null) {
                // 从连接池获取连接
                connection = getConnection();
                // 将连接放入ThreadLocal,这样下一次从ThreadLocal中获取的时候就有数据了
                threadLocal.set(connection);
            }
    
            return connection;
        }
    
        // 开启事务
        public static void startTransaction() throws SQLException {
            Connection connection = getConnectionTL();
            connection.setAutoCommit(false);
    
        }
    
        // 提交事务并释放资源
        public static void commitAndRelease() throws SQLException {
            Connection connection = getConnectionTL();
            // 提交事务
            connection.commit();
            if (connection != null) {
                connection.close();
                connection = null;
            }
            // 把连接对象和ThreadLocal解绑,可以让ThreadLocal尽快被释放,节约服务器内存资源
            threadLocal.remove();
    
        }
    
        // 回滚事务并释放资源
        public static void rollbackAndRelease() throws SQLException {
            Connection connection = getConnectionTL();
            // 回滚事务
            connection.rollback();   
            if (connection != null) {   
                connection.close();
                connection = null;
            }
            // 把连接对象和ThreadLocal解绑,可以让ThreadLocal尽快被释放,节约服务器内存资源
            threadLocal.remove();
    
        }
    
    }

3.6、 案例实现_为什么要使用事务?

![img03.png](http:https://img.haomeiwen.com/i5303154/013edb1268a6c647.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

4、事务的一些概念

事务特性

隔离性

隔离级别引发问题的小实验

丢失更新问题和悲观锁乐观锁机制【了解】

上一篇 下一篇

猜你喜欢

热点阅读