编写自定义的JDBC框架与策略模式
本篇根据上一篇利用数据库的几种元数据来仿造Apache公司的开源DbUtils工具类集合来编写自己的JDBC框架。也就是说在本篇中很大程度上的代码都和DbUtils中相似,学完本篇后即更容易了解DbUtils是如何使用的。
我们在使用JDBC对数据库进行操作时,基本都是在dao层,也就是说在dao层封装了最基本的增删改查(CRUD)的方法,但是我们以前都是怎么写的呢?
1 public class UserDao {
2 //添加用户
3 public void add(User user) throws SQLException {
4 Connection conn = null;
5 PreparedStatement st = null;
6 ResultSet rs = null;
7 try{
8 conn = JdbcUtils.getConnection();
9 String sql = "insert into user(id,name,age) values(?,?,?)";
10 st = conn.prepareStatement(sql);
11 st.setInt(1, user.getId());
12 st.setString(2, user.getName());
13 st.setInt(3, user.getAge());
14 st.executeUpdate();
15
16 }finally{
17 JdbcUtils.release(conn, st, rs);
18 }
19 }
20 //删除用户
21 public void delete(User user) throws SQLException {
22 Connection conn = null;
23 PreparedStatement st = null;
24 ResultSet rs = null;
25 try{
26 conn = JdbcUtils.getConnection();
27 String sql = "delete from user where id=?";
28 st = conn.prepareStatement(sql);
29 st.setInt(1, user.getId());
30 st.executeUpdate();
31
32 }finally{
33 JdbcUtils.release(conn, st, rs);
34 }
35 }
36 。。。//其他代码,这里省略
37 }
看看上面的代码,很熟悉吧,这里面就是我们以前经常写的“模板”代码,可以看出像在dao中对数据库的增加和删除方法,有大部分的代码都是重复的,需要变得只是其中一点点,我们说过对于重复的代码都要进行提取,简化开发,这就是本篇我们自定义框架要做的事,同时也是DbUtils的好处。
对于一个数据库的增删改查(CRUD)来说,其中增加、删除、修改这三种方式在我们的JDBC中代码都是类似的,因为都是调用PreparedStatement对象的executeUpdate()无参方法,只是SQL命令语句和占位符参数不同,并且方法返回的是代表影响数据库中数据行数的整型。而对于数据库的查询来说,除了调用的是PreparedStatement对象的executeQuery()无参方法这点不同,还有一个最关键的是该方法返回的是结果集ResultSet对象,因此要另外处理。
通过上面的分析,明白了如果要对以前dao层的增删改查进行简化,那么就要分两步走,一步简化增加、删除和修改;另一步简化查询。
接下来我们将以一个案例来简化一个dao层的JDBC增删改查方法。所有的简化方法都可以放在JDBC的工具类JdbcUtils中,这样我们在dao层编写CRUD方法只要从工具类调用即可,可能这么说有点抽象,那么就往下看好了。
注:对于增删改查方法分成两种方法,分别以update方法和query方法为名,这里我先从工具类JdbcUtils中提取出来,这样能重点讲解,但别忘了这两个方法我们都是放在工具类JdbcUtils中的。
需求:自定义的JDBC框架能满足向驱动管理器中注册驱动、获取连接、释放资源、封装了增删改查(CRUD)的简化方法等。
一、 在数据库中创建一个user表
SQL脚本内容如下:
create table user( id int primary key, name varchar(40), age int );
二、根据数据库表的实体创建相应的JavaBean对象
1 public class User { 2 private int id; 3 private String name; 4 private int age; 5 6 。。。//此处省略各个属性的getter和setter方法 7 8 }
三、编写数据库工具类JdbcUtils
我们知道数据库工具类要帮我们向Java驱动管理器注册数据库驱动并获取连接等操作,我们也学过各种开源的工具如DBCP和C3P0等。但是注意,我们要编写的是一个JDBC框架,这个框架是要给别人用的,所以应该尽量避免使用第三方开源工具,不然别人用我们的框架还得要导入另外的工具Jar包这就有点麻烦了。
因此我们可以使用《JDBC操作数据库的学习(2)》中的工具类(不推荐,因为没有使用连接池),或者《数据库连接池》中的JdbcPool工具类。这里我们使用后面一种方式,代码如下:
1 public class JdbcUtils implements DataSource {
2
3 private static LinkedList<Connection> connectionList = new LinkedList<>(); //以集合作为连接池
4 private static Properties config = new Properties();
5
6 static{
7 InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("database.properties");
8 try {
9 config.load(in);
10 Class.forName(config.getProperty("driver")); //注册驱动
11 String url = config.getProperty("url");
12 String username = config.getProperty("username");
13 String password = config.getProperty("password");
14 for(int i=0;i<10;i++) { //获取十个连接
15 Connection conn = DriverManager.getConnection(url, username, password);
16 connectionList.addLast(conn); //将每个连接都添加到集合(池)中
17 }
18
19 } catch (Exception e) {
20 throw new ExceptionInInitializerError(e);
21 }
22 }
23
24 @Override
25 public Connection getConnection() throws SQLException {
26 if(connectionList.size()<1) {
27 throw new RuntimeException("数据库连接忙");
28 }
29 Connection conn = connectionList.removeFirst();
30 MyConnection myConn = new MyConnection(conn); //从池中取出连接并使用包装类增强close方法
31
32 return myConn;
33 }
34
35 class MyConnection implements Connection{ //包装设计模式的类
36 private Connection conn;
37 public MyConnection(Connection conn) {
38 this.conn = conn;
39 }
40
41 @Override
42 public void close() throws SQLException {
43 connectionList.addFirst(this.conn); //调用close方法时只是将连接重新返回池中,而不会销毁
44 }
45
46 @Override
47 public Statement createStatement() throws SQLException {
48 this.conn.createStatement(); //对于不增强的方法则调用目标对象的方法即可,在MyConnection包装类中其他方法都是这样的
49 return null;
50 }
51 。。。 //以下省略Connection接口中覆写的其他方法,对于不想增强的方法都如上(createStatement方法)所示
52 } // MyConnection包装类完成
53
54 @Override
55 public Connection getConnection(String username, String password)
56 throws SQLException { //实现DataSource接口的其他方法,在本案例中对我们来说并没有什么作用
57 return null;
58 }
59 。。。//以下省略DataSource接口中覆写的其他方法,这些方法对我们来说暂时没有作用
60 }
当然自建数据库连接池麻烦的一点就是要自己动手增强从数据库直接获取的Connection对象,覆写其close方法,这里建议采用动态代理。
四、在工具类JdbcUtils中编写简化的增加、修改和删除的update方法
前面我们分析过,增加、删除、修改这三种方式在我们的JDBC中代码都是类似的,因为都是调用PreparedStatement对象的executeUpdate()无参方法,只是SQL命令语句和占位符参数不同。因此,对于SQL命令语句和占位符参数,我们就可以以方法参数的形式进行传入,而方法内都是以前冗余重复的代码。
1 //增加、删除、修改的统一方法 2 public static void update(String sql,Object[] params) throws SQLException { 3 Connection conn = null; 4 PreparedStatement st = null; 5 ResultSet rs = null; 6 try{ 7 conn = JdbcUtils.getConnection(); 8 st = conn.prepareStatement(sql); 9 for(int i=0;i<params.length;i++) { 10 st.setObject(i+1, params[i]); 11 } 12 st.executeUpdate(); 13 }finally{ 14 JdbcUtils.release(conn, st, rs); 15 } 16 }