在一般用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 工具类库。
