11. 利用反射机制建立数据库连接
解析数据库数据源配置文件(.xml),利用反射机制将配置文件中设置的数据源参数设置到 Class 类实例中,然后将该 Class 类实例强制转换成对应的数据源类。
1. 自定义数据源配置文件data-source-config.xml
简单提出一些约束条件。这些约束在解析该数据源配置文件时用到。
<?xml version="1.0" encoding="UTF-8"?>
<!-- quote from struts-config.xml -->
<!--
数据源配置文件几点约束:
1. <data-sources>节点下定义了一套数据源<data-source>[JDBC 2.0 Standard Extension]
2. 节点拼写大小写敏感,目前有且只有三级节点
3. 只允许有一个根节点<data-sources>
4. 出现多个 id 的<data-source>节点,以第一个为准
数据源查找方式:
1. <data-source>节点数 < 1, 即节点数为0
未设置数据源,报错
2. 默认数据源方式
<data-source>节点未设置 id 属性值或 id 属性值为null的<data-source>
3. 指定数据源方式
匹配<data-source>节点 id 属性值
-->
<data-sources>
<data-source type="org.apache.commons.dbcp.BasicDataSource">
<set-property property="driverClassName" value="com.mysql.jdbc.Driver" />
<set-property property="url" value="jdbc:mysql://localhost:3306/mydb" />
<set-property property="username" value="root" />
<set-property property="password" value="root" />
<set-property property="maxActive" value="30" />
<set-property property="maxIdle" value="10" />
<set-property property="maxWait" value="1000" />
</data-source>
</data-sources>
2. 解析配置文件
利用 XML 解析工具包解析配置文件。本文采用 jdom 来解析配置文件。
全局变量:
/**
* Copyright (c) 2011 Trusted Software and Mobile Computing(TSMC)
* All right reserved.
*
* Created on Aug 19, 2011 6:37:38 PM
* http://jarg.iteye.com/
* Author: Jarg Yee <yeshaoting@gmail.com>
*/
/** 配置文件路径 */
private static final String CONFIG = "data-source-config.xml";
/** 默认配置数据源 id 属性值 */
private static final String DEFAULT_ID = "null";
/** 配置文件节点属性名 */
private static final String ATT_TYPE = "type";
private static final String ATT_ID = "id";
private static final String ATT_PROPERTY = "property";
private static final String ATT_VALUE = "value";
private static final String ATT_REF = "ref";
/** 配置文件节点名 */
private static final String NODE_DATA_SOURCES = "data-sources";
private static final String NODE_DATA_SOURCE = "data-source";
private static final String NODE_SET_PROPERTY = "set-property";
/** 反射机制获取方法名前缀 */
private static final String PREFIX_SET = "set";
private static final String PREFIX_GET = "get";
/** 二种基本类型 */
private static final String TYPE_INT = "int";
private static final String TYPE_LONG = "long";
2.1 初始化配置文件
加载自定路径的配置文件,该路径是相对于类加载目录的路径。
Eclipse C/S项目中相对于classpath和bin目录,Eclipse B/S项目中相对于classpath和WebRoot/classes目录。
关键代码:
/**
* 初始化配置
* */
private void initConfig(String config)
{
InputStream in = this.getClass().getClassLoader().getResourceAsStream(config);
SAXBuilder builder = new SAXBuilder();
try
{
Document doc = builder.build(in);
root = doc.getRootElement();
}
catch (Exception e)
{
System.out.println("配置文件" + config + "丢失");
e.printStackTrace();
}
}
2.2 选择数据源节点
循环获取数据源节点<data-source>属性 id 值与传入的数据源节点 id 值匹配,获取对应的数据源节点。
关键代码:
/**
* 需要加上 "" ,否则无法与null匹配成功
* 不知道什么原因
* */
for(int i=0; i<dataSources.size(); i++)
{
Element element = dataSources.get(i);
if(id.equals("" + element.getAttributeValue(ATT_ID)))
{
dataSourceElement = element;
}
}
2.3 创建、设置、获取数据源实例
[创建数据源实例] 先获取数据源type属性值(声明数据源类完全限定名称),通过这个完全限定名称字符串创建 Class 类实例。
[设置数据源实例] 再获取当前数据源节点<datab-source>下所有属性设置节点<set-property>;
然后获取每个属性设置节点<set-property>的name、value属性值,根据name值获取字段类型。
不同类型值转换过程通过getMatchValue()方法来实现,其用途是:
a. 利用反射机制获取对应的setter方法的方法getMethod(),需要setter方法参数类型
b. value属性值读出来的是String类型(可以看作是Object类型),需要转换成setter方法对应的类型值。
例如:org.apache.commons.dbcp.BasicDataSource.BasicDataSource 中setMinIdle()方法:
public synchronized void setMinIdle(int minIdle)
[获取数据源实例] 最后,通过invoke()方法完成setter方法调用,将属性设置节点<set-property>属性值通过相应的setter方法设置到 Class 类实例相应的属性中。将 Class 类实例强制转换成 DataSource(所有数据源类的父类),返回。
关键代码:
/**
* 设置数据源数据
* @param dataSourceElement Element - 选择的数据源节点
* @throws Exception
* */
private DataSource newInstance(Element dataSourceElement) throws Exception
{
String type = dataSourceElement.getAttributeValue(ATT_TYPE);
if(type == null)
{
throw new Exception("未定义数据源类型");
}
Class<?> clazz = Class.forName(type);
Object obj = clazz.newInstance();
List<Element> properties = dataSourceElement.getChildren(NODE_SET_PROPERTY);
for(int i=0; i<properties.size(); i++)
{
Element property = properties.get(i);
String name = property.getAttributeValue(ATT_PROPERTY);
if(name == null)
{
throw new Exception("未定义属性名");
}
String value = property.getAttributeValue(ATT_VALUE);
if(value == null)
{
throw new Exception("未定义属性值");
}
/** 为了获取参数类型 */
Field field = clazz.getDeclaredField(name);
Class<?> fieldType = field.getType();
String methodName = getMethodName(PREFIX_SET, name);
Method method = clazz.getMethod(methodName, fieldType);
method.invoke(obj, getMatchValue(fieldType, value));
}
return (DataSource)obj;
}
/**
* 获取与字段类型相匹配的值
* */
private Object getMatchValue(Class<?> fieldType, String value)
{
/**
* 目前只有二类基本类型转换
* */
if(fieldType.getName().equals(TYPE_INT))
{
return Integer.parseInt(value);
}
else if(fieldType.getName().equals(TYPE_LONG))
{
return Long.parseLong(value);
}
else
return value;
}
2.4 建立数据库连接
通过上一步返回的数据源javax.sql.DataSource(这是一个接口)实例调用getConnection()方法建立数据库连接。
关键代码:
/**
* 获取指定数据源的数据库
* @throws Exception
* */
protected static Connection getConnection(DataSourceConfig dataSourceConfig, String id)
throws Exception
{
return dataSourceConfig.getDataSource(id).getConnection();
}
2.5 提供获取数据库连接用户接口
封装具体的数据库内部实现,将相关方法权限设置为 protected 。
利用getConnection(String id)获取指定数据源的数据库连接。
关键代码:
/**
* 获取指定数据源的数据库连接
* 不支持自定义用户名,密码方式
* @param id String
* */
public Connection getConnection(String id)
{
Connection conn = null;
try
{
conn = DataSourceConfig.getConnection(dataSourceConfig, id);
}
catch (Exception e)
{
e.printStackTrace();
}
return conn;
}