数据库连接池
数据库连接对象的个数是有限的。采用数据库连接池可以复用数据库连接,提高并发访问的速度、可靠性和安全性,不必反复创建和销毁数据库连接。常用的数据库连接池有Druid、HikariCP、Tomcat-jdbc、DBCP、C3PO,其中Druid是阿里巴巴开发的开源数据库连接池,在性能、监控、诊断、安全、扩展性等方面远超其它几个连接池。
javax.sql.DataSource是Java中数据连接池的接口,可以通过它获取到的数据库连接对象。对连接池使用close()方法不会再关闭连接,而是归还连接到连接池中。Druid连接池com.alibaba.druid.pool.DruidDataSource就是DataSource接口的实现。以下是一个简单的获取连接池代码:
DataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.getProperty("jdbc.driver"));
dataSource.setUrl(properties.getProperty("jdbc.url"));
dataSource.setUsername(properties.getProperty("jdbc.user"));
dataSource.setPassword(properties.getProperty("jdbc.password"));
dataSource.setInitialSize(5); // 初始连接个数
dataSource.setMaxActive(10); // 最大连接个数
dataSource.setLoginTimeout(5); // 登录超时秒数
dataSource.setQueryTimeout(5); // 查询超时秒数
Connection connection = dataSource.getConnection();
如果想直接从配置文件读取配置构建连接池对象,则为:
Properties properties = new Properties();
properties.load(ConnectionPoolTest.class
.getClassLoader().getResourceAsStream("druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
配置文件的键名是固定的:(规定在com.alibaba.druid.pool.DruidDataSourceFactory中)
driverClassName=oracle.jdbc.OracleDriver(驱动类名)
url=jdbc:oracle:thin:@localhost:1521:XE
username=jy
password=jy
initialSize=5(初始连接池个数)
maxActive=10(最大连接池个数)
maxWait=3000(最大等待时间)
封装
对JDBC的封装分为两个方面:对JDCB连接的过程进行封装、对从数据库获取的数据进行封装。
- 对过程的封装可以分为四个部分:获取连接对象、通过查询语句获取查询结果集、执行非查询语句、关闭连接。
- 对获取的数据进行封装,即将要数据表编成一个pojo类,将获取的数据转为对象,每条记录对应类的一个对象,再调用对象的toString方法对其进行输出。
其中要注意的地方有:
- 当从数据库连接池获取连接对象时,由于数据库连接池只创建一个,故可以将连接池的创建放在静态代码块中。
获取查询结果集时,有如下几个“境界”:
传入一个SQL查询、对获取的结果集进行手动封装成数据对象列表:可以在获得SQL语句后,执行语句并返回ResultSet对象,由主方法对RS进行封装。这一过程可以简化为传入一个SQL语句,并实现一个函数式接口(Function)。接口的实现即是对数据的封装过程。
public static <T> List<T> queryForList(String sql,
Function<ResultSet, List<T>> function) throws Exception {
Connection connection = getConnection(true); // 这里的true可以封装成常量
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
List<T> list = function.apply(resultSet);
close(connection, statement, resultSet);
return list;
}
public static void main(String[] args) {
// 获取连接略
List<KK> data = DBUtils.queryForList(sql, t -> {
try {
List<KK> list = new ArrayList<>();
while(t.next()) {
KK kk = new KK();
kk.setId(t.getInt("id"));
kk.setName(t.getString("name"));
kk.setAge(t.getInt("age"));
kk.setBirthday(t.getDate("birthday"));
list.add(kk);
}
return list;
} catch (SQLException e) {
return null;
}
});
data.forEach(System.out::println);
}
传入一个SQL查询、自动封装后返回数据对象列表:关键是自动封装的过程。首先需要传入pojo类的class对象。自动封装就需要获取当前数据的类型,并调用pojo类中当前数据的set方法。可以使用反射实现:①首先获取pojo类的属性列表,②遍历属性列表,获取属性名和属性类型,③根据属性类型确定调用的resultSet.getType系列方法和通过class.getDeclaredMethod获取的set方法的参数类型、根据属性名确定列名和set方法的名字。
/****** 查询方法中 ******/
while (resultSet.next()) {
U instance = clazz.newInstance(); // 通过类的class对象得到pojo类对象
List<TypeAndName> parse = TypeAndName.parse(clazz);
// 这里封装了一个TypeAndName的类
// 传入一个pojo类class对象,获取属性的类型和名字,并封装成TypeAndName对象存入一个列表
for (TypeAndName typeAndName : parse) { // 遍历每个属性的类型和名字
if ("int".equals(typeAndName.type)) { // 如果当前属性的类型是int
typeAndName.invokeSet(instance, int.class, resultSet.getInt(typeAndName.name));
// 通过当前属性的名字调用set方法,封装该属性
} else if (typeAndName.type.contains("String")) {
typeAndName.invokeSet(instance, String.class, resultSet.getString(typeAndName.name));
}
}
list.add(instance); // 将封装好的pojo类对象放入列表
}
/***** parse方法(TypeAndName类中) ******/
// <T> List<TypeAndName> parse(Class<T> clazz)
Field[] fields = clazz.getDeclaredFields(); // 获取属性列表
List<TypeAndName> list = new ArrayList<>();
for (Field field : fields) {
TypeAndName typeAndName = new TypeAndName(field.getType().getName(), field.getName());
// 将当前属性的类型和名字封装成TypeAndName对象并存入一个列表
list.add(typeAndName);
}
return list; // 将列表返回
/****** invokeSet方法(TypeAndName类中) ******/
// <T> void invokeSet(Object obj, Class<T> clazz, Object value)
String methodName = "set" + initCap(name); // 构造set方法名:set+属性名首字母大写
Method method = obj.getClass().getDeclaredMethod(methodName, clazz);
// 获取上面方法名、参数列表为属性类型的方法
method.invoke(obj, value); // 调用这个属性的set方法
传入一个表名、自动返回数据对象列表:首先需要通过表名构造SQL语句,然后通过反射封装对象后返回。
- 传入一个表名、自动生成这个表的类并返回该类的对象列表:
这太变态了。连表里有什么样的数据、有几个数据、每个数据的列名(属性名)和类型都不知道。这需要通过获取表的元数据才能确定。然后通过列名和数据类型构造pojo类,并封装对象后返回。
- 关闭连接时要注意进行判空处理。
元数据
可以通过连接对象获取数据库和数据库对象的元数据(MetaData),即定义数据的数据。其中java.sql.DatabaseMetaData是数据库的元数据,java.sql.ResultSetMetaData是数据库对象的元数据。
TODO