一. 代码重构原则
之前的DAO操作,除了sql语句和set值不一样,其它都一样,有太多的重复代码。因此需要重构。
重构代码原则:
二. 抽取DML方法
1. 具体抽取成executeUpdate方法在每一个dao实现中
1 /** 2 * 重构DML操作 3 */ 4 public int executeUpdate(String sql,Object...params) { 5 Connection connection=null; 6 PreparedStatement pStatement=null; 7 8 try { 9 //1.加载驱动 10 //2.连接数据库 11 connection=JDBCUtil.getConnection(); 12 //3.创建语句 13 pStatement=connection.prepareStatement(sql); 14 //4.遍历参数 15 for(int i=0;i<params.length;i++) { 16 pStatement.setObject(i+1, params[i]); 17 } 18 //4.执行语句 19 return pStatement.executeUpdate(); 20 } catch (SQLException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 }finally { 24 JDBCUtil.close(connection, pStatement, null); 25 } 26 return 0; 27 } 28 29 public void save(Stu stu) { 30 String sql="insert into stu(name,age) values(?,?)"; 31 this.executeUpdate(sql,stu.getName(),stu.getAge()); 32 }
测试:
1 @Test 2 public void testSave() { 3 IStuDao dao=new StuDaoImpl(); 4 Stu stu=new Stu(); 5 stu.setName("hehe"); 6 stu.setAge(23); 7 dao.save(stu); 8 }
这样可以成功,但是倘若每个dao的实现类里面都写这个executeUpdate方法,还是有写多余,因此可以抽取到一个类中。如果放在JDBCUtil类中,也不好,因为JDBCUtil是直接和数据交互的,因此可以新建立一个类CRUDTemplate。
2. 抽取到CRUDTemplate中----最终抽取地方
1 public class CRUDTemplate { 2 /** 3 * 重构DML操作 4 */ 5 public static int executeUpdate(String sql,Object...params) { 6 Connection connection=null; 7 PreparedStatement pStatement=null; 8 9 try { 10 //1.加载驱动 11 //2.连接数据库 12 connection=JDBCUtil.getConnection(); 13 //3.创建语句 14 pStatement=connection.prepareStatement(sql); 15 //4.遍历参数 16 for(int i=0;i<params.length;i++) { 17 pStatement.setObject(i+1, params[i]); 18 } 19 //4.执行语句 20 return pStatement.executeUpdate(); 21 } catch (SQLException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 }finally { 25 JDBCUtil.close(connection, pStatement, null); 26 } 27 return 0; 28 } 29 30 }
1 public void save(Stu stu) { 2 String sql="insert into stu(name,age) values(?,?)"; 3 CRUDTemplate.executeUpdate(sql,stu.getName(),stu.getAge()); 4 }
三. DQL抽取
1. DQL抽取
1 /** 2 * DQL抽取 3 */ 4 public static List<Stu> executeQuery(String sql,Object...params){ 5 Connection connection=null; 6 PreparedStatement pStatement=null; 7 ResultSet resultSet=null; 8 //创建一个集合 9 List<Stu> list=new ArrayList<Stu>(); 10 try { 11 //1.加载驱动 12 //2.连接数据库 13 connection=JDBCUtil.getConnection(); 14 //3.创建语句 15 pStatement=connection.prepareStatement(sql); 16 //遍历参数 17 for(int i=0;i<params.length;i++) { 18 pStatement.setObject(i+1, params[i]); 19 } 20 //4.执行语句 21 resultSet=pStatement.executeQuery(); 22 while(resultSet.next()) { 23 Stu stu=new Stu(); 24 stu.setName(resultSet.getString("name")); 25 stu.setAge(resultSet.getInt("age")); 26 stu.setId(resultSet.getInt("id")); 27 list.add(stu); 28 } 29 return list; 30 }catch (Exception e) { 31 e.printStackTrace(); 32 }finally { 33 //5.释放资源 34 JDBCUtil.close(connection, pStatement, resultSet); 35 } 36 return null; 37 }
1 public Stu get(int id) { 2 String sql="select *from stu where id=?"; 3 List<Stu> list=CRUDTemplate.executeQuery(sql, id); 4 return list.size()==1?list.get(0):null; 5 } 6 7 8 public List<Stu> getAll() { 9 String sql="select *from stu"; 10 return CRUDTemplate.executeQuery(sql); 11 }
1 @Test 2 public void testGet() { 3 IStuDao dao=new StuDaoImpl(); 4 Stu student=dao.get(2); 5 System.out.println(student); 6 System.out.println(student.getName()); 7 } 8 9 @Test 10 public void testGetAll() { 11 IStuDao dao=new StuDaoImpl(); 12 List<Stu> list=dao.getAll(); 13 System.out.println(list.toArray()); 14 }
2. 存在问题
上面的DQL抽取虽然简单了,但是还存在一个问题,那就是在while循环中,只操作了Stu这个表格,写死了,但是,当表格很多,不仅仅想让它适用于Stu表,还想使用更多的表,就需要额外处理。因此,表的类型处理应该放在executeQuery方法之外来执行。
四. 结果集处理代码实现
1. 定义一个结果集处理接口
1 package com.test.jdbctest.handler; 2 3 import java.sql.ResultSet; 4 import java.util.List; 5 6 public interface IResultSetHandler { 7 List handle(ResultSet rs); 8 9 }
2. 在dao实现类中添加新的结果处理接口实现类
1 class StuResultSetHandImp implements IResultSetHandler{ 2 3 public List handle(ResultSet rs) { 4 List<Stu> list=new ArrayList<Stu>(); 5 try { 6 while(rs.next()) { 7 Stu stu=new Stu(); 8 stu.setName(rs.getString("name")); 9 stu.setAge(rs.getInt("age")); 10 stu.setId(rs.getInt("id")); 11 list.add(stu); 12 } 13 } catch (SQLException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } 17 return list; 18 } 19 20 21 22 放在daoimpl类里面的末尾处
3. 修改template
1 /** 2 * DQL抽取 3 */ 4 public static List<Stu> executeQuery(String sql,IResultSetHandler handler,Object...params){ 5 Connection connection=null; 6 PreparedStatement pStatement=null; 7 ResultSet resultSet=null; 8 //创建一个集合 9 List list=new ArrayList(); 10 try { 11 //1.加载驱动 12 //2.连接数据库 13 connection=JDBCUtil.getConnection(); 14 //3.创建语句 15 pStatement=connection.prepareStatement(sql); 16 //4.执行语句 17 resultSet=pStatement.executeQuery(); 18 return handler.handle(resultSet); 19 }catch (Exception e) { 20 e.printStackTrace(); 21 }finally { 22 //5.释放资源 23 JDBCUtil.close(connection, pStatement, resultSet); 24 } 25 return null; 26 }
4. 使用泛型
上面的结果集都是返回的list,但是有时候我们可能不仅仅是返回对象,有可能返回的是int的数量,比如查询共有多少行。因此,需要把返回类型改写成泛型,到时候调用的时候再决定返回什么类型。
(1)修改结果处理集接口
public interface IResultSetHandler<T> { T handle(ResultSet rs); }
(2)修改实现
class StuResultSetHandImp implements IResultSetHandler<List<Stu>>{ public List<Stu> handle(ResultSet rs) { List<Stu> list=new ArrayList<Stu>(); try { while(rs.next()) { Stu stu=new Stu(); stu.setName(rs.getString("name")); stu.setAge(rs.getInt("age")); stu.setId(rs.getInt("id")); list.add(stu); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return list; }
(3)修改template
public static <T>T executeQuery(String sql,IResultSetHandler<T> handler,Object...params){ Connection connection=null; PreparedStatement pStatement=null; ResultSet resultSet=null; try { //1.加载驱动 //2.连接数据库 connection=JDBCUtil.getConnection(); //3.创建语句 pStatement=connection.prepareStatement(sql); for(int i=0;i<params.length;i++) { pStatement.setObject(i+1, params[i]); } //4.执行语句 resultSet=pStatement.executeQuery(); return handler.handle(resultSet); }catch (Exception e) { e.printStackTrace(); }finally { //5.释放资源 JDBCUtil.close(connection, pStatement, resultSet); } return null; }
5. 存在问题
发现其实每个表格查询做的事情差不多,如果每一个表都建立一个handler,是不好的。因此,可以统一处理:
使用内省可以解决上面的问题。
五. 内省
1. 内省的简单使用
假设有表stu,有id,name,age三个属性,且有对应的domain类
现在做如下操作:
1 class ClassTest{ 2 public Class class1; 3 public ClassTest(Class class1) { 4 this.class1=class1; 5 } 6 } 7 public class Test { 8 public static void main(String args[]) throws Exception { 9 Stu stu=Stu.class.newInstance(); 10 //获取指定字节码的属性信息 11 BeanInfo beanInfo=Introspector.getBeanInfo(Stu.class,Object.class); 12 //获取所有的属性描述器 13 PropertyDescriptor pds[]=beanInfo.getPropertyDescriptors(); 14 for(PropertyDescriptor pd:pds) { 15 System.out.println(pd.getName()); 16 System.out.println(pd.getReadMethod()); 17 System.out.println(pd.getWriteMethod()); 18 } 19 PropertyDescriptor propertyDescriptor=new PropertyDescriptor("name", Stu.class); 20 System.out.println(propertyDescriptor.getName()); 21 propertyDescriptor.getWriteMethod().invoke(stu, "zhangsan"); 22 System.out.println(stu.getName()); 23 } 24 25 }
1 age 2 public java.lang.Integer com.test.jdbctest.domain.Stu.getAge() 3 public void com.test.jdbctest.domain.Stu.setAge(java.lang.Integer) 4 id 5 public java.lang.Integer com.test.jdbctest.domain.Stu.getId() 6 public void com.test.jdbctest.domain.Stu.setId(java.lang.Integer) 7 name 8 public java.lang.String com.test.jdbctest.domain.Stu.getName() 9 public void com.test.jdbctest.domain.Stu.setName(java.lang.String) 10 name 11 zhangsan
2. 内省应用于结果处理集
(1)创建一个BeanHandler
修改dao实现
(2)新建立BeanListHandler
因此,上面每个类一个handerl就改成了BeanHanler和BeanListHandler了
六. DBUtils
之前几个章节都是介绍的如何一步步实现我们自己的JDBC重构,且可以把我们自己写的转换成jar包,以便自己项目中使用。实际上,有很多第三方很好用的JDBC工具,DBUtils就是其中的一款,它为我们提供了调用接口,我们直接使用就好。但是,底层的原理其实和我们之前学习的重构思想也有相似之处。可以接下来研究下。
参考文献
https://ke.qq.com/course/339214