事务&数据库连接池&DBUtils

事务

  1. Transaction 其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)

  2. 事务的作用:为了确保逻辑的成功。 例子: 银行的转账。

  3. 使用命令行方式演示事务。

    • 关闭自动提交功能。


      关闭自动提交
    • 开启事务 start transaction;

      开启事务

    • 提交或者回滚事务:事务提交后或者回滚就结束了

      1. commit; 提交事务, 数据将会写到磁盘上的数据库


        commit
      2. rollback ; 数据回滚,回到最初的状态


        rollback
  4. 使用代码方式演示事务

public void testTransaction() throws SQLException {
    Connection conn = null;
    PreparedStatement ps = null;

    try {
        conn = JDBCUtil.getConn();
        String sql = "update bank set money = money - ? where id = ?";
        ps = conn.prepareStatement(sql);

        // 关闭提交 事务只是针对连接连接对象,如果再开一个连接对象,那么还是默认的提交。
        conn.setAutoCommit(false);

        ps.setInt(1, 100);
        ps.setInt(2, 1);
        ps.executeUpdate();

        int a = 10 / 0;

        ps.setInt(1, -100);
        ps.setInt(2, 2);
        ps.executeUpdate();

        // 两段代码都执行成功 提交事务
        conn.commit();
    } catch (SQLException e) {
        // 出现异常,回滚事务
        conn.rollback();
        e.printStackTrace();
    } finally {
        JDBCUtil.release(conn, ps);
    }

}
  1. 事务的特性
    • 原子性:指的是 事务中包含的逻辑,不可分割。
    • 一致性:指的是 事务执行前后。数据完整性
    • 隔离性:指的是 事务在执行期间不应该受到其他事务的影响
    • 持久性:指的是 事务执行成功,那么数据应该持久保存到磁盘上。
  2. 事务的安全隐患 :不考虑隔离级别设置,那么会出现以下问题
      1. 脏读:一个事务读到另外一个事务还未提交的数据
      2. 不可重复读 :一个事务读到了另外一个事务提交的数据 ,造成了前后两次查询结果不一致。
      3. 幻读:一个事务读到了另一个事务insert的数据 ,造成前后查询结果不一致 。
    • 写:丢失更新


      丢失更新
      1. 悲观锁(认为一定会丢失更新):可以在查询的时候,加入 for update(数据库锁机制,排他锁),有点类似序列化


        悲观锁
      2. 乐观锁(认为一定不会丢失更新):要求程序员自己控制。


        乐观锁
  3. 事务的隔离级别:mySql 默认的隔离级别是 可重复读,Oracle 默认的隔离级别是 读已提交
    • 读未提交(Read Uncommitted):可以读到其他事务未提交的数据。引发问题: 脏读
    • 读已提交(Read Committed):只能读到已提交的数据。解决: 脏读 , 引发: 不可重复读
    • 可重复读(Repeatable Read):事务中读取的数据不受其他事务提交数据的影响,前后读取的数据一致。解决: 脏读 、 不可重复读 , 未解决: 幻读
    • 可串行化(Serializable) :如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。解决: 脏读、 不可重复读 、 幻读。
    • 按效率划分,从高到低 读未提交 > 读已提交 > 可重复读 > 可串行化
    • 按拦截程度 ,从高到底 可串行化 > 可重复读 > 读已提交 > 读未提交

数据库连接池

  1. 数据库的连接对象创建工作,比较消耗性能。 一开始先在内存中开辟一块空间(集合) , 先往池子里面放置 多个连接对象。 后面需要连接的话,直接从池子里面去。不要去自己创建连接了。 使用完毕, 要记得归还连接,确保连接对象能循环利用。Sun公司定义了一个连接池接口DataSource
  2. 自定义连接池
    • 连接池类
    **
    * 实现连接池,一开始在连接池中有十个连接对象
    */
    public class MyDataSource implements DataSource {
    
    List<Connection> list = new ArrayList<>(); // 连接池集合
    
    public MyDataSource() { // 构造函数 在连接池中初始化十个连接对象
        for (int i = 0; i < 10; i++) {
            list.add(JDBCUtil.getConn());
        }
    }
    
    /**
     * 该连接池对外公布获取连接池的方法
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {
        if(list.size() == 0 ) { // 连接池中没有连接对象 扩容
            for (int i = 0; i < 5; i++) {
                list.add(JDBCUtil.getConn());
            }
        }
    
        // 移除连接池中第一个连接对象,包装过后并将它返回
        Connection conn = list.remove(0);
        Connection connWrap = new ConnectionWrap(conn, list);
    
        return connWrap;
    }
    
    • 使用装饰者模式解决连接池的归还问题,符合面向接口编程
    public class ConnectionWrap implements Connection {
    
    Connection conn = null;
    List<Connection> list = null;
    
    public ConnectionWrap(Connection conn, List<Connection> list) {
        super();
        this.conn = conn;
        this.list = list;
    }
    
    @Override
    public void close() throws SQLException {
        // 在这里写归还操作
        System.out.println("归还前" + list.size());
        list.add(conn);
        System.out.println("归还后" + list.size());
    }
    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }
    
    • 连接池的使用
    @Test
    public  void testPool() throws SQLException {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        MyDataSource dataSource = new MyDataSource();
        try {
            // 其实在这里得到的是 ConnectionWrap 对象
            connection = dataSource.getConnection();
            String sql = "select * from bank";
    
            ps = connection.prepareStatement(sql);
            rs = ps.executeQuery();
    
            while(rs.next()) {
                String name = rs.getString("name");
                int id = rs.getInt("id");
                int money = rs.getInt("money");
                System.out.println(id + "---" + name + "---" + money);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.release(connection, ps, rs);
        }
    }
    
  3. 开源连接池
    • DBCP
      1. 导入jar包
      2. 不使用配置文件
      public class DBCPDemo {
        @Test
        public void testDBCP01() {
          Connection conn = null;
          PreparedStatement ps = null;
          ResultSet rs = null;
      
          try {
            // 1. 得到连接池对象
            BasicDataSource dataSource = new BasicDataSource();
      
            // 2. 设置连接属性
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3307/hgzdata");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
      
            // 3. 获取连接对象
            conn = dataSource.getConnection();
      
            // 4. 数据库操作
            String sql = "select * from bank";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int money = rs.getInt("money");
                System.out.println(id + "---" + name + "---" + money);
      
            }
          } catch (SQLException e) {
              e.printStackTrace();
          } finally {
              JDBCUtil.release(conn, ps, rs);
          }
        }
      }
      
      1. 使用配置文件 将配置文件 dbcpconfig.properties 放置在 src 目录下
      BasicDataSourceFactory factory = new BasicDataSourceFactory();
      Properties properties = new Properties();
      InputStream is = new FileInputStream("src/dbcpconfig.properties");
      properties.load(is);
      DataSource dataSource = factory.createDataSource(properties);
      
    • C3P0
      1. 导入 jar 包
      2. 不使用配置文件
      // 1. 得到连接池对象
      ComboPooledDataSource dataSource = new ComboPooledDataSource();
      
      // 2. 设置连接属性
      dataSource.setDriverClass("com.mysql.jdbc.Driver");
      dataSource.setJdbcUrl("jdbc:mysql://localhost:3307/hgzdata");
      dataSource.setUser("root");
      dataSource.setPassword("root");
      
      1. 使用配置文件 将配置文件 c3p0-config.xml 放在 src 目录下
      // 1. 得到连接池对象 默认读取配置文件 获取连接信息
      ComboPooledDataSource dataSource = new ComboPooledDataSource("configname");
      

DBUtils

  1. dbutils 只是帮我们简化了CRUD 的代码, 但是连接的创建以及获取工作。 不在他的考虑范围,导入 jar 包
  2. update 操作
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//增加
queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
    
//删除
queryRunner.update("delete from account where id = ?", 5);
    
//更新
queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);
  1. query 操作 new接口的匿名实现类
public void queryTest() {
    // 获取查询对象
    QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());

    try {
        User user = queryRunner.query("select * from bank where id = ?", new ResultSetHandler<User>() {

            @Override
            public User handle(ResultSet resultSet) throws SQLException {
                User user = new User();
                while (resultSet.next()) {
                    user.setName(resultSet.getString("name"));
                    user.setMoney(resultSet.getInt("money"));
                }
                return user;
            }
        }, 1);
        System.out.println(user.toString());
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
  1. query 操作 使用框架的 接口实现类 查询一行数据
public void queryTest1() {
    QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());

    // 查询单行数据
    try {
        User user = queryRunner.query("select * from bank where id = ?",
                new BeanHandler<User>(User.class)
                , 2);
        System.out.println(user.toString());
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
  1. query 操作 使用框架的 接口实现类 查询多行数据
public void queryTest2() {
    QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());

    // 查询单行数据
    try {
        List<User> users = queryRunner.query("select * from bank",
                new BeanListHandler<User>(User.class));
        for (User user: users
             ) {
            System.out.println(user.toString());
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
  1. ResultSetHandler(queryRunner.query()第二个参数) 常用的实现类
    • 最常用
    BeanHandler,  查询到的单个数据封装成一个对象
    BeanListHandler, 查询到的多个数据封装 成一个List<对象>
    
    ArrayHandler,  查询到的单个数据封装成一个数组
    ArrayListHandler,  查询到的多个数据封装成一个集合 ,集合里面的元素是数组。
    
    MapHandler,  查询到的单个数据封装成一个map
    MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。 
    
    • 不常用
    ColumnListHandler
    KeyedHandler
    ScalarHandler
    
  2. 模拟DBUtils功能代码
    • update
    public void update(String sql, Object ...args) {
        Connection conn = null;
        PreparedStatement ps = null;
    
        try {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
    
            conn = dataSource.getConnection();
    
            ps = conn.prepareStatement(sql);
    
            // 根据元数据参数 获取问号个数
            ParameterMetaData parameterMetaData = ps.getParameterMetaData();
            int paramerterCount = parameterMetaData.getParameterCount();
    
            for (int i = 0; i < paramerterCount ; i++) {
                ps.setObject(i + 1, args[i]);
            }
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(conn,ps);
        }
    }
    
    • query
    public <T> T query(String sql,ResultHandler<T> handler ,Object ...args) {
    
        Connection conn = null;
        PreparedStatement ps= null;
        ResultSet rs = null;
    
        // 1. 获取连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
    
        try {
            // 2. 获取连接对象
            conn = dataSource.getConnection();
            ps = conn.prepareStatement(sql);
            // 3. 根据问号个数填充参数
            ParameterMetaData parameterMetaData = ps.getParameterMetaData();
            int paramterCount = parameterMetaData.getParameterCount();
            for (int i = 0; i < paramterCount ; i++) {
                ps.setObject(i+1, args[i]);
            }
            rs = ps.executeQuery();
            // 4. 让传入的 结果处理对象来处理 结果集
            T t = (T) handler.handle(rs);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            JDBCUtil.release(conn, ps, rs);
        }
    }
    
    结果集处理接口
    public interface ResultHandler<T>{
        T handle(ResultSet rs);
    }
    
    query 方法的调用
        public void testQuery(){
        User user = query("select * from bank where id = ?", new ResultHandler<User>() {
            @Override
            public User handle(ResultSet rs) {
                User user = new User();
                try {
                    if (rs.next()) {
                        user.setName(rs.getString("name"));
                        user.setMoney(rs.getInt("money"));
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return user;
            }
        }, 3);
    
        System.out.println(user.toString());
    }
    

推荐阅读更多精彩内容