Learn JDBC-04

前言

之前说到了JDBC中的事务以及隔离性,后面还将CRUD操作进行封装,创建了一个DAO对象。不过如果需要获取一个连接,我们还是通过之前写的那个函数来获取连接。这样每次我们进行操作,或许都要获取一次连接,这极大的降低了效率。在创建线程的时候,我们使用线程池解决了这个问题。这里的或许数据库连接和创建多线程其实是一样的,都是一种比较耗时的操作,所以我们需要专门使用一个东西对他们进行管理。线程是线程池,数据库就是数据库连接池。

至于使用数据库连接池的好处和使用线程池的好处基本上是一样的。都是可以资源重用,效率更高,更好的管理等等一系列的好处。

数据库连接池

在Java当中,数据库连接池使用javax.sql.DataSource来表示。不过这个DateSource只是一个接口,该接口在JDK中是没有实现的,不过有很多的开源的组织提供了实现。下面稍微来介绍一下三种数据库连接池的使用。

C3P0数据库连接池

既然是开源软件实现的,我们就要上网上下载对应的jar包,然后导入到项目中。

和之前我们写jdbc.properties一样,我们要使用c3p0的话也是需要使用配置文件的。不过此时不是一个properties文件而是一个XML文件,名字也是固定的,必须要是c3p0-config.xml。文件中的内容大概如下。

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <named-config name="c3p0mysql">
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///sher?serverTimezone=UTC&amp;rewriteBatchedStatements=true</property>
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">50</property>
        <property name="maxStatements">20</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>
</c3p0-config>

根据前面的name基本上也可以猜出来设置的是什么东西。其中name-config中的name属性不是随便写完就没事了的,后面是需要使用到的。不过我们也可以设置一下默认连接,那么下面使用的时候就不需要特别的指定要连接哪个数据库了。

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <named-config name="c3p0mysql">
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///sher?serverTimezone=UTC&amp;rewriteBatchedStatements=true</property>
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">50</property>
        <property name="maxStatements">20</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>
    <default-config>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///sher?serverTimezone=UTC&amp;rewriteBatchedStatements=true</property>
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">50</property>
        <property name="maxStatements">20</property>
        <property name="maxStatementsPerConnection">5</property>
    </default-config>
</c3p0-config>

只要多写一个dafault-config就行了。

至于如果获取连接,方式是非常的简单。

private static DataSource cpds = new ComboPooledDataSource();
public static Connection getConnection() throws SQLException {
    return cpds.getConnection();
}

最重要的也就是new ComboPooledDataSource()这个东西了。

DBCP数据库连接池

DBCP是一种比较常用的数据库连接池,是Apache开源组织提供的。其中Tomcat的连接池就是用的这个东西实现的。

和上面的那个一样,我们还是需要从网上下载对应的jar包,然后导入项目中。其中需要注意的是,这个commons-dbco是依赖另外的两个包的,所以我们还需要下载commons-loggingcommons-pool,并将其导入到项目当中。

然后我们需要做的就是编写配置文件。和我们之前使用的那个jdbc.properties一样,DBCP也是需要我们编写properties文件,然后加载解析。

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///sher?serverTimezone=UTC&rewriteBatchedStatements=true
username=root
password=root

initialSize=10 

还有其他的参数这里就不多说了,直接看看获取连接的操作。

private static DataSource source = null;
static {
    try {
        Properties pros = new Properties();
        InputStream is = Demo1.class.getClassLoader().getResourceAsStream("dbcp.properties");
        pros.load(is);
        source = BasicDataSourceFactory.createDataSource(pros);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static Connection getConnection() throws SQLException {
    return source.getConnection();
}

最重要的也就是BasicDataSourceFactory.createDataSource(pros)这个东西了。

Druid数据库连接池

这个连接池有上面的连接池的所有的优点,是目前最好的数据库连接池。而且这个东西是阿里巴巴开发的。这个东西和DBCP是非常类似的。我们还是需要编写配置几乎相同的配置文件。

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///sher?serverTimezone=UTC&rewriteBatchedStatements=true
username=root
password=root

initialSize=5

然后读取配置文件,新建连接池。

private static DataSource source = null;

static {
    try {
        Properties pros = new Properties();
        InputStream is = Demo1.class.getClassLoader().getResourceAsStream("druid.properties");
        pros.load(is);
        source = DruidDataSourceFactory.createDataSource(pros);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static Connection getConnection() throws SQLException {
    return source.getConnection();
}

操作基本上都是一样的。不一样的就是DruidDataSourceFactory.createDataSource(pros),也就五个单词的不同罢了。

上面就是对三个数据库连接池的介绍,其实都调用这些东西都是蛮简单的,无非就是一个getConnection方法。有了上面的这三个东西,我们就不需要自己手写连接数据库的操作了。不过我们还是写了JDBCUtils中的很多的增删改查的操作,那么这些工作是否有其他人替我们已经完成了呢?当然是有的。

DBUtils

和我们之前学完了IO之后,来了一个commons-io,我们做了事情,其实人家早就写好了更好的工具。我们写的都是没有必要的。同样的,我们学完了JDBC之后,commons-dbutils也完成了几乎所有的操作,我们也不需要写任何东西了,直接拿来就用就行了。

首先还是下载jar包,然后导入到项目当中去。

  • DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法 如下:

    • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方 法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和 ResultSet。
    • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情 况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务, 然后关闭连接
    • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在 关闭连接时不抛出SQL异常。
    • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做 了判断
    • public static void rollbackAndClose(Connection conn)throws SQLException 回滚事务然后关闭连接
    • public static void rollbackAndCloseQuietly(Connection) 不抛出异常。
  • QueryRunner

    • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少 编码量。 QueryRunner类提供了两个构造器: 默认的构造器 需要一个 javax.sql.DataSource 来作参数的构造器 QueryRunner类的主要方法:
    • 更新 public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行 一个更新(插入、更新或删除)操作。
    • 插入 public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的 键值
    • 批处理 public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句 public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句 …..
    • 查询 public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句 的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

上面就是QueryRunner的使用,不过上面多次写到了ResultSetHandler.

ResultSetHandler接口及实现类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。 ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。 接口的主要实现类: ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
  • ColumnListHandler:将结果集中某一列的数据存放到List中。
  • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map 里,其key为指定的key。
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
  • ScalarHandler:查询单个值对象

主要使用的就是加黑的那几个。不过我们也可以选择自己来实现这个接口。可以选择使用匿名内部类的方式,不过lambda表达式是最好的,因为只有一个方法需要实现。

ResultSetHandler rsh = new ResultSetHandler() {
    @Override
    public Object handle(ResultSet rs) throws SQLException {
        if (rs.next()) {
            return new Student(rs.getInt("id"), rs.getString("username"));
        }
        return null;
    }
};
ResultSetHandler rsh = rs -> {
    if (rs.next()) {
        return new Student(rs.getInt("id"), rs.getString("username"));
    }
    return null;
};

关于JDBC的说明基本上就结束了。现在我们就可以将之前DAO中调用的我自己写的JDBCUtils的内容修改成为DBUtils中的方法了。经过我们的修改,BaseDAO就可以省略成这个样子,不可谓不简单。

package com.sher.dao;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.*;

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.HashMap;
import java.util.List;
import java.util.Map;

import static com.sher.java1.JDBCUtils.closeResource;

/**
 * @author SHeR
 * @time 10/28/2019 6:16 PM
 * @describe
 */
@SuppressWarnings("all")
public class BaseDao2<T> {

    private Class<T> type;
    private QueryRunner queryRunner = null;
    BeanHandler<T> beanHandler = null;
    BeanListHandler<T> beanListHandler = null;
    MapHandler mapHandler = null;
    MapListHandler mapListHandler = null;
    ScalarHandler scalarHandler = null;


    public BaseDao2() {
        Class<? extends BaseDao2> clazz = this.getClass();
        ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        this.type = (Class<T>) actualTypeArguments[0];

        queryRunner = new QueryRunner();
    }


    public int update(Connection conn, String sql, Object... objs) {
        int count = 0;
        try {
            count = queryRunner.update(conn, sql, objs);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    public T getInstance(Connection conn, String sql, Object... objs) throws Exception {
        if (beanHandler == null) {
            beanHandler = new BeanHandler<>(type);
        }
        return queryRunner.query(conn, sql, beanHandler, objs);
    }

    public List<T> getInstances(Connection conn, String sql, Object... objs) throws Exception {
        if (beanListHandler == null) {
            beanListHandler = new BeanListHandler<>(type);
        }
        return queryRunner.query(conn, sql, beanListHandler, objs);
    }

    public Map<String, Object> getMapInstance(Connection conn, String sql, Object... objs) throws Exception {
        if (mapHandler == null) {
            mapHandler = new MapHandler();
        }
        return queryRunner.query(conn, sql, mapHandler, objs);
    }

    public List<Map<String, Object>> getMapInstances(Connection conn, String sql, Object... objs) throws Exception {
        if (mapListHandler == null) {
            mapListHandler = new MapListHandler();
        }
        return queryRunner.query(conn, sql, mapListHandler, objs);
    }

    public Object getValue(Connection conn, String sql, Object... objs) throws Exception {
        if (scalarHandler == null) {
            scalarHandler = new ScalarHandler();
        }
        return queryRunner.query(conn, sql, scalarHandler, objs);
    }
}

上面基本上已经没什么方法是我们直接写的了,不可谓不无脑,不可谓不简单!!!

总结

上面基本上就是介绍了几个工具的使用。首先是三个数据库连接池(基本上就是用最好的那个druid连接池,其他的不看都是可以的。),然后就是DBUtils的使用。然后我们还使用DBUtils改进了之前写的那个BaseDAO。那么到此JDBC的简单学习就结束了。


一枚小菜鸡