最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

编写自己的JDBC框架

来源:动视网 责编:小采 时间:2020-11-09 14:32:39
文档

编写自己的JDBC框架

编写自己的JDBC框架:一、数据库连接池: 在一般用JDBC 进行连接数据库进行CRUD操作时,每一次都会: 通过:java.sql.Connection conn = DriverManager.getConnection(url,user,password); 重新获取一个数据库的链接再进行操作,这样用户每次请求都需要向数据库获得链接,而数
推荐度:
导读编写自己的JDBC框架:一、数据库连接池: 在一般用JDBC 进行连接数据库进行CRUD操作时,每一次都会: 通过:java.sql.Connection conn = DriverManager.getConnection(url,user,password); 重新获取一个数据库的链接再进行操作,这样用户每次请求都需要向数据库获得链接,而数
 一、数据库连接池:

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

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

所以为了减少服务器的压力,便可用连接池的方法:在启动Web应用时,数据就创建好一定数量的Connection链接
存放到一个容器中,然后当用户请求时,服务器则向容器中获取Connection链接来处理用户的请求,当用户的请求完成后,
又将该Connection 链接放回到该容器中。这样的一个容器称为连接池。

    
编写一个基本的连接池实现连接复用
  步骤:
    1、建立一个数据库连接池容器。(因为方便存取,则使用LinkedList集合)
     2、初始化一定数量的连接,放入到容器中。
    3、等待用户获取连接对象。(该部分要加锁)
       |---记得删除容器中对应的对象,放置别人同时获取到同一个对象。
     4、提供一个方法,回收用户用完的连接对象。
     5、要遵循先入先出的原则。

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;


/**
 * 一个基本的数据连接池: 
 * 1、初始化时就建立一个容器,来存储一定数量的Connection 对象
 * 2、用户通过调用MyDataSource 的getConnection 来获取Connection 对象。
 * 3、再通过release 方法来回收Connection 对象,而不是直接关闭连接。
 * 4、遵守先进先出的原则。
 * 
 * 
 * @author 贺佐安
 *
 */
public class MyDataSource {
 private static String url = null;
 private static String password = null;
 private static String user = null ;
 private static String DriverClass = null;
 private static LinkedList<Connection> pool = new LinkedList<Connection>() ;
// 注册数据库驱动
 static {
 try {
 InputStream in = MyDataSource.class.getClassLoader()
 .getResourceAsStream("db.properties");
 Properties prop = new Properties(); 
 prop.load(in);
 user = prop.getProperty("user"); 
 url = prop.getProperty("url") ;
 password = prop.getProperty("password") ; 
 DriverClass = prop.getProperty("DriverClass") ; 
 Class.forName(DriverClass) ; 
 
 } catch (Exception e) {
 throw new RuntimeException(e) ;
 } 
 }
 //初始化建立数据连接池
 public MyDataSource () {
 for(int i = 0 ; i < 10 ; i ++) {
 try {
 Connection conn = DriverManager.getConnection(url, user, password) ;
 pool.add(conn) ;
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 }
 //、从连接池获取连接
 public Connection getConnection() throws SQLException {
 return pool.remove() ;
 } 
 // 回收连接对象。
 public void release(Connection conn) {
 System.out.println(conn+"被回收");
 pool.addLast(conn) ;
 } 
 public int getLength() {
 return pool.size() ;
 }
}

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

import java.sql.Connection;
import java.sql.SQLException;

import org.junit.Test;


public class MyDataSourceTest {
 
 
 /**
 * 获取数据库连接池中的所有连接。
 */
 @Test
 public void Test() {
 MyDataSource mds = new MyDataSource() ; 
 Connection conn = null ;
 try {
 
 for (int i = 0 ; i < 20 ; i ++) {
 conn = mds.getConnection() ;
 System.out.println(conn+"被获取;连接池还有:"+mds.getLength()); 
 mds.release(conn) ;
 } 
 } catch (SQLException e) {
 e.printStackTrace();
 } 
 }
}

再运行的时候,可以发现,循环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 方法修改掉:

import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
/**
 * 对MyConnectionAdapter 进行包装处理 
 * @author 贺佐安
 *
 */
public class MyConnectionWrap extends MyConnectionAdapter {

 private LinkedList<Connection> pool = new LinkedList<Connection>() ;
 public MyConnectionWrap(Connection conn ,LinkedList<Connection> pool ) {
 super(conn); 
 this.pool = pool ; 
 }
 
 //改写要实现的方法
 public void close() throws SQLException {
 pool.addLast(conn) ;
 }
}

编写标准数据源:

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.ResourceBundle;

import javax.sql.DataSource;


/** 
 * 编写标准的数据源:
 * 1、实现DataSource 接口
 * 2、获取在实现类的构造方法中批量获取Connection 对象,并将这些Connection 存储
 * 在LinkedList 容器中。
 * 3、实现getConnection() 方法,调用时返回LinkedList容器的Connection对象给用户。
 * @author 贺佐安
 *
 */
public class MyDataSource implements DataSource{
 private static String url = null;
 private static String password = null;
 private static String user = null ;
 private static String DriverClass = null;
 private static LinkedList<Connection> pool = new LinkedList<Connection>() ;

 // 注册数据库驱动
 static {
 try { 
 ResourceBundle rb = ResourceBundle.getBundle("db") ;
 url = rb.getString("url") ; 
 password = rb.getString("password") ; 
 user = rb.getString("user") ; 
 DriverClass = rb.getString("DriverClass") ;
 Class.forName(DriverClass) ; 
 
 //初始化建立数据连接池
 for(int i = 0 ; i < 10 ; i ++) {
 Connection conn = DriverManager.getConnection(url, user, password) ;
 pool.add(conn) ;
 }
 } catch (Exception e) {
 throw new RuntimeException(e) ;
 } 
 
 }
 public MyDataSource () { 
 }
 
 //、从连接池获取连接:通过包装模式
 public synchronized Connection getConnection() throws SQLException {
 if (pool.size() > 0) {
 MyConnectionWrap mcw = new MyConnectionWrap(pool.remove(), pool) ;
 return mcw ;
 }else {
 throw new RuntimeException("服务器繁忙!"); 
 }
 }
 
 // 回收连接对象。
 public void release(Connection conn) {
 System.out.println(conn+"被回收");
 pool.addLast(conn) ;
 }
 
 public int getLength() {
 return pool.size() ;
 }
 
 
 @Override
 public PrintWriter getLogWriter() throws SQLException {
 return null;
 }
 @Override
 public void setLogWriter(PrintWriter out) throws SQLException {
 
 }
 @Override
 public void setLoginTimeout(int seconds) throws SQLException {
 
 }
 @Override
 public int getLoginTimeout() throws SQLException {
 return 0;
 }
 @Override
 public <T> T unwrap(Class<T> iface) throws SQLException {
 return null;
 }
 @Override
 public boolean isWrapperFor(Class<?> iface) throws SQLException {
 return false;
 }
 @Override
 public Connection getConnection(String username, String password)
 throws SQLException {
 return null;
 }
 
}

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

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

import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.ResourceBundle;

import javax.sql.DataSource;


/** 
 * 编写标准的数据源:
 * 1、实现DataSource 接口
 * 2、获取在实现类的构造方法中批量获取Connection 对象,并将这些Connection 存储
 * 在LinkedList 容器中。
 * 3、实现getConnection() 方法,调用时返回LinkedList容器的Connection对象给用户。
 * @author 贺佐安
 *
 */
public class MyDataSource implements DataSource{
 private static String url = null;
 private static String password = null;
 private static String user = null ;
 private static String DriverClass = null;
 private static LinkedList<Connection> pool = new LinkedList<Connection>() ;

 // 注册数据库驱动
 static {
 try { 
 ResourceBundle rb = ResourceBundle.getBundle("db") ;
 url = rb.getString("url") ; 
 password = rb.getString("password") ; 
 user = rb.getString("user") ; 
 DriverClass = rb.getString("DriverClass") ;
 Class.forName(DriverClass) ; 
 
 //初始化建立数据连接池
 for(int i = 0 ; i < 10 ; i ++) {
 Connection conn = DriverManager.getConnection(url, user, password) ;
 pool.add(conn) ;
 }
 } catch (Exception e) {
 throw new RuntimeException(e) ;
 } 
 }
 public MyDataSource () { 
 
 }
 
 //、从连接池获取连接:通过动态代理
 public Connection getConnection() throws SQLException {
 if (pool.size() > 0) {
 final Connection conn = pool.remove() ; 
 Connection proxyCon = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), 
 new InvocationHandler() {
 //策略设计模式:
 @Override
 public Object invoke(Object proxy, Method method, Object[] args)
 throws Throwable {
 if("close".equals(method.getName())){
 //谁调用,
 return pool.add(conn);//当调用close方法时,拦截了,把链接放回池中了
 }else{
 return method.invoke(conn, args);
 } 
 }
 });
 return proxyCon ;
 }else {
 throw new RuntimeException("服务器繁忙!"); 
 }
 } 
 
 public int getLength() {
 return pool.size() ;
 }
 
 
 @Override
 public PrintWriter getLogWriter() throws SQLException {
 return null;
 }
 @Override
 public void setLogWriter(PrintWriter out) throws SQLException {
 
 }
 @Override
 public void setLoginTimeout(int seconds) throws SQLException {
 
 }
 @Override
 public int getLoginTimeout() throws SQLException {
 return 0;
 }
 @Override
 public <T> T unwrap(Class<T> iface) throws SQLException {
 return null;
 }
 @Override
 public boolean isWrapperFor(Class<?> iface) throws SQLException {
 return false;
 }
 @Override
 public Connection getConnection(String username, String password)
 throws SQLException {
 return null;
 } 
}

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

    1、DBCP的使用:

import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

/**
 * 创建DBCP 工具类
 * @author 贺佐安
 *
 */
public class DbcpUtil {
 private static DataSource ds = null ;
 static {
 try {
 //读取配置文件
 InputStream in = DbcpUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties") ;
 Properties prop = new Properties() ; 
 prop.load(in) ;
 
 //通过BasicDataSourceFactory 的creatDataSurce 方法创建 BasicDataSource 对象。
 ds = BasicDataSourceFactory.createDataSource(prop) ;
 
 } catch (Exception e) {
 e.printStackTrace();
 } 
 }
 public static DataSource getDs() {
 return ds ; 
 }
 public static Connection getConnection () {
 try { 
 return ds.getConnection() ;
 } catch (SQLException e) {
 throw new RuntimeException() ;
 } 
 }
}

2、C3P0 的使用:

import java.sql.Connection;
import java.sql.SQLException;

import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
 * C3P0 开源数据源的使用
 * @author 贺佐安
 *
 */
public class C3p0Util {
 private static ComboPooledDataSource cpds = null ;
 static {
 
 cpds = new ComboPooledDataSource() ; 
 }
 public static Connection getConnection() {
 try {
 return cpds.getConnection() ;
 } catch (SQLException e) {
 throw new RuntimeException() ;
 }
 }
}

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

import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

/**
 * 实现JDBC 框架的核心类。
 * 在该类中定义了SQL语句完成的方法;
 * @author 贺佐安
 *
 */
public class MyJdbcFrame {
 /**
 * javax.sql.DataSource 实例的引用变量
 */
 private DataSource ds = null ;
 /**
 * 将用户指定的DataSource 指定给系统定义的DataSource 实例的引用变量
 * @param ds
 */
 public MyJdbcFrame(DataSource ds ) {
 this.ds = ds ; 
 }
 /**
 * 执行UPDATE、DELETE、INSERT 语句。
 * @param sql 
 * @param obj
 */
 public void update(String sql , Object[] obj) {
 Connection conn = null ; 
 PreparedStatement stmt = null ; 
 try {
 //获取Connection 对象
 conn = ds.getConnection() ;
 stmt = conn.prepareStatement(sql) ; 
 
 // 获取ParameterMetaData 元数据对象。
 ParameterMetaData pmd = stmt.getParameterMetaData() ;
 
 //获取SQL语句中需要设置的参数的个数
 int parameterCount = pmd.getParameterCount() ;
 if (parameterCount > 0) { 
 if (obj == null || obj.length != parameterCount) {
 throw new MyJdbcFrameException( "parameterCount is error!") ;
 } 
 //设置参数:
 for ( int i = 0 ; i < obj.length ; i++) {
 stmt.setObject(i+1, obj[i]) ;
 }
 } 
 //执行语句:
 stmt.executeUpdate() ; 
 
 } catch(Exception e ) {
 throw new MyJdbcFrameException(e.getMessage()) ;
 } finally {
 release(stmt, null, conn) ;
 }
 }
 
 public Object query(String sql , Object[] obj , ResultSetHandler rsh) {
 Connection conn = null ; 
 PreparedStatement stmt = null ; 
 ResultSet rs = null ;
 try {
 //获取Connection 对象
 conn = ds.getConnection() ;
 stmt = conn.prepareStatement(sql) ; 
 
 // 获取ParameterMetaData 元数据对象。
 ParameterMetaData pmd = stmt.getParameterMetaData() ;
 
 //获取SQL语句中需要设置的参数的个数
 int parameterCount = pmd.getParameterCount() ;
 
 if (obj.length != parameterCount) {
 throw new MyJdbcFrameException( "'" +sql +"' : parameterCount is error!") ;
 } 
 //设置参数:
 for ( int i = 0 ; i < obj.length ; i++) {
 stmt.setObject(i+1, obj[i]) ;
 }
 //执行语句:
 rs = stmt.executeQuery(); 
 
 return rsh.handler(rs);
 } catch(Exception e ) {
 throw new MyJdbcFrameException(e.getMessage()) ;
 } finally {
 release(stmt, null, conn) ;
 } 
 } 
 /**
 * 释放资源
 * @param stmt
 * @param rs
 * @param conn
 */
 public static void release(Statement stmt 
 , ResultSet rs 
 , Connection conn) {
 if(rs != null) {
 try {
 rs.close() ;
 } catch (SQLException e) {
 e.printStackTrace();
 }
 rs = null ;
 }
 if (stmt != null) { 
 try {
 stmt.close();
 } catch (SQLException e) {
 e.printStackTrace();
 } 
 stmt = null ;
 }
 if (conn != null) {
 try {
 conn.close();
 } catch (SQLException e) {
 e.printStackTrace();
 }
 conn = null ;
 }
 } 
 
}

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

 import java.sql.ResultSet;
//抽象策略模式
public interface ResultSetHandler {
 public Object handler(ResultSet rs) ;
 }

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

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 * 该类获取ResultSet 结果集中的第一个值,封装到JavaBean中
 * @author 贺佐安
 *
 */
public class BeanHandler implements ResultSetHandler { 
 //获取要封装的JavaBean的字节码
 private Class clazz ;
 public BeanHandler (Class clazz) {
 this.clazz = clazz ;
 }

 public Object handler(ResultSet rs) {
 try {
 if (rs.next()) {
 //1、获取结果集的元数据。
 ResultSetMetaData rsm = rs.getMetaData() ;
 //2、创建JavaBean的实例:
 Object obj = clazz.newInstance() ;
 //3、将数据封装到JavaBean中。 
 for (int i = 0 ; i < rsm.getColumnCount() ; i ++) {
 //获取属性名
 String columnName = rsm.getColumnName(i+1) ; 
 //获取属性值
 Object value = rs.getObject(i+1) ; 
 
 Field objField = obj.getClass().getDeclaredField(columnName) ;
 objField.setAccessible(true) ;
 objField.set(obj, value) ;
 }
 return obj ;
 } else {
 return null ;
 }
 } catch (Exception e) {
 throw new RuntimeException(e) ;
 } 
 } 
}

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

public class MyJdbcFrameException extends RuntimeException {
 public MyJdbcFrameException() {
 super() ; 
 }
 public MyJdbcFrameException(String e) {
 super(e) ;
 }
}

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

  当然,使用DBUtils 则更简单:Apache 组织提供的一个开源JDBC 工具类库。

文档

编写自己的JDBC框架

编写自己的JDBC框架:一、数据库连接池: 在一般用JDBC 进行连接数据库进行CRUD操作时,每一次都会: 通过:java.sql.Connection conn = DriverManager.getConnection(url,user,password); 重新获取一个数据库的链接再进行操作,这样用户每次请求都需要向数据库获得链接,而数
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top