连接池-DBUtils-事务

  1. 1.学习目标:
  2. a.利用原生的JDBC完成对数据库的增删改查
  3. b.利用预处理对象完成对数据库的增删改查
  4. c.会使用C3P0连接池以及Druid连接池

第一章.PreparedStatement预处理对象

1.用读取配置文件的方式编写工具类

  1. src下创建jdbc.properties文件:
  2. driverClassName=com.mysql.jdbc.Driver
  3. url=jdbc:mysql://localhost:3306/day04_37?characterEncoding=utf8
  4. username=root
  5. password=root
public class JDBCUtils2 {
  private static String driverClassName;
  private static String url;
  private static String username;
  private static String password;

  //注册驱动,初始化url,username,password是最先初始化的,所以放在static代码块中
    static{
        //注册驱动
      try {
         //创建Properties集合
          Properties properties = new Properties();
          //利用字节输入流读取配置文件
          InputStream in = JDBCUtils2.class.getClassLoader().getResourceAsStream("jdbc.properties");
          properties.load(in);

          //获取Properties集合中的数据
          driverClassName = properties.getProperty("driverClassName");
          url = properties.getProperty("url");
          username = properties.getProperty("username");
          password = properties.getProperty("password");

          Class.forName(driverClassName);
      } catch (Exception e) {
          e.printStackTrace();
      }
   }

   public static Connection getConn(){
       Connection connection = null;
       try {
           connection = DriverManager.getConnection(url, username, password);
       } catch (SQLException e) {
           e.printStackTrace();
       }
       return connection;
   }

   //关闭资源
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
       if (resultSet!=null){
           try {
               resultSet.close();
           } catch (SQLException e) {
               e.printStackTrace();
           }
       }

        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo06JDBC_PreparedStatement {
    public static void main(String[] args) throws Exception {
        //获取连接
        Connection conn = JDBCUtils2.getConn();
        //准备sql
        String sql = "select * from user";
        //获取PreparedStatement对象
        PreparedStatement pst = conn.prepareStatement(sql);
        //由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
        //执行sql
        ResultSet resultSet = pst.executeQuery();

        //处理结果集
        while(resultSet.next()){
            Object id = resultSet.getObject("id");
            Object username = resultSet.getObject("username");
            Object password = resultSet.getObject("password");
            System.out.println(id+"..."+username+"..."+password);
        }
        //释放资源
        JDBCUtils2.close(conn,pst,resultSet);

    }
}

2.mysql批量添加数据

修改properties文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day04_37?characterEncoding=utf8&rewriteBatchedStatements=true
username=root
password=root
1.在设置完所有要添加的参数,调用PreparedStatement中的addBatch(),将SQL语句添加到PreparedStatement中
2.调用PreparedStatement中的executeBatch()方法批处理sql语句
public class Test01 {
    @Test
    public void insert()throws Exception{
        //获取连接对象
        Connection connection = JDBCUtils.getConnection();

        String sql = "insert into user (username,password) values (?,?)";

        //获取执行平台
        PreparedStatement pst = connection.prepareStatement(sql);

        //批量添加
        for (int i = 0; i < 1000; i++) {
            pst.setObject(1,"名字"+i);
            pst.setObject(2,"密码"+i);
            pst.addBatch();
        }

        //执行sql
        int[] arr = pst.executeBatch();
        for (int i : arr) {
            System.out.println(i);
        }

        //关闭资源
        JDBCUtils.close(null,pst,connection);
    }
}

第二章.连接池

1.连接池之C3p0

1.连接池:容器,里面存有很多的连接对象,需要的话去连接池中拿连接对象,用完还回去
2.java为连接池提供了一个标准,接口:javax.sql.DataSource,不同的厂商如果要想实现自己的连接池就要实现这个DataSource接口

3.常用的连接池:C3p0  DRUID
1.导jar包:c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar
2.在src下面创建c3p0-config.xml->名字不能错
3.在xml中配置相关信息:
   <c3p0-config>
   <!-- 使用默认的配置读取连接池对象 -->
   <default-config>
     <!--  连接参数 -->
     <property name="driverClass">com.mysql.jdbc.Driver</property>
     <property name="jdbcUrl">jdbc:mysql://localhost:3306/day05?characterEncoding=utf8</property>
     <property name="user">root</property>
     <property name="password">root</property>

     <!-- 连接池参数 -->
     <property name="initialPoolSize">5</property>
     <property name="maxPoolSize">10</property>
     <property name="checkoutTimeout">2000</property>
     <property name="maxIdleTime">1000</property>
   </default-config>
 </c3p0-config>

初始连接数(initialPoolSize):刚创建好连接池的时候准备的连接数量
最大连接数(maxPoolSize):连接池中最多可以放多少个连接
最大等待时间(checkoutTimeout):连接池中没有连接时最长等待时间
最大空闲回收时间(maxIdleTime):连接池中的空闲连接多久没有使用就会回收

4.编写工具类
  实现类对象:ComboPooledDataSource
  获取连接:利用ComboPooledDataSource调用getConnection方法
/*
   C3p0工具类
 */
public class C3P0Utils {
  /*
      实现类对象:ComboPooledDataSource
      获取连接:利用ComboPooledDataSource调用getConnection方法

      创建连接池对象
   */
   private static ComboPooledDataSource cpds;
   static{
       cpds = new ComboPooledDataSource();
   }

   //获取连接
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = cpds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    //关闭资源
    /*
      由于Connection从连接池中获取的
      所以此处的Connection调用的close不是关闭资源,而是自动将Connection释放归还给连接池
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if (resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
/*
  测试类
*/
public class Demo01JDBC_C3P0 {
    public static void main(String[] args) throws Exception {
        //获取连接
        Connection conn = C3P0Utils.getConnection();
        //准备sql
        String sql = "select * from user";
        //获取PreparedStatement对象
        PreparedStatement pst = conn.prepareStatement(sql);
        //由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
        //执行sql
        ResultSet resultSet = pst.executeQuery();

        //处理结果集
        while(resultSet.next()){
            Object id = resultSet.getObject("id");
            Object username = resultSet.getObject("username");
            Object password = resultSet.getObject("password");
            System.out.println(id+"..."+username+"..."+password);
        }
        //释放资源
        C3P0Utils.close(conn,pst,resultSet);

    }
}

2.连接池之Druid

1.概述:Druid连接池阿里巴巴开发的
2.好处:性能好,抗造
3.使用:
   a.到jar包druid-1.1.6.jar
   b.在src下面编写properties配置文件->druid.properties
     driverClassName=com.mysql.jdbc.Driver
     url=jdbc:mysql://localhost:3306/day19?characterEncoding=utf8
     username=root
     password=root
   c.编写工具类->
     DruidDataSourceFactory.createDataSource(prop);->properties集合
public class DruidUtils {
    private static DataSource dataSource;
  static{
      try {
          //创建Properties集合
          Properties properties = new Properties();
          //利用字节输入流读取配置文件
          InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
          properties.load(is);
          dataSource = DruidDataSourceFactory.createDataSource(properties);
      } catch (Exception e) {
          e.printStackTrace();
      }
  }

    //获取连接
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    //关闭资源
    /*
      由于Connection从连接池中获取的
      所以此处的Connection调用的close不是关闭资源,而是自动将Connection释放归还给连接池
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if (resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo02JDBC_Druid {
    public static void main(String[] args) throws Exception {
        //获取连接
        Connection conn = DruidUtils.getConnection();
        //准备sql
        String sql = "select * from user";
        //获取PreparedStatement对象
        PreparedStatement pst = conn.prepareStatement(sql);
        //由于没有占位符,我们查询的是所有,所以不用为占位符赋值了
        //执行sql
        ResultSet resultSet = pst.executeQuery();

        //处理结果集
        while(resultSet.next()){
            Object id = resultSet.getObject("id");
            Object username = resultSet.getObject("username");
            Object password = resultSet.getObject("password");
            System.out.println(id+"..."+username+"..."+password);
        }
        //释放资源
        DruidUtils.close(conn,pst,resultSet);

    }
}

第三章.DBUtils工具类

1.准备工作

image-20210914231038327.png

2.DBUtils的介绍

1.为什么要学:使用原生jdbc开发,代码很多,比较难,会影响我们的开发效率,而DBUtils可以大大的简化原生jdbc的开发
2.什么是DBUtils:是简化jdbc的一个工具包
3.使用:
    a.导jar包->commons-dbutils-1.4.jar
4.三大核心对象
  a.QueryRunner:定义了执行sql的方法
  b.ResultSetHandler:有很多处理结果集的类
  c.Dbutils:是一个类,里面定义了很多关闭资源以及操作事务的方法

3.QueryRunner

3.1.空参的QueryRunner的介绍以及使用

QueryRunner():
  作用:需要我们维护连接,支持sql中使用占位符?
方法:
      int update(Connection conn, String sql, Object... params)->针对于增删改
             conn:连接对象
             sql:sql语句
             params:给sql中的?赋的值
   比如:update(conn,"inert into category (cid,cname) values (?,?)",1,"蔬菜")
  query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)->针对于
  查询
             conn:连接对象
             sql:sql语句
             rsh:处理结果集的方式
             params:给sql中的?赋的值
准备数据:
create database 372_day05;
use 372_day05;

create table category(
  cid int primary key,
  cname varchar(100)
);

insert into category (cid,cname) values (1,'蔬菜'),(2,'服装'),(3,'水果'),(4,'箱包');
//添加功能
  @Test
  public void insert() throws SQLException {
      //1.创建一个空参的QueryRunner对象
      QueryRunner qr = new QueryRunner();
      //2.获取连接
      Connection connection = DruidUtils.getConnection();
      //3.执行sql
      qr.update(connection, "insert into category (cid,cname) values (?,?)", 5, "化妆品");
      //4.关闭资源
      DruidUtils.close(connection,null,null);
  }

3.2.有参QueryRunner的介绍以及使用

QueryRunner(DataSource ds):
作用:自动维护连接对象,QueryRunner会自动从连接池中获取连接,用完自动归还

方法:
  int update(String sql, Object... params)->针对于增删改
      sql:要执行的sql语句
      params:为?赋的值
  query(String sql, ResultSetHandler<T> rsh, Object... params)->针对于查询
      sql:要执行的查询sql语句
      rsh:以什么方式处理结果集
      params:给?赋的值
public class DruidUtils {
    //创建对象
    private static DataSource dataSource;
    static {

        try {
            //读取配置文件
            InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            //创建Properties集合
            Properties properties = new Properties();
            properties.load(is);

            //获取连接池对象
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取DataSource的方法
    public static DataSource getDs(){
        return dataSource;
    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        Connection connection = dataSource.getConnection();
        return connection;
    }

    //关闭资源
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if (resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}
//添加功能
    @Test
    public void insert()throws Exception {
       //1.创建QueryRunner
        QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
        //2.由于连接对象不用我们维护,所以不用手动获取了,直接执行sql即可
        qr.update("insert into category (cid,cname) values (?,?)",6,"手机");
        //3.由于有参的QueryRunner工具类自动维护连接对象,所以我们也不用自己手动关闭
    }

4.ResultSetHandler结果集

4.1.标准的JavaBean

什么是JavaBean:实体类
开发的过程中:JavaBean中的成员变量要和具体表中的字段要对应
一个标准的JavaBean如何编写:成员变量   构造    get/set 方法    hashcode和equals方法  toString
                        此类还需要实现Serializable

1599356773135.png

public class Category implements Serializable {
   private Integer cid;
   private String cname;

    public Category() {
    }

    public Category(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }

    public int getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    @Override
    public String toString() {
        return "Category{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                '}';
    }
}

4.2.BeanHandler

1.作用:将查询出来的结果集中的第一行数据封装成一个JavaBean对象
2. <T> T query(String sql, ResultSetHandler<T> rsh, Object... params)  ->针对查询 
3.构造:BeanHandler(Class<T> type):
      传递的class对象其实就是我们想要封装的JavaBean类的class对象
      想将查询出来的结果封装成哪个JavaBean对象,就写哪个JavaBean的class对象
      返回值
4.理解方法:
      将查询出来的数据为JavaBean中的成员变量赋值
public class Demo01BeanHandler {
    public static void main(String[] args)throws Exception {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
        /*
           1.作用:将查询出来的结果集中的第一行数据封装成一个JavaBean对象
           2. <T> T query(String sql, ResultSetHandler<T> rsh, Object... params)  ->针对查询
           3.构造:BeanHandler(Class<T> type):
                 传递的class对象其实就是我们想要封装的JavaBean类的class对象
                 想将查询出来的结果封装成哪个JavaBean对象,就写哪个JavaBean的class对象
         */

        //2.执行sql->如果new BeanHandler时不加泛型,那么query方法返回的是Object类型,当然可以强转
        //Object query = qr.query("select * from category", new BeanHandler(Category.class));

        /*
          如果如果new BeanHandler时加泛型,那么query方法返回的是泛型的类型
         */
        Category category = qr.query("select * from category", new BeanHandler<Category>(Category.class));
        System.out.println(category);
    }
}

4.3.BeanListHandler

1.作用:将查询出来的结果每一条数据都封装成一个一个的JavaBean对象,将这些JavaBean对象放在List集合中
2.构造:
    BeanListHandler(Class<T> type)
      传递的class对象其实就是我们想要封装的JavaBean类的class对象
3.理解方法:
    将查询出来的数据为JavaBean中的成员变量赋值
public class Demo02BeanListHandler {
    public static void main(String[] args)throws Exception {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
        /*
          1.作用:将查询出来的结果每一条数据都封装成一个一个的JavaBean对象,将这些JavaBean对象放在List集合中
          2.构造:
              BeanListHandler(Class<T> type)
              传递的class对象其实就是我们想要封装的JavaBean类的class对象
         */

        //如果new BeanListHandler时不指定泛型,那么query返回的就是Object
        //Object query = qr.query("select * from category", new BeanListHandler(Category.class));
        //如果new BeanListHandler时指定泛型,那么query返回的就是放有指定泛型对象的List集合
        List<Category> list = qr.query("select * from category", new BeanListHandler<Category>(Category.class));

        //遍历list集合
        for (Category category : list) {
            System.out.println(category);
        }
    }
}

4.4.ScalarHandler

1.作用:处理单值查询结果,执行的select语句后,结果集只有1个->聚合函数
2.构造:
   ScalarHandler(int columnIndex)->不常用->指定第几列
   ScalarHandler(String columnName)->不常用->指定列名
   ScalarHandler()
public class Demo03ScalarHandler {
    public static void main(String[] args)throws Exception {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
        /*
          1.作用:处理单值查询结果,执行的select语句后,结果集只有1个->聚合函数
          2.构造:
             ScalarHandler(int columnIndex)->不常用->指定第几列的第一个数据
             ScalarHandler(String columnName)->不常用->指定列名的第一个数据
             ScalarHandler()
         */
        //Object o = qr.query("select * from category", new ScalarHandler(2));
        //Object o = qr.query("select * from category", new ScalarHandler("cname"));
        Object o = qr.query("select count(*) from category", new ScalarHandler());
        System.out.println(o);
    }
}

4.5.ColumnListHandler

1.作用: 将查询数据表结果集中的某一列数据,存储到List集合
2.构造:
    ColumnListHandler():显示查询结果集中的第一列
    ColumnListHandler(int columnIndex):指定显示查询结果集的第几列
    ColumnListHandler(String columnIndex):指定显示查询结果集的列名
public class Demo04ColumnListHandler {
    public static void main(String[] args)throws Exception {
     //1.创建QueryRunner
        QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
        /*
          1.作用: 将查询数据表结果集中的某一列数据,存储到List集合
          2.构造:
              ColumnListHandler():显示查询结果集中的第一列
              ColumnListHandler(int columnIndex):指定显示查询结果集的第几列
              ColumnListHandler(String columnIndex):指定显示查询结果集的列名
         */
         //空参的ColumnListHandler默认会将第一列数据放到list集合中
        //List<Object> list = qr.query("select * from category", new ColumnListHandler());

        //将指定第几列的数据放到List集合中
        //List<Object> list = qr.query("select * from category", new ColumnListHandler(2));

        //将指定列名中的数据放到List集合中
        List<Object> list = qr.query("select * from category", new ColumnListHandler("cname"));

        for (Object o : list) {
            System.out.println(o);
        }
    }
}

第四章.事务

1.事务

1.1.事务_转账分析图

1608520678753.png

create table account(
  id int primary key,
  `name` varchar(100),
  money double
);

1.2.实现转账(不加事务)

public class Demo01Transfer {
    public static void main(String[] args)throws Exception {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //2.获取连接
        Connection connection = DruidUtils.getConnection();
        //3.执行sql
        qr.update(connection,"update account set money = money - 1000 where name = ?","taoge");

        //System.out.println(1/0);

        qr.update(connection,"update account set money = money + 1000 where name = ?","shiqing");

        //4.关闭资源
        DruidUtils.close(connection,null,null);
    }
}

1.3.事务的介绍

1.作用:用于管理一组操作的,使这一组操作要不全部成功,要不全部失败
2.注意:mysql自带事务管理,但是它只能管理一条sql,如果想让mysql管理一组操作,需要手动操作事务,需要关闭mysql自带事务,开启手动事务
3.方法:Connection中的方法
  a.setAutoCommit(false):关闭自动事务,开启手动事务
  b.commit():提交事务,一旦提交,数据将永久保存,撤回不了
  c.rollback():回滚事务,一旦回滚,数据还原
4.注意:以上三个方法,需要Connection对象去调用,而且这一组操作需要同一条连接对象
5.怎么使用:
   try{
     开启事务
       报错了,走catch,一走catch证明有问题了,回滚事务,数据还原  
     提交事务
   }catch(异常对象){
     回滚事务
   }

总结:

1.事务可以管理多条sql,使其要不全成功,要不全失败

2.mysql是再带事务管理的,但是一个事务只能管理一条sql

所以,我们如果想要让事务管理一组操作,需要将mysql自带的事务管理关闭,开启手动事务

1.4.DBUtils实现转账(添加事务)

public class Demo02Transfer {
    public static void main(String[] args){
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //2.获取连接
        Connection connection = DruidUtils.getConnection();

        /*
            a.setAutoCommit(false):关闭自动事务,开启手动事务
            b.commit():提交事务,一旦提交,数据将永久保存,撤回不了
            c.rollback():回滚事务,一旦回滚,数据还原
         */
        try{
            //开启手动事务,关闭mysql自动事务
            connection.setAutoCommit(false);
            //3.执行sql
            qr.update(connection,"update account set money = money - 1000 where name = ?","taoge");

            System.out.println(1/0);

            qr.update(connection,"update account set money = money + 1000 where name = ?","shiqing");

            //提交事务,让数据永久保存,无法还原
            connection.commit();
            System.out.println("转账成功!");
        }catch(Exception e){
            //事务回滚
            try {
                connection.rollback();
                System.out.println("转账失败");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }

        }finally {
            //4.关闭资源
            DruidUtils.close(connection,null,null);
        }
    }
}

1.5.mysql中操作事务

#开启事务
BEGIN;

UPDATE account SET money = money - 1000 WHERE `name` = 'taoge';

UPDATE account SET money = money + 1000 WHERE `name` = 'shiqing';

#提交事务
COMMIT;

#回滚事务
ROLLBACK;

1.6.分层思想介绍以及架构搭建

1608535869067.png

分层:web层(和页面打交道)   service层(和业务打交道)    dao层(和数据库打交道)
分包:
  取包名:一般都是公司域名倒着写,小写
  cn.atguigu.domain   ->  专门装实体类->JavaBean
  cn.atguigu.web      ->  专门装和页面有关系的类->web层相关类
  cn.atguigu.dao       ->  专门装和数据库有关的类->dao层相关类
  cn.atguigu.service   -> 专门装和业务层有关的类->service层相关类
  cn.atguigu.utils     ->专门装工具类

好处:可维护性强,可扩展性强,解耦,好看

1.6.1.转账_web层实现

public class AccountWeb {
    public static void main(String[] args) {
        //创建Scanner对象
        Scanner sc = new Scanner(System.in);
        System.out.println("请你输入要减钱的名字:");
        String outName = sc.nextLine();
        System.out.println("请你输入要加钱的名字:");
        String inName = sc.nextLine();
        System.out.println("请你输入要转的金额:");
        int money = sc.nextInt();

        //创建Service对象
        AccountService accountService = new AccountService();
        //调用service层的转账方法
        try {
            accountService.transfer(outName,inName,money);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

1.6.2.转账_service层实现

public class AccountService {
    /**
     *
     * @param outName   减钱的人
     * @param inName    加钱的人
     * @param money     转账的金额
     */
    public void transfer(String outName, String inName, int money) throws SQLException {
        //创建dao对象
        AccountDao accountDao = new AccountDao();
        //调用dao层方法
        accountDao.outMoney(outName,money);

        //System.out.println(1/0);

        accountDao.inMoney(inName,money);
    }
}

1.6.3.转账_dao层实现

public class AccountDao {
    //减钱
    public void outMoney(String outName, int money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //获取连接
        Connection connection = DruidUtils.getConnection();
        //准备sql
        String sql = "update account set money = money-? where name = ?";

        //执行sql
        qr.update(connection,sql,money,outName);

        //关闭资源
        DruidUtils.close(connection,null,null);
    }

    //加钱
    public void inMoney(String inName, int money)throws SQLException{
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //获取连接
        Connection connection = DruidUtils.getConnection();
        //准备sql
        String sql = "update account set money = money+? where name = ?";

        //执行sql
        qr.update(connection,sql,money,inName);

        //关闭资源
        DruidUtils.close(connection,null,null);
    }
}

1621934341606.png

1.6.4.在service层添加事务(传递连接)

public class AccountService {
    /**
     *
     * @param outName   减钱的人
     * @param inName    加钱的人
     * @param money     转账的金额
     */
    public void transfer(String outName, String inName, int money) throws SQLException {
        //获取连接
        Connection connection = DruidUtils.getConnection();
        try{
            //开启事务
            connection.setAutoCommit(false);

            //创建dao对象
            AccountDao accountDao = new AccountDao();
            //调用dao层方法
            accountDao.outMoney(connection,outName,money);

            System.out.println(1/0);

            accountDao.inMoney(connection,inName,money);

            //提交事务
            connection.commit();
            System.out.println("转账成功");
        }catch(Exception e){
            //回滚事务
            connection.rollback();
            System.out.println("转账失败");
        }finally {
            DruidUtils.close(connection,null,null);
        }

    }
}

1.6.5.修改dao层代码

public class AccountDao {
    //减钱
    public void outMoney(Connection connection,String outName, int money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //准备sql
        String sql = "update account set money = money-? where name = ?";

        //执行sql
        qr.update(connection,sql,money,outName);

        //关闭资源
        //DruidUtils.close(connection,null,null);
    }

    //加钱
    public void inMoney(Connection connection,String inName, int money)throws SQLException{
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //准备sql
        String sql = "update account set money = money+? where name = ?";

        //执行sql
        qr.update(connection,sql,money,inName);

        //关闭资源
        //DruidUtils.close(connection,null,null);
    }
}

问题:

直接从连接池中获取连接的动作不应该是service直接干的,但是如果service不干从连接池中获取连接这个事儿,service中的事务操作失效了,dao层也不能接收到Connection对象了

解决思想:

1.不在service层中直接从连接池中获取连接

2.还要保证service和dao都能使用到连接对象,还得是同一条(能保证事务生效)

2.事务的特性以及隔离级别

2.1.事务特性:ACID

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency)事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是效率会非常低。
  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

2.2 并发访问问题

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

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

2.3 隔离级别:解决问题

  • 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
    a)存在:3个问题(脏读、不可重复读、虚读)。
    b)解决:0个问题
  2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    a)存在:2个问题(不可重复读、虚读)。
    b)解决:1个问题(脏读)
  3. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
    a)存在:1个问题(虚读)。
    b)解决:2个问题(脏读、不可重复读)
    4.serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。

a)存在:0个问题。

b)解决:3个问题(脏读、不可重复读、虚读)

  • 安全和性能对比
    • 安全性:serializable > repeatable read > read committed > read uncommitted
    • 性能 : serializable < repeatable read < read committed < read uncommitted
  • 常见数据库的默认隔离级别:
    • MySql:repeatable read
    • Oracle:read committed

2.4 演示

  • 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
  • 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;

h.png

  • 设置数据库的隔离级别
    • set session transactionisolation level 级别字符串
    • 级别字符串:readuncommittedread committedrepeatable readserializable
    • 例如:set session transaction isolation level read uncommitted;
  • 读未提交:readuncommitted
    • A窗口设置隔离级别
      • AB同时开始事务
      • A 查询
      • B 更新,但不提交
      • A 再查询?— 查询到了未提交的数据
      • B 回滚
      • A 再查询?— 查询到事务开始前数据
  • 读已提交:read committed
    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?—数据不变,解决问题【脏读】
      • B提交
      • A再查询?—数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read
    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?—数据不变,解决问题【脏读】
      • B提交
      • A再查询?—数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?—数据改变,另一个事务
  • 串行化:serializable
    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询
      • B更新?—等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?—等待结束,可以进行操作
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 
- 一致性(Consistency)事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是隔离级别越高,效率会非常低。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
如果不考虑隔离性,事务存在3中并发访问问题。(如果隔离级别低,事务跟事务之间有可能互相影响)

1. 脏读:一个事务读到了另一个事务未提交的数据.
2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
总结:
  我们最理想的状态是:一个事务和其他事务互不影响

  但是如果不考虑隔离级别的话,就会出现多个事务之间互相影响

  而事务互相影响的表现方式为:
    脏读
    不可重复读
    虚读/幻读