运行该程序, 可以看到在 Connection 关闭的情况下, 程序依然可以读取、 修改 RowSet 里的记录。为了将程序对离线 RowSet 所做的修改同步到底层数据库, 程序在调用 RowSet 的 acceptChanges()方法时必须传入 Connection。
6.3、离线 RowSet 的查询分页
由于 CachedRowSet 会将数据记录直接装载到内存中, 因此如果 sql 查询返回的记录过大,CachedRowSet 将会占用大量的内存, 在某些极端的情况下, 它甚至会直接导致内存溢出。为了解决该问题, CachedRowSet 提供了分页功能。 所谓分页功能就是一次只装载 ResultSet 里的某几条记录, 这样就可以避免 CachedRowSet 占用内存过大的问题。
CachedRowSet 提供了如下方法来控制分页:
- populate(ResultSet rs,int startRow): 使用给定的 ResultSet 装填 RowSet, 从 ResultSet 的第 startRow条记录开始装填。
- setPageSize(int pageSize): 设置 CachedRowSet 每次返回多少条记录。
- prevIoUsPage(): 在底层 ResultSet 可用的情况下, 让 CachedRowSet 读取上一页记录。
- nextPage(): 在底层 ResultSet 可用的情况下, 让 CachedRowSet 读取下一页记录。
下面程序示范了 CachedRowSet 的分页支持:
import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.*;
import javax.sql.rowset.*;
public class CachedRowSetPage
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public CachedRowSet query(String sql,int pageSize,int page)throws Exception
{
// 加载驱动
Class.forName(driver);
try(
// 获取数据库连接
Connection conn = DriverManager.getConnection(url,pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql))
{
// 使用RowSetProvider创建RowSetFactory
RowSetFactory factory = RowSetProvider.newFactory();
// 创建默认的CachedRowSet实例
CachedRowSet cachedRs = factory.createCachedRowSet();
// 设置每页显示pageSize条记录
cachedRs.setPageSize(pageSize);
// 使用ResultSet装填RowSet,设置从第几条记录开始
cachedRs.populate(rs,(page - 1) * pageSize + 1);
return cachedRs;
}
}
public static void main(String[] args)throws Exception
{
CachedRowSetPage cp = new CachedRowSetPage();
cp.initParam("MysqL.ini");
CachedRowSet rs = cp.query("select * from student_table",3,2); // ①
// 向后滚动结果集
while (rs.next())
{
System.out.println(rs.getString(1)
+ "t" + rs.getString(2)
+ "t" + rs.getString(3));
}
}
}
7、事务处理
对于任何数据库应用而言, 事务都是非常重要的, 事务是保证底层数据完整的重要手段, 没有事务支持的数据库应用, 那将非常脆弱。
7.1、事务的概念和 MysqL 事务支持
事务是由一步或几步数据库操作序列组成的逻辑执行单元, 这系列操作要么全部执行, 要么全部放弃执行。 程序和事务是两个不同的概念。 一般而言, 一段程序中可能包含多个事务。
事务具备 4 个特性: 原子性( Atomicity)、 一致性( Consistency)、 隔离性 (Isolation) 和持续性( Durability)。 这 4 个特性也简称为 ACID 性。
- 原 子 性(Atomicity): 事务是应用中最小的执行单位, 就如原子是自然界的最小颗粒, 具有不可再分的特征一样, 事务是应用中不可再分的最小逻辑执行体。
- 一致性(Consistency): 事务执行的结果, 必须使数据库从一个一致性状态, 变到另一个一致性状态。 当数据库只包含事务成功提交的结果时, 数据库处于一致性状态。 如果系统运行发生中断, 某个事务尚未完成而被迫中断, 而该未完成的事务对数据库所做的修改已被写入数据库,此时, 数据库就处于一种不正确的状态。 比如银行在两个账户之间转账: 从 A 账户向 B 账户转入 1000 元, 系统先减少 A 账户的 1000 元, 然后再为 B 账户增加 1000 元。 如果全部执行成功,数据库处于于一致性状态; 如果仅执行完 A 账户金额的修改, 而没有增加 B 账户的金额, 则数据库就处于不一致性状态; 因此, 一致性是通过原子性来保证的。
- 隔 离 性 (Isolation): 各个事务的执行互不干扰, 任意一个事务的内部操作对其他并发的事务都是隔离的。 也就是说, 并发执行的事务之间不能看到对方的中间状态, 并发执行的事务之间不能互相影响。
- 持续性( Durability): 持续性也称为持久性(Persistence),指事务一旦提交, 对数据所做的任何改变都要记录到永久存储器中, 通常就是保存进物理数据库。
数据库的事务由下列语句组成:
- 一组 DML 语句, 经过这组 DML 语句修改后的数据将保持较好的一致性。
- 一条 DDL 语句。
- 一条 DCL 语句。
DDL 和 DCL 语句最多只能有一条, 因为 DDL 和 DCL 语句都会导致事务立即提交。
当事务所包含的全部数据库操作都成功执行后, 应该提交( commit) 事务, 使这些修改永久生效。
事务提交有两种方式: 显式提交和自动提交。
- 显式提交: 使用 commit()
- 自动提交: 执行 DDL 或 DCL 语句, 或者程序正常退出。
-
当事务所包含的任意一个数据库操作执行失败后, 应该回滚 ( rollback) 事务, 使该事务中所做的修改全部失效。 事务回滚有两种方式: 显式回滚和自动回滚。
- 显式回滚: 使用 rollbacko
- 自动回滚: 系统错误或者强行退出。
MysqL 默认关闭事务( 即打开自动提交), 在默认情况下, 用户在 MysqL 控制台输入一条 DML 语句, 这条 DML 语句将会立即保存到数据库里。 为了幵启 MysqL 的事务支持, 可以显式调用如下命令:
SET AUTOCOMMIT = {0 1 1} o 为关闭自动提交, 即开启事务
一旦在 MysqL 的命令行窗口中输入 set autocommit=0 开启了事务, 该命令行窗口里的所有 DML语句都不会立即生效, 上一个事务结束后第一条 DML 语句将开始一个新的事务, 而后续执行的所有 sql语句都处于该事务中, 除非显式使用 commit 来提交事务, 或者正常退出, 或者运行 DDL、 DCL 语句导致事务隐式提交。 当然, 也可以使用 rollback 回滚来结束事务, 使用 rollback 结束事务将导致本次事务中 DML 语句所做的修改全部失效。
除此之外, 如果不想关闭整个命令行窗口的自动提交, 而只是想临时性地幵始事务, 则可以使用MysqL 提供的 start transaction 或 begin 两个命令, 它们都表示临时性地开始一次事务, 处于 start transaction 或 begin 后的 DML 语句不会立即生效, 除非使用 commit 显式提交事务, 或者执行 DDL、DCL 语者来隐式提交事务。 如下 sql 代码将不会对数据库有任何影响。
# 临时开始事务
begin;
# 向 student_table 表中插入 3 条记录
insert into student_table
values(null,'xx',1);
insert into student_table
values(null,'yy',1);
insert into student table
values(null,'zz',1)
# 查询 student_table 表的记录
select * from student table;
# ①
# 回滚事务
rollback;
# 再次查询
select from student table; # ②
执行上面 sql 语句中的第①条查询语句将会看到刚刚插入的 3 条记录, 如果打开 MysqL 的其他命令行窗口将看不到这 3 条记录一一这正体现了事务的隔离性。 接着程序 rollback 了事务中的全部修改,执行第②条查询语句时将看到数据库又恢复到事务开始前的状态。
提交, 不管是显式提交还是隐式提交, 都会结束当前事务; 回滚, 不管是显式回滚还是隐式回滚,都会结束当前事务。
除此之外, MysqL 还提供了 savepoint 来设置事务的中间点, 通过使用savepoint 设置事务的中间点可以让事务回滚到指定中间点, 而不是回滚全部事务。 如下 sql 语句设置了一个中间点:
savepoint a;
一旦设置了中间点后, 就可以使用 rollback 回滚到指定中间点, 回滚到指定中间点的代码如下:
rollback to a;
7.1、JDBC 的事务支持
JDBC 连接也提供了事务支持, JDBC 连接的事务支持由 Connection 提供, Connection 默认打开自动提交, 即关闭事务, 在这种情况下, 每条 sql 语句一旦执行, 便会立即提交到数据库, 永久生效,无法对其进行回滚操作。
可以调用 Connection 的 setAutoCommit()方法来关闭自动提交, 开启事务, 如下代码所示:
// 关闭自动提交, 开启事务
conn.setAutoCommit(false);
程序中还可调用 Connection 提供的 getAutoCommit()方法来返回该连接的自动提交模式。一旦事务开始之后, 程序可以像平常一样创建 Statement 对象, 创建了 Statement 对象之后, 可以执行任意多条 DML 语句, 如下代码所示:
stmt.executeUpdate( ...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
上面这些 sql 语句虽然被执行了, 但这些 sql 语句所做的修改不会生效, 因为事务还没有结束。 如果所有的 sql 语句都执行成功, 程序可以调用Connection 的 commit()方法来提交事务, 如下代码所示:
// 提交事务
conn.commit();
如果任意一条 sql 语句执行失败, 则应该用 Connection 的 rollback()方法来回滚事务,如下代码所示:
// 回 滚 事 务
conn.rollback();
下面程序示范了当程序出现未处理的 sqlException 异常时, 系统将自动回滚事务:
import java.sql.*;
import java.io.*;
import java.util.*;
public class TransactionTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void insertInTransaction(String[] sqls) throws Exception
{
// 加载驱动
Class.forName(driver);
try(
Connection conn = DriverManager.getConnection(url,pass))
{
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
try(
// 使用Connection来创建一个Statment对象
Statement stmt = conn.createStatement())
{
// 循环多次执行sql语句
for (String sql : sqls)
{
stmt.executeUpdate(sql);
}
}
// 提交事务
conn.commit();
}
}
public static void main(String[] args) throws Exception
{
TransactionTest tt = new TransactionTest();
tt.initParam("MysqL.ini");
String[] sqls = new String[]{
"insert into student_table values(null,'aaa',1)","insert into student_table values(null,'bbb','ccc',// 下面这条sql语句将会违反外键约束,
// 因为teacher_table中没有ID为5的记录。
"insert into student_table values(null,5)" //①
};
tt.insertInTransaction(sqls);
}
}
上面程序中的粗体字代码只是开启事务、 提交事务的代码, 并没有回滚事务的代码。 但当程序执行到 第 4 条 sql 语句( ①处代码) 时, 这条语句将会引起外键约束异常, 该异常没有得到处理, 引起程序非正常结束, 所以事务自动回滚。
Connection 也提供了设置中间点的方法:setSavepoint(),Connection 提供了两个方法来设置中间点。
- Savepoint setSavepoint(): 在当前事务中创建一个未命名的中间点, 并返回代表该中间点的Savepoint 对象。
- Savepoint setSavepoint(String name): 在当前事务中创建一个具有指定名称的中间点, 并返回代表该中间点的 Savepoint 对象。
通常来说, 设置中间点时没有太大的必要指定名称, 因为 Connection 回滚到指定中间点时, 并不是根据名字回滚的, 而是根据中间点对象回滚的,Connection 提供了 rollback(Savepoint savepoint)方法回滚到指定中间点。
7.3、Java 8 增强的批量更新
JDBC 还提供了一个批量更新的功能, 使用批量更新时, 多条 sql 语句将被作为一批操作被同时收集, 并同时提交。
使用批量更新也需要先创建一个 Statement 对象, 然后利用该对象的addBatch()方法将多条 sql 语句同时收集起来, 最后调用 Java 8 为 Statement 对象新增的 executeLargeBatch()( 或原有的 executeBatch())方法同时执行这些 sql 语句。 只要批量操作中任何一条 sql 语句影响的记录条数可能超过Integer.MAX VALUE,就应该使用 executeLargeBatch()方法, 而不是 executeBatch()方法。
如下代码片段示范了如何执行批量更新:
Statement stmt = conn.createStatement();
// 使用 Statement 同时收集多条 sql 语句
stmt.addBatch(sqll);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
……
// 同时执行所有的 sql 语句
stmt.executeLargeBatch();
执行 executeLargeBatch()方法将返回一个 long[]数组, 因为使用 Statement 执行 DDL、 DML 语句都将返回一个 long 值, 而执行多条 DDL、 DML 语句将会返回多个 long 值, 多个 long 值就组成了这个 long[]数组。 如果在批量更新的 addBatchO方法中添加了 select 查询语句, 程序将直接出现错误。
为了让批量操作可以正确地处理错误, 必须把批量执行的操作视为单个事务, 如果批量更新在执行过程中失败, 则让事务回滚到批量操作开始之前的状态。 为了达到这种效果, 程序应该在开始批量操作之前先关闭自动提交, 然后开始收集更新语句, 当批量操作执行结束后, 提交事务, 并恢复之前的自动
提交模式。
如下代码示范了如何使用 JDBC 的批量更新:
import java.sql.*;
import java.io.*;
import java.util.*;
public class BatchTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void insertBatch(String[] sqls) throws Exception
{
// 加载驱动
Class.forName(driver);
try(
Connection conn = DriverManager.getConnection(url,pass))
{
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 保存当前的自动的提交模式
boolean autoCommit = conn.getAutoCommit();
// 关闭自动提交
conn.setAutoCommit(false);
try(
// 使用Connection来创建一个Statement对象
Statement stmt = conn.createStatement())
{
// 循环多次执行sql语句
for (String sql : sqls)
{
stmt.addBatch(sql);
}
// 同时提交所有的sql语句
stmt.executeLargeBatch();
// 提交修改
conn.commit();
// 恢复原有的自动提交模式
conn.setAutoCommit(autoCommit);
}
// 提交事务
conn.commit();
}
}
public static void main(String[] args) throws Exception
{
TransactionTest tt = new TransactionTest();
tt.initParam("MysqL.ini");
String[] sqls = new String[]{
"insert into student_table values(null,};
tt.insertInTransaction(sqls);
}
}
8、分析数据库信息
大部分时候, 只需要对指定数据表进行插入( C)、 查询( R )、 修改( U)、 删除( D) 等 CRUD 操作;但在某些时候, 程序需要动态地获取数据库的相关信息, 例如数据库里的数据表信息、 列信息。 除此之外, 如果希望在程序中动态地利用底层数据库所提供的特殊功能, 则都需要动态分析数据库相关信息。
JDBC 提供了 DatabaseMetaData 来封装数据库连接对应数据库的信息, 通过 Connection 提供的getMetaData()方法就可以获取数据库对应DatabaseMetaData 对象。
DatabaseMetaData 接口通常由驱动程序供应商提供实现, 其目的是让用户了解底层数据库的相关信息。 使用该接口的目的是发现如何处理底层数据库, 尤其是对于试图与多个数据库一起使用的应用程序—因为应用程序需要在多个数据库之间切换, 所以必须利用该接口来找出底层数据库的功能, 例如,调用 supportsCorrelatedSubqueries()方法查看是否可以使用关联子查询, 或者调用 supportsBatchUpdates()方法查看是否可以使用批量更新。
许多DatabaseMetaData 方法以 ResultSet 对象的形式返回查询信息, 然后使用 ResultSet 的常规方法( 如 getString()和 getlnt()) 即可从这些 ResultSet 对象中获取数据。 如果查询的信息不可用, 则将返回一个空 ResultSet 对象。
DatabaseMetaData 的很多方法都需要传入一个 xxxPattem 模式字符串, 这里的 xxxPattem 不是正则表达式, 而是 sql 里的模式字符串, 即用百分号(% ) 代表任意多个字符, 使用下画线(_) 代表一个字符。 在通常情况下, 如果把该模式字符串的参数值设置为 mill, 即表明该参数不作为过滤条件。
下面程序通过 DatabaseMetaData 分析了当前 Connection 连接对应数据库的一些基本信息, 包括当前数据库包含多少数据表, 存储过程, student table 表的数据列、 主键、 外键等信息。
import java.sql.*;
import java.util.*;
import java.io.*;
public class DatabaseMetaDataTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void info() throws Exception
{
// 加载驱动
Class.forName(driver);
try(
// 获取数据库连接
Connection conn = DriverManager.getConnection(url,pass))
{
// 获取的DatabaseMetaData对象
DatabaseMetaData dbmd = conn.getMetaData();
// 获取MysqL支持的所有表类型
ResultSet rs = dbmd.getTableTypes();
System.out.println("--MysqL支持的表类型信息--");
printResultSet(rs);
// 获取当前数据库的全部数据表
rs = dbmd.getTables(null,null,"%",new String[]{"TABLE"});
System.out.println("--当前数据库里的数据表信息--");
printResultSet(rs);
// 获取student_table表的主键
rs = dbmd.getPrimaryKeys(null,"student_table");
System.out.println("--student_table表的主键信息--");
printResultSet(rs);
// 获取当前数据库的全部存储过程
rs = dbmd.getProcedures(null,"%");
System.out.println("--当前数据库里的存储过程信息--");
printResultSet(rs);
// 获取teacher_table表和student_table之间的外键约束
rs = dbmd.getCrossReference(null,"teacher_table","student_table");
System.out.println("--teacher_table表和student_table之间"
+ "的外键约束--");
printResultSet(rs);
// 获取student_table表的全部数据列
rs = dbmd.getColumns(null,"student_table","%");
System.out.println("--student_table表的全部数据列--");
printResultSet(rs);
}
}
public void printResultSet(ResultSet rs)throws sqlException
{
ResultSetMetaData rsmd = rs.getMetaData();
// 打印ResultSet的所有列标题
for (int i = 0 ; i < rsmd.getColumnCount() ; i++ )
{
System.out.print(rsmd.getColumnName(i + 1) + "t");
}
System.out.print("n");
// 打印ResultSet里的全部数据
while (rs.next())
{
for (int i = 0; i < rsmd.getColumnCount() ; i++ )
{
System.out.print(rs.getString(i + 1) + "t");
}
System.out.print("n");
}
rs.close();
}
public static void main(String[] args)
throws Exception
{
DatabaseMetaDataTest dt = new DatabaseMetaDataTest();
dt.initParam("MysqL.ini");
dt.info();
}
}
API:java.sql.DatabaseMetaData
8.1、使用系统表分析数据库信息
除可以使用 DatabaseMetaData 来分析底层数据库信息之外, 如果已经确定应用程序所使用的数据库系统, 则可以通过数据库的系统表来分析数据库信息。 系统表又称为数据字典, 数据字典的数据通常由数据库系统负责维护, 用户通常只能查询数据字典, 而不能修改数据字典的内容。
MysqL使用information_schema数据库来保存系统表 :
- tables:存放数据库里所有数据表的信息 。
- schemata:存放数据库里所有数据库(与MysqL 的 Schema对应)的信息。
- views: 存放数据库里所有视图的信息。
- columns: 存放数据库里所有列的信息。
- triggers: 存放数据库里所有触发器的信息。
- routines: 存放数据库里所有存储过程和函数的信息。
- key column usage: 存放数据库里所有具有约束的键信息。
- tahle constraints: 存放数据库里全部约束的表信息。
- statistics: 存放数据库里全部索引的信息。
通常来说, 如果需要获得数据库信息, 包括该数据库驱动提供了哪些功能, 则应该利用 Database MetaData 来了解该数据库支持哪些功能。 完全可能出现这样一种情况: 对于底层数据库支持的功能,但数据库驱动没有提供该功能, 程序还是不能使用该功能。 使用 DatabaseMetaData 则不会出现这种问题。
如果需要纯粹地分析数据库的静态对象, 例如分析数据库系统里包含多少数据库、 数据表、 视图、索引等信息, 则利用系统表会更加合适。
9、使用连接池管理连接
数据库连接的建立及关闭是极耗费系统资源的操作, 在多层结构的应用环境中, 这种资源的耗费对系统性能影响尤为明显。 通过 DriverManager 获取连接 获得的数据库连接, 一个数据库连接对象均对应一个物理数据库连接, 每次操作都打开一个物理连接, 使用完后立即关闭连接。频繁地打开、 关闭连接将造成系统性能低下。
数据库连接池的解决方案是: 当应用程序启动时, 系统主动建立足够的数据库连接, 并将这些连接组成一个连接池。 每次应用程序请求数据库连接时, 无须重新打开连接, 而是从连接池中取出己有的连接使用, 使用完后不再关闭数据库连接, 而是直接将连接归还给连接池。 通过使用连接池, 将大大提高程序的运行效率。
对于共享资源的情况, 有一个通用的设计模式: 资源池( Resource Pool),用于解决资源的频繁请求 、 释放所造成的性能下降。 为了解决数据库连接的频繁请求、 释放, JDBC 2.0 规范引入了数据库连接池技术。 数据库连接池是 Connection 对象的工厂。
数据库连接池的常用参数如下:
- 数据库的初始连接数。
- 连接池的最大连接数。
- 连接池的最小连接数。
- 连接池每次增加的容量。
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示, DataSource 只是一个接口, 该接口通常由商用服务器( 如 WebLogic、 WebSphere)等提供实现, 也有一些幵源组织提供实现( 如 DBCP 和 C3P0 等)。
9.1、DBCP数据源
DBCP 是 Apache 软件基金组织下的开源连接池实现, 该连接池依赖该组织下的另一个开源系统:common-pool。 如果需要使用该连接池实现, 则应在系统中增加如下两个 jar 文件。
- commons-dbcp.jar: 连接池的实现。
- commons-pool.jar: 连接池实现的依赖库。
登录 http://commons.apache.org/站点即可下载 commons-pool.zip 和 commons-dbcp.zip 两个压缩文件,
Tomcat 的连接池正是采用该连接池实现的。 数据库连接池既可以与应用服务器整合使用, 也可以由应用程序独立使用。 下面的代码片段示范了使用 DBCP 来获得数据库连接的方式:
// 创建数据源对象
BasicDataSource ds =new BasicDataSource();
// 设置连接池所需的驱动
ds.setDriverClassName("com.MysqL.jdbc.Driver");
// 设置连接数据库的 URL
ds.setUrl("jdbc:MysqL://localhost:3306/javaee");
// 设置连接数据库的用户名
ds.setUsername("root");
// 设置连接数据库的密码
ds.setPassword("pass");
// 设置连接池的初始连接数
ds.setlnitialSize(5);
// 设置连接池最多可有多少个活动连接数
ds.setMaxActive(20);
// 设置连接池中最少有 2 个空闲的连接
ds.setMinldle(2)
数据源和数据库连接不同, 数据源无须创建多个, 它是产生数据库连接的工厂, 因此整个应用只需要一个数据源即可。 也就是说, 对于一个应用, 上面代码只要执行一次即可。 建议把上面程序中的 ds设置成 static 成员变量, 并且在应用开始时立即初始化数据源对象, 程序中所有需要获取数据库连接的地方直接访问该 ds 对象, 并获取数据库连接即可。 通过 DataSource 获取数据库连接的代码示例如下:
// 通过数据源获取数据库连接
Connection conn = ds.getConnection();
当数据库访问结束后, 程序还是像以前一样关闭数据库连接, 如下代码所示:
// 释放数据库连接
conn.close();
但上面代码并没有关闭数据库的物理连接, 它仅仅把数据库连接释放, 归还给连接池, 让其他客户端可以使用该连接。
9.2、C3P0 数据源
相比之下, C3P0 数据源性能更胜一筹, Hibernate 就推荐使用该连接池。
C3P0 连接池不仅可以自动清理不再使用的 Connection,还可以自动清理Statement 和 ResultSet。 C3P0 连接池需要版本为 1.3 以上的 JRE,推荐使用 1.4 以上的 JRE。 如果需要使用 C3P0 连接池, 则应在系统中增加如下 JAR 文件。
- c3p0-0.9.1.2.jar: C3P0 连接池的实现。
登录 http://sourceforge.net/projects/c3p0/站点即可下载 C3P0 数据源的最新版本。
下面代码通过 C3P0 连接池获得数据库连接:
// 创建连接池实例
ComboPooledDataSource ds = new ComboPooledDataSource();
// 设置连接池连接数据库所需的驱动
ds.setDriverClass("com.MysqL.jdbc.Driver");
// 设置连接数据库的 URL
ds.setJdbcUrl("jdbc:MysqL://localhost:3306/javaee");
// 设置连接数据库的用户名
ds.setUser("root");
// 设置连接数据库的密码
ds.setPassword("32147");
// 设置连接池的最大连接数
ds.setMaxPoolSize(40);
// 设置连接池的最小连接数
ds.setMinPoolSize(2);
// 设置连接池的初始连接数
ds. setlnitialPoolSize(10);
// 设置连接池的缓存 Statement 的最大数
ds.setMaxStatements(180);
在程序中创建 C3P0 连接池的方法与创建 DBCP 连接池的方法基本类似。
一旦获取了 C3P0 连接池之后, 程序同样可以通过如下代码来获取数据库连接:
// 获得数据库连接
Connection conn = ds.getConnection();
参考:
【1】:《疯狂Java讲义》
【2】:JDBC 简介_w3cschool
【3】:JDBC 驱动类型_w3cschool
【4】:JDBC 连接数据库_w3cschool
【5】:JDBC 结果集_w3cschool
【6】:JDBC Statement 对象_w3cschool
【7】:JDBC:数据库操作:BLOB数据处理
【8】:JDBC 存储过程_w3cschool
【9】:JDBC 事务_w3cschool