您的位置:首页 > 数据库

JDBC学习之二_数据库链接池

2016-12-21 16:29 197 查看

数据库链接池

数据库链接池的简介

数据库链接池是什么

数据库链接-池,顾名思义就是存放数据库链接的池子。

数据库链接池的作用

存放数据库链接供人获取调用

数据库链接池的好处

不需要每个链接都需要自己创建,只在刚开始初始化的时候创建一定数量的链接,节省了资源时间,效率高,现拿现用。

模拟数据库链接池

package com.jyh.jdbc.pool;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.jyh.jdbc.JdbcUtil;

public class SimpleConnectionPool {

private static List<Connection> cl = Collections.synchronizedList(new ArrayList<Connection>());

static{
for(int i = 0;i < 10; i ++){
//初始化连接池向连接池中添加一定数量的链接
cl.add(JdbcUtil.getConnection());//该链接都是从已写好的jdbc工具类中获取的
}
}

public static Connection getConnection(){
if(cl.size() <= 0){
///如果链接池中没有链接了则可以再创建若干个链接放入池中
for(int i = 0;i < 10; i ++){
cl.add(JdbcUtil.getConnection());
}
}
//返回第一个链接并从链接池中删除
return cl.remove(0);
}

public static void release(Connection conn){
cl.add(conn);
}

public static void main(String[] args) {
//从链接池中获取链接
Connection conn = SimpleConnectionPool.getConnection();
//一系列操作
System.out.println(conn.getClass().getName());
//将链接放回链接池,这里不能调用.close()方法,而是自己定义了一个方法
SimpleConnectionPool.release(conn);
}
}


需要解决的问题

发现的问题

从池中获取一个链接后,用户用完自觉的调用conn.close()方法。应该达到的效果:不要关闭,而应该还回池中,而com.mysql.jdbc.Connection.close():只会关闭

解决方法

[b]1. 静态代理还是装饰设计模式?[/b]

我们需要将该方法变成返还到池中,然后我们立即想到了可以重写该方法啊,等我们准备继承该类的时候发现,无法继承,所以这个方法不行。

我们要用这个类是这类里面有我们需要的方法,所以我们自己建一个跟它一样的类不就行了么,继承同一个接口,然后方法实现也编写得一样,只把我们需要改变的方法改成我们需要的样子。想要方法实现是一样的,那么直接调用以前的不就行了了,所以需要在新建的类中new一个com.mysql.jdbc.Connection实例,不需要改变的方法直接调用实例的方法,需要改变的方法自己重写。然后又想到了,如果直接在新类中new一个com.mysql.jdbc.Connection实例就定死了,万一要换数据库呢,别的数据库可不是这个类实现java.sql.Connection接口的。所以不能定死,要从外面传过来。于是我们可以在新的类里面定义一个java.sql.Connection类型的变量,反正无论什么数据库驱动都是要实现这个类的,该变量在新类初始化的时候由外面传一个数据库驱动实例进来,通过构造函数进行赋值初始化,这样就好了吧。但是,不要忘了我们要改写的方法是什么样的—将close()方法改写成放入链接池,所以链接池的实例不能缺少,于是再定义一个链接池变量,也在新类初始化的时候赋值初始化。代码如下:

package com.jyh.jdbc.pool;

import java.util.List;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class MyConnection implements Connection {

private Connection conn;//需要初始化的数据库驱动
private List<Connection> cl;//需要初始化的链接池

public MyConnection(Connection conn, List<Connection> cl) {
this.conn = conn;
this.cl = cl;
}

//需要改写的close方法
public void close() throws SQLException {
System.out.println("这里是MyConnection的close方法");
cl.add(conn);
}

//其它不需要改写的方法直接调用传过来的实例里面的方法即可
public <T> T unwrap(Class<T> iface) throws SQLException {
return conn.unwrap(iface);
}

public boolean isWrapperFor(Class<?> iface) throws SQLException {
return conn.isWrapperFor(iface);
}

public Statement createStatement() throws SQLException {
return conn.createStatement();
}

public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}

public CallableStatement prepareCall(String sql) throws SQLException {
return conn.prepareCall(sql);
}

public String nativeSQL(String sql) throws SQLException {
return conn.nativeSQL(sql);
}

public void setAutoCommit(boolean autoCommit) throws SQLException {
conn.setAutoCommit(autoCommit);
}

public boolean getAutoCommit() throws SQLException {
// TODO Auto-generated method stub
return false;
}

public void commit() throws SQLException {
// TODO Auto-generated method stub

}

public void rollback() throws SQLException {
// TODO Auto-generated method stub

}

public boolean isClosed() throws SQLException {
// TODO Auto-generated method stub
return false;
}

public DatabaseMetaData getMetaData() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void setReadOnly(boolean readOnly) throws SQLException {
// TODO Auto-generated method stub

}

public boolean isReadOnly() throws SQLException {
// TODO Auto-generated method stub
return false;
}

public void setCatalog(String catalog) throws SQLException {
// TODO Auto-generated method stub

}

public String getCatalog() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void setTransactionIsolation(int level) throws SQLException {
// TODO Auto-generated method stub

}

public int getTransactionIsolation() throws SQLException {
// TODO Auto-generated method stub
return 0;
}

public SQLWarning getWarnings() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void clearWarnings() throws SQLException {
// TODO Auto-generated method stub

}

public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
// TODO Auto-generated method stub
return null;
}

public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
<
1acb3
span class="hljs-comment">// TODO Auto-generated method stub
return null;
}

public Map<String, Class<?>> getTypeMap() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
// TODO Auto-generated method stub

}

public void setHoldability(int holdability) throws SQLException {
// TODO Auto-generated method stub

}

public int getHoldability() throws SQLException {
// TODO Auto-generated method stub
return 0;
}

public Savepoint setSavepoint() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Savepoint setSavepoint(String name) throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void rollback(Savepoint savepoint) throws SQLException {
// TODO Auto-generated method stub

}

public void releaseSavepoint(Savepoint savepoint) throws SQLException {
// TODO Auto-generated method stub

}

public Statement createStatement(int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Clob createClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Blob createBlob() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public NClob createNClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public SQLXML createSQLXML() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public boolean isValid(int timeout) throws SQLException {
// TODO Auto-generated method stub
return false;
}

public void setClientInfo(String name, String value)
throws SQLClientInfoException {
// TODO Auto-generated method stub

}

public void setClientInfo(Properties properties)
throws SQLClientInfoException {
// TODO Auto-generated method stub

}

public String getClientInfo(String name) throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Properties getClientInfo() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void setSchema(String schema) throws SQLException {
// TODO Auto-generated method stub

}

public String getSchema() throws SQLException {
// TODO Auto-generated method stub
return null;
}

public void abort(Executor executor) throws SQLException {
// TODO Auto-generated method stub

}

public void setNetworkTimeout(Executor executor, int milliseconds)
throws SQLException {
// TODO Auto-generated method stub

}

public int getNetworkTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}

}


注意:我们要面向接口编程,在创建实例对象调用方法的时候通常都是接口对象指向实现类的引用,所以接口里面没有的方法你不能添加,因为调用不了。

以上到底是装饰设计模式还是静态代理模式没搞清,上网查了一下有如下几种说法:

有的说装饰模式就是在原有的方法基础上增强,代理模式就是修改原有的方法。

这个说法上是代理模式的功能>装饰器模式的功能

有的说装饰设计模式被装饰的类需要从外面传过来,所以调用者知道被装饰的是什么类,而代理模式是自己写死的,调用者不知道被代理的是什么类。

这个说法是装饰器模式的功能>静态代理的功能

但是我自己是不纠结的,因为学习设计模式知识学习前人的思想,又不是学习一个个设计模式的名字,知道为什么这样设计,这样设计有什么好处,解决了什么问题就行了。

[b]2. 动态代理设计模式[/b]

把上面看做代理模式好了,用上面的方法那样太麻烦了,如果代理的一个类有一百个方法,而我只需要改写一个方法,但是却要全部实现一次,所以我们看看有什么需要改进的地方。需要改进的麻烦的地方如下:

1. 每个代理类都要实现一遍被代理类的方法,很麻烦,能不能省略那些实现呢?

2. 想要代理一个类就要创建一个代理类太麻烦了,能不能动态的创建实例呢?

动态代理就解决了这些问题。动态的创建代理类实例,只编写自己想要重写的代码

代理类有什么特点呢?也就是说,如果创建了很多个代理类,这些代理类有什么共同点呢?然后观察得出如下结论:

1. 需要实现与被代理类相同的接口

2. 需要被代理类的实例

3. 还需要改变方法的具体行为

以上三点就是代理类的共同点,而动态代理模式就利用了这三个共同点

实现动态代理的类与方法:

javaProxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, >InvocationHandler h)


以下为动态代理的应用:

接口:

package com.jyh.proxy;

/**
* 接口
* @author OverrideRe
*
*/
public interface Human {

void sing(String name);
void dance(String name);
}


实现类:

package com.jyh.proxy;

/**
* 实现接口的类
* @author OverrideRe
*
*/
public class Human1 implements Human {

public Human1() {
}

public void sing(String name) {
System.out.println(name + "在唱歌");
}

public void dance(String name) {
System.out.println(name + "在跳舞");
}

}


动态代理的实现:

package com.jyh.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test1 {

/**
* 基于接口的动态代理
* @param args
*/
public static void main(String[] args) {
//被代理的类
final Human human = new Human1();
//代理类(传入的参数分别是:被代理类的类加载器,被代理类实现的接口们,传一个具体的策略方法)
Human h = (Human)Proxy.newProxyInstance(human.getClass().getClassLoader(), human.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//每次调用方法都会通过这里,method就是调用的方法,args是方法的参数
if("sing".equals(method.getName())){
System.out.println("你知道是谁在唱歌吗?");
//这里用到了反射,human是该方法所在的实例
method.invoke(human, (String)args[0]);
}else{
method.invoke(human, (String)args[0]);
}
return "这是什么";
}
});
h.sing("不知道是谁");
h.dance("同学");
}
}


观察上面发现,创建一个什么类的实例,都会通过
javaProxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, >InvocationHandler h)


而创建好实例之后无论调用被代理类的什么方法都会通过

javaInvocationHandler.invoke


方法,这个好像就是面向切面编程0.0(AOP)

按照我的理解就是,好比砧板上一捆面,一刀切下去,形成一个切面,每条面都通过这个切面。

数据库链接池的使用

原始的数据库链接池

package com.jyh.jdbc.pool;

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.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import javax.sql.DataSource;

import com.jyh.jdbc.JdbcUtil;

//实现javax.sql.DataSource接口
public class MyDataSource implements DataSource {

//定义一个线程安全的集合
private static List<Connection> cl = Collections.synchronizedList(new ArrayList<Connection>());

//初始化链接池
static{
for(int i = 0;i < 10; i ++){
cl.add(JdbcUtil.getConnection());
}
}

//从链接池中获取链接
public Connection getConnection(){
if(cl.size() > 0){
final Connection conn = cl.remove(0);//从连接池获取连接
//1.静态代理,需要用到装饰类
//Connection mconn = new MyConnection(conn, cl);//将连接和连接池交给自己编写的装饰类装饰一下
//2.动态代理
Connection c = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(),
new Class[]{Connection.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if("close".equals(method.getName())){
cl.add(conn);
System.out.println("添加进连接池了");
}else{
method.invoke(conn, args);
}
return null;
}
});

return c;
}else{
throw new RuntimeException("服务器忙!");
}
}

public static List<Connection> getCl() {
return cl;
}

public static void setCl(List<Connection> cl) {
MyDataSource.cl = cl;
}

public PrintWriter getLogWriter() throws SQLException {
return null;
}

public void setLogWriter(PrintWriter out) throws SQLException {

}

public void setLoginTimeout(int seconds) throws SQLException {

}

public int getLoginTimeout() throws SQLException {
return 0;
}

public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}

public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}

public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}

public Connection getConnection(String username, String password)
throws SQLException {
return null;
}

}


DBCP的使用

是apache的开源组件。

1. 导入jar包commons-dbcp.jar commons-pool.jar

2. 添加配置文件

xml

<bean id="dataSource"

class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

<property name="driverClassName" value="com.mysql.jdbc.Driver"/>

<property name="url" value="jdbc:mysql://192.168.0.109:3306/test?useUnicode=true&characterEncoding=UTF-8"/>

<property name="username" value="root"/>

<property name="password" value="root"/>

<!--maxActive: 最大连接数量-->

<property name="maxActive" value="150"/>

<!--minIdle: 最小空闲连接-->

<property name="minIdle" value="5"/>

<!--maxIdle: 最大空闲连接-->

<property name="maxIdle" value="20"/>

<!--initialSize: 初始化连接-->

<property name="initialSize" value="30"/>

<!-- 连接被泄露时是否打印 -->

<property name="logAbandoned" value="true"/>

<!--removeAbandoned: 是否自动回收超时连接-->

<property name="removeAbandoned"  value="true"/>

<!--removeAbandonedTimeout: 超时时间(以秒数为单位)-->

<property name="removeAbandonedTimeout" value="10"/>

<!--maxWait: 超时等待时间以毫秒为单位 1000等于60秒-->

<property name="maxWait" value="1000"/>

<!-- 在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. -->

<property name="timeBetweenEvictionRunsMillis" value="10000"/>

<!--  在每次空闲连接回收器线程(如果有)运行时检查的连接数量 -->

<property name="numTestsPerEvictionRun" value="10"/>

<!-- 1000 * 60 * 30  连接在池中保持空闲而不被空闲连接回收器线程-->

<property name="minEvictableIdleTimeMillis" value="10000"/>

<property name="validationQuery" value="SELECT NOW() FROM DUAL"/>

</bean>


3. 编写工具类:

package com.jyh.util;

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;

public class DBCPUtil {
private static DataSource ds;
static{
InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.xml");
Properties ppt = new Properties();
try {
ppt.load(in);
ds = BasicDataSourceFactory.createDataSource(ppt);//根据配置文件创建数据连接池
} catch (Exception e) {
throw new ExceptionInInitializerError();
}

}

public static DataSource getDataSource(){
return ds;
}

public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException("获取数据库连接失败!");
}
}
}


C3P0的使用

开源的数据源。c3p0-0.9.1.2.jar

编写配置文件

<!--acquireIncrement:链接用完了自动增量3个。 -->
<property name="acquireIncrement">3</property>

<!--acquireRetryAttempts:链接失败后重新试30次。-->
<property name="acquireRetryAttempts">30</property>

<!--acquireRetryDelay;两次连接中间隔1000毫秒。 -->
<property name="acquireRetryDelay">1000</property>

<!--autoCommitOnClose:连接关闭时默认将所有未提交的操作回滚。 -->
<property name="autoCommitOnClose">false</property>

<!--automaticTestTable:c3p0测试表,没什么用。-->
<property name="automaticTestTable">Test</property>

<!--breakAfterAcquireFailure:出错时不把正在提交的数据抛弃。-->
<property name="breakAfterAcquireFailure">false</property>

<!--checkoutTimeout:100毫秒后如果sql数据没有执行完将会报错,如果设置成0,那么将会无限的等待。 -->
<property name="checkoutTimeout">100</property>

<!--connectionTesterClassName:通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->
<property name="connectionTesterClassName"></property>

<!--factoryClassLocation:指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可。-->
<property name="factoryClassLocation">null</property>

<!--forceIgnoreUnresolvedTransactions:作者强烈建议不使用的一个属性。-->
<property name="forceIgnoreUnresolvedTransactions">false</property>

<!--idleConnectionTestPeriod:每60秒检查所有连接池中的空闲连接。-->
<property name="idleConnectionTestPeriod">60</property>

<!--initialPoolSize:初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。 -->
<property name="initialPoolSize">3</property>

<!--maxIdleTime:最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。-->
<property name="maxIdleTime">60</property>

<!--maxPoolSize:连接池中保留的最大连接数。 -->
<property name="maxPoolSize">15</property>

<!--maxStatements:最大链接数。-->
<property name="maxStatements">100</property>

<!--maxStatementsPerConnection:定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0  -->
<property name="maxStatementsPerConnection"></property>

<!--numHelperThreads:异步操作,提升性能通过多线程实现多个操作同时被执行。Default: 3-->
<property name="numHelperThreads">3</property>

<!--overrideDefaultUser:当用户调用getConnection()时使root用户成为去获取连接的用户。主要用于连接池连接非c3p0的数据源时。Default: null-->
<property name="overrideDefaultUser">root</property>

<!--overrideDefaultPassword:与overrideDefaultUser参数对应使用的一个参数。Default: null-->
<property name="overrideDefaultPassword">password</property>

<!--password:密码。Default: null-->
<property name="password"></property>

<!--preferredTestQuery:定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意: 测试的表必须在初始数据源的时候就存在。Default: null-->
<property name="preferredTestQuery">select id from test where id=1</property>

<!--propertyCycle:用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
<property name="propertyCycle">300</property>

<!--testConnectionOnCheckout:因性能消耗大请只在需要的时候使用它。Default: false -->
<property name="testConnectionOnCheckout">false</property>

<!--testConnectionOnCheckin:如果设为true那么在取得连接的同时将校验连接的有效性。Default: false -->
<property name="testConnectionOnCheckin">true</property>

<!--user:用户名。Default: null-->
<property name="user">root</property>

<!--usesTraditionalReflectiveProxies:动态反射代理。Default: false-->
<property name="usesTraditionalReflectiveProxies">false</property>


在配置文件中可以设置多套配置

<!-- 这个包裹下的是默认的一套配置 -->
<default-config></default-config>
<!-- 这个包裹下的是其它配置,可以在调用获取数据源的方法的时候传入name属性值指定那一套配置 -->
<named-config name="name"></named-config>


3. 编写工具类

package com.jyh.util;

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

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {
private  static ComboPooledDataSource ds = new ComboPooledDataSource();//参数指代配置文件里的哪一套配置

public static DataSource getDataSource(){
return ds;
}

public static Connection getConnection(){
try {
return ds.getConnection();
} catch (SQLException e) {
throw new RuntimeException("获取数据库连接失败!");
}
}
}


利用Web服务器管理数据源(JNDI)

JNDI:Java Naming and Directory Interface

主流的web服务器都提供数据源的实现。基本只是配置一下即可。

配置步骤:

a. 把数据库的驱动jar包,拷贝到Tomcat\lib目录下

b. 配置JNDI数据源

在应用的META-INF目录下,建立一个context.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!--
name:放到JNDI容器中的名称
-->
<Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
maxActive="30" maxIdle="30" maxWait="10000"
username="root" password="ying1995520***" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/database1"/>
</Context>


c. 获取数据源

package com.jyh.utils;

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

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DataSourceUtil {

private static DataSource ds;

public static DataSource getDataSource(){
Context initContext;
try {
initContext = new InitialContext();
ds  = (DataSource)initContext.lookup("java:/comp/env/jdbc/test");//路径
} catch (NamingException e) {
throw new RuntimeException("获取数据源失败!");
}
return ds;
}

public static Connection getConnection() throws SQLException{
return ds.getConnection();
}
}


注意:该数据源是基于Web服务器的,所以要启动Web服务器才能使用

JDBC框架简析

元信息的获取

package com.jyh.test;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.junit.Test;

import com.jyh.util.DBCPUtil;

/**
* 数据库本身元数据的获取(数据库类型、版本、名称、表、列名等一系列信息)
* @author OverrideRe
*
*/
public class MetaDataTest {
//获取数据库名(MySQL?Oracle?)
@Test
public void test1() throws SQLException{
Connection conn = DBCPUtil.getConnection();
DatabaseMetaData dmd = conn.getMetaData();
System.out.println(dmd.getDatabaseProductName());
conn.close();
}

//获取占位符个数
@Test
public void test2() throws Exception{
Connection conn = DBCPUtil.getConnection();
PreparedStatement st = conn.prepareStatement("??");
ParameterMetaData pmd = st.getParameterMetaData();
System.out.println(pmd.getParameterCount());//打印占位符的个数
conn.close();
}

//获取列的个数、列名和列的类型
@Test
public void test3() throws Exception{
Connection conn = DBCPUtil.getConnection();
PreparedStatement st = conn.prepareStatement("select * from customers");
ResultSet rs = st.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int num = rsmd.getColumnCount();//获取多少列
for(int i = 1;i <= num; i ++){
String name = rsmd.getColumnName(i);//获取列名
int type = rsmd.getColumnType(i);//列的类型(数字表示)
//类型会打印出各种类型对应的数字,如int类型就是4,varchar(String)类型就是12
System.out.println("列名=" + name + ",类型=" + type);
}
conn.close();
}
}


自己编写小型框架(约定优于编码)

问题:

每次访问数据库都要建立链接太麻烦,所以丢给数据库链接池做了,那么每次访问数据库都要获取链接然后编写一大堆重复的代码能不能丢给别人做呢?我们可不可以封装一个类专门链接数据库更新数据获取数据呢?

思考:

每次处理数据库请求有什么相同的呢?

啊,增加、删除和修改都是没有返回值的,而且除了sql语句和参数不同,其它都相同。而查找获取数据是有返回值的,除了sql语句和参数不同,还需要对返回的数据进行封装处理。

根据这种思想,我们就可以利用元信息的获取来封装数据库的链接了。

处理返回的结果集的接口:

package com.jyh.assist.test;

import java.sql.ResultSet;

/**
* 定义一个接口,应对不同的结果集有不同的处理方法,也就是本接口的不同实现
* @author OverrideRe
*
*/
public interface ResultSetHandle {

public Object handle(ResultSet rs);
}


处理返回的结果集中的一个实现类(实现对返回结果是对象集合的处理):

package com.jyh.assist.test;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
* 本类是应对结果集为对象集合的处理方法
* @author OverrideRe
*
*/

public class ResultSetHnadleList implements ResultSetHandle {

Class<?> clazz;

public ResultSetHnadleList(Class<?> clazz){
this.clazz = clazz;
}

@Override
public Object handle(ResultSet rs) {
try {
List<Object> list = new ArrayList<>();
//遍历结果集中的数据
while(rs.next()){
//获取传过来的对象实例
Object bean = clazz.newInstance();
//获取结果集中的元数据(列的个数、列名和类型等)
ResultSetMetaData rsmd = rs.getMetaData();
//获取列的个数
int colNum = rsmd.getColumnCount();
//循环遍历列
for(int i = 0; i < colNum; i ++){
//获取列名
String colName = rsmd.getColumnName(i + 1);
//获取对应列名的数值
Object colValue = rs.getObject(i + 1);
//根据列名获取传过来的对象的属性
Field field = clazz.getDeclaredField(colName);
//进行私有属性爆破
field.setAccessible(true);
//如果该属性是Date类型则进行转换
if(Date.class.equals(field.getType())){
String cv = (String)colValue;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
colValue = df.parse(cv);
} catch (ParseException e) {
e.printStackTrace();
}
}
//对该属性进行赋值
field.set(bean, colValue);
}
//将该类添加到集合中去
list.add(bean);
}
return list;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}


具体的访问数据库的方法:

package com.jyh.assist.test;

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;

public class AssistDemo {

private DataSource ds;

public AssistDemo(DataSource ds){
this.ds = ds;
}

//处理没有返回结果集的方法
public void update(String sql,Object...params){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = ds.getConnection();
st =conn.prepareStatement(sql);
//获取关于sql语句中的元数据(占位符个数)
ParameterMetaData pmd = st.getParameterMetaData();
int num = pmd.getParameterCount();//获取占位符个数
if(num > 0){
if(params == null){
throw new IllegalArgumentException("参数为空");
}
if(params.length != num){
throw new IllegalArgumentException("参数个数不匹配");
}
for(int i = 1; i <= num; i ++){
st.setObject(i, params[i-1]);
}
}
st.executeUpdate();
}catch(Exception e){
throw new RuntimeException(e);
}finally{
release(rs, st, conn);
}
}

//处理有结果集的方法   rsh参数代表处理结果集的方法
public Object query(String sql,ResultSetHandle rsh,Object...params) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try{
conn = ds.getConnection();
st =conn.prepareStatement(sql);
ParameterMetaData pmd = st.getParameterMetaData();
int num = pmd.getParameterCount();
if(num > 0){
if(params == null){
throw new IllegalArgumentException("参数为空");
}
if(params.length != num){
throw new IllegalArgumentException("参数个数不匹配");
}
for(int i = 1; i <= num; i ++){
st.setObject(i, params[i-1]);
}
}
//获取结果集
rs = st.executeQuery();
return rsh.handle(rs);
}catch(Exception e){
throw new RuntimeException(e);
}finally{
release(rs, st, conn);
}
}

public void release(ResultSet rs,Statement st,Connection conn){
if(st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
st = null;
}
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}


调用:

package com.jyh.assist.test;

import java.util.List;

import jyh.com.domain.Account;

import org.junit.Test;

import com.jyh.utils.DBCPUtil;

public class AssistTest {

private AssistDemo a = new AssistDemo(DBCPUtil.getDataSource());
@Test
public void addAccount(){
a.update("insert into account(name,salary) values(?,?)", "ddd","1000");
}

@Test
public void getAllAccount(){
@SuppressWarnings("unchecked")
List<Account> list = (List<Account>) a.query("select * from account", new ResultSetHnadleList(Account.class));
System.out.println(list.size());
}

@Test
public void getCount(){
Integer num = (Integer) a.query("select count(*) from account", null);
System.out.println(num);
}
}


策略模式?

看以前写的和今天写的代码,发现已经好几次出现自己编写一个类继承某个接口实现“一个”方法的情况了。就拿上面的例子,我编写了一个ResultSetHandle接口,该接口只有一个public Object handle(ResultSet rs);方法,然后我在编写一个ResultSetHnadleList类去实现这个接口,然后编写方法实现。然后怎么用这个呢?是将该类的实例作为一个参数传递过去。貌似这种方式就是策略模式0.0,在我的理解就是,某些时候需要传递的并不是数据,而是一个具体的方法,但是方法并不能传递,只能通过类的实例调用,于是就传一个类的实例好了,这个类里面实现了需要的方法,但是为什么要接口呢?因为不同的情况有不同的实现啊!!!比如说上面我实现了如果结果集是集合数据,万一返回的是一个int类型数字呢?那肯定要不同的实现方法了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息