摘要:
如果我们只会使用一些工具,那是知其然,但是如果我们也能写出一个类似的工具,那就是知其所以然;一步步理解下QueryRunner,提高下java的编程思想,理解底层原理;站在框架设计者的角度看本文,主要理解QueryRunner底层的思想,设计原理,同时还可以辅助理解动态代理,希望下面的思路能帮助大家好好理解
涉及到的知识点:
1.QueryRunner的使用
2.mysql数据库
3.c3p0数据库连接池及配置文件
4.可变参数(实质就是数组)
5.接口回调
源码及所有配置文件:https://download.csdn.net/download/u010452388/10398934
一 使用DBUtils下的QueryRunner
1-1 数据库表
1-2 c3p0配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</property>
<property name="user">root</property>
<property name="password">123</property>
<!-- 如果连接池中连接不够时一次性增长多少连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化连接池时池子中有多少个连接 -->
<property name="initialPoolSize">20</property>
<!-- 池子中最小连接数 -->
<property name="minPoolSize">10</property>
<!-- 池子中最大连接数 -->
<property name="maxPoolSize">40</property>
</default-config>
<named-config name="day06">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day06</property>
<property name="user">root</property>
<property name="password">123</property>
</named-config>
</c3p0-config>
1-3 文件目录
1-4 User类代码
public class User {
private int id;
private String username;
private String password;
private String email;
public User(int id, String username, String password, String email) {
super();
this.id = id;
this.username = username;
this.password = password;
this.email = email;
}
public User() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", email=" + email + "]";
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getEmail() {
return email;
}
public void setId(int id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEmail(String email) {
this.email = email;
}
}
1-5 DbutilsDemo测试代码
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DbutilsDemo {
public static void main(String[] args) throws SQLException {
// 首先创建c3p0的数据库连接池(属于开源包c3p0-0.9.12.jar)
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 再创建QueryRunner(属于开源包commons-dbutils-1.6.jar),并传入连接池(可以用c3p0,或者dbcp的连接池);
QueryRunner runner = new QueryRunner(dataSource);
// 声明查询sql语句
String sql = "select * from user where username=? and password=?";
// 可变参数其实就是数组,要查询的顺序和数量要和sql语句一致
Object[] params = { "zs", "123" };
ResultSetHandler<User> rsh = new ResultSetHandler<User>() {
@Override
public User handle(ResultSet rs) throws SQLException {
if (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
User user = new User(id, username, password, email);
return user;
}
return null;
}
};
// query的第三个参数是可变参数,可变参数其实就是数组
User user = runner.query(sql, rsh, params);
System.out.println(user);
}
}
1-6 测试结果
红色部分不用管,是c3p0自动打印的
从结果可以看出,控制台已经打印出我们需要的信息
二 自定义MyQueryRunner
现在我们要站在框架设计者的角度去思考问题
第1步-定义一个构造函数
构造函数的参数是DataSource
可能你有疑问:
1. 为什么传DataSource,而不传c3p0的的数据库连接池?
答:我们目前是架构设计者,并不清楚调用者用的是哪种连接池,有可能是c3p0,也有可能是Apache的dbcp连接池,所以我们用DataSource接口,让调用者根据自己实际用的连接池来实现DataSource接口
import javax.sql.DataSource;
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds) {
this.ds = ds;
}
}
第2步-创建查询方法query
站在框架设计者的角度思考:
1.调用者调用我们query方法之后,我们需要与数据库进行连接
2.调用者查询语句,应该要传入一个要进行预编译sql语句,所以要提供一个参数给调用者传入
3.调用者传入了需要预编译的sql语句,还要传入一个预编译的参数个数,但是我们不清楚调用者到底传入几个参数,该如何处理?
(这里我们可以运用可变参数,即可以解决)
方法的返回值我们先用User(有助于大家理解),后面会改成泛型
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.dbutils.User;
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds) {
this.ds = ds;
}
//sql为调用者传入的查询语句,params为设置的参数
public User query(String sql,Object...params) throws SQLException{
//获取连接池中的连接
Connection conn = ds.getConnection();
//对sql进行预编译
PreparedStatement statement = conn.prepareStatement(sql);
//先返回null,避免编译报错,后面代码会更改
return null;
}
}
第3步-判断查询语句与传入的参数个数
这里我们需要考虑一个问题:
如果调用者传入的参数个数与sql预编译语句里需要的参数不对怎么办?
所以我们要先进行判断
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.dbutils.User;
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds) {
this.ds = ds;
}
//sql为调用者传入的查询语句
public User query(String sql,Object...params) throws SQLException{
//获取连接池中的连接
Connection conn = ds.getConnection();
//对sql进行预编译
PreparedStatement statement = conn.prepareStatement(sql);
//获取预编译查询语句里的元数据
ParameterMetaData metaData = statement.getParameterMetaData();
//获取元数据的个数
int count = metaData.getParameterCount();
//如果元数据的个数和传入的params数据个数不一致,则抛出异常
if(count!=params.length){
throw new IllegalArgumentException("你个傻鸟,传进来的数据个数不对");
}
//到这一步,说明说明传进来的params数据个数一致了
//循环对查询语句sql中的?进行赋值
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1, params[i]);
}
//执行查询语句,并将结果集返回给resultSet
ResultSet resultSet = statement.executeQuery();
//先返回null,避免编译报错,后面代码会更改
return null;
}
}
第4步 关键点-返回值问题
根据上面最后的代码,我们要考虑几个问题
1.我们目前有什么?resultSet
2.我们需要什么?User
3.如何将resultSet 变成User对象?
站在框架设计者角度想:如果有个方法,可以将resultSet变成User,但是怎么实现我们不清楚,可以想到用抽象方法。能够将resuletSet传入,给我们返回一个User多好?
比如:public abstract User handle(ResultSet resultSet) ,看到这里,我们可以想到抽象类或者接口,我们这里不选用抽象类,因为抽象类无法实例化,我们选用接口,让调用者实现此接口,然后将其实现对象传入query方法,在方法内部调用handle方法,将resultSet变成User;这样我们就解决了刚刚1,2,3的问题了
竟然要实现了此接口,那么调用者就要重写handle方法,即我们给调用者提供resultSet,调用者返回给我们User。
下面我们添加接口MyResultSetHandle,以及对query方法添加一个MyResultSetHandle参数,让调用者传入其实现对象
就此完成MyQueryRunner完整代码,下面我们开始测试query方法,最后会给出
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.dbutils.User;
//创建resultSet处理接口
interface MyResultSetHandle{
//返回值为User,传入resultSet
User handle(ResultSet resultSet) throws SQLException;
}
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds) {
this.ds = ds;
}
//sql为调用者传入的查询语句
public User query(String sql,MyResultSetHandle mrsh,Object...params) throws SQLException{
//获取连接池中的连接
Connection conn = ds.getConnection();
//对sql进行预编译
PreparedStatement statement = conn.prepareStatement(sql);
//获取预编译查询语句里的元数据
ParameterMetaData metaData = statement.getParameterMetaData();
//获取元数据的个数
int count = metaData.getParameterCount();
//如果元数据的个数和传入的params数据个数不一致,则抛出异常
if(count!=params.length){
throw new IllegalArgumentException("你个傻鸟,传进来的数据个数不对");
}
//到这一步,说明说明传进来的params数据个数一致了
//循环对查询语句中的?进行赋值
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1, params[i]);
}
//执行查询语句,并将结果集返回给resultSet
ResultSet resultSet = statement.executeQuery();
//执行调用者重写的方法,实现接口回调
User user = mrsh.handle(resultSet);
//返回user对象
return user;
}
}
三-测试MyQueryRunner
3-1 测试Demo
下面MyTestDemo代码是调用自己写的MyQueryRunner实现查询功能,可以观察和1-5测试的代码一致,只是1-5测试里用到了泛型,后面我们只要在接口和方法加上泛型即可
import java.sql.ResultSet;
import java.sql.SQLException;
import com.dbutils.User;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class MyTestDemo {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//调用自己编写的MyQueryRunner
MyQueryRunner runner = new MyQueryRunner(dataSource);
String sql="select * from user where username=? and password=?";
Object[] params={"zs","123"};
MyResultSetHandle mrsh=new MyResultSetHandle() {
@Override
public User handle(ResultSet rs) throws SQLException {
if(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
User user = new User(id, username, password, email);
return user;
}
return null;
}
};
User user = runner.query(sql, mrsh, params);
System.out.println(user);
}
}
3-2 测试结果
3-3 代码执行流程
这里有个注意点:
main方法中,mrsh对象创建好的时候,其重写的handle方法并没有执行,方法只有被调用的时候才会执行内部细节
3-4 添加泛型后的代码
MyQuerRunner添加泛型后代码
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
//创建resultSet处理接口
interface MyResultSetHandle<T> {
// 返回值为User,传入resultSet
T handle(ResultSet resultSet) throws SQLException;
}
public class MyQueryRunner {
DataSource ds;
public MyQueryRunner(DataSource ds) {
this.ds = ds;
}
public <T> T query(String sql, MyResultSetHandle<T> mrsh, Object... params) throws SQLException {
Connection conn = ds.getConnection();
PreparedStatement statement = conn.prepareStatement(sql);
ParameterMetaData metaData = statement.getParameterMetaData();
int count = metaData.getParameterCount();
if (count != params.length) {
throw new IllegalArgumentException("你个傻鸟,传进来的数据个数不对");
}
for (int i = 0; i < params.length; i++) {
statement.setObject(i + 1, params[i]);
}
ResultSet resultSet = statement.executeQuery();
T t = mrsh.handle(resultSet);
return t;
}
}
MyTestDemo代码:
import java.sql.ResultSet;
import java.sql.SQLException;
import com.dbutils.User;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class MyTestDemo {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
MyQueryRunner runner = new MyQueryRunner(dataSource);
String sql = "select * from user where username=? and password=?";
Object[] params = { "zs", "123" };
MyResultSetHandle<User> mrsh = new MyResultSetHandle<User>() {
@Override
public User handle(ResultSet rs) throws SQLException {
if (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
User user = new User(id, username, password, email);
return user;
}
return null;
}
};
User user = runner.query(sql, mrsh, params);
System.out.println(user);
}
}