数据库连接池

数据库连接对象的个数是有限的。采用数据库连接池可以复用数据库连接,提高并发访问的速度、可靠性和安全性,不必反复创建和销毁数据库连接。常用的数据库连接池有Druid、HikariCP、Tomcat-jdbc、DBCP、C3PO,其中Druid是阿里巴巴开发的开源数据库连接池,在性能、监控、诊断、安全、扩展性等方面远超其它几个连接池。

javax.sql.DataSource是Java中数据连接池的接口,可以通过它获取到的数据库连接对象。对连接池使用close()方法不会再关闭连接,而是归还连接到连接池中。Druid连接池com.alibaba.druid.pool.DruidDataSource就是DataSource接口的实现。以下是一个简单的获取连接池代码:

  1. DataSource dataSource = new DruidDataSource();
  2. dataSource.setDriverClassName(properties.getProperty("jdbc.driver"));
  3. dataSource.setUrl(properties.getProperty("jdbc.url"));
  4. dataSource.setUsername(properties.getProperty("jdbc.user"));
  5. dataSource.setPassword(properties.getProperty("jdbc.password"));
  6. dataSource.setInitialSize(5); // 初始连接个数
  7. dataSource.setMaxActive(10); // 最大连接个数
  8. dataSource.setLoginTimeout(5); // 登录超时秒数
  9. dataSource.setQueryTimeout(5); // 查询超时秒数
  10. Connection connection = dataSource.getConnection();

如果想直接从配置文件读取配置构建连接池对象,则为:

  1. Properties properties = new Properties();
  2. properties.load(ConnectionPoolTest.class
  3. .getClassLoader().getResourceAsStream("druid.properties"));
  4. DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

配置文件的键名是固定的:(规定在com.alibaba.druid.pool.DruidDataSourceFactory中)

  1. driverClassName=oracle.jdbc.OracleDriver(驱动类名)
  2. url=jdbc:oracle:thin:@localhost:1521:XE
  3. username=jy
  4. password=jy
  5. initialSize=5(初始连接池个数)
  6. maxActive=10(最大连接池个数)
  7. maxWait=3000(最大等待时间)

封装

对JDBC的封装分为两个方面:对JDCB连接的过程进行封装、对从数据库获取的数据进行封装。

  • 对过程的封装可以分为四个部分:获取连接对象、通过查询语句获取查询结果集、执行非查询语句、关闭连接。
  • 对获取的数据进行封装,即将要数据表编成一个pojo类,将获取的数据转为对象,每条记录对应类的一个对象,再调用对象的toString方法对其进行输出。

其中要注意的地方有:

  • 当从数据库连接池获取连接对象时,由于数据库连接池只创建一个,故可以将连接池的创建放在静态代码块中。
  • 获取查询结果集时,有如下几个“境界”:

    • 传入一个SQL查询、对获取的结果集进行手动封装成数据对象列表:可以在获得SQL语句后,执行语句并返回ResultSet对象,由主方法对RS进行封装。这一过程可以简化为传入一个SQL语句,并实现一个函数式接口(Function)。接口的实现即是对数据的封装过程。

      1. public static <T> List<T> queryForList(String sql,
      2. Function<ResultSet, List<T>> function) throws Exception {
      3. Connection connection = getConnection(true); // 这里的true可以封装成常量
      4. Statement statement = connection.createStatement();
      5. ResultSet resultSet = statement.executeQuery(sql);
      6. List<T> list = function.apply(resultSet);
      7. close(connection, statement, resultSet);
      8. return list;
      9. }
      10. public static void main(String[] args) {
      11. // 获取连接略
      12. List<KK> data = DBUtils.queryForList(sql, t -> {
      13. try {
      14. List<KK> list = new ArrayList<>();
      15. while(t.next()) {
      16. KK kk = new KK();
      17. kk.setId(t.getInt("id"));
      18. kk.setName(t.getString("name"));
      19. kk.setAge(t.getInt("age"));
      20. kk.setBirthday(t.getDate("birthday"));
      21. list.add(kk);
      22. }
      23. return list;
      24. } catch (SQLException e) {
      25. return null;
      26. }
      27. });
      28. data.forEach(System.out::println);
      29. }
    • 传入一个SQL查询、自动封装后返回数据对象列表:关键是自动封装的过程。首先需要传入pojo类的class对象。自动封装就需要获取当前数据的类型,并调用pojo类中当前数据的set方法。可以使用反射实现:①首先获取pojo类的属性列表,②遍历属性列表,获取属性名和属性类型,③根据属性类型确定调用的resultSet.getType系列方法和通过class.getDeclaredMethod获取的set方法的参数类型、根据属性名确定列名和set方法的名字。

      1. /****** 查询方法中 ******/
      2. while (resultSet.next()) {
      3. U instance = clazz.newInstance(); // 通过类的class对象得到pojo类对象
      4. List<TypeAndName> parse = TypeAndName.parse(clazz);
      5. // 这里封装了一个TypeAndName的类
      6. // 传入一个pojo类class对象,获取属性的类型和名字,并封装成TypeAndName对象存入一个列表
      7. for (TypeAndName typeAndName : parse) { // 遍历每个属性的类型和名字
      8. if ("int".equals(typeAndName.type)) { // 如果当前属性的类型是int
      9. typeAndName.invokeSet(instance, int.class, resultSet.getInt(typeAndName.name));
      10. // 通过当前属性的名字调用set方法,封装该属性
      11. } else if (typeAndName.type.contains("String")) {
      12. typeAndName.invokeSet(instance, String.class, resultSet.getString(typeAndName.name));
      13. }
      14. }
      15. list.add(instance); // 将封装好的pojo类对象放入列表
      16. }
      17. /***** parse方法(TypeAndName类中) ******/
      18. // <T> List<TypeAndName> parse(Class<T> clazz)
      19. Field[] fields = clazz.getDeclaredFields(); // 获取属性列表
      20. List<TypeAndName> list = new ArrayList<>();
      21. for (Field field : fields) {
      22. TypeAndName typeAndName = new TypeAndName(field.getType().getName(), field.getName());
      23. // 将当前属性的类型和名字封装成TypeAndName对象并存入一个列表
      24. list.add(typeAndName);
      25. }
      26. return list; // 将列表返回
      27. /****** invokeSet方法(TypeAndName类中) ******/
      28. // <T> void invokeSet(Object obj, Class<T> clazz, Object value)
      29. String methodName = "set" + initCap(name); // 构造set方法名:set+属性名首字母大写
      30. Method method = obj.getClass().getDeclaredMethod(methodName, clazz);
      31. // 获取上面方法名、参数列表为属性类型的方法
      32. method.invoke(obj, value); // 调用这个属性的set方法
    • 传入一个表名、自动返回数据对象列表:首先需要通过表名构造SQL语句,然后通过反射封装对象后返回。

    • 传入一个表名、自动生成这个表的类并返回该类的对象列表:这太变态了。连表里有什么样的数据、有几个数据、每个数据的列名(属性名)和类型都不知道。这需要通过获取表的元数据才能确定。然后通过列名和数据类型构造pojo类,并封装对象后返回。
  • 关闭连接时要注意进行判空处理。

    元数据

    可以通过连接对象获取数据库和数据库对象的元数据(MetaData),即定义数据的数据。其中java.sql.DatabaseMetaData是数据库的元数据,java.sql.ResultSetMetaData是数据库对象的元数据。

TODO