JDBC学习总结(二)JDBC操作Blob类型字段/高效的批量插入/JDBC处理数据库事务/将多个SQL看成一个事务执行/数据库连接池C3P0/DBCP/Druid/DBUtils工具类实现CRUD
一、JDBC操作BLOB类型字段
(一)MySQL的BLOB类型
- MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
- 插入BLOB类型的数据必须使用PreparedStatement,不能使用Statement,因为BLOB类型的数据无法使用字符串拼接写的。
- MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
- 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
- 需要注意的是:如果存储的文件过大,数据库的性能会下降。
- 如果在指定了相关的Blob类型以后,且文件没超过最大范围,还报错:
Packet for query is too large
,那么我们需要在mysql的安装目录下,找到my.ini文件加上如下的配置参数:max_allowed_packet=16M
。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
(二)向数据表中插入Blob类型数据
案例:
import com.fox.util.JDBCUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class BlobTest1 {
public static void main(String[] args){
Connection connection = null;
PreparedStatement ps = null;
FileInputStream fis =null;
try {
connection = JDBCUtils.getConnection();
String sql="insert into customers(name,email,birth,photo) values(?,?,?,?)";
ps = connection.prepareStatement(sql);
ps.setString(1,"彭于晏");
ps.setString(2,"pengyuyan@qq.com");
ps.setString(3,"1992-02-02");//会隐式转化为Date类型
fis = new FileInputStream(new File("D:/test/pyy.jpg"));
ps.setBlob(4,fis);//该方法需要传入一个InputStream对象
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(三)修改数据表中Blob类型的字段
案例:
import com.fox.util.JDBCUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class BlobTest2 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
FileInputStream fis = null;
try {
connection = JDBCUtils.getConnection();
String sql="update customers set photo=? where id=?";
ps = connection.prepareStatement(sql);
fis = new FileInputStream(new File("D:/test/pengyuyan.jpg"));
ps.setBlob(1,fis);
ps.setInt(2,22);
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(四)查询数据表中Blob类型的字段
java.sql.Blob接口中有一个方法:
InputStream getBinaryStream()
将此 Blob实例指定的BLOB值作为流 Blob 。
import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import java.io.*;
import java.sql.*;
public class BlobTest3 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
InputStream is =null;
FileOutputStream fos =null;
ResultSet rs =null;
try {
connection = JDBCUtils.getConnection();
String sql="select id,name,email,birth,photo from customers where name=?";
ps = connection.prepareStatement(sql);
ps.setString(1,"彭于晏");
rs = ps.executeQuery();
if(rs.next()){
//方式一:
//int id = rs.getInt(1);
//String name = rs.getString(2);
//String email = rs.getString(3);
//Date birth = rs.getDate(4);
//方式二:(推荐)
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
//将Blob类型的字段下载下来,以文件的方式保存在本地
Blob photo = rs.getBlob("photo");
is = photo.getBinaryStream();
fos = new FileOutputStream("pengyuyan.jpg");
byte[] buffer=new byte[1024];
int len=0;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps,rs);
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
二、批量插入
(一)批量执行SQL语句
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC的批量处理语句包括下面三个方法:
(方法在java.sql.Statement及其子接口中)
void addBatch(String)
:添加需要批量处理的SQL语句或是参数;int[] executeBatch()
:执行批量处理语句;void clearBatch()
:清空缓存的数据
通常我们会遇到两种批量执行SQL语句的情况:
- 多条SQL语句的批量处理;
- 一个SQL语句的批量传参;
update、delete本身就具有批量操作的效果。此时的批量操作,主要指的是批量插入。
(二)高效的批量插入
案例:向数据表中插入100000条数据
首先,数据库中提供一个goods表。创建如下:
1.实现层次一:使用Statement
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.Statement;
public class InsertTest1 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
for (int i = 0; i < 20000; i++) {
String sql="insert into goods(name) values('name_"+i+"')";
statement.executeUpdate(sql);
}
long end = System.currentTimeMillis();
System.out.println("批量插入耗时:"+(end-start)/1000+"秒");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,statement);
}
}
}
效率太低了,在实际的开发中通常都不会使用Statement
2.实现层次二:使用PreparedStatement
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class InsertTest2 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
String sql="insert into goods(name) values(?)";
ps = connection.prepareStatement(sql);
for (int i = 0; i < 20000; i++) {
ps.setString(1,"name_"+i);
ps.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("批量插入耗时:"+(end-start)/1000+"秒");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
}
}
}
可以看到,使用PreparedStatement比使用Statement做批量插入耗时少一点。使用PreparedStatement的性能一定是更高的,因为使用Statement,每一次插入都需要编译一次sql语句,而每次编译都需要进行语法检查,语义检查,翻译成二进制命令,缓存等等,性能低;使用PreparedStatement就能够实现sql语句预编译,只编译一次,每一次插入只需要填充占位符就好。
3.实现层次三:使用批处理
使用批处理即使用 addBatch() 、executeBatch() 、clearBatch()
注意:
- MySQL服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
rewriteBatchedStatements=true
写在配置文件的url后面
- 如果写完参数还是不能使用批处理,是因为MySQL驱动太老了,需要下载新一点的驱动。部分老的MySQL驱动不支持批处理比如mysql-connector-java-5.1.7 ,这里我使用的驱动是mysql-connector-java-5.1.37。
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class InsertTest3 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
String sql="insert into goods(name) values(?)";
ps = connection.prepareStatement(sql);
int j=600;//一次批处理多少条SQL语句
int k=20000;//要批量插入的数量
for (int i = 0; i < k; i++) {
ps.setString(1,"name_"+i);
//1、"攒"sql
ps.addBatch();
if(i%j==0){
//2、执行"攒"的那批sql
ps.executeBatch();
//3、清空batch
ps.clearBatch();
}
}
if(k/j!=0){//如果j/k!=0证明循环结束,batch里还有语句
ps.executeBatch();
ps.clearBatch();
}
long end = System.currentTimeMillis();
System.out.println("批量插入耗时:"+(end-start)+"毫秒");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,ps);
}
}
}
4.实现层次四:关闭自动提交
我们知道,每执行一次execute,SQL语句就会被执行,执行完毕默认自动提交(将数据的改动提交到数据库),而这个提交的过程也是有耗时的,因此我们希望所有SQL语句都执行完后再进行提交。
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class InsertTest4 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
connection = JDBCUtils.getConnection();
//关闭自动提交
connection.setAutoCommit(false);
String sql="insert into goods(name) values(?)";
ps = connection.prepareStatement(sql);
int j=600;//一次批处理多少条SQL语句
int k=100000;//要批量插入的数量
for (int i = 0; i < k; i++) {
ps.setString(1,"name_"+i);
//1、"攒"sql
ps.addBatch();
if(i%j==0){
//2、执行"攒"的那批sql
ps.executeBatch();
//3、清空batch
ps.clearBatch();
}
}
if(k/j!=0){//如果j/k!=0证明循环结束,batch里还有语句
ps.executeBatch();
ps.clearBatch();
}
//所有语句执行完毕了,再提交
connection.commit();
long end = System.currentTimeMillis();
System.out.println("批量插入耗时:"+(end-start)+"毫秒");
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭数据库连接之前,先将自动提交改回来
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.closeResource(connection,ps);
}
}
}
三、JDBC处理数据库事务
(一)数据库事务介绍
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 事务处理(事务操作):**保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被**提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务 回滚(rollback) 到最初状态。
- 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
(二)事务的ACID属性
1.原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2.一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
3.隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个事务请求同一数据。不同的事务之间彼此没有任何干扰。
4.持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
(三)JDBC事务处理
1.更新(增删改)的事务处理
案例1:user_table表中的用户小明向小红转账100(未考虑事务版本)
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class TransactionTest {
public static void main(String[] args) {
String sql1 = "update user_table set balance = balance - 100 where user = ?";
update(sql1, "小明");
//模拟网络异常
System.out.println(1/0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
update(sql2, "小红");
System.out.println("转账成功");
}
//未考虑事务的通用的增删改操作
public static int update(String sql, Object... args) {
Connection connection = null;
PreparedStatement ps = null;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, ps);
}
return 0;
}
}
我们发现,在小明给小红转账100的过程中,小明扣了100还未转到小红账上的时候,假设发生了异常,那么小明的余额就少了100,而小红的余额并没加上这100,这是我们所不希望的。我们应该把两个更新操作看成一个事务,要么都不执行,要么就都执行成功。而如何将多个操作组合成一个事务呢,那么就需要保证出现异常时,数据能够回滚到最开始的状态。
而我们知道数据一旦提交,就不可回滚。
那么 数据什么时候会提交?
- 默认情况下是自动提交事务:默认情况下,每次执行一个 DML语句时,如果执行成功,就会向数据库自动提交。
- 一旦关闭数据库连接,此连接中的数据就会自动地提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务一致性。即同一个事务的多个操作必须在同一个连接下
JDBC程序中为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的
setAutoCommit(false);
以取消自动提交事务 - 在所有的 SQL 语句都成功执行后,才调用
commit();
方法提交事务 - 在出现异常时,调用
rollback();
方法回滚事务 - 事务中的所有操作执行完毕后再关闭此数据库连接。
注意:若此时 Connection 没有被关闭,还可能被其他事务重复使用,则需要恢复其自动提交状态 setAutoCommit(true)
。尤其是在使用数据库连接池技术时,执行释放资源的方法前,建议先恢复自动提交状态。
案例2:user_table表中的用户小明向小红转账100(考虑事务版本)
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionTest {
public static void main(String[] args) {
//之前的update()方法调用一次,结束会自动关闭数据库连接,这样数据就会被提交
//为了保证两次操作执行完才提交数据,因此我们要执行完两次操作才关闭数据库连接
//那么我们就要把连接数据库的工作移到update()方法体外,并把连接作为参数传给update()
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
//为了将两个操作看成一个事务,防止一个操作执行结束就自动提交无法回滚,我们要关闭自动提交
connection.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ?";
update(connection,sql1, "小明");
//模拟网络异常
System.out.println(1/0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
update(connection,sql2, "小红");
System.out.println("转账成功");
//如果中途没有异常,事务结束后就提交
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//如果中途有异常,那么就回滚
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
//关闭数据库连接之前,先将自动提交改回来
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//关闭数据库连接
JDBCUtils.closeResource(connection,null);
}
}
//考虑了事务的通用的增删改操作
public static int update(Connection connection,String sql,Object... args){
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//不关闭数据库连接,只是释放PreparedStatement资源
JDBCUtils.closeResource(null, ps);
}
return 0;
}
}
遇到了异常,数据回滚,两人的余额还都是最初的1000
2.查询的事务处理
和更新的事务处理类似
①通用的查询操作,用于返回数据表中的一条记录(考虑事务版本)
public <T> T query(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columValue = rs.getObject(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
②通用的查询操作,用于返回数据表中的多条记录(考虑事务版本)
public <T> List<T> query(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList<T> list = new ArrayList<T>();
while (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
Object columValue = rs.getObject(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
③通用的查询操作,用于返回分组函数等特殊值的一条记录(考虑事务版本)
案例:查询user_table表中余额的最大和最小值
import com.fox.util.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class MyTest {
public static void main(String[] args){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
String sql="select min(balance),max(balance) from user_table";
List list = query(connection, sql);
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
//通用的查询操作,用于返回分组函数等特殊值的一条记录(考虑事务版本)
public static List query(Connection conn, String sql, Object...args){
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList list = new ArrayList();
if(rs.next()){
for (int i = 0; i < columnCount; i++) {
list.add(rs.getObject(i+1));
}
}
return list;
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
④通用的查询操作,用于返回分组函数等特殊值的多条记录(考虑事务版本)
案例:查询myemployees表中各部门的平均工资
import com.fox.util.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql="select department_id,avg(salary) from employees group by department_id;";
List<ArrayList> list = query(connection, sql);
for (ArrayList arrayList : list) {
System.out.println(arrayList);
}
}
//通用的查询操作,用于返回分组函数等特殊值的多条记录(考虑事务版本)
public static List<ArrayList> query(Connection conn, String sql, Object...args){
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList<ArrayList> allList = new ArrayList<>();
while(rs.next()){
ArrayList list = new ArrayList();
for (int i = 0; i < columnCount; i++) {
list.add(rs.getObject(i+1));
}
allList.add(list);
}
return allList;
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
(四)数据库的并发问题
-
对于同时运行的多个事务, 当这些事务访问
数据库中相同的数据
时,如果没有采取必要的隔离机制,就会导致各种并发问题:
- 脏读:对于两个事务 T1、 T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后,若 T2 回滚,T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1、T2,T1 读取了一个字段,然后 T2 更新了该字段。之后,T1再次读取同一个字段,值就不同了。
- 幻读:对于两个事务T1、T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。之后,如果 T1 再次读取同一个表,就会多出几行。
-
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
-
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
(五)四种隔离级别
数据库提供的四种事务隔离级别:
隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ UNCOMMITTED(读未提交数据) | 允许事务读取未被其他事务提交的变更 | 会出现 | 会出现 | 会出现 |
READ COMMITTED(读已提交数据) | 只允许事务读取已经被其它事务提交的变更 | 不会出现 | 会出现 | 会出现 |
REPEATABLE READ(可重复读) | 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新 | 不会出现 | 不会出现 | 会出现 |
SERIALIZABLE(串行化) | 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。但性能十分低下。 | 不会出现 | 不会出现 | 不会出现 |
- ORACLE支持的2种事务隔离级别:READ COMMITTED,SERIALIZABLE。ORACLE默认的事务隔离级别为:READ COMMITTED
- MySQL支持4种事务隔离级别。MySQL默认的事务隔离级别为:REPEATABLE READ
(六)查看与设置隔离级别
- 查看当前的隔离级别:
connection.getTransactionIsolation()
- 设置当前MySQL连接的隔离级别为READ COMMITTED:
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)
四、数据库连接池
(一)JDBC数据库连接传统模式的弊端
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
- 这种模式的开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭连接,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- 这种模式的开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
(二)数据库连接池技术
1.介绍
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
2.数据库连接池技术的优点
(1)资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
(2)更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间.
(3)新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源。
(4)统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
(三)多种开源的数据库连接池
- JDBC 的数据库连接池使用
javax.sql.DataSource
来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。Hibernate官方推荐使用
- DBCP 是Apache提供的数据库连接池。 Tomcat 服务器自带dbcp数据库连接池。速度相对C3P0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点
- BoneCP 是一个开源组织提供的数据库连接池,速度快
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
- DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
- 特别注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:
connection.close();
但和传统模式的关闭不一样,这里的connection.close()
并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
1.C3P0数据库连接池
首先需要导入驱动,我们可以去Maven官网搜索c3p0下载驱动,再将jar包放进项目下
(1)获取连接方式一(不建议)
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class C3P0Test {
public static void main(String[] args) {
Connection connection = null;
try {
//创建数据源(连接池)对象
ComboPooledDataSource cpds = new ComboPooledDataSource();
//设置获取连接的四个基本信息
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
cpds.setUser("root");
cpds.setPassword("123456");
//还可以设置其他涉及数据库连接池管理的相关属性:
//设置初始时数据库连接池中的连接数
cpds.setInitialPoolSize(10);//初始时共10个数据库连接
//获取一个数据库连接
connection = cpds.getConnection();
System.out.println(connection);
//销毁C3P0数据库连接池,但一般不这样做
//DataSources.destroy(cpds);
} catch (Exception e) {
e.printStackTrace();
} finally {//用完后将连接放回连接池
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)获取连接方式二(配置文件方式)
首先创建一个XML文件在src下:【c3p0-config.xml】
<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
<named-config name="myc3p0">
<!-- 获取连接的4个基本信息 -->
<property name="user">root</property>
<property name="password">123456</property>
<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>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
将此方法写进工具类JDBCUtils中
//创建数据源(连接池)写在方法体外面,因为并不是调用一次getConnection()就创建一个连接池
private static ComboPooledDataSource cpds = new ComboPooledDataSource("myc3p0");
public static Connection testGetConnection() throws SQLException{
Connection connection = cpds.getConnection();
return connection;
}
1234567
2.DBCP数据库连接池
- DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool。如需使用该连接池实现,应首先去Maven官网搜索DBCP下载驱动,下载后在项目中增加如下两个 jar 文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
- Tomcat 的连接池正是采用DBCP连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
- 配置属性说明
属性 | 默认值 | 说明 |
---|---|---|
initialSize | 0 | 连接池启动时创建的初始化连接数量 |
maxActive | 8 | 连接池中可同时连接的最大的连接数 |
maxIdle | 8 | 连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制 |
minIdle | 0 | 连接池中最小的空闲的连接数,低于这个数量会被创建新的连接。该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大。 |
maxWait | 无限制 | 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待 |
poolPreparedStatements | false | 开启池的Statement是否prepared |
maxOpenPreparedStatements | 无限制 | 开启池的prepared 后的同时最大连接数 |
minEvictableIdleTimeMillis | 连接池中连接,在时间段内一直空闲, 被逐出连接池的时间 | |
removeAbandonedTimeout | 300 | 超过时间限制,回收没有用(废弃)的连接 |
removeAbandoned | false | 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收 |
(1)获取连接方式一(不推荐)
import org.apache.commons.dbcp.BasicDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DBCPTest {
public static void main(String[] args) {
Connection connection =null;
try {
//创建一个数据源
BasicDataSource source = new BasicDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setUsername("root");
source.setPassword("123456");
//还可以设置其他涉及数据库连接池管理的相关属性:
source.setInitialSize(10);
source.setMaxActive(10);
//获取一个数据库连接
connection = source.getConnection();
System.out.println(connection);
} catch (SQLException e) {
e.printStackTrace();
} finally {//用完后将连接放回连接池
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)获取连接方式二(配置文件方式)
首先,在src下创建一个配置文件:【dbcp.properties】
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
initialSize=10
将此方法写进工具类JDBCUtils中
//数据源(连接池)应该放到testGetConnnection()外面,不然每次获取一个数据库连接就要创建一个连接池
private static DataSource source =null;
//静态代码块随着类的加载而加载,只执行一次
static {
try {
Properties pros = new Properties();
FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection testGetConnection() throws Exception {
//获取一个数据库连接
Connection connection = source.getConnection();
return connection;
}
3.Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等数据库连接池的优点,同时加入了日志监控,可以很好的监控数据库连接池和SQL的执行情况,可以说是针对监控而生的数据库连接池,可以说是目前最好的连接池之一。在Maven官网搜索druid下载。
- 配置属性说明
配置 | 缺省 | 说明 |
---|---|---|
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,是组合关系,并非替换关系 |
(1)获取连接方式一(不推荐)
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DruidTest {
public static void main(String[] args) {
Connection connection= null;
try {
DruidDataSource dds = new DruidDataSource();
//获取连接的四个基本信息
dds.setDriverClassName("com.mysql.jdbc.Driver");
dds.setUrl("jdbc:mysql://localhost:3306/test");
dds.setUsername("root");
dds.setPassword("123456");
//还可以设置其他涉及数据库连接池管理的相关属性:
dds.setInitialSize(8);
connection = dds.getConnection();
System.out.println(connection);
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(2)获取连接方式二(配置文件方式)
首先,在src下创建一个配置文件:【druid.properties】
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=10
将此方法写进工具类JDBCUtils中
//数据源(连接池)应该放到testGetConnnection()外面,不然每次获取一个数据库连接就要创建一个连接池
private static DataSource source =null;
static {
try {
Properties properties = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
source = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection testGetConnection() throws SQLException {
Connection connection = source.getConnection();
return connection;
}
五、Apache-DBUtils实现CRUD操作
在Maven官网搜索dbutils下载。
(一)Apache-DBUtils简介
- commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
- API介绍:
- org.apache.commons.dbutils.QueryRunner 提供数据库操作的一系列重载的update()和query()操作
- org.apache.commons.dbutils.ResultSetHandler 此接口用于处理数据库查询操作得到的结果集。不同的结果集的情形,由其不同的子类来实现。
- 工具类:org.apache.commons.dbutils.DbUtils
(二)主要API的使用
1.DbUtils工具类
是提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
public static void close(…) throws java.sql.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)
:回滚并关闭连接,并且在关闭连接时不抛出SQL异常。public static boolean loadDriver(java.lang.String driverClassName)
:这一方法加载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
2.QueryRunner类
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
- QueryRunner类提供了两个构造器:
- 默认的构造器
- 需要一个 javax.sql.DataSource 来作参数的构造器
- QueryRunner类的主要方法:
- 更新
public int update(Connection conn, String sql, Object... params) throws SQLException
:用来执行一个更新(插入、更新或删除)操作。
- 插入
public <T> T insert(Connection conn,String sql,ResultSetHandler<T> rsh, Object... params) throws SQLException
:只支持INSERT语句
- 批处理
public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException
:此批处理支持INSERT, UPDATE或者DELETE语句public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[][] params)throws SQLException
:只支持INSERT语句
- 查询
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException
:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
- 更新
3.ResultSetHandler接口及实现类
此接口用于处理数据库查询操作得到的结果集,不同的结果集的形式,由其不同的子类来实现。
- 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:查询单个值对象
(三)案例演示
1.插入
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.*;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int insertCount = runner.update(conn, sql, "蔡徐坤","caixukun@126.com","1997-09-08");
System.out.println("添加了" + insertCount + "条记录");
} catch (Exception e) {
e.printStackTrace();
}finally{//关闭资源,update()方法已经将statement关闭了,因此不需要再关闭
DbUtils.closeQuietly(conn);//用DbUtils提供的closeQuietly()关闭连接,不用捕获异常,但它的close()要捕获异常
}
}
}
注意:QueryRunner类中的update()已经关闭了Statement资源,因此我们只需要关闭Connection
2.查询
(1)查询的结果对应一个Java实体类,结果只有一条记录
①使用BeanHandler
import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.*;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
//创建一个ResultSetHandler接口的实现类BeanHandler,用于封装结果
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 23);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
}finally{//关闭资源,query()方法已经将Statement和ResultSet关闭了,因此不需要再关闭
DbUtils.closeQuietly(conn);
}
}
}
注意:QueryRunner类中的query()已经关闭了Statement和ResultSet资源,因此我们只需要关闭Connection
②使用MapHandler
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapHandler;
import java.sql.*;
import java.util.Map;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
//创建一个ResultSetHandler接口的实现类MapHandler,用于封装结果
//将字段及相应字段的值作为map中的key和value
MapHandler handler = new MapHandler();
Map<String, Object> map = runner.query(conn, sql, handler, 23);
System.out.println(map);
} catch (Exception e) {
e.printStackTrace();
}finally{
DbUtils.closeQuietly(conn);
}
}
}
(2)查询的结果对应一个Java实体类,结果有多条记录
①使用BeanListHandler
import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.*;
import java.util.List;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
//创建一个ResultSetHandler接口的实现类 BeanListHandler,用于封装表中的多条记录构成的集合。
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 6);
for (Customer customer : list) {
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
DbUtils.closeQuietly(conn);
}
}
}
②使用MapListHandler
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import java.sql.*;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
//创建一个ResultSetHandler接口的实现类MapListHandler,对应表中的多条记录。
//将字段及相应字段的值作为map中的key和value。将这些map添加到List中
MapListHandler handler = new MapListHandler();
List<Map<String, Object>> list = runner.query(conn, sql, handler, 6);
for (Map<String, Object> map : list) {
System.out.println(map);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
DbUtils.closeQuietly(conn);
}
}
}
(3)查询分组函数等特殊值
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.*;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select count(*) from customers";
//创建一个ResultSetHandler接口的实现类ScalarHandler,用于查询特殊值
ScalarHandler handler = new ScalarHandler();
Long count = (Long) runner.query(conn, sql, handler);
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
}finally{
DbUtils.closeQuietly(conn);
}
}
}
(4)自定义ResultSetHandler的实现类
import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import java.sql.*;
public class Test {
public static void main(String[] args){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
//自定义一个ResultSetHandler接口的实现类
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>(){
@Override
public Customer handle(ResultSet rs) throws SQLException {
if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer customer = new Customer(id, name, email, birth);
return customer;
}
return null;
}
};
Customer customer = runner.query(conn, sql, handler,23);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
}finally{
DbUtils.closeQuietly(conn);
}
}
}