您的位置:首页 > 其它

编写自己的JDBC框架

2016-04-24 02:20 459 查看

一、数据库连接池:

  在一般用JDBC 进行连接数据库进行CRUD操作时,每一次都会:

    通过:java.sql.Connection conn = DriverManager.getConnection(url,user,password); 重新获取一个数据库的链接再进行操作,这样用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。



  所以为了减少服务器的压力,便可用连接池的方法:在启动Web应用时,数据就创建好一定数量的Connection链接

存放到一个容器中,然后当用户请求时,服务器则向容器中获取Connection链接来处理用户的请求,当用户的请求完成后,

又将该Connection 链接放回到该容器中。这样的一个容器称为连接池。

    


编写一个基本的连接池实现连接复用

  步骤:

    1、建立一个数据库连接池容器。(因为方便存取,则使用LinkedList集合)

     2、初始化一定数量的连接,放入到容器中。

    3、等待用户获取连接对象。(该部分要加锁)

       |---记得删除容器中对应的对象,放置别人同时获取到同一个对象。

     4、提供一个方法,回收用户用完的连接对象。

     5、要遵循先入先出的原则。

1 import java.io.InputStream;
2 import java.sql.Connection;
3 import java.sql.DriverManager;
4 import java.sql.SQLException;
5 import java.util.LinkedList;
6 import java.util.Properties;
7
8
9 /**
10  * 一个基本的数据连接池:
11  * 1、初始化时就建立一个容器,来存储一定数量的Connection 对象
12  * 2、用户通过调用MyDataSource 的getConnection 来获取Connection 对象。
13  * 3、再通过release 方法来回收Connection 对象,而不是直接关闭连接。
14  * 4、遵守先进先出的原则。
15  *
16  *
17  * @author 贺佐安
18  *
19  */
20 public class MyDataSource {
21     private static String url = null;
22     private static String password = null;
23     private static String user = null ;
24     private static String DriverClass = null;
25     private static LinkedList<Connection> pool = new LinkedList<Connection>() ;
26 //    注册数据库驱动
27     static {
28         try {
29             InputStream in = MyDataSource.class.getClassLoader()
30                     .getResourceAsStream("db.properties");
31             Properties prop = new Properties();
32             prop.load(in);
33             user = prop.getProperty("user");
34             url = prop.getProperty("url") ;
35             password = prop.getProperty("password") ;
36             DriverClass = prop.getProperty("DriverClass") ;
37             Class.forName(DriverClass) ;
38
39         } catch (Exception e) {
40             throw new RuntimeException(e) ;
41         }
42     }
43     //初始化建立数据连接池
44     public MyDataSource ()  {
45         for(int i = 0 ; i < 10 ; i ++) {
46             try {
47                 Connection conn = DriverManager.getConnection(url, user, password) ;
48                 pool.add(conn) ;
49             } catch (SQLException e) {
50                 e.printStackTrace();
51             }
52         }
53     }
54     //、从连接池获取连接
55     public Connection getConnection() throws SQLException {
56         return pool.remove() ;
57     }
58     // 回收连接对象。
59     public void release(Connection conn) {
60         System.out.println(conn+"被回收");
61         pool.addLast(conn) ;
62     }
63     public int getLength() {
64         return pool.size() ;
65     }
66 }


[/code]

  这样当我们要使用Connection 连接数据库时,则可以直接使用连接池中Connection 的对象。测试如下:

1 import java.sql.Connection;
2 import java.sql.SQLException;
3
4 import org.junit.Test;
5
6
7 public class MyDataSourceTest {
8
9
10     /**
11      * 获取数据库连接池中的所有连接。
12      */
13     @Test
14     public void Test() {
15         MyDataSource mds = new MyDataSource() ;
16         Connection conn = null ;
17         try {
18
19             for (int i = 0 ; i < 20 ; i ++) {
20                 conn = mds.getConnection() ;
21                 System.out.println(conn+"被获取;连接池还有:"+mds.getLength());
22                 mds.release(conn) ;
23             }
24         } catch (SQLException e) {
25             e.printStackTrace();
26         }
27     }
28 }

  再运行的时候,可以发现,循环10次后,又再一次获取到了第一次循环的得到的Connection对象。所以,这样可以大大的减轻数据库的压力。上面只是一个简单的数据库连接池,不完美的便是,回收需要调用数据池的release() 方法来进行回收,那么可以不可以直接调用Connection 实例的close 便完成Connection 对象的回收呢?

二、数据源: 

    > 编写连接池需实现javax.sql.DataSource接口。

   > 实现DataSource接口,并实现连接池功能的步骤:

    1、在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。

      2、实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。

    利用动态代理和包装设计模式来标准的数据源。

    1、包装设计模式实现标准数据源:

      这里的用包装设计模式,便是将Connection 接口进行包装。简单总结一下包装设计模式的步骤:

          a)定义一个类,实现与被包装类()相同的接口。

             |----可以先自己写一个适配器,然后后面继承这个适配器,改写需要改写的方法,提高编程效率。

         b)定义一个实例变量,记住被包装类的对象的引用。

         c)定义构造方法,转入被包装类的对象。

          e)对需要改写的方法,改写。

    f)对不需要改写的方法,调用原来被包装类的对应方法。

      所以先编写一个类似适配器的类,将Connection 接口的方法都进行实现:

      然后再对Connection 接口进行包装,将close 方法修改掉:

<pre name="code" class="java"> 1 import java.sql.Connection;
2 import java.sql.SQLException;
3 import java.util.LinkedList;
4 /**
5  * 对MyConnectionAdapter 进行包装处理
6  * @author 贺佐安
7  *
8  */
9 public class MyConnectionWrap extends MyConnectionAdapter {
10
11     private LinkedList<Connection> pool = new LinkedList<Connection>() ;
12     public MyConnectionWrap(Connection conn ,LinkedList<Connection> pool ) {
13         super(conn);
14         this.pool = pool ;
15     }
16
17     //改写要实现的方法
18     public void close() throws SQLException {
19         pool.addLast(conn) ;
20     }
21 }


[/code]


      编写标准数据源:

1 import java.io.PrintWriter;
2 import java.sql.Connection;
3 import java.sql.DriverManager;
4 import java.sql.SQLException;
5 import java.util.LinkedList;
6 import java.util.ResourceBundle;
7
8 import javax.sql.DataSource;
9
10
11 /**
12  * 编写标准的数据源:
13  * 1、实现DataSource 接口
14  * 2、获取在实现类的构造方法中批量获取Connection 对象,并将这些Connection 存储
15  * 在LinkedList 容器中。
16  * 3、实现getConnection() 方法,调用时返回LinkedList容器的Connection对象给用户。
17  * @author 贺佐安
18  *
19  */
20 public class MyDataSource implements DataSource{
21     private static String url = null;
22     private static String password = null;
23     private static String user = null ;
24     private static String DriverClass = null;
25     private static LinkedList<Connection> pool = new LinkedList<Connection>() ;
26
27     //    注册数据库驱动
28     static {
29         try {
30             ResourceBundle rb = ResourceBundle.getBundle("db") ;
31             url = rb.getString("url") ;
32             password = rb.getString("password") ;
33             user = rb.getString("user") ;
34             DriverClass = rb.getString("DriverClass") ;
35             Class.forName(DriverClass) ;
36
37             //初始化建立数据连接池
38             for(int i = 0 ; i < 10 ; i ++) {
39                 Connection conn = DriverManager.getConnection(url, user, password) ;
40                 pool.add(conn) ;
41             }
42         } catch (Exception e) {
43             throw new RuntimeException(e) ;
44         }
45
46     }
47     public MyDataSource ()  {
48     }
49
50     //、从连接池获取连接:通过包装模式
51     public synchronized Connection getConnection() throws SQLException {
52         if (pool.size() > 0) {
53             MyConnectionWrap mcw = new MyConnectionWrap(pool.remove(), pool) ;
54             return mcw ;
55         }else {
56             throw new RuntimeException("服务器繁忙!");
57         }
58     }
59
60     // 回收连接对象。
61     public void release(Connection conn) {
62         System.out.println(conn+"被回收");
63         pool.addLast(conn) ;
64     }
65
66     public int getLength() {
67         return pool.size() ;
68     }
69
70
71     @Override
72     public PrintWriter getLogWriter() throws SQLException {
73         return null;
74     }
75     @Override
76     public void setLogWriter(PrintWriter out) throws SQLException {
77
78     }
79     @Override
80     public void setLoginTimeout(int seconds) throws SQLException {
81
82     }
83     @Override
84     public int getLoginTimeout() throws SQLException {
85         return 0;
86     }
87     @Override
88     public <T> T unwrap(Class<T> iface) throws SQLException {
89         return null;
90     }
91     @Override
92     public boolean isWrapperFor(Class<?> iface) throws SQLException {
93         return false;
94     }
95     @Override
96     public Connection getConnection(String username, String password)
97             throws SQLException {
98         return null;
99     }
100
101


[/code]

  2、动态代理实现标准数据源:

    相对于用包装设计来完成标准数据源,用动态代理则方便许多:

[code]  1 import java.io.PrintWriter;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Method;
4 import java.lang.reflect.Proxy;
5 import java.sql.Connection;
6 import java.sql.DriverManager;
7 import java.sql.SQLException;
8 import java.util.LinkedList;
9 import java.util.ResourceBundle;
10
11 import javax.sql.DataSource;
12
13
14 /**
15  * 编写标准的数据源:
16  * 1、实现DataSource 接口
17  * 2、获取在实现类的构造方法中批量获取Connection 对象,并将这些Connection 存储
18  * 在LinkedList 容器中。
19  * 3、实现getConnection() 方法,调用时返回LinkedList容器的Connection对象给用户。
20  * @author 贺佐安
21  *
22  */
23 public class MyDataSource implements DataSource{
24     private static String url = null;
25     private static String password = null;
26     private static String user = null ;
27     private static String DriverClass = null;
28     private static LinkedList<Connection> pool = new LinkedList<Connection>() ;
29
30     //    注册数据库驱动
31     static {
32         try {
33             ResourceBundle rb = ResourceBundle.getBundle("db") ;
34             url = rb.getString("url") ;
35             password = rb.getString("password") ;
36             user = rb.getString("user") ;
37             DriverClass = rb.getString("DriverClass") ;
38             Class.forName(DriverClass) ;
39
40             //初始化建立数据连接池
41             for(int i = 0 ; i < 10 ; i ++) {
42                 Connection conn = DriverManager.getConnection(url, user, password) ;
43                 pool.add(conn) ;
44             }
45         } catch (Exception e) {
46             throw new RuntimeException(e) ;
47         }
48     }
49     public MyDataSource ()  {
50
51     }
52
53     //、从连接池获取连接:通过动态代理
54     public Connection getConnection() throws SQLException {
55         if (pool.size() > 0) {
56             final Connection conn  = pool.remove() ;
57             Connection proxyCon = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(),
58                     new InvocationHandler() {
59                         //策略设计模式:
60                         @Override
61                         public Object invoke(Object proxy, Method method, Object[] args)
62                                 throws Throwable {
63                             if("close".equals(method.getName())){
64                                 //谁调用,
65                                 return pool.add(conn);//当调用close方法时,拦截了,把链接放回池中了
66                             }else{
67                                 return method.invoke(conn, args);
68                             }
69                         }
70                     });
71           return proxyCon ;
72         }else {
73             throw new RuntimeException("服务器繁忙!");
74         }
75     }
76
77     public int getLength() {
78         return pool.size() ;
79     }
80
81
82     @Override
83     public PrintWriter getLogWriter() throws SQLException {
84         return null;
85     }
86     @Override
87     public void setLogWriter(PrintWriter out) throws SQLException {
88
89     }
90     @Override
91     public void setLoginTimeout(int seconds) throws SQLException {
92
93     }
94     @Override
95     public int getLoginTimeout() throws SQLException {
96         return 0;
97     }
98     @Override
99     public <T> T unwrap(Class<T> iface) throws SQLException {
100         return null;
101     }
102     @Override
103     public boolean isWrapperFor(Class<?> iface) throws SQLException {
104         return false;
105     }
106     @Override
107     public Connection getConnection(String username, String password)
108             throws SQLException {
109         return null;
110     }
111 }

</pre><br />

    当然觉得麻烦的则可以直接使用一些开源的数据源如:DBCP、C3P0等。DBCP的原理是用包装设计模式开发的数据源,而C3P0则是动态代理的。

    1、DBCP的使用:

1 import java.io.InputStream;
2 import java.sql.Connection;
3 import java.sql.SQLException;
4 import java.util.Properties;
5
6 import javax.sql.DataSource;
7
8 import org.apache.commons.dbcp.BasicDataSourceFactory;
9
10 /**
11  * 创建DBCP 工具类
12  * @author 贺佐安
13  *
14  */
15 public class DbcpUtil {
16     private static DataSource ds = null ;
17     static {
18         try {
19             //读取配置文件
20             InputStream in = DbcpUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties") ;
21             Properties prop = new Properties() ;
22             prop.load(in) ;
23
24             //通过BasicDataSourceFactory 的creatDataSurce 方法创建 BasicDataSource 对象。
25             ds = BasicDataSourceFactory.createDataSource(prop) ;
26
27         } catch (Exception e) {
28             e.printStackTrace();
29         }
30     }
31     public static DataSource getDs() {
32         return ds ;
33     }
34     public static Connection getConnection () {
35         try {
36             return ds.getConnection() ;
37         } catch (SQLException e) {
38             throw new RuntimeException() ;
39         }
40     }
41 }

[/code]

    2、C3P0 的使用:

[code]1 import java.sql.Connection;
2 import java.sql.SQLException;
3
4 import com.mchange.v2.c3p0.ComboPooledDataSource;
5 /**
6  * C3P0 开源数据源的使用
7  * @author 贺佐安
8  *
9  */
10 public class C3p0Util {
11     private static ComboPooledDataSource cpds  = null ;
12     static {
13
14         cpds = new ComboPooledDataSource() ;
15     }
16     public static Connection getConnection() {
17         try {
18             return cpds.getConnection() ;
19         } catch (SQLException e) {
20             throw new RuntimeException() ;
21         }
22     }
23 }

[/code]

  使用这两个数据源时,直接调用获取到的Connection 连接的close 方法,也是将连接放到pool中去。

三、元数据(DatabaseMetaData)信息的获取

  > 元数据:数据库、表、列的定义信息。

  > 元数据信息的获取:为了编写JDBC框架使用。

      1、数据库本身信息的获取:java.sql.DataBaseMateData java.sql.Connection.getMetaData() ;

      DataBaseMateData 实现类的常用方法:

        getURL():返回一个String类对象,代表数据库的URL。

        getUserName():返回连接当前数据库管理系统的用户名。

        getDatabaseProductName():返回数据库的产品名称。

        getDatabaseProductVersion():返回数据库的版本号。

        getDriverName():返回驱动驱动程序的名称。

        getDriverVersion():返回驱动程序的版本号。

        isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。

      2、ParameterMetaData: 代表PerparedStatment 中的SQL 参数元数据信息: java.sql.ParameterMetaData java.sql.PerparedStatement.getParameterMetaData() ;       

      ParameterMetaData 实现类常用方法:

        getParameterCount() :获得指定参数的个数

        getParameterType(int param) :获得指定参数的sql类型(驱动可能不支持)

      3、ResultSetMetaData : 代表结果集的源数据信息:相当于SQL 中的 :DESC java.sql.ResultSetMetaData java.sql.ResultSet.getMetaData() ;         

      java.sql.ResultSetMetaData 接口中常用的方法:

        a) getColumnCount() : 获取查询方法有几列。

        b) getColumnName(int index) : 获取列名:index从1开始。

        c) getColumnType(int index) : 获取列的数据类型。返回的是TYPES 中的常量值

四、编写自己的JDBC框架:

    JDBC框架的基本组成:  

    1、核心类:

      a、定义一个指定javax.sql.DataSource 实例的引用变量,通过构造函数获取指定的实例并给定义的变量。

      b、编写SQL运行框架。

         DML 语句的编写:

          1、通过获取的javax.sql.DataSource 实例,获取Connection 对象。

           2、通过ParamenterMeteData 获取数据库元数据。

        DQL 语句的编写:

          1、通过获取的DataSource 实例,获取Connection 对象。

          2、通过ParamenterMeteData、ResultSetMetaData 等获取数据库元数据。

          3、用抽象策略设计模式:设计一个ResultSetHandler 接口,作用:将查找出的数据封装到指定的JavaBean中。

                |————这里的JavaBean,由用户来指定。

                抽象策略模式,用户可以更具具体的功能来扩展成具体策略设计模式。如:查找的一条信息、查找的所有信息。

[code]  1 import java.sql.Connection;
2 import java.sql.ParameterMetaData;
3 import java.sql.PreparedStatement;
4 import java.sql.ResultSet;
5 import java.sql.SQLException;
6 import java.sql.Statement;
7
8 import javax.sql.DataSource;
9
10 /**
11  * 实现JDBC 框架的核心类。
12  * 在该类中定义了SQL语句完成的方法;
13  * @author 贺佐安
14  *
15  */
16 public class  MyJdbcFrame {
17     /**
18      * javax.sql.DataSource 实例的引用变量
19      */
20     private DataSource ds = null ;
21     /**
22      * 将用户指定的DataSource 指定给系统定义的DataSource 实例的引用变量
23      * @param ds
24      */
25     public MyJdbcFrame(DataSource ds ) {
26         this.ds = ds ;
27     }
28     /**
29      * 执行UPDATE、DELETE、INSERT 语句。
30      * @param sql
31      * @param obj
32      */
33     public void update(String sql , Object[] obj) {
34         Connection conn = null ;
35         PreparedStatement stmt = null ;
36         try {
37             //获取Connection 对象
38             conn = ds.getConnection() ;
39             stmt = conn.prepareStatement(sql) ;
40
41             // 获取ParameterMetaData 元数据对象。
42             ParameterMetaData pmd = stmt.getParameterMetaData() ;
43
44             //获取SQL语句中需要设置的参数的个数
45             int parameterCount = pmd.getParameterCount() ;
46             if (parameterCount > 0) {
47                 if (obj == null || obj.length != parameterCount) {
48                     throw new MyJdbcFrameException( "parameterCount is error!") ;
49                 }
50                 //设置参数:
51                 for ( int i = 0 ; i < obj.length ; i++) {
52                     stmt.setObject(i+1, obj[i]) ;
53                 }
54             }
55             //执行语句:
56             stmt.executeUpdate() ;
57
58         } catch(Exception e ) {
59             throw new MyJdbcFrameException(e.getMessage()) ;
60         } finally {
61             release(stmt, null, conn) ;
62         }
63     }
64
65     public Object query(String sql , Object[] obj , ResultSetHandler rsh) {
66         Connection conn = null ;
67         PreparedStatement stmt = null ;
68         ResultSet rs = null ;
69         try {
70             //获取Connection 对象
71             conn = ds.getConnection() ;
72             stmt = conn.prepareStatement(sql) ;
73
74             // 获取ParameterMetaData 元数据对象。
75             ParameterMetaData pmd = stmt.getParameterMetaData() ;
76
77             //获取SQL语句中需要设置的参数的个数
78             int parameterCount = pmd.getParameterCount() ;
79
80             if (obj.length != parameterCount) {
81                 throw new MyJdbcFrameException( "'" +sql +"' : parameterCount is error!") ;
82             }
83             //设置参数:
84             for ( int i = 0 ; i < obj.length ; i++) {
85                 stmt.setObject(i+1, obj[i]) ;
86             }
87             //执行语句:
88             rs = stmt.executeQuery();
89
90             return rsh.handler(rs);
91         } catch(Exception e ) {
92             throw new MyJdbcFrameException(e.getMessage()) ;
93         } finally {
94             release(stmt, null, conn) ;
95         }
96     }
97     /**
98      * 释放资源
99      * @param stmt
100      * @param rs
101      * @param conn
102      */
103     public static void release(Statement stmt
104                              , ResultSet rs
105                              , Connection conn) {
106         if(rs != null) {
107             try {
108                 rs.close() ;
109             } catch (SQLException e) {
110                 e.printStackTrace();
111             }
112             rs = null ;
113         }
114         if (stmt != null) {
115             try {
116                 stmt.close();
117             } catch (SQLException e) {
118                 e.printStackTrace();
119             }
120             stmt = null ;
121         }
122         if (conn != null) {
123             try {
124                 conn.close();
125             } catch (SQLException e) {
126                 e.printStackTrace();
127             }
128             conn = null ;
129         }
130     }
131
132 }

复制代码

    2、接口:策略模式的接口:ResultSetHandler 。

1 import java.sql.ResultSet;
2
3 //抽象策略模式
4 public interface ResultSetHandler {
5     public Object handler(ResultSet rs) ;
6 }

[/code]

    这里对ResultSetHandler 接口实现一个BeanHandler 实例 :

1 import java.lang.reflect.Field;
2 import java.sql.ResultSet;
3 import java.sql.ResultSetMetaData;
4
5 /**
6  * 该类获取ResultSet 结果集中的第一个值,封装到JavaBean中
7  * @author 贺佐安
8  *
9  */
10 public class BeanHandler implements ResultSetHandler {
11     //获取要封装的JavaBean的字节码
12     private Class clazz ;
13     public BeanHandler (Class clazz) {
14         this.clazz = clazz ;
15     }
16
17     public Object handler(ResultSet rs) {
18         try {
19             if (rs.next()) {
20                 //1、获取结果集的元数据。
21                 ResultSetMetaData rsm = rs.getMetaData() ;
22                 //2、创建JavaBean的实例:
23                 Object obj = clazz.newInstance() ;
24                 //3、将数据封装到JavaBean中。
25                 for (int i = 0 ; i < rsm.getColumnCount() ; i ++) {
26                     //获取属性名
27                     String columnName = rsm.getColumnName(i+1) ;
28                     //获取属性值
29                     Object value = rs.getObject(i+1) ;
30
31                     Field objField = obj.getClass().getDeclaredField(columnName) ;
32                     objField.setAccessible(true) ;
33                     objField.set(obj, value) ;
34                 }
35                 return obj ;
36             } else {
37                 return null ;
38             }
39         } catch (Exception e) {
40             throw new RuntimeException(e) ;
41         }
42     }
43 }


[/code]

    3、自定义异常类:继承RuntimeException。如:

1 public class MyJdbcFrameException extends RuntimeException {
2     public MyJdbcFrameException() {
3         super() ;
4     }
5     public MyJdbcFrameException(String e) {
6         super(e) ;
7     }
8 }


  然后就可以将其打包发布,在以后写数据库操作时就可以用自己的JDBC框架了,如果要完成查询多条语句什么的,则要实现ResultSetHandler 接口。来完成更多的功能。

  当然,使用DBUtils 则更简单:Apache 组织提供的一个开源JDBC 工具类库。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: