引言
本篇博文为 JDBC 技术的常见概念及相关细节梳理,意在重学 Java 查漏补缺。
博文随时会进行更新,补充新的内容并修正错漏,该系列博文旨在帮助自己巩固扎实 Java 技能。
毕竟万丈高楼,基础为重,借此督促自己时常温习回顾。
一、JDBC
数据的持久化:持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用
1.1、Java 中的数据存储技术
- JDBC:直接访问数据库
- JDO(Java Data Object) 技术
- 第三方 O/R 工具,如 Hibernate、Mybatis 等
JDBC 是 Java 访问数据库的基础,JDO、Hibernate、Mybatis 等只是更好的封装了 JDBC
1.2、JDBC 技术
JDBC(Java Database Connectivity) 是一个独立于特定数据库管理系统、通用的 SQL 数据库存取和操作的公共接口,定义了用来访问数据库的标准 Java 类库(java.sql、javax.sql),使用这些类库可以以一种标准的方法方便地访问数据库资源
- 为访问不同的数据库提供了一种统一的途径
1.2.1、JDBC 体系结构
- 面向应用的 API:Java API,抽象接口,供应用程序开发人员使用
- 面向数据库的 API:Java Driver API,供开发商开发数据库驱动程序
1.2.2、DAO 及相关实现类
DAO:Data Access Object 访问数据信息的类和接口,包括了对数据的 CRUD (Create、Retrival、Update、Delete),而不包含任何业务相关的信息,也称作 BaseDAO
- 作用:实现功能的模块化,更有利于代码的维护和升级
1.3、获取数据库连接
1.3.1、Driver 接口
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口
- 供数据库厂商使用,以提供不同的实现(以 jar 包形式提供)
- Oracle 驱动:oracle.jdbc.driver.OracleDriver
- MySQL 驱动:com.mysql.jdbc.Driver
- 习惯上置于 Java 工程目录 lib 下 Add to Build Path
- 针对 Dynamic Web Project 置于 WebContent(或 WebRoot) 中 WEB-INF 中 lib 下
- 不需要直接访问实现 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些 Driver 实现
1.3.2、加载与注册 JDBC 驱动
加载驱动:调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名
- Class.forName("com.mysql.jdbc.Driver");
注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
- DriverManager.registerDriver(com.mysql.jdbc.Driver);
- 通常不需要显式调用,Driver 接口的驱动程序类都包含了静态代码块,在其中会调用 DriverManager.registerDriver() 方法来注册自身的一个实例
- MySQL 的 Driver 实现类的源码:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
1.3.3、url
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接
JDBC URL 的标准由三部分组成,各部分间用冒号( : )分隔
- jdbc:子协议:子名称
- 协议:JDBC URL 中的协议总是 jdbc
- 子协议:子协议用于标识一个数据库驱动程序
- 子名称:一种标识数据库的方法
- 子名称可以依不同的子协议而变化,子名称为定位数据库提供足够的信息;包含主机名(对应服务端的 ip 地址)、端口号、数据库名
- jdbc:mysql://localhost:3306/test
- 协议:jdbc
- 子协议:mysql
- 子名称:localhost:3306/test
常用数据库的 JDBC URL:
- MySQL
- jdbc:mysql://主机名称:MySQL 服务端口号/数据库名称?参数=值&参数=值
- 参数:useUnicode=true&characterEncoding=utf8
- 若 JDBC 程序与服务器端字符集不一致,会导致乱码,可通过参数指定服务器端的字符集
1.3.4、建立并获取连接
调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
- user、password 可通过 "属性名=属性值" 的方式传递
示例:
@Test
public void testGetConnection() throws Exception {
// 加载配置
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(inputStream);
// 读取配置
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClassName = pros.getProperty("driverClassName");
// 加载驱动
Class.forName(driverClassName);
// 建立并获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
jdbc.properties:
user=user
password=password
url=jdbc:mysql://localhost:3306/test
driverClassName=com.mysql.jdbc.Driver
1.4、CRUD 操作
java.sql 包含 3 个接口分别定义了对数据库的调用的不同方式
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象
- PreparedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行 SQL 语句
- CallableStatement:用于执行 SQL 存储过程
1.4.1、Statement 操作数据库的弊端
通过调用 Connection 对象的 createStatement() 方法创建该对象,Statement 接口中定义了如下方法用于执行 SQL 语句:
- int executeUpdate(String sql):执行更新操作(INSERT、UPDATE、DELETE)
- ResultSet executeQuery(String sql):执行查询操作(SELECT)
使用 Statement 操作数据表存在的弊端:
- 需要拼接字符串,操作繁琐
- 存在 SQL 注入问题
- 利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令从而利用系统的 SQL 引擎完成恶意行为
1.4.2、PreparedStatement
通过调用 Connection 对象 的 prepareStatement(String sql) 方法获取 PreparedStatement 对象
- PreparedStatement 接口是 Statement 的子接口,表示一条预编译过的 SQL 语句
- PreparedStatement 对象所代表的 SQL 语句中的参数使用问号( ? )表示,调用 PreparedStatement 对象的 setXxx(index, value) 方法设置具体参数值
- index 代表参数的索引(从 1 起始)
- value 代表具体的参数值
1.4.3、PreparedStatement 与 Statement 的对比
PreparedStatement 能最大可能提高性能:
DBServer 会对预编译语句提供性能优化
因预编译语句有可能被重复调用,所以语句在被 DBServer 的编译器编译后的执行代码被缓存下来,下一次调用时只要是相同的预编译语句便只需将参数直接传入编译过的语句执行代码中就会得到执行
PreparedStatement 可以防止 SQL 注入
1.4.4、Java 与 SQL 数据类型的对应关系
Java 类型 | SQL 类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR、VARCHAR、LONGVARCHAR |
byte array | BINARY、VARBINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
1.4.5、PreparedStatement 实现增、删、改操作
DBUtils:import org.apache.commons.dbutils.DbUtils;
BaseDAO:
public abstract class BaseDao<T> {
private Class<T> cls;
{
/* 获取带泛型的父类 */
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
/* 获取父类的泛型 */
Type[] types = parameterizedType.getActualTypeArguments();
cls = (Class<T>) types[0];
}
/* 增、删、改操作 */
public int update(Connection connection, String sql, Object ...args) {
...
}
/* 查询操作,获取单条记录 */
public T getQuery(Connection connection, String sql, Object ...args) {
...
}
}
PreparedStatement 实现增、删、改操作:
public int update(Connection connection, String sql, Object ...args) {
PreparedStatement pStatement = null;
try {
pStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; ++i) {
pStatement.setObject(i + 1, args[i]);
}
return pStatement,executeUpdate();
} catch (Exceptopn exception) {
exception.printStackTrace();
} finally {
DBUtils.closeQuietly(pStatement);
}
return 0;
}
1.4.6、PreparedStatement 实现查询操作
返回单条记录:
public T getQuery(Connection connection, String sql, Object ...args) {
PreparedStatement pStatement = null;
ResultSet resultSet = null;
try {
pStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; ++i) {
pStatement.setObject(i + 1, args[i]);
}
resultSet = pStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int columnCount = resultSetMetaData.getColumnCount();
if (resultSet.next()) {
T t = cls.newInstance();
for (int i = 0; i < columnCount; ++i) {
String columnLabel = resultSetMetaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(i + 1);
Field field = cls.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
} catch (Exception exception) {
exception.printStackTrace();
} finally {
DBUtils.closeQuietly(pStatement);
DBUtils.closeQuietly(resultSet);
}
return null;
}
}
返回多条记录:
public List<T> getQueryList(Connection connection, String sql, Object ...args) {
PreparedStatement pStatement = null;
ResultSet resultSet = null;
try {
pStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; ++i) {
pStatement.setObject(i + 1, args[i]);
}
resultSet = pStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
ArrayList<T> list = new ArrayList<>();
int columnCount = resultSetMetaData.getColumnCount();
while (resultSet.next()) {
T t = cls.getInstance();
for (int i = 0; i < columnCount; ++i) {
String columnLabel = resultSetMetaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(i + 1);
Field field = cls.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception exception) {
exception.printStackTrace();
} finally {
DBUtils.closeQuietly(pStatement);
DBUtils.closeQuietly(resultSet);
}
return null;
}
ResultSet:
PreparedStatement 对象的 executeQuery() 方法返回一个 ResultSet 对象
ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现
ResultSet 返回的实际上就是一张数据表,有一个指针指向数据表的第一条记录的前面
ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行
- 调用 next() 方法检测下一行是否有效,若有效返回 true,且指针下移。相当于 Iterator 对象的 hasNext() 和 next() 方法的结合体
当指针指向一行时, 可以通过调用 getXxx(index) 或 getXxx(columnName) 获取每一列的值
- 例:getInt(1)、getString("name")
ResultSetMetaData:
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
- getColumnName(int column):获取指定列的名称
- getColumnLabel(int column):获取指定列的别名
- getColumnCount():返回当前 ResultSet 对象中的列数
- getColumnTypeName(int column):返回指定列的数据库特定的类型名称
- getColumnDisplaySize(int column):返回指定列的最大标准宽度,以字符为单位
- isNullable(int column):返回指定列中的值是否可以为 null
- isAutoIncrement(int column):返回是否自动为指定列进行编号,这样这些列仍然是只读的
资源的释放:
- 释放 ResultSet、Statement、Connection
- Connection 的使用原则是尽量晚创建,尽量早释放
- 可以在 finally 中关闭,保证即使其他代码出现异常,资源也一定能被关闭
1.4.7、ORM 思想(Object Relational Mapping)
- 一个数据表对应一个 Java 类
- 表中的一条记录对应 Java 类的一个对象
- 表中的一个字段对应 Java 类的一个属性
1.4.8、操作 BLOB 类型字段
MySQL BLOB 类型:
- MySQL 中,BLOB 是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据
- 插入 BLOB 类型的数据必须使用 PreparedStatement,因为 BLOB 类型的数据无法使用字符串拼接
- MySQL 的四种 BLOB 类型(除了在存储的最大信息量上不同外,它们是等同的)
- TinyBlob:最大 255 字节
- Blob:最大 65 K
- MediumBlob:最大 16 M
- LongBlob:最大 4 G
- 实际使用中根据需要存入的数据大小定义不同的 BLOB 类型
- 需要注意的是:如果存储的文件过大,数据库的性能会下降
- 若在指定了相关的 Blob 类型以后报错:xxx too large
- 需如下配置:MySQL 的安装目录下 my.ini 文件追加配置参数:max_allowed_packet=16M
- 修改 my.ini 文件后需要重新启动 MySQL 服务
1.4.9、批量执行SQL语句
当需要成批插入或者更新记录时,可以采用 Java 的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理
JDBC 的批量处理语句包括下面三个方法:
- addBatch():添加需要批量处理的 SQL 语句或是参数
- executeBatch():执行批量处理语句
- clearBatch():清空缓存的数据
通常会遇到两种批量执行 SQL 语句的情况:
- 多条 SQL 语句的批量处理
- 一个 SQL 语句的批量传参
例子:
...
String sql = "insert into table(name) values(?)";
PreparedStatement pStatement = connection.prepareStatement(sql);
for (int i = 1; i <= 1000000; ++i) {
pStatement.setString(1, "name_" + i);
pStatement.addBatch();
if (i % 500 == 0) {
pStatement.executeBatch();
pStatement.clearBatch();
}
}
...
例子(优化):
...
// 不进行自动提交
connection.setAutoCommit(false);
String sql = "insert into table(name) values(?)";
PreparedStatement pStatement = connection.prepareStatement(sql);
for (int i = 1; i <= 1000000; ++i) {
pStatement.setString(1, "name_" + i);
pStatement.addBatch();
if (i % 500 == 0) {
pStatement.executeBatch();
pStatement.clearBatch();
}
}
// 提交
connection.commit();
...
二、数据库事务
2.1、概念
-
事务:一组逻辑操作单元,使数据从一种状态转变到另一种状态
-
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障都不能改变这种执行方式
- 当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;
要么数据库管理系统将放弃所做的所有修改,整个事务回滚(rollback)到最初状态
- 当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;
为确保数据库中数据的一致性,数据的操作应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为失败,所有从起始点以后的操作应全部回退到开始状态
2.2、JDBC 事务处理
- 数据一旦提交便不可回滚
- 数据什么时候意味着提交:
- 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交而不能回滚
- 关闭数据库连接,数据就会自动的提交
- 若有多个操作,每个操作使用的是自己单独的连接,则无法保证事务
- 即同一个事务的多个操作必须在同一个连接下
- JDBC 中让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
- 若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态(setAutoCommit(true))
- 尤其在使用数据库连接池技术时,执行 close() 方法前,建议恢复自动提交状态
2.3、事务的 ACID 属性
- 原子性(Atomicity)
- 原子性指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性(Consistency)
- 一致性指事务必须使数据库从一个一致性状态转换到另外一个一致性状态
- 隔离性(Isolation)
- 隔离性指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对于并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
- 持久性(Durability)
- 持久性指一个事务一旦被提交,它对数据库中数据的改变就是永久的,接下来的其他操作和数据库故障不应该对其有任何影响
2.4、数据库的并发问题
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,若没有采取必要的隔离机制,就会导致各种并发问题
- 脏读:对于两个事务 T1、T2,T1 读取了已经被 T2 更新但还未被提交的字段。若 T2 回滚,T1 读取的内容就是临时且无效的
- 不可重复读:对于两个事务 T1、T2,T1 读取了一个字段,然后 T2 更新了该字段。若 T1 再次读取同一个字段,值被更新
- 幻读:对于两个事务 T1、T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。若 T1 再次读取同一个表,便会多出新行
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
2.4.1、四种隔离级别
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED (读未提交数据) |
允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读的问题都会出现 |
READ COMMITTED (读已提交数据) |
只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
REPEATABLE READ (可重复读) |
确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在 |
SERIALIZABLE (串行化) |
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发问题都可以避免,但性能十分低下 |
- Oracle 支持的两种事务隔离级别:READ COMMITTED、SERIALIZABLE
- 默认的事务隔离级别为:READ COMMITTED
- MySQL 支持四种事务隔离级别
- 默认的事务隔离级别为:REPEATABLE READ
2.4.2、在 MySQL 中设置隔离级别
- 每启动一个 MySQL 程序就会获得一个单独的数据库连接,每个数据库临界都有一个全局变量 @@tx_isolation 表示当前的事务隔离级别
- 查看当前的隔离级别:SELECT @@tx_isolation;
- 设置当前 MySQL 连接的隔离级别:set transaction isolation level read committed;
- 设置数据库系统的全局的隔离级别:set global transaction isolation level read committed;
三、数据库连接池
3.1、数据库连接池的必要性
普通的 JDBC 数据库连接:
-
使用 DriverManager 获取
-
每次向数据库建立连接时都要将 Connection 加载到内存中,再验证用户名、密码
-
需要时申请,执行完成断开连接,消耗大量资源和时间、数据库的连接资源并没有很好的重复利用
-
频繁的进行数据库连接操作将占用很多的系统资源
-
不能控制被创建的连接对象数
3.2、数据库连接池技术
- 数据库连接池的基本思想:为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的
- 无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量
- 连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
3.2.1、数据库连接池技术的优点
-
资源重用
- 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销
- 在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
-
更快的系统反应速度
- 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用
- 此时连接的初始化工作均已完成
- 对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
-
新的资源分配手段
- 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
-
统一的连接管理,避免数据库连接泄漏
- 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
3.3、开源的数据库连接池
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP:Apache 提供的数据库连接池
- C3P0:速度相对较慢,稳定性尚可,Hibernate 官方推荐使用
- Druid: 阿里巴巴提供的数据库连接池
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
- DataSource 用来取代 DriverManager 来获取 Connection,获取速度快,同时可以大幅度提高数据库访问速度
- 注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:connection.close(); 但 connection.close() 并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池
3.3.1、C3P0 数据库连接池
如需使用该连接池实现,应在系统中增加 jar 文件:c3p0.jar
通过配置文件方式:
import com.mchange.v2.c3p0.ComboPooledDataSource;
...
private static DataSource cpds = new ComboPooledDataSource("intergalactoApp");
public static Connection getConnection() throws Exception {
return cpds.getConnection();
}
...
src 下配置文件:c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="intergalactoApp">
<!-- 获取连接的 4 个基本信息 -->
<property name="user">user</property>
<property name="password">password</property>
<!-- localhost:3306 可省略:(jdbc:mysql:///test) -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 涉及到数据库连接池的管理的相关属性的设置 -->
<!-- 连接池中连接用尽时, 一次向数据库服务器申请连接个数 -->
<property name="acquireIncrement">5</property>
<!-- 初始化连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 连接池中的最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 连接池中的最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
3.3.2、DBCP 数据库连接池
- DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool
- 如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
- Tomcat 的连接池采用该连接池实现该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用
配置参数:
参数 | 默认值 | 说明 |
---|---|---|
initialSize | 0 | 连接池启动时创建的初始化连接数量 |
maxActive | 8 | 连接池中可同时连接的最大的连接数 |
maxIdle | 8 | 连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制 |
minIdle | 0 | 连接池中最小的空闲的连接数,低于这个数量会被创建新的连接。该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大。 |
maxWait | 无限制 | 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待 |
poolPreparedStatements | false | 开启池的Statement是否prepared |
maxOpenPreparedStatements | 无限制 | 开启池的prepared 后的同时最大连接数 |
minEvictableIdleTimeMillis | 连接池中连接,在时间段内一直空闲, 被逐出连接池的时间 | |
removeAbandonedTimeout | 300 | 超过时间限制,回收没有用(废弃)的连接 |
removeAbandoned | false | 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收 |
通过配置文件方式:
import org.apache.commons.dbcp.BasicDataSourceFactory;
...
private static DataSource dataSource;
static {
try {
Properties pros = new Properties();
InputStream inputStream = ClassLoader.getSystemResourceAsStream("dbcp.properties");
pros.load(inputStream);
dataSource = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
...
src 下配置文件:dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=username
password=password
initialSize=10
3.3.3、Druid(德鲁伊)数据库连接池
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、Proxool 等的优点,同时加入了日志监控,可以很好的监控数据库连接池连接和 SQL 的执行情况
配置参数:
参数 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的 url | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
driverClassName | 根据 url 自动识别,这一项可配可不配,如果不配置 druid 会根据 url 自动识别 dbType,然后选择相应的 driverClassName(建议配置) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁 | |
poolPreparedStatements | false | 是否缓存 preparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 MySQL 下建议关闭 |
maxOpenPreparedStatements | -1 | 要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100 |
validationQuery | 用来检测连接是否有效的 sql,要求是一个查询语句。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会其作用 | |
testOnBorrow | true | 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能 |
testOnReturn | false | 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy 线程会检测连接的间隔时间 2)testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个 DruidDataSource 只支持一个 EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的 sql | |
exceptionSorter | 根据 dbType 自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的 filter:stat 日志用的 filter:log4j 防御 sql 注入的 filter:wall | |
proxyFilters | 类型是 List,如果同时配置了 filters 和 proxyFilters,是组合关系,并非替换关系 |
示例:
import com.alibaba.druid.pool.DruidDataSourceFactory;
...
private static DataSource dataSource;
static {
try {
Properties pros = new Properties();
InputStream inputStream = ClassLoader.getSystemResourceAsStream("druid.properties");
pros.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
src 下配置文件:druid.properties
url=jdbc:mysql://localhost:3306/test
username=username
password=password
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=10
四、Apache-DBUtils 实现 CRUD 操作
4.1、Apache-DBUtils 简介
commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类库,它是对 JDBC 的简单封装,能极大简化 JDBC 编码的工作量,同时也不会影响程序的性能
4.2、主要 API 的使用
4.2.1、DbUtils
DbUtils:提供如关闭连接、装载 JDBC 驱动程序等常规工作的工具类,所有方法都是静态的:
- public static void close(...) throws 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
- rollbackAndCloseQuietly(Connection)
- public static boolean loadDriver(String driverClassName)
- 装载并注册 JDBC 驱动程序,如果成功就返回 true
- 该方法不需要捕捉 ClassNotFoundException 异常
4.2.2、QueryRunner 类
该类简单化了 SQL 查询,它与 ResultSetHandler 组合使用可以完成大部分的数据库操作
-
QueryRunner 类提供了两个构造器:
- 默认的构造器
- 需要一个 javax.sql.DataSource 来作参数的构造器
-
QueryRunner 类的主要方法:
- 更新
- public int update(Connection conn, String sql, Object ...params) throws SQLException
- 用来执行一个更新(插入、更新或删除)操作
- 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
- 批处理
- 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 int[] batch(Connection conn, String sql, Object[][] params) throws SQLException
- 查询
- public Object query(Connection conn, String sql, ResultSetHandler rsh, Object ...params) throws SQLException
- 执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数
- 该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭
- public Object query(Connection conn, String sql, ResultSetHandler rsh, Object ...params) throws SQLException
- 更新
4.2.3、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:查询单个值对象