• 事务


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

  • 相关阅读:
    动手动脑之异常处理
    git一些概念
    jquery each函数使用
    数据库客户端
    plotly.js
    网站跳转汇总
    jquery 实现间隔运行
    学习 在线调试
    Robot限制字典的key大写的class
    Gerrit 相关
  • 原文地址:https://www.cnblogs.com/qq1312583369/p/11187000.html
Copyright © 2020-2023  润新知