最近写了个监控系统,需要从各个数据库中获取数据,为了可配行,各个数据库的信息(数据库类型,用户名,密码等)都存放在我们自己的数据库中,
暂时想到的方法就是直接用JDBC代码访问数据库,取数据,然后将数据库连接进行池化。
唉,以前都是J2EE开发,用spring配置使用,这次直接使用JDBC还不太习惯了,OK,不废话了
JDBC步骤:
1.加载数据驱动
Class.forName("com.mysql.jdbc.Driver");
2. 得到数据库连接
DriverManager.getConnection(url, user, password);
当时到这步就产生两个疑问:
1. 为什么加载了数据库驱动,就能得到数据库连接,看上面的两行代码,是看出来两者之间联系
2. 除了Class.forName()加载数据库驱动,貌似没有看到过其他加载数据库驱动方式,是否有其他方式?为什么不用其他方式?
OK,问题出来了,就看看问题
问题1:
后来结合数据库驱动源代码和JDBC规范了解到,每个数据库驱动,都必须在加载的时候,自己实现数据库驱动注册(注册?貌似前面观察者模式就有注册行为,但是数据库驱动却没有update行为,所以不存在通知)
mysql驱动源代码:
public class Driver extends NonRegisteringDriver
implements java.sql.Driver
{
public Driver()
throws SQLException
{
}
static
{
try
{
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
DriverManager.registerDriver()代码如下:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
if (!initialized) {
initialize();
}
DriverInfo di = new DriverInfo();
di.driver = driver;
di.driverClass = driver.getClass();
di.driverClassName = di.driverClass.getName();
// Not Required -- drivers.addElement(di);
writeDrivers.addElement(di);
println("registerDriver: " + di);
/* 由于可以注册多个数据库驱动类型,这里更新下数据库驱动 */
readDrivers = (java.util.Vector) writeDrivers.clone();
}
这样在DriverManager.getConnection()的时候,就可以用到数据库驱动,得到连接了,代码如下
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
// Gets the classloader of the code that called this method, may
// be null.
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, callerCL));
}
private static Connection getConnection(
String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
java.util.Vector drivers = null;
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if(callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
if (!initialized) {
initialize();
}
synchronized (DriverManager.class){
// 得到注册的驱动
drivers = readDrivers;
}
// Walk through the loaded drivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
DriverInfo di = (DriverInfo)drivers.elementAt(i);
// 根据数据库类型判断,得到这次请求的数据库连接
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println(" skipping: " + di);
continue;
}
try {
println(" trying " + di);
Connection result = di.driver.connect(url, info);
if (result != null) {
// Success!
println("getConnection returning " + di);
return (result);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
OK,到此问题的答案算是明白了
下面,问题2:
加载类的方式几种,目前我想到的就下面几种,不够以后在补充,呵呵:
1. Class.forName()
2. Class.forName().newInstance();
3. new
4.Class.class
5.Class.class.newInstance();
6.Thread.currentThread().getContextClassLoader().loadClass()
可以知道方法2和5都是有点多余,加载的类,还实例化了一个对象出来,可以Pass掉。而3跟前面一个问题一样,实例化了一个对象出来,多此一举,此对象还占据内存。
OK,还剩下三种方法1.4.6,先来分析1和6的区别
Class.forName()的源代码如下:
public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true, ClassLoader.getCallerClassLoader());
}
调用此方法等效于:
Class.forName(className, true, currentLoader)
第二次参数表示装载类的时候是否初始化该类, 即调用类的静态块的语句及初始化静态成员变量。
Thread.currentThread().getContextClassLoader().loadClass()的代码如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
调用此方法等效于调用 loadClass(name,false)
。
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
此方法使用指定的 二进制名称来加载类。此方法的默认实现将按以下顺序搜索类:
-
调用
findLoadedClass(String)
来检查是否已经加载类。 -
在父类加载器上调用
loadClass
方法。如果父类加载器为 null,则使用虚拟机的内置类加载器。 -
调用
findClass(String)
方法查找类。
如果使用上述步骤找到类,并且 resolve 标志为真,则此方法将在得到的 Class 对象上调用 resolveClass(Class)
方法。
-
参数:
-
resolve
- 如果该参数为 true,则分析这个类 -
所以此方法没有初始化类,即没有加载类的静态代码,所以此方法不行
OK,下面只剩下方法1和4了,方法4加载类会获得编译检查,可以保证此类一定存在,所以无法做到运行时动态加载未知数据库驱动,并且Class.class前面还必须给一个变量赋值才行: Class cc = Class.class
所以加载数据库,大家看到的都是方法1。
好了,到这,收工,呵呵。。。。