Day21- JDBC事务(JDBC加强)

来源:互联网 发布:淘宝reebonz海外旗舰店 编辑:程序博客网 时间:2024/06/05 09:01

事物:

 事物指的是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全部失败 简而言之,一组操作要么都成功,要么都失败; 一件事情有n个组成单元 要不这n个组成单元同时成功 要不n个单元就同时失败。

mysql的事务
1)默认的事务:
一条sql语句就是一个事务,默认就开启事务并提交事务
MySql默认自动提交,即执行一条sql语句就提交一次事务;
2)手动事务:
1)显示的开启一个事务:start transaction;
2)事务提交:commit代表从开启事务到事务提交 中间的所有的sql都认为有效 真正的更新数据库
3)事务的回滚:rollback 代表事务的回滚 从开启事务到事务回滚 中间的所有的 sql操作都认为无效数据库没有被更新
这里写图片描述

MySql中可以有两种方式对事务进行管理:
方式一:关闭mysql的自动事务

     set autocommit = 0;

注意:这个设置只对当前的连接有效,新开的窗口,依然是自动事务

方式二:手动开启一个事务:

start transaction; -- 手动开启一个事务-- 假装做了一系列的sql操作commit; -- 提交事务 : 相当于把数据持久保存到数据据库中rollback; -- 回滚事务 : 相当于撤销刚才所有的操作

注意:
1)事务开启之后,最终必须提交或者回滚
2)oracle中的默认是手动事务,mysql默认是自动事务
3)控制事务的connnection必须是同一个
执行sql的connection与开启事务的connnection必须是同一个才能对事务进行控制


jdbc中的事务操作:

Connection接口的API(jdk api中搜connection )
- setAutoCommit(false) : 设置默认不自动提交事务
- commit(); 提交事务。将数据持久保存在数据库中
- rollback(); 事务的回滚。撤销事物中的操作
- rollback(savepoint sp); 事务的回滚至保存点


转账案例:

 事务的控制需要在同一个connection之中,所以下述案例中统一传了一个对象connection,保证是在同一个连接之中。
package com.itheima.test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.SQLException;import org.junit.Test;import com.itheima.domain.Transfer;public class Test01 {      @Test      public void test01() throws Exception{           //注册驱动           Class.forName("com.mysql.jdbc.Driver");           //连接数据库           String url = "jdbc:mysql://localhost:3306/day21";           String user = "root";           String password = "123456";           Connection connection = DriverManager.getConnection(url, user, password);           //关闭自动事务           connection.setAutoCommit(false);           //封装一个javaBean           Transfer transfer = new Transfer();           transfer.setFromUser("jack");           transfer.setToUser("rose");           transfer.setMoney(500);           try {                 //转出钱                 transferOut(connection, transfer);                 //如果发生意外                 //int i = 5/0;                 //转入钱                 transferIn(connection, transfer);                 //提交事务                 connection.commit();           } catch (Exception e) {                 e.printStackTrace();                 //事务回滚                 connection.rollback();                 System.out.println("转账失败");           }finally{                 //关闭资源                  connection.close();           }      }      /**       * @Title: Test01.java       * @Description: TODO(资金从转账人账户转出)       * @param connection       * @author jjizh       * @date 2017年6月30日 下午8:33:00       * @version V1.0       * @throws SQLException       */      public void transferOut(Connection connection,Transfer transfer) throws SQLException{           //获得转出金额和从谁转出           double money = transfer.getMoney();           String name = transfer.getFromUser();           //创建执行sql语句的对象           String sql = "update user set money = money - ? where name = ? ";           PreparedStatement prepareStatement = connection.prepareStatement(sql);           prepareStatement.setDouble(1, money);           prepareStatement.setString(2, name);           //执行sql语句           prepareStatement.executeUpdate();           //关闭资源           prepareStatement.close();      }      /**       * @Title: Test01.java       * @Description: TODO(资金转入给收款人)       * @param connection       * @param transfer       * @throws SQLException       * @author jjizh       * @date 2017年6月30日 下午8:48:22       * @version V1.0       */      public void transferIn(Connection connection,Transfer transfer) throws SQLException{           //获得转入金额和转入给谁           double money = transfer.getMoney();           String name = transfer.getToUser();           //创建执行sql语句的对象           String sql = "update user set money = money + ? where name = ? ";           PreparedStatement prepareStatement = connection.prepareStatement(sql);           prepareStatement.setDouble(1, money);           prepareStatement.setString(2, name);           //执行sql语句           prepareStatement.executeUpdate();           //关闭资源           prepareStatement.close();      }}

DBUtils事务操作

DBUtils中的api

* commitAndClose(Connection conn);* commitAndCloseQuietly(Connection conn); 不抛异常* rollbackAndClose(Connction conn);* rollbackAndCloseQuietly(Connection conn);不抛异常

queryRunner操作sql:
1)new QueryRunner(DataSource ds): 默认就是自动事务(这是我们之前一直用的方法)
query(String sql,ResultSetHandler rsh,Object…params);
update(String sql,Object…params);
使用上述方法的时候,我们没有调用过setAutoCommit(false),最后也没有提交过事务,也没有关闭资源

2)new QueryRunner() : 手动事务
query(Connection conn,String sql,ResultSetHandler rsh,Object…params);
update(Connection conn,String sql,Object…params);
使用query和update方法的时候,我们需要传入一个connection对象,最后需要手动提交或者回滚事务,还需要手动释放conn;


案例补充知识点:

1)主动抛异常

throw new Exception("")if(!flag2){      throw new RuntimeException("对不起,转入失败");}

2)获取异常中的抛出信息

Exception.getMessage();catch (Exception e) {                 e.printStackTrace();                 request.setAttribute("msg", e.getMessage());}

代码:
Transferservlet:

public class TransferServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        request.setCharacterEncoding("utf-8");        try {            //创建转账对象            Transfer t = new Transfer();            //接收参数            Map<String, String[]> map = request.getParameterMap();            //将参数传入到对象中去            BeanUtils.populate(t, map);            //调用service完成转账            TransferService ts = new TransferService();            ts.transfer(t);            request.setAttribute("msg", "转账成功");            request.setAttribute("transfer", t);            request.getRequestDispatcher("/index.jsp").forward(request, response);        } catch (Exception e) {            e.printStackTrace();            request.setAttribute("msg", e.getMessage());            request.getRequestDispatcher("/index.jsp").forward(request, response);        }    }    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);    }}

TransferService:

public class TransferService {    public void transfer(Transfer t) throws Exception {        Connection connection = null;        try {            //获得连接            connection = JDBCUtils.getConnection();            //开启手动事务            connection.setAutoCommit(false);            TransferDao td = new TransferDao();            //转出            boolean flag1 = td.moneyOut(connection,t);            if(!flag1){                throw new RuntimeException("对不起,转出失败");            }            //转入            boolean flag2 = td.moneyIn(connection,t);            if(!flag2){                throw new RuntimeException("对不起,转入失败");            }            //提交commit            connection.commit();        } catch (Exception e) {            JDBCUtils.rollbackQietly(connection);            throw e;        } finally{            //释放资源            JDBCUtils.closeQuietly(connection);        }    }}

转账案例终极版:

ThreadLocal
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如:

private static ThreadLocal tl = new ThreadLocal<T>();private static ThreadLocal tl = new ThreadLocal<Connection>();
 threadLocal相当于一个map,以当前线程的id作为一个key,对应存入一个value

api中:
set(T value)直接传入一个value,设置一个值
get():不需要传入任何参数,key就是当前线程
remove():不需要传入任何一个参数, key就是当前线程
这里写图片描述

所以转账的优化流程可以优化成这个样子:
这里写图片描述
所以:
service层的代码优化成为:
TransferService_thrl:

public class TransferService_thrl {      public void transfer(Transfer t) throws Exception {           try {                 //开启手动事物                 JDBCUtils.startTransaction();                 TransferDao_thrl td = new TransferDao_thrl();                 //转出                 boolean flag1 = td.moneyOut(t);                 if(!flag1){                      throw new RuntimeException("转出失败!!!!");                 }                 //转入                 boolean flag2 = td.moneyIn(t);                 if(!flag2){                      throw new RuntimeException("转入失败!!!!");                 }                 //提交事务                 JDBCUtils.commitAndClose();           } catch (Exception e) {                 JDBCUtils.rollbackAndClose();                 e.printStackTrace();           }      }}

JDBCUtils中的函数优化为:

public class JDBCUtils {    private static DataSource ds = new ComboPooledDataSource();    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();    public static DataSource getDataSource(){        return ds;    }    public static Connection getConnection() throws SQLException{        //从当前线程中去获取connection;        Connection connection = tl.get();        if(connection == null){            connection = ds.getConnection();            tl.set(connection);        }        return connection;    }    //开启一个事务    public static void startTransaction() throws SQLException {        Connection connection = getConnection();        connection.setAutoCommit(false);    }    //提交事务    public static void commitAndClose() {        try {            Connection connection = getConnection();            connection.commit();            connection.close();        } catch (SQLException e) {            // quiet//            e.printStackTrace();        }    }    public static void rollbackAndClose()  {        try {            Connection connection = getConnection();            connection.rollback();            connection.close();        } catch (SQLException e) {            // quiet//            e.printStackTrace();        }    }}

备注:servlet中的service每次是在一个新的线程中去执行的


事务特性ACID

1)原子性:Atomicity
- 事务不可切分,事务中可以包含多个操作,这些操作要么都成功,要么都失败
2)一致性:Consistency
- 事务执行前后的业务状态要和其它业务状态保持一致
3)隔离性:isolation
- 一个事务的执行,不受其它事务的影响
4)持久性:Durability
- 事务一旦提交,数据就持久保存到数据库中

并发访问问题:
如果不考虑隔离性,事务存在3种并发访问问题:
- 脏读: 一个事务读取到了另一个事务没有提交的数据
- 不可重复读:在一个事务中,两次查询的结果不一致(针对update)
- 虚读(幻读):在一个事务中,两次查询的结果不一致(针对insert)

设置隔离级别,避免问题产生
- read uncommitted 读未提交
- 上面所有的问题全都不可避免
- read committed 读取已提交
- 可以避免脏读的发生,不可重复读和幻读会发生
- repeatable read 可重复读
- 可以避免脏读和不可重复读的发生,不能避免幻读的发生
- serializable 串行化
- 可以避免所有的问题,一个事务在执行,另外一个事务等待

  • 安全性: serializable > repeatable read > read committed > read uncommitted
  • 效率刚好和上面的相反

  • mysql的默认隔离级别: repeatable read

  • oracle的默认隔离级别: read commttied – read only – serializable

  • java代码修改数据库的隔离级别(了解):

    • conn.setTransactionIsolation(int level)

MySql中:
1)将数据库的隔离级别设为read uncommitted
set session transaction isolation level read uncommitted;

2)查看隔离级别
select @@tx_isolation;

3)将数据库的隔离级别设为read committed
set session transaction isolation level read committed;
不能避免不可重复读和幻读的发生

4)将数据库的隔离级别设为repeatable read
set session transaction isolation level repeatable read;

5)将数据库的隔离级别设为serializable
set session transaction isolation level serializable;


jsp中输出菱形

在控制台中输出:

      @Test      public void test01(){           int size = 10;           for(int x=-size;x<=size;x++){                 for(int y=-size;y<=size;y++){                      if(y+x<=size && y+x>=-size && y-x<=size && y-x>=-size ){                            System.out.print("*");                      }else{                            System.out.print(" ");                      }                 }                 System.out.println();           }      }}

在jsp页面输出
因为jsp页面中使用jstl,循环是只能从0开始,不能够从负数开始的,所以需要手工调整坐标系位置

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><!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>      <c:forEach var="x" begin="0" end="5" step="1" varStatus="xStatus">           <c:forEach var="y" begin="0" end="5" step="1" varStatus="yStatus">                 <c:choose>                      <c:when test="${x+y<=6 && x+y>=2 && x-y<=2 && x-y>=-2}">                            *                      </c:when>                      <c:otherwise>                            &nbsp;                      </c:otherwise>                 </c:choose>           </c:forEach>           <br/>      </c:forEach></body></html>

JSP三层架构
包名:
这里写图片描述

原创粉丝点击