到现在我们先想想,我们学习了数据库操作哪些内容?????
-
先是JDBC入门学习,学习Java连接数据库
-
然后将连接数据库getConnection和释放连接release提取出来, 封装成jdbcutils
-
然后就是事务,主要是学习下面几个函数使用
Connection.setAutoCommit(false);//开启事务(start transaction) Connection.rollback();//回滚事务(rollback) Connection.commit();//提交事务(commit) Savepoint sp = conn.setSavepoint(); Conn.rollback(sp); Conn.commit();//回滚后必须通知数据库提交事务
-
然后就是学习数据库连接池,主要学习两个 DBCP和C3P0,主要是改进jdbcutils
-
然后就是编写自己的JDBC框架,说白了就是将CRUD整合成update函数和query函数
我们先来说说 update 万能更新
看看下面的几个sql语句
INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson'
DELETE FROM Person WHERE LastName = 'Wilson'
认真观察, 实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,我们就把sql语句拆分成一个框架sql语句和实体sql对象 String sql, Object params[]
这个好理解,所以写成万能update也简单
/**
* @Method: update
* @Description: 万能更新
* @param sql 要执行的SQL
* @param params 执行SQL时使用的参数
* @throws SQLException
*/
public static void update(String sql,Object params[]) throws SQLException{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = getConnection();
st = conn.prepareStatement(sql);
for(int i=0;i<params.length;i++){
st.setObject(i+1, params[i]);
}
st.executeUpdate();
}finally{
release(conn, st, rs);
}
}
但是!!!!!!
query不好理解,因为select * from table 这个* 不同select 就有不同的对象,所以写万能query就有点麻烦。
万幸有Object对象来指向它的万物子孙
Object obj = new Person() ; Object obj1 = new Cat() ; Object obj2 = new Shop() ; .......
因此, 我们需要将具体的类传入到参数中,来保存select查询的结果
我们来一步步看看用户的操作思路
首先,我们要从数据库中把我们想要查询的字段给查询出来嘛,
每个使用select 的用户知道数据库的table , 知道table包含哪些字段嘛, 所以就会设置相应的类来存储这些字段
不仅获取到table内容,
我们还要知道字段名,这个很重要 ,你想想,我们光光从数据库取出数据是不够的, 还要把数据装填给对象啊,
比如:select * from student ,执行了条sql后, 内容保存在哪里呢?
好吧,我们在java的domain 写个Student类, 然后new一个Student对象嘛,
但是咋个一一赋值呢? 所以我们要知道字段名,下面这几行代码就可以获取到,
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account ";
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
ResultSetMetaData metadata = rs.getMetaData();
弱弱解释一下ResultSetMetaData可以干什么事情,如下:
/**
* <p>Title: testResultSetMetaData</p>
* <p>Description: 获取结果集的元数据</p>
* @throws SQLException
*
* getColumnCount() 返回resultset对象的列数
* getColumnName(int column) 获得指定列的名称
* getColumnTypeName(int column)获得指定列的类型
*/
接下来就是写类, 类的成员变量要跟数据库的字段一一对应,
然后怎么把查询的字段内容复制给这个类呢???
我们来梳理一下正常操作流程,先查询数据库 ,获取到列名和列的内容,
用BeanUtils,将字段复制给类的对象
while (rs.next()) {
// 2.赋值给对象
int colCount = metaData.getColumnCount();
Account account = new Account();
for (int i = 1; i <= colCount; i++) {
String colName = metaData.getColumnName(i );
Object objCol = rs.getObject(i);
BeanUtils.setProperty(account, colName, objCol);
}
list.add(account);
}
主要用到了BeanUtils ,这样就把列的内容复制给对象了
-
commons-beanutils-1.9.3.jar
-
commons-logging-1.2.jar
但这里也有一个问题, 就是sql 必须是select * from *** ,
如果用户不想用这条sql呢? 怎么办 , 比如:select name , money from account where id=?
那么就制定一个规则,用户要查询什么,就封装什么对象
比如你只想查询表中某几个字段的内容, 那么你就要将这几个字段封装成对象,
没办法, 系统不可能预先知道你要查询啥子吧,只有用户你自己知道,自己知道就自己动手封装
select id , name from account where id = xxx
抽取出来, 就是 :
框架 + 参数 + 对象
String sqlString = " select id,money from account where name =? ";
Object[] paramsObjects = { "aaa"};
Account account = new Account();
query(sqlString, paramsObjects, account);
然而, apache的dbutils不是使用BeanUtils ,肯定是自己写
也就是这句话BeanUtils.setProperty(account, colName, objCol); 要想办法重新写, 咋写????
反射,还记得吗?
需要自动化赋值类成员 的代码模板
// 1.获取Class对象
Class clazz = Class.forName("com.reflect.domain.Student");
// 2.获取一个对象,或者调用者手动传进来一个
Object obj = clazz.getConstructor().newInstance();// 产生Student对象--》Student stu = new Student();
// 3.获取某个字段(可以是私有的)
Field field1 = clazz.getDeclaredField("phoneNum");
// 4.赋值
field1.setAccessible(true);//暴力反射,解除私有限定
field1.set(obj, "18888889999");
上面的phoneNum是成员变量,感觉好像不智能,,,,
getColumnName(int column) 获得指定列的名称 , 忘啦??? 这一步步来着呢
到现在就顺了,因为我们啥都理顺了,总结一下步骤:
-
使用jdbcutils连接数据库
-
用conn.prepareStatement(sql);预执行sql语句
-
获取结果集的元数据
-
使用反射给对象赋值
-
binggo!!!
/**
* @param sql 用户传递的sql语句
* @param params sql语句中包含的参数
* @param obj 用户传进来的对象, 比如Account account = new Account();
*/
public void query(String sql, Object[] params, Object obj) {
// 1.首先,获取数据库的连接
Connection conn = JdbcUtils.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
ResultSetMetaData metaData = null;
try {
//2.用conn.prepareStatement(sql);预执行sql语句
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
rs = ps.executeQuery();
//3.获取结果集的元数据
metaData = rs.getMetaData();
int colCount = metaData.getColumnCount();
//4.使用反射给对象赋值
Class<? extends Object> clazz = obj.getClass();
//这是好像应该用list,因为rs获取的结果有可能是多个
while (rs.next()) {
for (int i = 1; i <= colCount; i++) {
String colName = metaData.getColumnName(i);
Object objCol = rs.getObject(i);
//4.使用反射给对象赋值
Field field = clazz.getDeclaredField(colName);
field.setAccessible(true);
field.set(obj, objCol);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.releaseConnection(conn, ps, rs);
}
}
上面就是JdbcUtils框架查询函数的流程, 只是说JdbcUtils框架将上面的函数做了处理,用BeanHandler 和BeanListHandler 来封装了上面一部分的代码,
感觉是不是高级些,取个名字都这么氧气?
BeanHandler 和BeanListHandler都实现了ResultSetHandler 这个接口, ResultSetHandler 是系统暴露给用户的接口
还是那个话,用户query数据库, 数据库返回结果集, 这个结果集该咋处理, 系统不知道, 只有用户知道,
用户要是都不知道, 那用户干嘛查询来了?????
既然用户知道,那我系统就给你一个接口, 你来实现呗,只要你按照我的框架协议写就好了
我们现在来看看上面的query如何改造嘛:
第1、2 步骤是标准数据库操作,这个不宜分离开来
第3、4步则是处理结果集合,我们想办法改造,说是改造, 还不如说向Apache的dbutils靠拢, 看看他们是怎么写的
我们来看看 3、4步,
传入的参数是ResultSet rs , 返回的是Object obj 或者是List<Object> ,这也说明了返回的结果不相同,众口难调,于是系统写个接口, 让用户自个儿实现
我们先想想,系统如何写接口???? 本身就这几行代码,系统能玩出什么花样????
既然是接口, 那么就要抽出共性,步骤4肯定不能, 因为返回的是Object的话,就没有while循环,如果是list,则有while循环,这个就是个性
步骤3又跟步骤4紧密联系,也不能作为共性,所以干脆来个猛的,
系统写个接口, 啥都不干,就来规范!!!
/**
* @ClassName: ResultSetHandler
* @Description:结果集处理器接口
*
*/
public interface ResultSetHandler {
/**
* @Method: handler
* @Description: 结果集处理方法
* @param rs 查询结果集
* @return
*/
public Object handler(ResultSet rs);
}
把个性的操作全部放在handler中实现,
接下来就是BeanHandler 和BeanListHandler , 一个是返回Ojbect 一个是返回list
一个个来
BeanHandler 其实就是不用while嘛
BeanHandler 写法,跟上面的代码有点不一样, 但说白了,都差不多, 我们来看嘛
/**
* @ClassName: BeanHandler
* @Description: 将结果集转换成bean对象的处理器
*
*/
public class BeanHandler implements ResultSetHandler {
private Class<?> clazz;
public BeanHandler(Class<?> clazz){
this.clazz = clazz;
}
public Object handler(ResultSet rs) {
try{
if(!rs.next()){
return null;
}
Object bean = clazz.newInstance();
//得到结果集元数据
ResultSetMetaData metadata = rs.getMetaData();
int columnCount = metadata.getColumnCount();//得到结果集中有几列数据
for(int i=0;i<columnCount;i++){
String coulmnName = metadata.getColumnName(i+1);//得到每列的列名
Object coulmnData = rs.getObject(i+1);
Field f = clazz.getDeclaredField(coulmnName);//反射出类上列名对应的属性
f.setAccessible(true);
f.set(bean, coulmnData);
}
return bean;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
BeanHandler 把clazz作为了成员变量, 然后遵守
public Object handler(ResultSet rs); 返回bean object
是不是差不多嘛???
不过它这里有点巧妙的,就是把clazz作为了成员变量 , 返回Object bean = clazz.newInstance(); 也就是传入进去的对象是什么,然后装填该对象, 返回的就是什么对象, 从这个角度来看,BeanHandler 像是一个容器,可以装可乐,也可以装芬达。。。。 但是主体框架没有变化,有点类似 Object obj = new student();
想哈我们怎么调用它嘛
(Account) JdbcUtils.query(sql, params, new BeanHandler(Account.class));
对不对, 传入的是BeanHandler框架,只是BeanHandler装的内容不一样,返回的就是该内容
BeanListHandler
其实也一样, 无非就多了个while循环嘛
看代码
/**
* @ClassName: BeanListHandler
* @Description: 将结果集转换成bean对象的list集合的处理器
*
*/
public class BeanListHandler implements ResultSetHandler {
private Class<?> clazz;
public BeanListHandler(Class<?> clazz){
this.clazz = clazz;
}
public Object handler(ResultSet rs) {
try{
List<Object> list = new ArrayList<Object>();
while(rs.next()){
Object bean = clazz.newInstance();
ResultSetMetaData metadata = rs.getMetaData();
int count = metadata.getColumnCount();
for(int i=0;i<count;i++){
String name = metadata.getColumnName(i+1);
Object value = rs.getObject(name);
Field f = bean.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);
}
list.add(bean);
}
return list.size()>0?list:null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
是不是多了个while嘛?
然后看调用
(List<Account>) JdbcUtils.query(sql, params,new BeanListHandler(Account.class));
有点意思
我们回过头来说说query ,现在我们是不是改造了嘛,该咋写
第1、2步不变,第3、4步封装成接口,让用户自己去实现,实现的方式有BeanHandler 和BeanListHandler
所以
/**
* @Method: query
* @Description:万能查询
* 实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,
* @param sql 要执行的SQL
* @param params 执行SQL时使用的参数
* @param rsh 查询返回的结果集处理器
* @return
* @throws SQLException
*/
public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
// 1.首先,获取数据库的连接
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = getConnection();
//2.用conn.prepareStatement(sql);预执行sql语句
st = conn.prepareStatement(sql);
for(int i=0;i<params.length;i++){
st.setObject(i+1, params[i]);
}
rs = st.executeQuery();
/**
* 对于查询返回的结果集处理使用到了策略模式,
* 在设计query方法时,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,
* 即不知道结果集的处理策略,
* 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理
* 策略进行处理
* 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler
* 用户只要实现了ResultSetHandler接口,那么query方法内部就知道用户要如何处理结果集了
*/
return rsh.handler(rs);
}finally{
release(conn, st, rs);
}
}
基本上就说完了。接下来我们学习Apache的dbutils