博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编写自己的JDBC框架
阅读量:2171 次
发布时间:2019-05-01

本文共 21329 字,大约阅读时间需要 71 分钟。

http://www.cnblogs.com/jbelial/archive/2013/07/18/3199061.html

一、数据库连接池: 

  在一般用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
pool = new LinkedList
() ;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 }

  这样当我们要使用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     @Test14     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 接口的方法都进行实现:

 
View Code

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

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
pool = new LinkedList
() ;12 public MyConnectionWrap(Connection conn ,LinkedList
pool ) {13 super(conn); 14 this.pool = pool ; 15 }16 17 //改写要实现的方法18 public void close() throws SQLException {19 pool.addLast(conn) ;20 }21 }

      编写标准数据源:

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
pool = new LinkedList
() ; 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 unwrap(Class
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 }

 

 

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

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

  

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
pool = new LinkedList
() ; 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 unwrap(Class
iface) throws SQLException {100 return null;101 }102 @Override103 public boolean isWrapperFor(Class
iface) throws SQLException {104 return false;105 }106 @Override107 public Connection getConnection(String username, String password)108 throws SQLException {109 return null;110 } 111 }

    当然觉得麻烦的则可以直接使用一些开源的数据源如: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 }

    2、C3P0 的使用:

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 }

  使用这两个数据源时,直接调用获取到的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,由用户来指定。
                    抽象策略模式,用户可以更具具体的功能来扩展成具体策略设计模式。如:查找的一条信息、查找的所有信息。

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 stmt100      * @param rs101      * @param conn102      */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 }

    这里对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 }

    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 工具类库。

你可能感兴趣的文章
一文了解强化学习
查看>>
CART 分类与回归树
查看>>
seq2seq 的 keras 实现
查看>>
seq2seq 入门
查看>>
什么是 Dropout
查看>>
用 LSTM 做时间序列预测的一个小例子
查看>>
用 LSTM 来做一个分类小问题
查看>>
详解 LSTM
查看>>
按时间轴简述九大卷积神经网络
查看>>
详解循环神经网络(Recurrent Neural Network)
查看>>
为什么要用交叉验证
查看>>
用学习曲线 learning curve 来判别过拟合问题
查看>>
用验证曲线 validation curve 选择超参数
查看>>
用 Grid Search 对 SVM 进行调参
查看>>
用 Pipeline 将训练集参数重复应用到测试集
查看>>
PCA 的数学原理和可视化效果
查看>>
机器学习中常用评估指标汇总
查看>>
什么是 ROC AUC
查看>>
Bagging 简述
查看>>
详解 Stacking 的 python 实现
查看>>