zoukankan      html  css  js  c++  java
  • MySQL的事务

    一、事务概述

    1.什么是事务

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

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

    2.mysql的事务

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

    手动事务:

    1)显示的开启一个事务:start transaction

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

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

    二、JDBC事务操作

    默认是自动事务:

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

    通过jdbc的API手动事务:

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

    提交事务:conn.commit();

    回滚事务:conn.rollback();

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

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

    示例:

    package jdbc;
    
    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 {
                // 注册驱动
                Class.forName("com.mysql.jdbc.Driver");
                // 获得连接
                conn = DriverManager.getConnection("jdbc:mysql:///web", "root", "xuyiqing");
                //开启事务
                conn.setAutoCommit(false);//false表示不自动提交
                // 获得执行平台
                Statement stmt = conn.createStatement();            
                // 执行sql
                stmt.executeUpdate("update user set password=123456 where username=zhangsan");
                //提交事务
                conn.commit();
                
                stmt.close();
                conn.close();
            } catch (Exception e) {
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }
        }
    }
    View Code

    三、DBUtils事务操作

    1.QueryRunner

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

    有参构造将数据源(连接池)作为参数传入QueryRunner,QueryRunner会从连   接池中获得一个数据库连接资源操作数据库,所以直接使用无Connection参数      的update方法即可操作数据库

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

    无参的构造没有将数据源(连接池)作为参数传入QueryRunner,那么我们在使 用QueryRunner对象操作数据库时要使用有Connection参数的方法

    示例:

    package dbutils;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    import org.apache.commons.dbutils.QueryRunner;
    
    import utils.DataSourceUtils;
    
    public class DBUtilsDemo {
        public static void main(String[] args) {
            Connection conn = null;
            try {
                // 无参构造
                QueryRunner runner = new QueryRunner();
                // 获得连接
                conn = DataSourceUtils.getConnection();
                // 开启事务
                conn.setAutoCommit(false);
                runner.update(conn, "update user set password=123456 where username=zhangsan");
                // 提交或回滚事务
                conn.commit();
            } catch (Exception e) {
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
    View Code

    工具类:

    package utils;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    import javax.sql.DataSource;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    public class DataSourceUtils {
    
        private static DataSource dataSource = new ComboPooledDataSource();
    
        private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    
        // 直接可以获取一个连接池
        public static DataSource getDataSource() {
            return dataSource;
        }
        
        public static Connection getConnection() throws SQLException{
            return dataSource.getConnection();
        }
    
        // 获取连接对象
        public static Connection getCurrentConnection() throws SQLException {
    
            Connection con = tl.get();
            if (con == null) {
                con = dataSource.getConnection();
                tl.set(con);
            }
            return con;
        }
    
        // 开启事务
        public static void startTransaction() throws SQLException {
            Connection con = getCurrentConnection();
            if (con != null) {
                con.setAutoCommit(false);
            }
        }
    
        // 事务回滚
        public static void rollback() throws SQLException {
            Connection con = getCurrentConnection();
            if (con != null) {
                con.rollback();
            }
        }
    
        // 提交并且 关闭资源及从ThreadLocall中释放
        public static void commitAndRelease() throws SQLException {
            Connection con = getCurrentConnection();
            if (con != null) {
                con.commit(); // 事务提交
                con.close();// 关闭资源
                tl.remove();// 从线程绑定中移除
            }
        }
    
        // 关闭资源方法
        public static void closeConnection() throws SQLException {
            Connection con = getCurrentConnection();
            if (con != null) {
                con.close();
            }
        }
    
        public static void closeStatement(Statement st) throws SQLException {
            if (st != null) {
                st.close();
            }
        }
    
        public static void closeResultSet(ResultSet rs) throws SQLException {
            if (rs != null) {
                rs.close();
            }
        }
    
    }
    View Code

    案例:简单的转账代码:

    代码实现:

     页面transfer.jsp

    <%@ 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>Insert title here</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath }/transfer" 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>
    </body>
    </html>
    View Code

    web层:

    package transfer.web;
    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import transfer.service.TransferService;
    
    public class TransferServlet extends HttpServlet {
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            
            //接受转账的参数
            String out = request.getParameter("out");
            String in = request.getParameter("in");
            String moneyStr = request.getParameter("money");
            double money = Double.parseDouble(moneyStr);
            
            //调用业务层的转账方法
            TransferService service = new TransferService();
            boolean isTransferSuccess = service.transfer(out,in,money);
            
            response.setContentType("text/html;charset=UTF-8");
            
            if(isTransferSuccess){
                response.getWriter().write("转账成功!!!");
            }else{
                response.getWriter().write("转账失败!!!");
            }
            
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    View Code

    service层:

    package transfer.service;
    
    import java.sql.SQLException;
    
    import transfer.dao.TransferDao;
    import utils.MyDataSourceUtils;
    
    public class TransferService {
    
        public boolean transfer(String out, String in, double money) {
            
            TransferDao dao = new TransferDao();
            
            boolean isTranferSuccess = true;
            try {
                //开启事务
                MyDataSourceUtils.startTransaction();
                //转出前的方法
                dao.out(out,money);
                //转入钱的方法
                dao.in(in,money);
                
            } catch (Exception e) {
                isTranferSuccess = false;
                //回滚事务
                try {
                    MyDataSourceUtils.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            } finally{
                try {
                    MyDataSourceUtils.commit();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            
            return isTranferSuccess;
            
        }
    
    }
    View Code

    dao层:

    package transfer.dao;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    import org.apache.commons.dbutils.QueryRunner;
    
    import utils.MyDataSourceUtils;
    
    public class TransferDao {
    
        public void out(String out, double money) throws SQLException {
            QueryRunner runner = new QueryRunner();
            Connection conn = MyDataSourceUtils.getCurrentConnection();
            String sql = "update account set money=money-? where name=?";
            runner.update(conn, sql, money, out);
        }
    
        public void in(String in, double money) throws SQLException {
            QueryRunner runner = new QueryRunner();
            Connection conn = MyDataSourceUtils.getCurrentConnection();
            String sql = "update account set money=money+? where name=?";
            runner.update(conn, sql, money, in);
        }
    
    }
    View Code

    工具类:

    package utils;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    public class MyDataSourceUtils {
    
        //获得Connection ----- 从连接池中获取
        private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
        
        //创建ThreadLocal
        private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
        
        //开启事务
        public static void startTransaction() throws SQLException{
            Connection conn = getCurrentConnection();
            conn.setAutoCommit(false);
        }
        
        //获得当前线程上绑定的conn
        public static Connection getCurrentConnection() throws SQLException{
            //从ThreadLocal寻找 当前线程是否有对应Connection
            Connection conn = tl.get();
            if(conn==null){
                //获得新的connection
                conn = getConnection();
                //将conn资源绑定到ThreadLocal(map)上
                tl.set(conn);
            }
            return conn;
        }
        
        public static Connection getConnection() throws SQLException{
            return dataSource.getConnection();
        }
    
        //回滚事务
        public static void rollback() throws SQLException {
            getCurrentConnection().rollback();
        }
    
        //提交事务
        public static void commit() throws SQLException {
            Connection conn = getCurrentConnection();
            conn.commit();
            //将Connection从ThreadLocal中移除
            tl.remove();
            conn.close();
            
        }
    
    }
    View Code

    注意c3p0和web的配置文件

    四、事务的特性和隔离级别(概念性问题---面试)

    1.事务的特性ACID

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

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

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

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

    2.并发访问问题----由隔离性引起

    如果不考虑隔离性,事务存在3中并发访问问题。

    1)脏读:B事务读取到了A事务尚未提交的数据   ------  要求B事务要读取A事 务提交的数据

    2)不可重复读:一个事务中 两次读取的数据的内容不一致  ----- 要求的是一个事 务中多次读取时数据是一致的  --- unpdate

    3)幻读/虚读:一个事务中 两次读取的数据的数量不一致  ----- 要求在一个事务多 次读取的数据的数量是一致的 --insert  delete

    3.事务的隔离级别

    1)read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决

    2)read committed:读取已经提交的数据 :可以解决脏读 ---- oracle默认的

    3)repeatable read:重读读取:可以解决脏读 和 不可重复读 ---mysql默认的

    4)serializable:串行化:可以解决 脏读 不可重复读 和 虚读---相当于锁表,效率过低不使用

    注意:mysql数据库默认的隔离级别

    查看mysql数据库默认的隔离级别:select @@tx_isolation

    设置mysql的隔离级别:set session transaction isolation level 设置事务隔离级别

    隔离级别的性能:read uncommitted>read committed>repeatable read>serialazable

    安全性:read uncommitted<read committed<repeatable read<serialazable

  • 相关阅读:
    NodeJs 的Module.export 和 export
    Angular 调试
    设计模式 -- 访问者
    typescript 枚举
    Swagger 实践 <二>
    eventFlow 系列 <三> 查询所有
    成员变量的隐藏和方法的重写
    Facetoprocess_program_design
    ATM_tests
    transmission protocol
  • 原文地址:https://www.cnblogs.com/xuyiqing/p/8430214.html
Copyright © 2011-2022 走看看