//加载及注册JDBC驱动程序 Class.forName("com.mysql.jdbc.Driver"); Class.forName("com.mysql.jdbc.Driver").newInstance(); Class.forName("oracle.jdbc.driver.OracleDriver") Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); //建立连接对象 String url="jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password"; Connection con = DriverManager.getConnection(url); //建立SQL陈述式对象(Statement Object) Statement stmt = con.createStatement(); //执行SQL语句 executeQuery() String query = "select * from test"; ResultSet rs=stmt.executeQuery(query); //结果集ResultSet while(rs.next()) {rs.getString(1);rs.getInt(2);}
2、那么问题来了,为什么在加载数据库驱动包的时候有用的是Class.forName( ),却没有调用newInstance( )?
Class.forName("");的作用是要求JVM查找并加载指定的类,首先要明白,java里面任何class都要装载在虚拟机上才能运行,而静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再走这段静态代码了。
而我们前面也说了,Class.forName(xxx.xx.xx)的作用就是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。
而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下:
public class MyJDBCDriver implements Driver { static { DriverManager.registerDriver(new MyJDBCDriver()); } }
读完上面的解释,然后想去找了一个oracle驱动ojdbc6.jar,没找到源代码,好吧,那就用mysql-jdbc。5.1.11.jar代替吧。
以下是源代码:
<span style="font-size:14px;">package com.mysql.jdbc; import java.sql.SQLException; //源代码地址:http://www.docjar.com/html/api/com/mysql/jdbc/Driver.java.html public class Driver extends NonRegisteringDriver implements java.sql.Driver { // ~ Static fields/initializers // --------------------------------------------- // // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } // ~ Constructors // ----------------------------------------------------------- /** * Construct a new driver and register it with DriverManager * * @throws SQLException * if a database error occurs. */ public Driver() throws SQLException { // Required for Class.forName().newInstance() } }</span>
当Class.forName("com.mysql.jdbc.Driver");用的时候,就相当于调用了Driver类中的java.sql.DriverManager.registerDriver(new Driver());,实例化一个Driver,然后再java.sql.DriverManager中进行注册。
我猜在Oracle的驱动ojdbc6类oracle.jdbc.driver.OracleDriver中也有一段java.sql.DriverManager.registerDriver(new Driver());,把自己注册给DriverManager。
3、那么下一步就看看DriverManager吧
<span style="font-size:14px;">public class DriverManager { private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>(); ...... static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } ...... public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); } public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); } private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized (DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } ...... for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } } }</span>
3.1 Reflection.getCallerClass()获得是谁调用了它的类
3.2 properties文件是一个文本文件。一般这么用
InputStream is = new FileInputStream("D:\\myprojects\\lession4\\src\\stu\\ttt.properties"); //创建一个Properties容器 Properties prop = new Properties(); //从流中加载properties文件信息 prop.load(is); //循环输出配置信息 for (Object key : prop.keySet()) { System.out.println(key + "=" + prop.get(key)); }
3.3 ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。
这一句是重点:registeredDrivers.addIfAbsent(new DriverInfo(driver));
方法registerDriver中,放driver放到了registeredDrivers中,DriverInfo只是Driver的一个包装类,当看到addIfAbsent,就能猜出来,registerDriver大约是一个list,再看看定义:CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>(); CopyOnWriteArrayList肯定是一个ArrayList吧。然后再看类CopyOnWriteArrayList,
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ...... }
查一下JDK,解释如下:
public interface RandomAccess
List 实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
将操作随机访问列表的最佳算法(如 ArrayList)应用到连续访问列表(如 LinkedList)时,可产生二次项的行为。如果将某个算法应用到连续访问列表,那么在应用可能提供较差性能的算法前,鼓励使用一般的列表算法检查给定列表是否为此接口的一个instanceof,如果需要保证可接受的性能,还可以更改其行为。
现在已经认识到,随机和连续访问之间的区别通常是模糊的。例如,如果列表很大时,某些 List 实现提供渐进的线性访问时间,但实际上是固定的访问时间。这样的List 实现通常应该实现此接口。实际经验证明,如果是下列情况,则List 实现应该实现此接口,即对于典型的类实例而言,此循环:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
的运行速度要快于以下循环:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
强调:if (list instance of RandomAccess) { for(int m = 0; m < list.size(); m++){} }else{ Iterator iter = list.iterator(); while(iter.hasNext()){} }
一说到这里,就又想到一设计模式,迭代模式,看到要改再写了......
好吧,现在搞清楚了RandomAccess接口。原来list.size(),和iterater.hasNext()遍历是不一样的啊!!
搞明白了RandomAccess接口,就明白了registeredDrivers,那么谁用到了registeredDrivers呢?
4、是方法getConnection用到了registeredDrivers啊,通过遍历for(DriverInfo aDriver : registeredDrivers),然后创建Connection con = aDriver.driver.connect(url, info);最后返回一个con,这样就和Connection con = DriverManager.getConnection(url);连起来啦
这就是顺了吧,Class.forName("com.mysql.jdbc.Driver")之后用Connection con = DriverManager.getConnection(url)就知道为什么了吧。
那么Jdbc桥连接是怎么应用的呢?
5、因为刚学完Class.forName会加载静态代码块,看DriverManager时,就看到一个静态代码块,里面写着loadInitialDrivers();那就点进去看看吧。看到这么一段代码,感觉瞬间就高逼格了,靠,虽然在用富客户端JavaFx2的时候,也是写匿名类,形式和这个差不多,但没有深思过。
AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } });PrivilegedAction类:
public interface PrivilegedAction<T> { T run(); }注意到AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {}} 没,貌似是在New接口啊!!!!
5.1然后查了一,解释如下:
new一个对象的时候,相当于new了一个实现该接口的类。相当于动态的实现了接口。
//按照书本来说,接口是不可以被new的,但你要new一个也可以这样用。就像下面的代码: //例1 Runnable= new Runnable() { public void run() { //..... } }); //例2 Thread thread = new Thread(new Runnable() { @Override public void run() { //... } }); //例3 //比如 一个接口 A ,里面有一个方法fun1(),一般我们是先定义它的实现再引用它,比如 public class ImpA implements A{ public void fun1(){ //do some thing... } } //然后在另一个类调用 public class Class1 { public void method1(){ A a = new ImpA(); } } //<span style="color:#FF0000;">但有时我想对这个接口做另一个实现不想使用ImpA,但由于是临时的又不想去做定义,则你就可以这样</span> public class Class1 { public void method1(){ A a = new A(){ //相当于在内存中直接产生了一个接口的实现并引用这个内存对象。动态的代码 public void fun1(){ //do some thing... } }; } }
点进去AccessController的方法:public static native <T> T doPrivileged(PrivilegedAction<T> action);
看到一个关键字:native
5.2 那么native是干嘛的?
简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。
6、ReentrantLock是干嘛的
在看到registeredDrivers.addIfAbsent(new DriverInfo(driver));的时候,点进去了,看了一下addIfAbsent的方法,一进去,就看到这么2行:
public boolean addIfAbsent(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { ....... return true; } finally { lock.unlock(); } }
ReentrantLock是干嘛的,看起来很吊的样子,一看肯定是上锁的嘛,那为什么不用synchronized呢?
一查,吓一跳,看起来很复杂的样子,而且非常高大尚
对 synchronized 的改进
如此看来同步相当好了,是么?那么为什么 JSR 166 小组花了这么多时间来开发 java.util.concurrent.lock 框架呢?答案很简单-同步是不错,但它并不完美。它有一些功能性的限制 —— 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
ReentrantLock 类
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
再对比一下原博文,一般情况下还是用synchronized,它还是很好用的。
------------------------------------ 结论的分割线 ------------------------------------
7、绕了这么大一圈,那么Jdbc和桥连接是怎么应用的呢? 没有一个抽象类是持有持有一个接口,所以也不存在抽象和现实分离啊?
搜索了一下,在网上看到这么一个解释:
从上面的示例可以看出,我们写的应用程序,是面向JDBC的API在开发,这些接口就相当于桥接模式中的抽象部分的接口。那么怎样得到这些API的呢?是通过DriverManager来得到的。此时的系统结构如图9所示:
图9 基于JDBC开发的应用程序结构示意图
那么这些JDBC的API,谁去实现呢?光有接口,没有实现也不行啊。
该驱动程序登场了,JDBC的驱动程序实现了JDBC的API,驱动程序就相当于桥接模式中的具体实现部分。而且不同的数据库,由于数据库实现不一样,可执行的Sql也不完全一样,因此对于JDBC驱动的实现也是不一样的,也就是不同的数据库会有不同的驱动实现。此时驱动程序这边的程序结构如图10所示:
图10 驱动程序实现结构示意图
有了抽象部分——JDBC的API,有了具体实现部分——驱动程序,那么它们如何连接起来呢?就是如何桥接呢?
就是前面提到的DriverManager来把它们桥接起来,从某个侧面来看,DriverManager在这里起到了类似于简单工厂的功能,基于JDBC的应用程序需要使用JDBC的API,如何得到呢?就通过DriverManager来获取相应的对象。
那么此时系统的整体结构如图11所示:
图11 JDBC的结构示意图
通过上图可以看出,基于JDBC的应用程序,使用JDBC的API,相当于是对数据库操作的抽象的扩展,算作桥接模式的抽象部分;而具体的接口实现是由驱动来完成的,驱动这边自然就相当于桥接模式的实现部分了。而桥接的方式,不再是让抽象部分持有实现部分,而是采用了类似于工厂的做法,通过DriverManager来把抽象部分和实现部分对接起来,从而实现抽象部分和实现部分解耦。
JDBC的这种架构,把抽象和具体分离开来,从而使得抽象和具体部分都可以独立扩展。对于应用程序而言,只要选用不同的驱动,就可以让程序操作不同的数据库,而无需更改应用程序,从而实现在不同的数据库上移植;对于驱动程序而言,为数据库实现不同的驱动程序,并不会影响应用程序。而且,JDBC的这种架构,还合理的划分了应用程序开发人员和驱动程序开发人员的边界。
对于有些朋友会认为,从局部来看,体现了策略模式,比如在上面的结构中去掉“JDBC的API和基于JDBC的应用程序”这边,那么剩下的部分,看起来就是一个策略模式的体现。此时的DriverManager就相当于上下文,而各个具体驱动的实现就相当于是具体的策略实现,这个理解也不算错,但是在这里看来,这么理解是比较片面的。
对于这个问题,再次强调一点:对于设计模式,要从整体结构上、从本质目标上、从思想体现上来把握,而不要从局部、从表现、从特例实现上来把握。
--------------------- 个人的理解 ---------------------
在源代码中:Connection con = aDriver.driver.connect(url, info); 这句话是重点,告诉我们 Connection是怎么来的。aDriver是从registeredDrivers来的,而Class.forName("com.mysql.jdbc.Driver");时,New了一个Driver注册给registeredDrivers的,此时再用driver.connect,我认为是一个回调。
Class.forName是用Mysql还是Oracle,这个Driver一定会实现接口java.sql.Driver,然后通过DriverManager.registerDriver(new Driver());DriverManager类持有一个Driver,是否可以把DriverManager当成桥,当成桥连接中的抽象类?然后持有一个接口Driver,至于是Mysql还是Oracle,不关心,坐等传参。
PS:Connection 是怎么来的?通过driver的回调函数connect(url, info)得来的。Connection是一个接口,继承了Wrapper, AutoCloseable接口。接口可以继续多个接口!Connection会创建成什么样子,完成由Mysql或Oracle的Driver去决定。
最后有一个问题:为什么在com.mysql.jdbc.Driver中,并没有connect方法呢?求解惑。不知道有没有更好的理解?!欢迎!!
PS:
不允许类多重继承的主要原因是,如果A同时继承B和C,而b和c同时有一个D方法,A如何决定该继承那一个呢?
但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口。一个类如果实现了一个借口,则要实现该接口的所有方法。而接口则不用实现继承接口的方法。
看源代码,能涉及到这么多的知识点,路漫漫.......
参考:
jdbc的Class.forName解释1:http://blog.csdn.net/jackterq/article/details/4480745
jdbc的Class.forName解释2:http://blog.csdn.net/kaiwii/article/details/7405761
参考RandomAccess:http://blog.csdn.net/keda8997110/article/details/8635005
有关匿名类:http://bbs.csdn.net/topics/390133247
native参考:http://blog.csdn.net/wike163/article/details/6635321
ReentrantLock和synchronized两种锁定机制的对比:http://blog.csdn.net/fw0124/article/details/6672522
研磨设计模式之桥接模式 http://chjavach.iteye.com/blog/750381
转载请注明:http://blog.csdn.net/paincupid/article/details/43614029
原文地址:http://blog.csdn.net/paincupid/article/details/43614029