写在前面:JDBCUtils类代码主要提供对MySQL数据库的连接、关闭功能,Customer和Order类主要封装对应数据库相关属性并提供get、set、toString方法及构造器,具体代码请看前面的博文。
1.事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
2.事务处理的原则:
保证所事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所修改,整个事务回滚(rollback)到最初状态。
3.代码的体现:
@Test
public void testUpdateWithTx() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();//①手动实现 ②数据库连接池(C3P0 / DBCP)
//1.设置不能自动提交数据
conn.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ?";
String sql2 = "update user_table set balance = balance + 100 where user = ?";
//过程一:
updateWithTx(conn,sql1,"AA"); //① 手动实现:version 2.0 ② 使用dbUtils包里QueryRunner实现
//出现异常
System.out.println(10 / 0);
//过程二:
updateWithTx(conn,sql2,"BB");//① 手动实现:version 2.0
//2.提交数据
conn.commit();
System.out.println("转账成功!");
} catch (Exception e) {
e.printStackTrace();
try {
//3.回滚数据
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
if(conn != null) {
//关闭连接
JDBCUtils.closeResource(conn, null); //① 手动关闭 ② DbUtils类的关闭方法
}
}
}
4.考虑到事务以后,实现的通用的增删改操作: version 2.0
// 通用的增删改方法 (version 2.0
public void update(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
try {
// 1.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行sql语句
// ps.execute();
int count = ps.executeUpdate();
System.out.println("影响了" + count + "条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.关闭资源
JDBCUtils.closeResource(null, ps);
}
}
考虑到事务以后,实现的通用的查询: version 2.0
// 通用的查询方法,返回一个对象 (version 2.0)
public T getInstance(Connection conn, Class clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
// 4.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {// 遍历每一个列
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 5.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的查询方法,返回多个对象构成的集合 (version 2.0)
public List getForList(Connection conn, Class clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
List list = new ArrayList<>();
try {
// 1.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
// 4.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
while (rs.next()) {// ①判断下一行是否有数据 ②如果数据,指针下移一位
T t = clazz.newInstance();
// 装配一个行数对应的一个对象
for (int i = 0; i < columnCount; i++) {// 遍历每一个列
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 5.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//DAO:data(base) access Object 数据(库)访问对象
【DAO.java】
//DAO:data(base) access object
public class DAO {
private Class clazz = null; //clazz:T
public DAO(){
//获取泛型参数
clazz = ReflectionUtils.getGenericSuperClassParam(this.getClass());
// System.out.println(this.getClass());
}
//获取特殊意义的数据
public E getValue(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
//4.处理结果集数据
if(rs.next()){
return (E) rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
//5.关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的查询方法,返回多个对象构成的集合 (version 2.0
public List getForList(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
List list = new ArrayList<>();
try {
// 1.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
// 4.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
while (rs.next()) {// ①判断下一行是否有数据 ②如果数据,指针下移一位
T t = clazz.newInstance();
// 装配一个行数对应的一个对象
for (int i = 0; i < columnCount; i++) {// 遍历每一个列
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 5.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的查询方法,返回一个对象 (version 2.0
public T getInstance(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.预编译sql语句,得到PreparedStatement对象
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行executeQuery(),得到结果集:ResultSet
rs = ps.executeQuery();
// 4.得到结果集的元数据:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 5.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {// 遍历每一个列
// 获取列值
Object columnVal = rs.getObject(i + 1);
// 获取列的别名:列的别名,使用类的属性名充当
String columnLabel = rsmd.getColumnLabel(i + 1);
// 5.2使用反射,给对象的相应属性赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的增删改方法 (version 2.0
public void update(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
try {
// 1.获取PreparedStatement的实例 (或:预编译sql语句)
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行sql语句
// ps.execute();
int count = ps.executeUpdate();
System.out.println("影响了" + count + "条数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.关闭资源
JDBCUtils.closeResource(null, ps);
}
}
}
class ReflectionUtils{
//获取形参clazz的父类的泛型
public static Class getGenericSuperClassParam(Class clazz){//比如:clazz:CustomerDAO,OrderDAO
Type genericSuperClass = clazz.getGenericSuperclass();
ParameterizedType paramsType = (ParameterizedType) genericSuperClass;
Type[] arguments = paramsType.getActualTypeArguments();
return (Class)arguments[0];
}
}
public class CustomerDAO extends DAO{
public CustomerDAO(){
}
//获取表中的条目数
public long getCount(Connection conn){
String sql = "select count(*) from customers";
long count = getValue(conn, sql);
return count;
}
//获取最大的生日
public Date getMaxBirth(Connection conn){
String sql = "select max(birth) from customers";
Date birth = getValue(conn, sql);
return birth;
}
/**
* 返回数据表中的所客户记录
* @author shkstart 邮箱:[email protected]
* @param conn
* @return
*/
public List getAll(Connection conn){
String sql = "select id,name,email,birth from customers";
List list = getForList(conn, sql);
return list;
}
/**
* 返回指定id位置上的客户
*
* @author shkstart 邮箱:[email protected]
* @param conn
* @param id
* @return
*/
public Customer getCustomerById(Connection conn,int id){
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(conn, sql, id);
return customer;
}
/**
* 修改cust.getId()位置的客户信息。
*
* @author shkstart 邮箱:[email protected]
* @param conn
* @param cust
*/
public void update(Connection conn ,Customer cust){
String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
update(conn, sql, cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}
/**
* 删除指定id位置的customer
* @author shkstart 邮箱:[email protected]
* @param conn
* @param id
*/
public void deleteById(Connection conn,int id){
String sql = "delete from customers where id = ?";
update(conn, sql, id);
}
/**
* 将 cust添加到数据库中
* @author shkstart 邮箱:[email protected]
* @param conn
* @param cust
*/
public void addCustomer(Connection conn,Customer cust){
String sql = "insert into customers(name,email,birth)values(?,?,?)";
update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
}
@Test
public void test(){
Connection conn = null;
try {
CustomerDAO dao = new CustomerDAO();
conn = JDBCUtils.getConnection(); //①手动创建连接 ②数据库连接池
conn.setAutoCommit(false);
//针对于一个事务,体现对表的增、删、改、查
dao.addCustomer();
dao.updateCustomer(); //①手动使用PreparedStatement实现CRUD操作
//②调用DBUtils包里的QueryRunner类实现CRUD操作
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
if(conn != null)
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
JDBCUtils.closeResource(conn, null);//①手动的关闭资源 ②调用DBUtils类的方法关闭资源
}
}
1.传统连接的问题:
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
2.如何解决传统开发中的数据库连接问题:使用数据库连接池技术
3.使用数据库连接池的好处:
①资源重用
②效率更高
③便于连接池的管理
4.实现的方式:
使用C3P0数据库连接池 :hibernate官方推荐使用
使用DBCP数据库连接池 :tomcat 服务器自带dbcp数据库连接池
导入jar包:
菜单栏选择File>ProjectStructure>
选择jar包并展开
使用配置文件
private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
// 使用C3P0实现数据库的连接2:使用配置文件
public static Connection getConnection2() throws Exception {
Connection conn = cpds.getConnection();
return conn;
}
其中,配置文件定义在src下。名为:c3p0-config.xml
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/test
root
abc123
5
10
10
100
100
2
导入jar包:
自行展开,不再叙述,eclipse直接buildPath。
使用配置文件:
//使用DBCP数据库连接池获取数据库连接:使用配置文件
private static DataSource dataSource = null;
static{
try {
Properties pros = new Properties();
InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
pros.load(is);
dataSource = BasicDataSourceFactory.createDataSource(pros);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws Exception {
Connection conn = dataSource.getConnection();
return conn;
}
其中,配置文件定义在src下:dbcp.properties
username=root
password=abc123
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
maxActive=10
导入jar包并展开:
使用现成的jar中的QueryRunner测试增、删、改的操作
@Test
public void testDelete() throws Exception {
Connection conn = JDBCUtils.getConnection3();
QueryRunner runner = new QueryRunner();
String sql = "delete from customers where id >= ?";
int count = runner.update(conn, sql, 25);
System.out.println("删除了" + count + "条记录");
JDBCUtils.closeResource(conn, null);
}
@Test
public void testInsert() throws Exception {
Connection conn = JDBCUtils.getConnection3();
QueryRunner runner = new QueryRunner();
String sql = "insert into customers(name,email,birth) values(?,?,?)";
int count = runner.update(conn, sql, "武壮", "[email protected]", "2000-7-8");
System.out.println("插入了" + count + "条记录");
JDBCUtils.closeResource(conn, null);
}
使用现成的jar中的QueryRunner测试查询的操作. ResultSetHandler接口
// BeanHandler:用于返回一个java类的对象
@Test
public void testQuery() throws Exception {
Connection conn = JDBCUtils.getConnection3();
QueryRunner runner = new QueryRunner();
String sql = "select id,name,email,birth from customers where id = ?";
BeanHandler handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 6);
System.out.println(customer);
JDBCUtils.closeResource(conn, null);
}
// BeanListHandler:用于返回一个由多个java类的对象构成的集合
@Test
public void testQueryForList() throws Exception {
Connection conn = JDBCUtils.getConnection3();
QueryRunner runner = new QueryRunner();
String sql = "select id,name,email,birth from customers where id <= ?";
//方式一:
// BeanListHandler handler = new BeanListHandler<>(Customer.class);
//方式二:
ResultSetHandler> handler = new BeanListHandler<>(Customer.class);
List customers = runner.query(conn, sql, handler, 6);
customers.forEach(System.out::println);
JDBCUtils.closeResource(conn, null);
}
//ScalarHandler:返回特殊值的handler
@Test
public void testScalarHandler() throws Exception{
Connection conn = JDBCUtils.getConnection3();
QueryRunner runner = new QueryRunner();
ScalarHandler handler = new ScalarHandler();
// String sql = "select count(*) from customers";
// long count = (long) runner.query(conn, sql,handler);
// System.out.println(count);
String sql = "select max(birth) from customers";
Date maxBirth = (Date) runner.query(conn, sql,handler);
System.out.println(maxBirth);
JDBCUtils.closeResource(conn, null);
}
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
为了让多个 SQL 语句作为一个事务执行:
调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭, 则需要恢复其自动提交状态
public void testJDBCTransaction() {
Connection conn = null;
try {
// 1.获取数据库连接
conn = JDBCUtils.getConnection();
// 2.开启事务
conn.setAutoCommit(false);
// 3.进行数据库事务操作
// 4.若没有异常,则提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 5.若有异常,则回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}} finally {
JDBCUtils.close(null, null, conn); } }
事务的ACID(acid)属性
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
以第一个 DML 语句的执行作为开始
以下面的其中之一作为结束:
COMMIT 或 ROLLBACK 语句
DDL 或 DCL 语句(自动提交)
用户会话正常结束
系统异常终了
DDL: Data Definition Language 数据定义语言(用来定义数据库结构): create table; alter table; drop table; create index; drop index
DCL: Data Control Language 数据控制语言(用来控制数据库的访问):grant; revoke; commit; rollback; lock;
DML: Data Manipulation Language 数据操纵语言(用来查询与更新记录): insert; update; delete
使用COMMIT 和 ROLLBACK语句,我们可以:
确保数据完整性。
数据改变被提交之前预览。
将逻辑上相关的操作分组。
数据完整性:
存储在数据库中的所有数据值均处于正确的状态。如果数据库中存储有不正确的数据值,则该数据库称为已丧失数据完整性。
数据库采用多种方法来保证数据完整性,包括外键、束约、规则和触发器。
改变前的数据状态是可以恢复的
执行 DML 操作的用户可以通过 SELECT 语句查询提交或回滚之前的修正
其他用户不能看到当前用户所做的改变,直到当前用户结束事务。
DML语句所涉及到的行被锁定, 其他用户不能操作。
数据的改变已经被保存到数据库中。
改变前的数据已经丢失。
所有用户可以看到结果。
锁被释放, 其他用户可以操作涉及到的数据。
改变数据
DELETE FROM employees
WHERE employee_id = 99999;
1 row deleted.
INSERT INTO departments
VALUES (290, 'Corporate Tax', NULL, 1700);
1 row inserted.
提交改变
COMMIT;
Commit complete.
使用 ROLLBACK 语句可使数据变化失效:
数据改变被取消。
修改前的数据状态可以被恢复。
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱.
数据库提供的 4 种事务隔离级别:
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED
Mysql 支持 4 种事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别.
查看当前的隔离级别: SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
SET autocommit = 0; 禁止操作自动提交
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
在主程序(如servlet、beans)中建立数据库连接
进行sql操作
断开数据库连接
这种模式开发,存在的问题:
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
统一的连接管理,避免数据库连接泄露
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
DBCP 数据库连接池
C3P0 数据库连接池
DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool. 如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
范 例
将常用的操作数据库的JDBC的类和方法集合在一起,就是DBUtils.
BeanHandler:把结果集转为一个 Bean
BeanListHandler:把结果集转为一个 Bean 的集合
MapHandler:把结果集转为一个 Map
MapListHandler:把结果集转为一个 Map 的 List
ScalarHandler:把结果集转为一个类型的数据返回, 该类型通 常指 String 或其它 8 种基本数据类型.