JDBC_核心技术2

351 阅读18分钟

第六章: 数据库事务 ★

数据库事务介绍

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

JDBC事务处理

  • 数据一旦提交,就不可回滚。
  • 数据什么时候意味着提交?
    • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
    • 关闭当前数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下执行
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:
    • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    • 在出现异常时,调用 rollback(); 方法回滚事务

注意:若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

总结:

  1. 什么是数据库事务

    • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
    • 一组逻辑操作单元,一个或多个 DML操作。
  2. 事务处理的原则:

    • 当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;
    • 要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
  3. 数据一旦提交,就不可回滚

  4. 哪些操作会导致数据的自动提交

    • DDL操作一旦执行,都会自动提交,且不能更改
    • DML默认情况下,一旦执行,就会自动提交
    • 可以通过 set autocommit = false;取消自动提交
    • 默认在关闭数据库连接时,会自动提交数据

案例:用户AA向用户BB转账100

考虑数据库事务下的转账操作

  • 考虑数据库事务下的转账操作
    // **************************考虑数据库事务下的转账操作**************************
    @Test
    public void testTransactionUpdates () {

        Connection connection = null;

        try {
            connection = JDBCUtils.getConnection();

            // 1.取消数据的自动提交
            connection.setAutoCommit(false);

            String sql = "update user_table set balance = balance - 100 where user = ?";
            int aa = transactionUpdates(connection, sql, "AA");

            // 模拟网络异常
            // System.out.println(10 / 0);

            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            int bb = transactionUpdates(connection, sql2, "BB");

            System.out.println(aa + " : " + bb);
            if (aa > 0 && bb > 0) {
                System.out.println("转账成功");
            }
            // 2.手动提交
            connection.commit();

        } catch (Exception e) {
            e.printStackTrace();

            // 3.回滚数据
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            // 修改其为自动提交数据,主要针对于使用数据库连接池的使用
            try {
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JDBCUtils.closeResource(connection, null);
        }

    }

  • (考虑数据库事务)通用 增删改操作,方式二:
    // (考虑数据库事务)通用 增删改操作,方式二:
    public int transactionUpdates (Connection connect, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        try {
            // 1.获取数据库的连接
            // connect = JDBCUtils.getConnection();

            // 1.预编译 sql语句,返回 PreparedStatement的实例
            pps = connect.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            // 3.执行
            // pps.execute();
            return pps.executeUpdate();

        } catch (Exception e) {
            // System.out.println(e.getStackTrace());
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, pps);
        }
        return 0;
    }

未考虑数据库事务下的转账操作

  • 未考虑数据库事务下的转账操作
    @Test
    public void testUpdates () {

        String sql = "update user_table set balance = balance - 100 where user = ?";
        int aa = updates(sql, "AA");

        // 模拟网络异常
        // System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ?";
        int bb = updates(sql2, "BB");

        System.out.println(aa + " : " + bb);
        if (aa > 0 && bb > 0) {
            System.out.println("转账成功");
        }

    }

  • (未考虑数据库事务)通用 增删改操作,方式一:
    // 通用 增删改操作,方式一:
    public int updates (String sql, Object ...args) {

        Connection connect = null;
        PreparedStatement pps = null;
        try {
            // 1.获取数据库的连接
            connect = JDBCUtils.getConnection();
            // 2.预编译 sql语句,返回 PreparedStatement的实例
            pps = connect.prepareStatement(sql);
            // 3.填充占位符
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            // 4.执行
            // pps.execute();
            return pps.executeUpdate();

        } catch (Exception e) {
            // System.out.println(e.getStackTrace());
            e.printStackTrace();
        } finally {
            // 5.资源的关闭
            JDBCUtils.closeResource(connect, pps);
        }
        return 0;
    }

事务的ACID属性

  1. 原子性(Atomicity): 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency): 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  3. 隔离性(Isolation): 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability): 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱

四种隔离级别

  • 数据库提供的4种事务隔离级别:
隔离级别描述
READ UNCOMMITED(读取未提交数据)允许事务读取未被其它事务提交的变更,脏读、不可重复度和 幻读的问题都会出现
READ COMMITED(读取已提交数据)只允许事务读取已经被其它事务提交的变更,可以避免 脏读,但不能避免 不可重复读和 幻读问题
REPEATABLE READ(可以重复读,默认,推荐)确保同一事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其它事务对此字段进行更新,可以避免 脏读和不可重复读,但 幻读问题任然存在
SERIALIZABLE(串行化)禁止其它事务对此表执行 插入、更新和 删除操作,从而所有并发问题都可以避免,但性能十分低下
  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。

  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

在MySql中设置隔离级别

  • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。
  • 查看当前的隔离级别:
    • SELECT @@tx_isolation;
  • 设置当前 mySQL 连接的隔离级别:
    • set transaction isolation level read committed;
  • 设置数据库系统的全局的隔离级别:
    • set global transaction isolation level read committed;

补充操作:

  • 创建mysql数据库用户:
create user tom identified by 'abc123';
  • 授予权限
// 授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
grant all privileges on *.* to tom@'%'  identified by 'abc123'; 

// 给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123'; 

设置隔离级别演示代码:

  • connection.getTransactionIsolation(); 返回当前sql的隔离级别
  • connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); 设置数据库的隔离级别
  • Connection.TRANSACTION_READ_COMMITTED:数据库连接隔离级别常量
    // 以下两个测试,是对 数据库隔离级别的演示
    @Test
    public void testTransactionGetInstance () {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();

            // connection.getTransactionIsolation():返回当前sql的隔离级别
            System.out.println("getTransactionIsolation:" + connection.getTransactionIsolation());
            // 设置数据库的隔离级别
            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            // System.out.println("getTransactionIsolation2:" + connection.getTransactionIsolation());

            // 取消自动提交数据
            connection.setAutoCommit(false);
            String sql = "select user, password, balance from user_table where user = ?";
            User cc = getInstance(connection, User.class, sql, "CC");
            System.out.println("CC:" + cc);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // JDBCUtils.closeResource(connection, null);
        }
    }
    @Test
    public void testTransactionUpdates1 () {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();

            // 取消自动提交数据
            connection.setAutoCommit(false);
            String sql = "update user_table set balance = ? where user = ?";
            int cc = transactionUpdates(connection, sql, "5000", "CC");
            Thread.sleep(15000);
            System.out.println("CC:修改结束," + cc);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // JDBCUtils.closeResource(connection, null);
        }
    }

    /**
     * (考虑数据库事务)通用 查询操作方式二:
     *
     * @param connect
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance (Connection connect, Class<T> clazz, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            // connect = JDBCUtils.getConnection();
            pps = connect.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();
            if (rs.next()) {
                T t = clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    Object colVal = rs.getObject(i + 1);
                    String colLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(colLabel);
                    field.setAccessible(true);
                    field.set(t, colVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }
    // (考虑数据库事务)通用 增删改操作,方式二:
    public int transactionUpdates (Connection connect, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        try {
            // 1.获取数据库的连接
            // connect = JDBCUtils.getConnection();

            // 1.预编译 sql语句,返回 PreparedStatement的实例
            pps = connect.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            // 3.执行
            // pps.execute();
            return pps.executeUpdate();

        } catch (Exception e) {
            // System.out.println(e.getStackTrace());
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, pps);
        }
        return 0;
    }

第七章:DAO及相关实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

BaseDAO.java

  • BaseDAO:抽象类
  • 封装了,针对于数据表的通用操作
  • 后续,根据具体的表去提供具体的 xxxDAO,并继承 BaseDAO
package com.atguigu.dao;

/**
 * @author lv
 * @create 2021-06-23 22:18
 */

import com.atguigu.util.JDBCUtils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * BaseDAO:抽象类
 *
 * 分装了,针对于数据表的通用操作
 * 后续,根据具体的表去提供具体的 DAO,并继承 BaseDAO
 */
public abstract class BaseDAO {
    // (考虑数据库事务)通用 增删改操作,方式二:
    public int Updates (Connection connect, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        try {
            // 1.获取数据库的连接
            // connect = JDBCUtils.getConnection();

            // 1.预编译 sql语句,返回 PreparedStatement的实例
            pps = connect.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            // 3.执行
            // pps.execute();
            return pps.executeUpdate();

        } catch (Exception e) {
            // System.out.println(e.getStackTrace());
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, pps);
        }
        return 0;
    }

    /**
     * (考虑数据库事务)通用 查询操作方式二:
     *
     * @param connect
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public <T> T getInstance (Connection connect, Class<T> clazz, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            // connect = JDBCUtils.getConnection();
            pps = connect.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();
            if (rs.next()) {
                T t = clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    Object colVal = rs.getObject(i + 1);
                    String colLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(colLabel);
                    field.setAccessible(true);
                    field.set(t, colVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }

    // (考虑数据库事务)返回结果为 List的通用查询操作
    public <T> List<T> getForList (Connection connect, Class<T> clazz, String sql, Object ...args) {
        // Connection connect = null;
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            // connect = JDBCUtils.getConnection();
            pps = connect.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();

            ArrayList<T> list = new ArrayList<>();

            while (rs.next()) {
                T t = clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();

                for (int i = 0; i < columnCount; i++) {

                    Object colVal = rs.getObject(i + 1);
                    String colLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(colLabel);
                    field.setAccessible(true);
                    field.set(t, colVal);
                }

                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }

    /**
     * 处理组函数返回的唯一值
     *  》用于查寻特殊值的通用方法
     */
    public <E> E getValue (Connection connec, String sql, Object ...args) {
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            pps = connec.prepareStatement(sql);

            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();
            if (rs.next()) {
                // 返回结果只会是一个值
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }
}

优化:BaseDAO

  • BaseDAO添加泛型 BaseDAO<T>
  • BaseDAO添加 private Class<T> clazz = null;通过泛型获取 clazz实例
  • 在代码块中通过泛型获取 clazz实例
package com.atguigu.optimization_dao;

/**
 * @author lv
 * @create 2021-06-23 22:18
 */

import com.atguigu.util.JDBCUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * BaseDAO:抽象类
 *
 * 分装了,针对于数据表的通用操作
 * 后续,根据具体的表去提供具体的 DAO,并继承 BaseDAO
 */

/**
 * 优化:
 *  1.根据泛型来获取当前类的 class,见下面代码块
 */
public abstract class BaseDAO<T> {
    // 获取当前类的父类的泛型
    private Class<T> clazz = null;

    // 代码块是在 构造器执行后才执行
    // 功能:获取当前 BaseDAO的子类继承的父类中的泛型的实例 class
    {
        // 带泛型的父类 = 当前对象.的类.的带泛型的父类
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        // 带泛型的类通过下面的转换可以得到带参数的type
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        // 获取类的泛型参数,可能多个泛型参数
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        // 获取泛型的第一个参数
        clazz = (Class<T>) actualTypeArguments[0];
    }

    // (考虑数据库事务)通用 增删改操作,方式二:
    public int Updates (Connection connect, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        try {
            // 1.获取数据库的连接
            // connect = JDBCUtils.getConnection();

            // 1.预编译 sql语句,返回 PreparedStatement的实例
            pps = connect.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            // 3.执行
            // pps.execute();
            return pps.executeUpdate();

        } catch (Exception e) {
            // System.out.println(e.getStackTrace());
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, pps);
        }
        return 0;
    }

    /**
     * (考虑数据库事务)通用 查询操作方式二:
     *
     * @param connect
     * @param clazz
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public T getInstance (Connection connect, String sql, Object ...args) {

        // Connection connect = null;
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            // connect = JDBCUtils.getConnection();
            pps = connect.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();
            if (rs.next()) {
                // 反射:获取当前类的父类的泛型的问题,见上面代码块
                T t = clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    Object colVal = rs.getObject(i + 1);
                    String colLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(colLabel);
                    field.setAccessible(true);
                    field.set(t, colVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }

    // (考虑数据库事务)返回结果为 List的通用查询操作
    public List<T> getForList (Connection connect, String sql, Object ...args) {
        // Connection connect = null;
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            // connect = JDBCUtils.getConnection();
            pps = connect.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();

            ArrayList<T> list = new ArrayList<>();

            while (rs.next()) {
                T t = clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();

                for (int i = 0; i < columnCount; i++) {

                    Object colVal = rs.getObject(i + 1);
                    String colLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(colLabel);
                    field.setAccessible(true);
                    field.set(t, colVal);
                }

                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }

    /**
     * 处理组函数返回的唯一值
     *  》用于查寻特殊值的通用方法
     */
    public <E> E getValue (Connection connec, String sql, Object ...args) {
        PreparedStatement pps = null;
        ResultSet rs = null;
        try {
            pps = connec.prepareStatement(sql);

            for (int i = 0; i < args.length; i++) {
                pps.setObject(i + 1, args[i]);
            }
            rs = pps.executeQuery();
            if (rs.next()) {
                // 返回结果只会是一个值
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, pps, rs);
        }

        return null;
    }
}

CustomerDAO.java:Customers表的具体 接口DAO

  • 在接口中声明 对于此表表的操作方法
package com.atguigu.dao;

import com.atguigu.bean.Customer;

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * @author lv
 * @create 2021-06-28 21:36
 */
public interface CustomerDAO {
    /**
     * 将 cust对象添加到数据库中
     * @param con
     * @param cust
     */
    public abstract void insert (Connection con, Customer cust);

    /**
     * 删除指定 id的人员
     * @param con
     * @param id
     */
    void deleteById (Connection con, int id);

    /**
     * 针对内存中的 cust对象,去修改数据表中指定的记录
     * @param con
     * @param cust
     */
    void update (Connection con, Customer cust);

    /**
     * 针对指定的 id查询得到对应的 customer对象
     * @param con
     * @param id
     * @return
     */
    Customer getCustomerById (Connection con, int id);

    /**
     * 获取所有 customer对象构成的集合
     * @param con
     * @return
     */
    List<Customer> getAll (Connection con);

    /**
     * 返回数据表中的数据的总条目数
     * @param con
     * @return
     */
    Long getCount (Connection con);

    /**
     * 返回数据标准中最大的生日
     * @param con
     * @return
     */
    Date getMaxBirth (Connection con);
}

CustomerDAOImpl.java:操作数据库 customers表的具体 实现类DAO

  • 继承抽象类 BaseDAO,及实现接口 CustomerDAO的具体类
package com.atguigu.dao;

import com.atguigu.bean.Customer;

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * DAO:data(base)access object 数据库访问对象
 *   使用 java代码实现操作 数据库的功能方法
 * @author lv
 * @create 2021-06-28 21:55
 */
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
    @Override
    public void insert(Connection con, Customer cust) {

        String sql = "insert into customers(name, email, birth)values(?, ?, ?)";
        Updates(con, sql, cust.getName(), cust.getEmail(), cust.getBirth());
    }

    @Override
    public void deleteById(Connection con, int id) {

        String sql = "delete from customers where id = ?";
        Updates(con, sql, id);
    }

    @Override
    public void update(Connection con, Customer cust) {

        String sql = "update customers set name = ?, email = ?, birth = ? where id = ?";
        Updates(con, sql, cust.getName(), cust.getEmail(), cust.getBirth(), cust.getId());
    }

    @Override
    public Customer getCustomerById(Connection con, int id) {
        String sql = "select id, name, email, birth from customers where id = ?";
        return getInstance(con, Customer.class, sql, id);
//        return null;
    }

    @Override
    public List<Customer> getAll(Connection con) {
        String sql = "select id, name, email, birth from customers";
        List<Customer> forList = getForList(con, Customer.class, sql);
        return forList;
    }

    @Override
    public Long getCount(Connection con) {
        String sql = "select count(*) from customers";
        return getValue(con, sql);
//        Object value = getValue(con, sql);
//        return (Long) value;
    }

    @Override
    public Date getMaxBirth(Connection con) {
        String sql = "select max(birth) from customers";
        return getValue(con, sql);
//        Object value = getValue(con, sql);
//        return (Date) value;
    }
}

优化:CustomerDAOImpl

  • BaseDAO添加泛型 BaseDAO<Customer>
  • 使得方法 getCustomerById方法和 getAll方法,不必单独传入 Customer.class实例
package com.atguigu.optimization_dao;

import com.atguigu.bean.Customer;

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * DAO:data(base)access object 数据库访问对象
 *   使用 java代码实现操作 数据库的功能方法
 * @author lv
 * @create 2021-06-28 21:55
 */

/**
 * 优化:
 *  1.根据泛型来获取当前类的 class
 */
//public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {

public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {
    @Override
    public void insert(Connection con, Customer cust) {

        String sql = "insert into customers(name, email, birth)values(?, ?, ?)";
        Updates(con, sql, cust.getName(), cust.getEmail(), cust.getBirth());
    }

    @Override
    public void deleteById(Connection con, int id) {

        String sql = "delete from customers where id = ?";
        Updates(con, sql, id);
    }

    @Override
    public void update(Connection con, Customer cust) {

        String sql = "update customers set name = ?, email = ?, birth = ? where id = ?";
        Updates(con, sql, cust.getName(), cust.getEmail(), cust.getBirth(), cust.getId());
    }

    @Override
    public Customer getCustomerById(Connection con, int id) {
        String sql = "select id, name, email, birth from customers where id = ?";
        // return getInstance(con, Customer.class, sql, id);
        return getInstance(con, sql, id);
//        return null;
    }

    @Override
    public List<Customer> getAll(Connection con) {
        String sql = "select id, name, email, birth from customers";
        // List<Customer> forList = getForList(con, Customer.class, sql);
        List<Customer> forList = getForList(con, sql);
        return forList;
    }

    @Override
    public Long getCount(Connection con) {
        String sql = "select count(*) from customers";
        return getValue(con, sql);
//        Object value = getValue(con, sql);
//        return (Long) value;
    }

    @Override
    public Date getMaxBirth(Connection con) {
        String sql = "select max(birth) from customers";
        return getValue(con, sql);
//        Object value = getValue(con, sql);
//        return (Date) value;
    }
}

数据库连接池

JDBC数据库连接池的必要性

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:  
    • 在主程序(如servlet、beans)中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 这种模式开发,存在的问题:
    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
    • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
    • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池技术的优点

  1. 资源重用

由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

  1. 更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

  1. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

  1. 统一的连接管理,避免数据库连接泄漏

在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

C3P0数据库连接池

方式一:
package com.atguigu.connection_source;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author lv
 * @create 2021-06-30 21:38
 */
public class C3P0Test {
    // 方式一:
    @Test
    public void testGetConnection () {
        // 获取 C3P0数据库连接池
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        Connection connect = null;
        try {
            cpds.setDriverClass("com.mysql.jdbc.Driver");
            cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
            cpds.setUser("root");
            cpds.setPassword("root");
            /**
             * 通过设置相关的参数,对数据库连接池进行管理
             *  参考文档:11.Appendix A: Configuration Properties
             */
            // 设置初始时数据库连接池中的连接数
            cpds.setInitialPoolSize(10);

            // 获取连接池中的某个连接
            connect = cpds.getConnection();
            System.out.println(connect);

            // 销毁 C3P0数据库连接池
            // DataSources.destroy(cpds);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
//            try {
//                if (null != connect)
//                    connect.close();
//            } catch (SQLException e) {
//                e.printStackTrace();
//            }
//            cpds.close();
        }
    }
}
方式二:
package com.atguigu.connection_source;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author lv
 * @create 2021-06-30 21:38
 */
public class C3P0Test {
    // 方式二:使用配置文件 c3p0-config.xml
    @Test
    public void testGetConnection2 () {
        ComboPooledDataSource cpds = new ComboPooledDataSource("c3p0Properties");
        try {
            Connection connect = cpds.getConnection();
            System.out.println(connect);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}    
  • c3p0-config.xml
<?xml version="1.0" encoding="utf-8" ?>
<c3p0-config>

    <!-- This app is massive! -->
    <named-config name="c3p0Properties">
        <!--
            提供获取连接的四个基本信息
        -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 数据库连接池的基本属性 -->
        <!-- 当数据库连接池中的连接个数不够时,C3P0一次性向数据库申请的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- 连接池中初始的连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最少连接数 -->
        <property name="minPoolSize">10</property>
        <!-- 最大连接数 -->
        <property name="maxPoolSize">100</property>

        <!-- intergalactoApp adopts a different approach to configuring statement caching -->
        <!-- C3P0连接池最多维护的 Statement的个数 -->
        <property name="maxStatements">50</property>
        <!-- 单个连接中最多可以使用的 Statement的个数 -->
        <property name="maxStatementsPerConnection">2</property>

    </named-config>
</c3p0-config>
方式三:推荐
  • 封装获取连接池的连接方式:JDBCUtilsC3P0
package com.atguigu.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author lv
 * @create 2021-07-01 21:03
 */
public class JDBCUtilsC3P0 {
    // 创建唯一的数据库连接池
    private static ComboPooledDataSource cpds = new ComboPooledDataSource("c3p0Properties");

    public static Connection getConnection () throws SQLException {

        Connection connect = cpds.getConnection();

        return connect;
    }
}
  • c3p0-config.xml
<?xml version="1.0" encoding="utf-8" ?>
<c3p0-config>

    <!-- This app is massive! -->
    <named-config name="c3p0Properties">
        <!--
            提供获取连接的四个基本信息
        -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 数据库连接池的基本属性 -->
        <!-- 当数据库连接池中的连接个数不够时,C3P0一次性向数据库申请的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- 连接池中初始的连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最少连接数 -->
        <property name="minPoolSize">10</property>
        <!-- 最大连接数 -->
        <property name="maxPoolSize">100</property>

        <!-- intergalactoApp adopts a different approach to configuring statement caching -->
        <!-- C3P0连接池最多维护的 Statement的个数 -->
        <property name="maxStatements">50</property>
        <!-- 单个连接中最多可以使用的 Statement的个数 -->
        <property name="maxStatementsPerConnection">2</property>

    </named-config>
</c3p0-config>

DBCP数据库连接池

方式一:
package com.atguigu.connection_source;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author lv
 * @create 2021-07-01 21:22
 */
public class DBCPTest {

    // 方式一:不推荐
    @Test
    public void testGetConnection () {
        // 创建 DBCP数据库连接池
        BasicDataSource bds = new BasicDataSource();

        // 设置基本信息
        bds.setDriverClassName("com.mysql.jdbc.Driver");
        bds.setUrl("jdbc:mysql://localhost:3306/test");
        bds.setUsername("root");
        bds.setPassword("root");
        // 涉及数据库连接池的属性设置
        bds.setInitialSize(10);
        bds.setMaxActive(10);
        // 获取连接池中的一个连接
        Connection connect = null;
        try {
            connect = bds.getConnection();

            System.out.println(connect);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
方式二:
package com.atguigu.connection_source;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author lv
 * @create 2021-07-01 21:22
 */
public class DBCPTest {

    // 方式二:(推荐)使用配置文件 dbcp.properties
    @Test
    public void testGetConnection1 () {
        Properties pros = new Properties();
        // 方式一:使用类的加载器加载文件流
        // InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
        FileInputStream fis = null;
        Connection connect = null;
        try {
            // 方式二:使用 FileInputStream加载输入流
            // File:当前工程下
            fis = new FileInputStream(new File("src/dbcp.properties"));
            pros.load(fis);
            DataSource ds = BasicDataSourceFactory.createDataSource(pros);
            connect = ds.getConnection();

            System.out.println(connect);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • src下的配置文件为:dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
initialSize=10
方式三:推荐
  • 创建唯一的数据库连接池
  • 静态代码块随着类的加载而加载,且只执行一次
package com.atguigu.util;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;

/**
 * @author lv
 * @create 2021-07-01 21:03
 */
public class JDBCUtilsDBCP {

    private static DataSource ds = null;
    // 创建唯一的数据库连接池
    // 静态代码块随着类的加载而加载,且只执行一次
    static {

        Properties pros = new Properties();
        // 方式一:使用类的加载器加载文件流
        // InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
        FileInputStream fis = null;
        try {
            // 方式二:使用 FileInputStream加载输入流
            // File:当前工程下
            fis = new FileInputStream(new File("src/dbcp.properties"));
            pros.load(fis);
            DataSource ds = BasicDataSourceFactory.createDataSource(pros);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection () throws Exception {

        Connection connect = ds.getConnection();

        return connect;
    }
}

Druid(德鲁伊)数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一

方式一:
package com.atguigu.connection_source;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.atguigu.util.JDBCUtilsDurid;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author lv
 * @create 2021-07-03 21:10
 */
public class DuridTest {
    // 方式二:
    @Test
    public void testJDBCtilsDurid () {
        try {
            Connection connect = JDBCUtilsDurid.getConnection();
            System.out.println(connect);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    // 方式一:
    @Test
    public void testDurid () {
//        DataSource dds = new DruidDataSource();
        Properties pros = new Properties();
//        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("durid.properties");
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("durid.properties");
        DataSource ds = null;
        Connection connect = null;
        try {
            pros.load(is);
            /**
             * initialSize的个数要 》= maxSize的个数
             */
            ds = DruidDataSourceFactory.createDataSource(pros);

            connect = ds.getConnection();

            System.out.println(connect);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
方式二:推荐
package com.atguigu.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.dbutils.DbUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * @author lv
 * @create 2021-07-03 21:49
 */
public class JDBCUtilsDurid {
    private static DataSource ds = null;
    static {
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("durid.properties");
        try {
            pros.load(is);
            ds = DruidDataSourceFactory.createDataSource(pros);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection () throws SQLException {
        return ds.getConnection();
    }

    public static void closeResource (Connection connect, Statement pps) {
        if (connect != null) {
            try {
                connect.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pps != null) {
            try {
                pps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void closeResource (Connection connect, Statement pps, ResultSet res) {
        if (connect != null) {
            try {
                connect.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pps != null) {
            try {
                pps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (res != null) {
            try {
                res.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用 dbutils.jar中提供的工具类 DbUtils关闭资源
     */
    public static void dbUtilsCloseResource (Connection connect, Statement pps, ResultSet res) {
//        DbUtils.closeQuietly(connect);
        try {
            DbUtils.close(connect);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            DbUtils.close(pps);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            DbUtils.close(res);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

问题:

事务处理的原则:

保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态

考虑到事务以后的数据库操作:

获取数据库的连接方式:
// 1.手动获取连接;2.数据库连接池获取连接
Connection connect = JDBCUtils.getConnection();
// 体现事务,取消自动提交
connect.setAutoCommit(false);
如下多个 DML操作,作为一个事务出现:
  • 方式一:使用 PreparedStatement实现
  • 方式二:使用 dbutils.jar的 QueryRunner类实现
1. 增删改操作
2. 查询操作
3. ...

connect.commit();
出现异常,则:
connect.rollback();
关闭资源:
// 1.手动关闭;2.使用 DbUtils类关闭
JDBCUtils.closeRource(...);

数据库连接池:

  • 提高程序的响应速度(减少创建连接的时间)
  • 降低资源的消耗(可以重复使用已经提供好的连接)
  • 便于连接的管理