JDBC


JDBC 概述

数据的持久化

  • 把数据存储在可掉电设备中以供以后使用
  • 主要是存储在关系型数据库中

Java中的数据存储技术

  • JDBC
  • JDO
  • 第三方O/R工具,例如Hibernate,Mybatis等
  • JDBC是Java访问数据库的基石,其他方式只是更好的封装了JDBC

JDBC技术

  • JDBC主要提供了一组用于访问数据库的通用接口(规范)
  • JDBC会调用相应数据库的驱动来完成对数据库的操作
  • JDBC包含了面向应用的API和面向数据库的API
  • 面向接口的编程思想
  • 具体过程
  • Statement完成具体的操作
  • Driver(驱动)接口提供了取得链接的方法

实现

实现方式1:

@Test
    public void testConnection1() throws SQLException {
        Driver driver = new com.mysql.cj.jdbc.Driver();

        String url = "jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC";

        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password","123456Asdfgh");

        Connection conn = driver.connect(url, info);

        System.out.println(conn);

    }

注:未加?serverTimezone=UTC时可能会出现报错

实现方式2:

        //使用反射获得实现类对象,增加可修改性
        Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver)clazz.newInstance();

        String url = "jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC";

        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password","123456Asdfgh");

        Connection conn = driver.connect(url, info);

        System.out.println(conn);

实现方式3:

        //使用反射获得实现类对象
        Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver)clazz.newInstance();

        String url = "jdbc:mysql://localhost:3306/JDBCLearning";
        String user = "root";
        String password = "123456Asdfgh";

        //注册驱动
        DriverManager.registerDriver(driver);

        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);

实现方式4::boom:

        String url = "jdbc:mysql://localhost:3306/JDBCLearning";
        String user = "root";
        String password = "123456Asdfgh";

        //加载驱动,对应的静态代码块会被执行
        Class.forName("com.mysql.cj.jdbc.Driver");
//        Driver driver = (Driver)clazz.newInstance();
//        //注册驱动
//        DriverManager.registerDriver(driver);

        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);

通过查看com.mysql.cj.jdbc.Driver的源代码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

不难发现,在此段代码中的静态代码块会在Driver对象被创建时执行,也即会自行创建一个驱动。

其实这里的Class.forName(“com.mysql.cj.jdbc.Driver”)甚至也可以省略,由于,MySQL的驱动META-INF/service/java.sql.Driver中已经记录了MySQL的驱动名称,但是,极其不推荐这样做,因为这样的做法不一定对所有的数据库(如Oracle)均有效。

实现方式5:

        //读取配置文件的信息
        //用户自定义类获取的是系统类加载器
        InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("src/jdbc.properties");
        Properties properties = new Properties();
        properties.load(inputStream);

        String user=properties.getProperty("user");
        String password=properties.getProperty("password");
        String url=properties.getProperty("url");
        String driverClass=properties.getProperty("driverClass");

        //加载驱动
        Class.forName(driverClass);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

通过将参数单独配置在配置文件中,进而可以进一步提高代码的可重用性,实现了数据与代码的分离,降低了代码耦合性。

同时,这种方式也避免了修改文件时重新打包造成的时间浪费。

使用PreparedStatement实现CRUD操作

  • 三种调用方式的区别:

    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PreparedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程
  • Statement的弊端:

    • SQL注入问题:SELECT user,password FROM user_table WHERE user = ‘“ + user + “‘ AND PASSWORD = ‘“ + “password”‘

      ​ SELECT user,password FROM user_table WHERE user = ‘1’ or ‘ AND PASSWORD = ‘=1 or ‘1’ = ‘1’

      注意这里的and逻辑被改为了or逻辑,导致SQL注入问题

    • 字符串拼接问题

具体实现代码

insert:

    public void testInsert() throws IOException, ClassNotFoundException, SQLException, ParseException {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //读取配置文件的信息
            InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(inputStream);

            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");

            //加载驱动
            Class.forName(driverClass);

            //获取连接
            connection = DriverManager.getConnection(url, user, password);
            //System.out.println(connection);

            //预编译sql语句,返回PreparedStatement实例
            String sql = "insert into customers(name,email,birth) values (?,?,?)";
            preparedStatement = connection.prepareStatement(sql);

            //填充占位符,注意索引是从1开始
            preparedStatement.setString(1, "ysz001");
            preparedStatement.setString(2, "ysz001@ysz.com");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = simpleDateFormat.parse("2000-01-01");
            preparedStatement.setDate(3, new Date(date.getTime()));

            //执行SQL
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
  • 这里请注意数据库的下标是从1开始的,而数组的下标仍然是从0开始的。

  • 具体在书写时可以先将Exceptions全部抛出,由于是涉及到资源关闭的问题,所以需要在完成主体代码后将throws改为try-catch-finally结构。

  • 注意到这里的建立连接与关闭连接和statement均为可复用结构,故将这两个部分单独抽取出来。

JDBCUtil:

    //获取数据库的连接
    public static Connection getConnection() throws ClassNotFoundException, SQLException, IOException {
        //读取配置文件的信息
        InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(inputStream);

        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        //加载驱动
        Class.forName(driverClass);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }

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

注:这里将ConnectionTest.class.getClassLoader().getResourceAsStream(“jdbc.properties”)改为了ClassLoader.getSystemClassLoader().getResourceAsStream(“jdbc.properties”),是因为对于用户自定义类,ConnectionTest.class.getClassLoader()的结果就是ClassLoader.getSystemClassLoader()

update:

    public void testUpdate() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //获取数据库连接
            connection = JDBCUtils.getConnection();

            //预编译SQL,返回PreparedStatement实例
            String sql = "update customers set name=? where id=?";
            preparedStatement = connection.prepareStatement(sql);

            //填充占位符
            preparedStatement.setObject(1, "ysz002");
            preparedStatement.setObject(2, "19");

            //执行
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //资源的关闭
            JDBCUtils.closeResource(connection, preparedStatement);
        }
    }

update,最后这里给出一个通用的增删改操作函数:

    //通用增删改操作
    public void update(String sql, Object... args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //获取连接
            connection = JDBCUtils.getConnection();
            //预编译SQL语句
            preparedStatement = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);//数据的下标是从1开始的哦
            }
            //执行
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //资源的关闭
            JDBCUtils.closeResource(connection, preparedStatement);
        }
    }

ORM的编程思想

  • 一个数据表对应一个Java类

  • 表中一条记录对应Java类的一个对象

  • 表中一个字段对应Java类的一个属性

Java类型 SQL类型(MySQL)
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY,VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP

在如下位置构建bean对象,并生成其有参无参构造器以及getter,setter方法(代码就不贴了,alt+insert就出来了)

这里先给出一个基本的代码示例

    public void testQuery1() throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id =?";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();

            //判断结果集中是否还有数据
            if (resultSet.next()) {
                //获取结果集中的各个字段
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);

                ////使用数组封装数据
                //Object[] data = {id, name, email, birth};

                //将数据封装为一个对象
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
    }

观察后我们可以发现上面的代码具有较强的耦合性,为了降低耦合性,我们可以联想到学习数据库时提到的**元数据**的概念,根据元数据中记录的属性值,我们可以获得结果的列数以及特定列的列名。

connection = JDBCUtils.getConnection();

preparedStatement = connection.prepareStatement(sql);

for (int i = 0; i < args.length; i++) {
    preparedStatement.setObject(i + 1, args[i]);
}

resultSet = preparedStatement.executeQuery();

//获取结果的元数据
ResultSetMetaData metaData = resultSet.getMetaData();

//可以通过元数据获取结果的列数
int columnCount = metaData.getColumnCount();

同时,对于结果集中的每一行记录,我们可以尝试使用**反射**的机制来为其对应属性赋值

//获取每个列的列名
String columnName = metaData.getColumnName(i + 1);

//给customer的某一属性赋值为value,**反射**
Field declaredField = Customer.class.getDeclaredField(columnName);
declaredField.setAccessible(true);
declaredField.set(customer, value);

总的代码如下所示

    public Customer queryForCustomers(String sql,Object... args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();

            preparedStatement = connection.prepareStatement(sql);

            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            resultSet = preparedStatement.executeQuery();

            //获取结果的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();

            //可以通过元数据获取结果的列数
            int columnCount = metaData.getColumnCount();

            if (resultSet.next()) {
                Customer customer = new Customer();
                //处理某一行数据
                for (int i = 0; i < columnCount; i++) {
                    Object value = resultSet.getObject(i + 1);

                    //获取每个列的列名
                    String columnName = metaData.getColumnName(i + 1);

                    //给customer的某一属性赋值为value,**反射**
                    Field declaredField = Customer.class.getDeclaredField(columnName);
                    declaredField.setAccessible(true);
                    declaredField.set(customer, value);
                }
                return customer;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
        return null;
    }

数据库字段名称与Java程序属性名不一致时

  • 声明SQL时,要使用类的属性名来命名字段的别名

  • 使用ResultSetMetaData时,要使用getColumnLable()来取代getColumnName()

  • 若没有别名时,getColumnLable()返回列名

    也即,对于SQL “select Name name” ,该语句中Name为数据库中的字段名称,而name为Java程序中的属性名。使用getColumnName()返回Name,而使用getColumnLable()时返回name。

    //获取每个列的列名
    //getColumnName():获取列的列名
    //getColumnLabel():获取列的别名
    String columnLabel = metaData.getColumnLabel(i + 1);
    
    //通过反射将指定名的属性赋值为指定值
    Field declaredField = Order.class.getDeclaredField(columnLabel);
    declaredField.setAccessible(true);
    declaredField.set(order, value);

通用单个对象get

通过进一步观察我们可以发现,在Customer customer = new Customer()时的代码耦合性依然较强,这里我们可以采用反射+范型的方法来进一步改进代码。

    public <T> T getInstance(Class<T> clazz, String sql, Object... args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();

            preparedStatement = connection.prepareStatement(sql);

            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }

            resultSet = preparedStatement.executeQuery();

            //获取结果的元数据
            ResultSetMetaData metaData = resultSet.getMetaData();

            //可以通过元数据获取结果的列数
            int columnCount = metaData.getColumnCount();

            if (resultSet.next()) {
                T t = clazz.newInstance();
                //处理某一行数据
                for (int i = 0; i < columnCount; i++) {
                    Object value = resultSet.getObject(i + 1);

                    //获取每个列的列名
                    String columnName = metaData.getColumnName(i + 1);

                    //给customer的某一属性赋值为value,**反射**
                    Field declaredField = Customer.class.getDeclaredField(columnName);
                    declaredField.setAccessible(true);
                    declaredField.set(t, value);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, preparedStatement, resultSet);
        }
        return null;
    }

通用的查询操作,返回一个List集合

//通用的查询操作
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        connection = JDBCUtils.getConnection();

        preparedStatement = connection.prepareStatement(sql);

        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }

        resultSet = preparedStatement.executeQuery();

        //获取结果的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();

        //可以通过元数据获取结果的列数
        int columnCount = metaData.getColumnCount();

        //用于保存结果集合
        ArrayList<T> list = new ArrayList<>();

        while (resultSet.next()) {
            T t = clazz.newInstance();
            //处理某一行数据
            for (int i = 0; i < columnCount; i++) {
                Object value = resultSet.getObject(i + 1);

                //获取每个列的列名
                String columnName = metaData.getColumnName(i + 1);

                //给customer的某一属性赋值为value,**反射**
                Field declaredField = Customer.class.getDeclaredField(columnName);
                declaredField.setAccessible(true);
                declaredField.set(t, value);
            }
            list.add(t);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection, preparedStatement, resultSet);
    }
    return null;
}

解决SQL注入问题

由于PreparedStatement采用预编译的方式,所以原有SQL逻辑并不会发生改变,所以也能很好的解决SQL注入问题。

同时PreparedStatement还可以解决拼串问题。

此外,PreparedStatement还具有一些其他方面的优势。

存储Blob数据类型

Blob类型

  • 最大支持4G的单个文件

    类型 大小max
    TinyBlob 255B
    Blob 65KB
    MediumBlob 16MB
    LongBlob 4GB
  • 只能使用PreparedStatement操作Blob类型

  • 若出现文件过大报错(PacketTooBigException),则应找到目录的安装路径下对应的./MySQL Serverx.x/my.ini文件,在其中加入max_allowed_packet=16M(大小按照自己需求),并重启服务器即可。

插入Blob

//向数据表customers中插入Blob类型的字段
@Test
public void testInsert() throws Exception {
    Connection connection = JDBCUtils.getConnection();
    String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";

    PreparedStatement preparedStatement = connection.prepareStatement(sql);

    preparedStatement.setObject(1, "ysz003");
    preparedStatement.setObject(2, "ysz003@ysz.com");
    preparedStatement.setObject(3, "2000-01-01");
    FileInputStream fileInputStream = new FileInputStream(new File("src/main/resources/testPhoto.jpg"));
    preparedStatement.setObject(4, fileInputStream);

    preparedStatement.execute();

    JDBCUtils.closeResource(connection, preparedStatement);
}

查询并查看Blob内容

注意getInt这样的方法中的参数也可以直接写参数的名称

public void testQuery() throws Exception {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    InputStream binaryStream = null;
    FileOutputStream yszPhoto = null;
    try {
        connection = JDBCUtils.getConnection();
        String sql = "select id,name,email,birth,photo from customers where id=?";

        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1, 21);

        resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) {
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            String email = resultSet.getString("email");
            Date birth = resultSet.getDate("birth");

            Customer customer = new Customer(id, name, email, birth);

            Blob photo = resultSet.getBlob("photo");
            binaryStream = photo.getBinaryStream();
            yszPhoto = new FileOutputStream("src/main/resources/yszPhoto.jpg");

            byte[] bytes = new byte[1024];
            int length;
            while ((length = binaryStream.read(bytes)) != -1) {
                yszPhoto.write(bytes, 0, length);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (binaryStream != null) {
                binaryStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            binaryStream.close();
        }
        try {
            if (yszPhoto != null) {
                yszPhoto.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            yszPhoto.close();
        }
        JDBCUtils.closeResource(connection, preparedStatement, resultSet);
    }
}

支持批量操作

利用PreparedStatement进行批量插入操作

PreparedStatement预编译SQL语句,加快了一定的访问速度

public void testInsert1() throws Exception {

    //使用PreparedStatement操作
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    try {
        connection = JDBCUtils.getConnection();
        String sql = "insert into goods(name)values(?)";
        preparedStatement = connection.prepareStatement(sql);

        for (int i = 0; i < 20000; i++) {
            preparedStatement.setObject(1, "name_" + i);
            preparedStatement.execute();
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection,preparedStatement);
    }

使用Batch

使用Batch可以减少与数据库交互的次数,将多次的SQL请求一次发送,再由数据库进行统一的处理。

public void testInsert2() throws Exception {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    try {
        connection = JDBCUtils.getConnection();
        String sql = "insert into goods(name)values(?)";
        preparedStatement = connection.prepareStatement(sql);

        for (int i = 0; i < 20000; i++) {
            preparedStatement.setObject(1, "name_" + i);
            //积累SQL
            preparedStatement.addBatch();

            if (i % 500 == 0) {
                //执行Batch
                preparedStatement.executeBatch();
                //清空Batch
                preparedStatement.clearBatch();
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection,preparedStatement);
    }
}

同时,数据库默认情况下是不支持Batch的,这是需要在jdbc.properties的url字段后加入?rewriteBatchedStatements=true

再加入了Batch以后,插入两万条数据的时间由59s变为了0.6s

此外,还可以通过批量提交来进一步加快速度

connection = JDBCUtils.getConnection();
//关闭自动提交
connection.setAutoCommit(false);

String sql = "insert into customers(name)values(?)";
preparedStatement = connection.prepareStatement(sql);

for (int i = 0; i < 20000; i++) {
    preparedStatement.setObject(1, "name_" + i);
    //积累SQL
    preparedStatement.addBatch();

    if (i % 500 == 0) {
        //执行Batch
        preparedStatement.executeBatch();
        //清空Batch
        preparedStatement.clearBatch();
    }
}
//一次性提交所有数据
connection.commit();

PreparedStatement与Statement的对比

  1. PreparedStatement是Statement的一个子接口,Statement仅负责将SQL语句传送到数据库,而PreparedStatement会先预编译SQL,在开发中大都使用PreparedStatement
  2. Statement不支持参数只可以通过字符串拼接,而PreparedStatement可以使用?来代表一个参数
  3. PreparedStatement可以很好的解决SQL注入问题
  4. PreparedStatement可以实现对Blob类型的操作
  5. PreparedStatement支持Batch批量操作,且仅编译一次

数据库事务

关于数据库事务的基本信息在学校的数据库课程中都有讲过,所以在这里就不再赘述。在此仅涉及JDBC的事务处理

  • set autocommit=false对DDL语句无效
  • 关闭连接时会自动提交
  • DML语言执行完毕后会自动提交
    • set autocommit=false可以取消DML自动提交

事务回滚

具体过程

  1. connection.setAutoCommit(false)
  2. connection.commit()
  3. catch到Exception时调用connection.rollback()
  4. 如果连接是从外部传入的,则不要在内部关闭连接,并且在操作完毕后应尽量将连接恢复到传入时的状态

事务隔离级别的JDBC实现

connection.setTransactionIsolation(Conntion.TRANSACTION_READ_COMMITTED)

具体的隔离级别可以在Connection源码中找到

DAO封装

  1. 在工作目录下创建如下的文件

其中BaseDAO里面放置了通用的CRUD操作函数,CustomerDAO接口定义了通用的规范,CustomerDAOImpl中为CustomerDAO的具体实现类,由于代码片段几乎与前述相同,故在此不再赘述

数据库连接池技术

概述

我们之前已经学习了线程池与字符串常量池的概念,这里,我们将学习一门相似的技术 – 数据库连接池

  • 现有数据库连接池存在的问题

    • 反复创建断开与数据库的连接会造成反复的申请与释放资源

    • 若有的连接未被正常释放,时间长了以后容易造成内存泄漏

    • 不易控制连接的线程数目

  • 数据库连接池技术的基本思想

    • 在一个缓冲池中放入一定数量的连接,当需要建立与数据库的连接时,程序向连接池发送请求
    • 连接池负责分配,管理连接,允许连接的复用
    • 连接池有着最小数据库连接数与最大数据库连接数

分类

DBCP:速度较快,但是稳定性不好

C3P0:速度较慢,稳定性好

Druid:阿里提供的数据库连接池,在速度和稳定性方面有着比较好的表现

实现

C3P0实现

public void testGetConnection1() throws Exception {
    //简单测试
    //获取数据库连接池
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    comboPooledDataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC");
    comboPooledDataSource.setUser("root");
    comboPooledDataSource.setPassword("123456Asdfgh");
    //设置初始数据库连接池中连接数
    comboPooledDataSource.setInitialPoolSize(10);

    Connection connection = comboPooledDataSource.getConnection();
    System.out.println("connection = " + connection);
    //销毁连接池
    DataSources.destroy(comboPooledDataSource);
}

    //获取数据库连接池,推荐写法
    private static ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("helloc3p0");

    //使用文件进行配置
    @Test
    public void testGetConnection2() throws Exception {
        Connection connection = comboPooledDataSource.getConnection();
        System.out.println("connection = " + connection);
    }

配置文件信息

<c3p0-config>
    <named-config name="helloc3p0">
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/JDBCLearning?serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">******</property>

        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </named-config>
</c3p0-config>

Druid实现

private static DataSource dataSource1;
static{
    try {
        Properties properties = new Properties();
        InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        properties.load(resourceAsStream);

        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public void getConnection() throws Exception {
    Connection connection = dataSource1.getConnection();
    System.out.println(connection);
}

Apache-DBUtils实现CRUD操作

常用API

  • QueryRunner
  • ResultSetHandler
  • DbUtils

实现

QueryRunner进行修改

    Connection connection = null;
    try {
        QueryRunner runner = new QueryRunner();
        connection = DruidTest.getConnection();
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        int queryCount = runner.update(connection, sql, "ysz004", "", "");

        System.out.println(queryCount);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection, null);
    }

QueryRunner的内部实现其实与我们自己写的封装是十分相似的,例如以下是QueryRunner中update方法中的部分代码

stmt = this.prepareStatement(conn, sql);
this.fillStatement(stmt, params);
rows = stmt.executeUpdate();

可见QueryRunner内部也是通过创建PrepareStatement实例来完成对数据库的各种操作

QueryRunner进行查询

public void testQuery1() {
    Connection connection = null;
    try {
        QueryRunner runner = new QueryRunner();
        connection = DruidTest.getConnection();
        String sql = "select id,name,email,birth from customers where id=?";
        BeanHandler<Customer> customerBeanHandler = new BeanHandler<Customer>(Customer.class);
        Customer customer = runner.query(connection, sql, customerBeanHandler, 23);
        System.out.println(customer.toString());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection,null);
    }
}

此外,ResultSetHandler还有众多的实现类,如BeanListHandler可以封装多条结果的集合,MapHandler可以封装一个结果集,ScalaHandler查询特殊值。

自定义ResultSetHandler的实现类

public void testQuery3() throws Exception{
    Connection connection = null;
    try {
        QueryRunner runner = new QueryRunner();
        connection = DruidTest.getConnection();
        String sql = "select id,name,email,birth from customers where id=?";

        ResultSetHandler<Customer> handler=new ResultSetHandler<Customer>() {
            @Override
            public Customer handle(ResultSet resultSet) throws SQLException {
                System.out.println("handler");
                return null;
            }
        };

        Customer customer = runner.query(connection, sql, handler, 23);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(connection,null);
    }
}

通过重写ResultSetHandler中的handler方法可以自定义ResultSetHandler的实现类

一些其他方面的注意事项

  1. 使用PreparedStatement.execute()时,返回值的Boolean并不是执行的成功与否,而是当前操作是否是查询操作。
  2. if判断时,使用”a”.equals(object),可以避免一些空指针异常
  3. 注意try-catch与throws的使用
    • 内部被调用的方法使用throws,而外部的方法使用try-catch
    • 涉及到资源关闭往往需要try-catch

本次遇到的错误与总结

  1. private static DataSource dataSource1;
    static{
     try {
         Properties properties = new Properties();
         InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
         properties.load(resourceAsStream);
    
         DataSource dataSource1 = DruidDataSourceFactory.createDataSource(properties);
     } catch (Exception e) {
         e.printStackTrace();
     }
    }
    public static Connection getConnection() throws Exception {
     Connection connection = dataSource1.getConnection();
     return connection;
    }

在本代码中由于采用“先throws再try-catch”的方法,导致在修改代码时try语句内的DataSource忘记删除,而且直接覆盖了外层的DataSource,这个问题并不会引起报错。


文章作者: Dale Mo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Dale Mo !
 上一篇
解决idea无法读取properties文件的解决方案 解决idea无法读取properties文件的解决方案
解决idea无法读取properties文件的解决方案最近在学JDBC连接的时候遇到的idea无法读取properties文件的问题,一直在报无法读取文件的错误,后经查询发现,idea与eclipse不同,idea有着 Content Ro
2020-09-04
下一篇 
test test
document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return
2020-09-02 Dale Mo
  目录