maven之构建jdbc连oracle的四种方式实例和注册驱动源码浅析
还是先来看看参考吧
关于ServiceLoader的解释 http://blog.csdn.net/hintcnuie/article/details/37922089
oracle数据库手册下载 很好的资源 强烈推荐 http://docs.oracle.com/cd/E11882_01/nav/portal_5.htm
还有oracle官网下载的jdbc源码 http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html
还有maven配置oracle的jdbc依赖 http://www.cnblogs.com/leiOOlei/archive/2013/10/21/3380568.html
好的 还是看看 oracle官网的描述
Java Database Connectivity (JDBC) is a Java standard that provides the interface for connecting from Java to relational databases. The JDBC standard is defined by Sun Microsystems and implemented through the standard java.sql interfaces. This allows individual providers to implement and extend the standard with their own JDBC drivers. JDBC is based on the X/Open SQL Call Level Interface (CLI). JDBC 4.0 complies with the SQL 2003 standard.
恩 就不翻译了 很简单 关键是 翻译了 也不太到位 哎
oracle 按照功能与用途 支持了四种类型的驱动
In addition to supporting the standard JDBC application programming interfaces (APIs), Oracle drivers have extensions to support Oracle-specific data types and to enhance performance. Oracle provides the following JDBC drivers: ■Thin driver It is a pure Java driver used on the client-side, without an Oracle client installation. It can be used with both applets and applications. ■Oracle Call Interface (OCI) driver It is used on the client-side with an Oracle client installation. It can be used only with applications. ■Server-side Thin driver It is functionally similar to the client-side Thin driver. However, it is used for code that runs on the database server and needs to access another session either on the same server or on a remote server on any tier. ■Server-side internal driver It is used for code that runs on the database server and accesses the same session. That is, the code runs and accesses data from a single Oracle session.
还有一处 是oracle的jdbc的javadoc的 觉得也不错 贴出来 看看
Oralce provides four types of JDBC driver. Thin Driver, a 100% Java driver for client-side use without an Oracle installation, particularly with applets. The Thin driver type is thin. To connect user scott with password tiger to a database with SID (system identifier) orcl through port 1521 of host myhost, using the Thin driver, you would write : Connection conn = DriverManager.getConnection ("jdbc:oracle:thin:@myhost:1521:orcl", "scott", "tiger"); OCI Driver for client-side use with an Oracle client installation. The OCI driver type is oci. To connect user scott with password tiger to a database with SID (system identifier) orcl through port 1521 of host myhost, using the OCI driver, you would write : Connection conn = DriverManager.getConnection ("jdbc:oracle:oci:@myhost:1521:orcl", "scott", "tiger"); Note that you can also specify the database by a TNSNAMES entry. You can find the available TNSNAMES entries listed in the file tnsnames.ora on the client computer from which you are connecting. For example, if you want to connect to the database on host myhost as user scott with password tiger that has a TNSNAMES entry of MyHostString, enter: Connection conn = DriverManager.getConnection ("jdbc:oracle:oci8:@MyHostString","scott","tiger"); If your JDBC client and Oracle server are running on the same machine, the OCI driver can use IPC (InterProcess Communication) to connect to the database instead of a network connection. An IPC connection is much faster than a network connection. Connection conn = DriverManager.getConnection ("jdbc:oracle:oci8:@","scott","tiger"); Server-Side Thin Driver, which is functionally the same as the client-side Thin driver, but is for code that runs inside an Oracle server and needs to access a remote server, including middle-tier scenarios. The Server-Side Thin driver type is thin and there is no difference in your code between using the Thin driver from a client application or from inside a server. Server-Side Internal Driver for code that runs inside the target server, that is, inside the Oracle server that it must access. The Server-Side Internal driver type is kprb and it actually runs within a default session. You are already "connected". Therefore the connection should never be closed. To access the default connection, write: DriverManager.getConnection("jdbc:oracle:kprb:"); or: DriverManager.getConnection("jdbc:default:connection:"); You can also use the Oracle-specific defaultConnection() method of the OracleDriver class which is generally recommended: OracleDriver ora = new OracleDriver(); Connection conn = ora.defaultConnection();
/** * 测试oracle 的三种连接方式 * 三种连接方式在使用上 没有太多的不一样 只是每个方式的使用定位 实现方式不一样 * thin--多使用在web开发 通过tcp/ip的socket与服务器oracle的tcp/ip的socket监听器进行连接 进而与oracle服务器进行通信 属于第4类驱动 独立于平台的 * oci--不太适合使用在web端 ,因为oracle call interface 通过jni调用本地oracle客户端的c库 从而实现与服务器oracle进行通信 属于第2类驱动 * kprb(server-side internal driver)--为oracle服务器端内部的驱动 用于嵌入在服务器oracle中的java程序/jsp代码与oracle进行通信 连接使用当前会话用户名与密码 这个连接也不用关闭 使用的是kprb的c库进行通信 * 还有一种 server-side thin ----用于oracle服务器端内部需要访问远程服务器时使用或者是同一个数据库不同的session访问 例如调用中间件 与client thin没有太多的区别 * @author Administrator * */
还有一张oracle jdbc的驱动和oracle数据库的 关系图
好吧 还是来看看具体的例子吧
1、客户端thin协议 也就是我们常用的jdbc:oracle:thin:@localhost:1521:orcl
thin--多使用在web开发 通过tcp/ip的socket与服务器oracle的tcp/ip的socket监听器进行连接 进而与oracle服务器进行通信 属于第4类驱动 独立于平台的
package com.undergrowth.jdbc.thin; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Enumeration; import java.util.Iterator; import java.util.ServiceLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import oracle.jdbc.OracleDriver; import oracle.jdbc.pool.OracleDataSource; /** * 测试连接oracle的thin协议 * 来源于oracle的ojdbc6的例子 * @author Administrator * */ public class ThinTest { static final String connect_string = "jdbc:oracle:thin:u1/u1@//localhost:1521/orcl"; static final Logger LOGGER=LoggerFactory.getLogger(ThinTest.class); // This is the kind of string you would use if going through the // Oracle 8 connection manager which lets you run the database on a // different host than the Web Server. See the on-line documentation // for more information. // static final String connect_string = // "jdbc:oracle:thin:hr/hr@(description=(address_list=(address=(protocol=tcp)(host=localhost)(port=1610))(address=(protocol=tcp)(host=localhost)(port=1521)))(source_route=yes)(connect_data=(service_name=orcl.oracle.com)))"; // The query we will execute static final String query = "select 'Hello JDBC: ' || to_char(sysdate,'yyyy-mm-dd') from dual"; Connection conn;; StringBuffer output=new StringBuffer(); public String testThin(){ try { // See if we need to open the connection to the database if (conn == null) { // Create a OracleDataSource instance and set URL OracleDataSource ods = new OracleDataSource(); ods.setURL(connect_string); // Connect to the databse output.append("Connecting to " + connect_string + "\n"); conn = ods.getConnection(); output.append("Connected\n"); } // Create a statement Statement stmt = conn.createStatement(); // Execute the query output.append("Executing query " + query + "\n"); ResultSet rset = stmt.executeQuery(query); // Dump the result while (rset.next()) output.append(rset.getString(1) + "\n"); // We're done output.append("done.\n"); LOGGER.info(output.toString()); } catch (Exception e) { e.printStackTrace(); // Oops output.append(e.getMessage() + "\n"); LOGGER.info(output.toString()); }finally{ if(conn!=null) { try { conn.close(); conn=null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return output.toString(); } /** * 使用DriverManager进行注册驱动 * @return */ public String testThinDriverManager(){ try { output=new StringBuffer(); LOGGER.info("开始使用DriverManager进行驱动注册"); //注册oracle jdbc驱动 //DriverManager.registerDriver(new OracleDriver()); Enumeration<Driver> driversEnumeration=DriverManager.getDrivers(); while(driversEnumeration.hasMoreElements()){ System.out.println(driversEnumeration.nextElement().toString()); } if (conn == null) { // Create a OracleDataSource instance and set URL //这里为什么不要 DriverManager.registerDriver(new OracleDriver()) 这么一句话 哈哈 里面有很多学问啊 //简单的说 就是 DriverManager在类加载的时候 在一个静态块中执行了loadInitialDrivers方法 此方法使用 //ServiceLoader.load(Driver.class)的方式加载了位于ojdbc6-11.2.0.jar包下的meta-inf/services/java.sql.Driver文件中的oracle.jdbc.OracleDriver的驱动 //那为什么加载了oracle.jdbc.OracleDriver的驱动 就不需要注册了呢 原因在于 OracleDriver也有一个静态块里面有一个 DriverManager.registerDriver(m_defaultDriver); //所以无需注册了 conn=DriverManager.getConnection(connect_string); // Connect to the databse output.append("Connecting to " + connect_string + "\n"); output.append("Connected\n"); } // Create a statement Statement stmt = conn.createStatement(); // Execute the query output.append("Executing query " + query + "\n"); ResultSet rset = stmt.executeQuery(query); // Dump the result while (rset.next()) output.append(rset.getString(1) + "\n"); // We're done output.append("done.\n"); LOGGER.info(output.toString()); } catch (Exception e) { e.printStackTrace(); // Oops output.append(e.getMessage() + "\n"); LOGGER.info(output.toString()); }finally{ if(conn!=null) { try { conn.close(); conn=null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return output.toString(); } }
上面有一段 个人觉得大家可以研究下 还是很有作用的
if (conn == null) { // Create a OracleDataSource instance and set URL //这里为什么不要 DriverManager.registerDriver(new OracleDriver()) 这么一句话 哈哈 里面有很多学问啊 //简单的说 就是 DriverManager在类加载的时候 在一个静态块中执行了loadInitialDrivers方法 此方法使用 //ServiceLoader.load(Driver.class)的方式加载了位于ojdbc6-11.2.0.jar包下的meta-inf/services/java.sql.Driver文件中的oracle.jdbc.OracleDriver的驱动 //那为什么加载了oracle.jdbc.OracleDriver的驱动 就不需要注册了呢 原因在于 OracleDriver也有一个静态块里面有一个 DriverManager.registerDriver(m_defaultDriver); //所以无需注册了 conn=DriverManager.getConnection(connect_string); // Connect to the databse output.append("Connecting to " + connect_string + "\n"); output.append("Connected\n"); }
看到上面的代码片段 我没有使用DriverManager.register或者是Class.forName进行驱动的注册 但是却依旧可以连接上oracle数据库 其实看看源码 和我上面的注释 就知道原因了 哈哈
对于上面的 补充一下
If you are using JSE 6 and later, then there is no need to explicitly load the JDBC driver. This means that the Java run-time loads the driver when needed and you need not include Class.forName("oracle.jdbc.OracleDriver") or new oracle.jdbc.OracleDriver() in your code. But if you are using J2SE 5.0, then you need to load the JDBC driver explicitly.
2、oci协议
oci--不太适合使用在web端 ,因为oracle call interface 通过jni调用本地oracle客户端的c库 从而实现与服务器oracle进行通信 属于第2类驱动
//常规的写法 /*String url = "jdbc:oracle:oci:@localhost:1521:orcl";*/ //如果客户端和服务器在同一台电脑上 使用ipc 进行通信 如下 /* String url ="jdbc:oracle:oci:@";*/ //使用oracle\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN\tnsnames.ora 中的连接项进行连接 String url ="jdbc:oracle:oci8:@ORCL";看到上面 oci协议有3中写法 分别对应与不同的情况
第一种 常用方法 jdbc:oracle:oci:@localhost:1521:orcl
第二种 仅适用于 oracle客户端和oracle服务器在同一台电脑上 使用内部进程间进行通信
第三种 使用tns的配置连接
package com.undergrowth.jdbc.oci; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import oracle.jdbc.oci.OracleOCIConnection; import oracle.jdbc.pool.OracleOCIConnectionPool; /** * 测试oracle的oci协议 * 来源oracle的ojdbc例子 * @author Administrator * */ public class OciTest { final static Logger LOGGER=LoggerFactory.getLogger(OciTest.class); public boolean testOci(){ boolean b=true; OracleOCIConnectionPool cpool=null; OracleOCIConnection conn1=null,conn2=null; ResultSet rset =null; Statement stmt =null; //常规的写法 /*String url = "jdbc:oracle:oci:@localhost:1521:orcl";*/ //如果客户端和服务器在同一台电脑上 使用ipc 进行通信 如下 /* String url ="jdbc:oracle:oci:@";*/ //使用oracle\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN\tnsnames.ora 中的连接项进行连接 String url ="jdbc:oracle:oci8:@ORCL"; try { String url1 = System.getProperty("JDBC_URL"); if (url1 != null) url = url1; // Create an OracleOCIConnectionPool instance with // default configuration cpool = new OracleOCIConnectionPool ("u1", "u1", url, null); // Print out the default configuration for the // OracleOCIConnectionPool LOGGER.info ("-- The default configuration for the OracleOCIConnectionPool --"); displayPoolConfig(cpool); // Get a connection from the pool conn1 = (OracleOCIConnection) cpool.getConnection("u1", "u1"); // Create a Statement stmt = conn1.createStatement (); // Select the ID, NAME, birthday, age column from the student table rset = stmt.executeQuery ("SELECT ID, NAME, birthday, age FROM student st WHERE st.id<10"); // Iterate through the result and print the student table LOGGER.info ("-- Use the connection from the OracleOCIConnectionPool --"); while (rset.next ()) LOGGER.info (rset.getString (1) + " " + rset.getString (2)); LOGGER.info ("-- Use another connection from the OracleOCIConnectionPool --"); // Get another connection from the pool // with different userID and password conn2 = (OracleOCIConnection) cpool.getConnection("u2", "u2"); // Create a Statement stmt = conn2.createStatement (); // Select the USER from DUAL to test the connection rset = stmt.executeQuery ("select to_char(sysdate,'yyyy-mm-dd') from DUAL"); // Iterate through the result and print it out rset.next (); LOGGER.info (rset.getString (1)); // Reconfigure the OracleOCIConnectionPool in case the performance // is too bad. This might happen when many users are trying to connect // at the same time. In this case, increase MAX_LIMIT to some larger // number, and also increase INCREMENT to a positive number. Properties p = new Properties(); p.put (OracleOCIConnectionPool.CONNPOOL_MIN_LIMIT, Integer.toString(cpool.getMinLimit())); p.put (OracleOCIConnectionPool.CONNPOOL_MAX_LIMIT, Integer.toString(cpool.getMaxLimit() * 2)) ; if (cpool.getConnectionIncrement() > 0) // Keep the old value p.put (OracleOCIConnectionPool.CONNPOOL_INCREMENT, Integer.toString(cpool.getConnectionIncrement())); else // Set it to a number larger than 0 p.put (OracleOCIConnectionPool.CONNPOOL_INCREMENT, "1") ; // Enable the new configuration cpool.setPoolConfig(p); // Print out the current configuration for the // OracleOCIConnectionPool LOGGER.info ("-- The new configuration for the OracleOCIConnectionPool --"); displayPoolConfig(cpool); } catch (Exception e) { // If there is any security exception, ignore it // and use the default b=false; }finally{ // Close the RseultSet try { rset.close(); rset = null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Close the Statement try { stmt.close(); stmt = null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Close the connections try { conn1.close(); conn1 = null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { conn2.close(); conn2 = null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Close the OracleOCIConnectionPool try { cpool.close(); cpool = null; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return b; } // Display the current status of the OracleOCIConnectionPool private void displayPoolConfig (OracleOCIConnectionPool cpool) throws SQLException { LOGGER.info (" Min poolsize Limit: " + cpool.getMinLimit()); LOGGER.info (" Max poolsize Limit: " + cpool.getMaxLimit()); LOGGER.info (" Connection Increment: " + cpool.getConnectionIncrement()); LOGGER.info (" NoWait: " + cpool.getNoWait()); LOGGER.info (" Timeout: " + cpool.getTimeout()); LOGGER.info (" PoolSize: " + cpool.getPoolSize()); LOGGER.info (" ActiveSize: " + cpool.getActiveSize()); } }
3、kprb协议
kprb(server-side internal driver)--为oracle服务器端内部的驱动 用于嵌入在服务器oracle中的java程序/jsp代码与oracle进行通信 连接使用当前会话用户名与密码 这个连接也不用关闭 使用的是kprb的c库进行通信
package com.undergrowth.jdbc.kprb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 测试oracle的kprb协议 * 来源oracle的安装文件 oracle\Administrator\product\11.2.0\dbhome_1\javavm\demo\examples\jsproc\basic\basic_kprb * @author Administrator * 测试表 * * create table employee (enum int, ename varchar2(20), position varchar2(20), salary int); insert into employee values (100, 'John Doe', 'Secretary', 40000); insert into employee values (101, 'Jane Johnson', 'Engineer', 60000); */ public class KprbTest { //final static Logger LOGGER=LoggerFactory.getLogger(KprbTest.class); /* * update the position and salary of an employee with the * given employee number * * Here, emp_num and raise are IN parameters; position is an IN/OUT * parameter; salary is an OUT parameter. * */ public static void updatePositionSalary(int emp_num, String[] position, int[] salary, int raise) { String pos = null; int sal = 0; Connection conn = null; Statement stmt = null; PreparedStatement pstmt = null; try { // create connection and statement DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver ()); conn = DriverManager.getConnection("jdbc:oracle:kprb:"); stmt = conn.createStatement(); pstmt = conn.prepareStatement("UPDATE EMPLOYEE SET POSITION = ?, " + " SALARY = SALARY + ? WHERE ENUM = ?"); // set up bind values and execute the update pstmt.setString(1, position[0]); pstmt.setInt(2, raise); pstmt.setInt(3, emp_num); pstmt.execute(); // retrieve the updated position and salary to verify that // the data has been updated in the database ResultSet rset = stmt.executeQuery( "SELECT POSITION, SALARY FROM EMPLOYEE WHERE ENUM = " + emp_num); while (rset.next()) { pos = rset.getString ("position"); sal = rset.getInt ("salary"); } }catch(SQLException sqlException){ //LOGGER.error(sqlException.getMessage()); System.out.println(sqlException.getMessage()); }finally { try { if(stmt!=null) stmt.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } position[0] = pos; salary[0] = sal; //LOGGER.info("ENUM:"+emp_num+"\t position:"+pos+"\tsalary:"+sal); System.out.println("ENUM:"+emp_num+"\t position:"+pos+"\tsalary:"+sal); } } }
基于kprb的测试方式 有点特殊 由于其用途所决定的
如下‘
/* * kprb的测试方法 有点特殊 由于其用途所决定 * 1、编写好KprbTest.java 编译成KprbTest.class文件 * 2、将编译好的KprbTest.class加载到oracle服务器中 作为oracle的模式对象之一 * loadjava -r -f -o -user u1/u1@orcl KprbTest.class * 查看是否加载成功 SELECT uo.* from User_Objects uo WHERE uo.OBJECT_NAME LIKE '%KprbTest%'; * 3、使用存储过程或者函数进行包装你的 KprbTest.class * create or replace procedure annualReview (enum IN number, positon IN OUT varchar2, sal OUT number, raise IN number) as language java name 'com.undergrowth.jdbc.kprb.KprbTest. updatePositionSalary(int, java.lang.String[], int[], int)'; / 4、test annualReview存储过程 即可看到结果 begin -- Call the procedure annualreview(enum => :enum, positon => :positon, sal => :sal, raise => :raise); end; */
4、附测试类
package com.undergrowth; import java.sql.SQLException; import java.util.ServiceLoader; import com.undergrowth.jdbc.kprb.KprbTest; import com.undergrowth.jdbc.message.IMessage; import com.undergrowth.jdbc.oci.OciTest; import com.undergrowth.jdbc.thin.ThinTest; import junit.framework.TestCase; /** * 测试oracle 的三种连接方式 * 三种连接方式在使用上 没有太多的不一样 只是每个方式的使用定位 实现方式不一样 * thin--多使用在web开发 通过tcp/ip的socket与服务器oracle的tcp/ip的socket监听器进行连接 进而与oracle服务器进行通信 属于第4类驱动 独立于平台的 * oci--不太适合使用在web端 ,因为oracle call interface 通过jni调用本地oracle客户端的c库 从而实现与服务器oracle进行通信 属于第2类驱动 * kprb(server-side internal driver)--为oracle服务器端内部的驱动 用于嵌入在服务器oracle中的java程序/jsp代码与oracle进行通信 连接使用当前会话用户名与密码 这个连接也不用关闭 使用的是kprb的c库进行通信 * 还有一种 server-side thin ----用于oracle服务器端内部需要访问远程服务器时使用或者是同一个数据库不同的session访问 例如调用中间件 与client thin没有太多的区别 * @author Administrator * */ public class OracleJdbcTest extends TestCase { public void testTestThin() { ThinTest tt=new ThinTest(); String actual=tt.testThin(); assertNotSame("", actual); actual=tt.testThinDriverManager(); assertNotSame("", actual); } public void testTestOci() { OciTest tt=new OciTest(); Boolean condition=tt.testOci(); assertTrue(condition); } public void testKprbTest() { /* * kprb的测试方法 有点特殊 由于其用途所决定 * 1、编写好KprbTest.java 编译成KprbTest.class文件 * 2、将编译好的KprbTest.class加载到oracle服务器中 作为oracle的模式对象之一 * loadjava -r -f -o -user u1/u1@orcl KprbTest.class * 查看是否加载成功 SELECT uo.* from User_Objects uo WHERE uo.OBJECT_NAME LIKE '%KprbTest%'; * 3、使用存储过程或者函数进行包装你的 KprbTest.class * create or replace procedure annualReview (enum IN number, positon IN OUT varchar2, sal OUT number, raise IN number) as language java name 'com.undergrowth.jdbc.kprb.KprbTest. updatePositionSalary(int, java.lang.String[], int[], int)'; / 4、test annualReview存储过程 即可看到结果 begin -- Call the procedure annualreview(enum => :enum, positon => :positon, sal => :sal, raise => :raise); end; */ /*KprbTest tt=new KprbTest(); int[] salary=new int[1]; String[] position=new String[1]; tt.updatePositionSalary(100, position, salary, 60000); assertNotSame("", position[0]);*/ } /** * 测试服务加载者用于加载位于meta-inf/services/下服务提供者 */ public void testServiceLoader(){ ServiceLoader<IMessage> messageLoader=ServiceLoader.load(IMessage.class); for (IMessage iMessage : messageLoader) { System.out.println(iMessage.getBuffer().toString()); } } }
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.undergrowth</groupId> <artifactId>jdbc</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>jdbc</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- 添加oracle jdbc的依赖 --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> </dependencies> </project>
哦 对了 对于oracle的maven依赖 我们需要自己安装到版本仓库中
参考如下
mvn install:install-file -Dfile={Path/to/your/ojdbc.jar} -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0 -Dpackaging=jar详细参考 http://www.cnblogs.com/leiOOlei/archive/2013/10/21/3380568.html
5、最后来看看 注册驱动类DriverManager 是如何与oracle的OriaclDriver挂上关系的
恩 这里为了不是事情更复杂化 选择常用方法 DriverManager.registerDriver方法入手
先
DriverManager.registerDriver(new OracleDriver());
看看registerDriver方法
/** * Registers the given driver with the <code>DriverManager</code>. * A newly-loaded driver class should call * the method <code>registerDriver</code> to make itself * known to the <code>DriverManager</code>. * * @param driver the new JDBC Driver that is to be registered with the * <code>DriverManager</code> * @exception SQLException if a database access error occurs */ 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); }
添加一个不存在的元素到列表中
/** * Append the element if not present. * * @param e element to be added to this list, if absent * @return <tt>true</tt> if the element was added */ public boolean addIfAbsent(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // Copy while checking if already present. // This wins in the most common case where it is not present Object[] elements = getArray(); int len = elements.length; Object[] newElements = new Object[len + 1]; for (int i = 0; i < len; ++i) { if (eq(e, elements[i])) return false; // exit, throwing away copy else newElements[i] = elements[i]; } newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }使用 ReentrantLock保证了同步
重点是元素是否相等 if (eq(e, elements[i]))
/** * Test for equality, coping with nulls. */ private static boolean eq(Object o1, Object o2) { return (o1 == null ? o2 == null : o1.equals(o2)); }
当注册驱动时 o1==null 为false 执行 o1.equals(o2)
/** * Compares the specified object with this list for equality. * Returns {@code true} if the specified object is the same object * as this object, or if it is also a {@link List} and the sequence * of elements returned by an {@linkplain List#iterator() iterator} * over the specified list is the same as the sequence returned by * an iterator over this list. The two sequences are considered to * be the same if they have the same length and corresponding * elements at the same position in the sequence are <em>equal</em>. * Two elements {@code e1} and {@code e2} are considered * <em>equal</em> if {@code (e1==null ? e2==null : e1.equals(e2))}. * * @param o the object to be compared for equality with this list * @return {@code true} if the specified object is equal to this list */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; List<?> list = (List<?>)(o); Iterator<?> it = list.iterator(); Object[] elements = getArray(); int len = elements.length; for (int i = 0; i < len; ++i) if (!it.hasNext() || !eq(elements[i], it.next())) return false; if (it.hasNext()) return false; return true; }
当向
// List of registered JDBC drivers private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
添加了驱动后 即 DriverManager.registerDriver(new OracleDriver()); 执行完了
接着第二步是
conn=DriverManager.getConnection(connect_string);
/** * Attempts to establish a connection to the given database URL. * The <code>DriverManager</code> attempts to select an appropriate driver from * the set of registered JDBC drivers. * * @param url a database url of the form * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code> * @return a connection to the URL * @exception SQLException if a database access error occurs */ public static Connection getConnection(String url) 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(); return (getConnection(url, info, callerCL)); }
// Worker method called by the public getConnection() methods. private static Connection getConnection( String url, java.util.Properties info, ClassLoader callerCL) throws SQLException { /* * 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 + "\")"); // Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLException reason = null; 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()); } } // 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"); }
重点来了
Connection con = aDriver.driver.connect(url, info);
这里的 aDriver.driver 就是我们上面注册的 OracleDriver了 那看看OracleDriver的connect是什么吧
public Connection connect(String s, Properties properties) throws SQLException { if(s.regionMatches(0, "jdbc:default:connection", 0, 23)) { String s1 = "jdbc:oracle:kprb"; int j = s.length(); if(j > 23) s = s1.concat(s.substring(23, s.length())); else s = s1.concat(":"); s1 = null; } int i = oracleAcceptsURL(s); if(i == 1) return null; if(i == 2) { DBError.throwSqlException(67); return null; } Hashtable hashtable = parseUrl(s); if(hashtable == null) return null; String s2 = properties.getProperty("user"); String s3 = properties.getProperty("password"); String s4 = properties.getProperty("database"); if(s4 == null) s4 = properties.getProperty("server"); if(s2 == null) s2 = (String)hashtable.get("user"); s2 = parseLoginOption(s2, properties); if(s3 == null) s3 = (String)hashtable.get("password"); if(s4 == null) s4 = (String)hashtable.get("database"); String s5 = (String)hashtable.get("protocol"); properties.put("protocol", s5); if(s5 == null) { DBError.throwSqlException(40, "Protocol is not specified in URL"); return null; } String s6 = properties.getProperty("dll"); if(s6 == null) properties.put("dll", "ocijdbc9"); String s7 = properties.getProperty("prefetch"); if(s7 == null) s7 = properties.getProperty("rowPrefetch"); if(s7 == null) s7 = properties.getProperty("defaultRowPrefetch"); if(s7 != null && Integer.parseInt(s7) <= 0) s7 = null; String s8 = properties.getProperty("batch"); if(s8 == null) s8 = properties.getProperty("executeBatch"); if(s8 == null) s8 = properties.getProperty("defaultExecuteBatch"); if(s8 != null && Integer.parseInt(s8) <= 0) s8 = null; String s9 = properties.getProperty("remarks"); if(s9 == null) s9 = properties.getProperty("remarksReporting"); String s10 = properties.getProperty("synonyms"); if(s10 == null) s10 = properties.getProperty("includeSynonyms"); String s11 = properties.getProperty("restrictGetTables"); String s12 = properties.getProperty("fixedString"); String s13 = properties.getProperty("dataSizeUnits"); String s14 = properties.getProperty("AccumulateBatchResult"); if(s14 == null) s14 = "true"; Enumeration enumeration; for(enumeration = DriverManager.getDrivers(); enumeration.hasMoreElements();) { Driver driver = (Driver)enumeration.nextElement(); if(driver instanceof OracleDriver) break; } while(enumeration.hasMoreElements()) { Driver driver1 = (Driver)enumeration.nextElement(); if(driver1 instanceof OracleDriver) DriverManager.deregisterDriver(driver1); } Connection connection = getConnectionInstance(s5, s, s2, s3, s4, properties); if(s7 != null) ((oracle.jdbc.driver.OracleConnection)connection).setDefaultRowPrefetch(Integer.parseInt(s7)); if(s8 != null) ((oracle.jdbc.driver.OracleConnection)connection).setDefaultExecuteBatch(Integer.parseInt(s8)); if(s9 != null) ((oracle.jdbc.driver.OracleConnection)connection).setRemarksReporting(s9.equalsIgnoreCase("true")); if(s10 != null) ((oracle.jdbc.driver.OracleConnection)connection).setIncludeSynonyms(s10.equalsIgnoreCase("true")); if(s11 != null) ((oracle.jdbc.driver.OracleConnection)connection).setRestrictGetTables(s11.equalsIgnoreCase("true")); if(s12 != null) ((oracle.jdbc.driver.OracleConnection)connection).setDefaultFixedString(s12.equalsIgnoreCase("true")); if(s13 != null) ((oracle.jdbc.driver.OracleConnection)connection).setDataSizeUnits(s13); ((oracle.jdbc.driver.OracleConnection)connection).setAccumulateBatchResult(s14.equalsIgnoreCase("true")); hashtable = null; return connection; }
看到 好多 啊 其实 显示解析URL和解析默认属性 中间才是连接 后面又是解析URL和设置默认属性
主要的是 这个方法
注意哦 这个地方s5 如果我们用的是thin的协议的话 s5=thin 了 这个很有用啊 直接关系到下面找不找的具体的socket代码
Connection connection = getConnectionInstance(s5, s, s2, s3, s4, properties);
看到下面的连接方法
private Connection getConnectionInstance(String s, String s1, String s2, String s3, String s4, Properties properties) throws SQLException { Object obj = null; if(s.compareTo("ultra") == 0) { try { Class aclass[] = null; Object aobj[] = new Object[6]; aobj[0] = s; aobj[1] = s1; aobj[2] = s2; aobj[3] = s3; aobj[4] = s4; aobj[5] = properties; Class class1 = Class.forName("oracle.jdbc.ultra.client.Driver"); Method amethod[] = class1.getMethods(); for(int i = 0; i < amethod.length; i++) { if(!amethod[i].getName().equals("getConnection")) continue; aclass = amethod[i].getParameterTypes(); break; } Method method = class1.getMethod("getConnection", aclass); obj = (Connection)method.invoke(class1.newInstance(), aobj); } catch(Exception exception) { exception.printStackTrace(); DBError.throwSqlException(1); } } else { String s5 = null; if(s.equals("thin") && System.getProperty("oracle.jserver.version") != null) s5 = "thin-server"; else if((s.equals("oci8") || s.equals("oci")) && System.getProperty("oracle.jserver.version") != null) s5 = "oci-server"; else s5 = s; String s6 = (String)m_driverAccess.get(s5); if(s6 == null) DBError.throwSqlException(67, "Invalid protocol " + s); DBAccess dbaccess = null; try { dbaccess = (DBAccess)Class.forName(s6).newInstance(); } catch(Exception _ex) { return null; } if(properties.getProperty("is_connection_pooling") == "true") { properties.put("database", s4 != null ? ((Object) (s4)) : ""); obj = new OracleOCIConnection(dbaccess, s1, s2, s3, s4, properties); } else { obj = new oracle.jdbc.driver.OracleConnection(dbaccess, s1, s2, s3, s4, properties); } } return ((Connection) (obj)); }
有两句话比较关键
String s5 = null; if(s.equals("thin") && System.getProperty("oracle.jserver.version") != null) s5 = "thin-server"; else if((s.equals("oci8") || s.equals("oci")) && System.getProperty("oracle.jserver.version") != null) s5 = "oci-server"; else s5 = s; String s6 = (String)m_driverAccess.get(s5);哈哈 看到上面说了 如果使用的是 client-thin的话 那么s5=s=thin
那么s6的值是什么呢
m_driverAccess = new Properties(); m_driverAccess.put("thin-server", "oracle.jdbc.thinserver.ServerTTC7Protocol"); m_driverAccess.put("oci-server", "oracle.jdbc.ociserver.ServerOCIDBAccess"); m_driverAccess.put("thin", "oracle.jdbc.ttc7.TTC7Protocol"); m_driverAccess.put("oci8", "oracle.jdbc.oci8.OCIDBAccess"); m_driverAccess.put("oci", "oracle.jdbc.oci8.OCIDBAccess"); m_driverAccess.put("kprb", "oracle.jdbc.kprb.KprbDBAccess"); m_defaultDriver = null;这个实在OracleDriver的静态代码块中的
所以s6= oracle.jdbc.ttc7.TTC7Protocol 就是你了
我看了看 当使用的是thin写的时候
执行 最后一步
obj = new oracle.jdbc.driver.OracleConnection(dbaccess, s1, s2, s3, s4, properties);
所以此时 传递的dbaccess及为 TTC7Protocal的实例了
dbaccess = (DBAccess)Class.forName(s6).newInstance();
好的 知道了 这里 DBAccess实际上是指向TTC7Protocal的实例 就可以了
接着走
oracle.jdbc.driver.OracleDriver的构造函数
public OracleConnection(DBAccess dbaccess, String s, String s1, String s2, String s3, Properties properties) throws SQLException { m_txn_mode = 0; m_clientIdSet = false; m_clientId = null; include_synonyms = false; restrict_getTables = false; m_accumulateBatchResult = true; dataSizeScale = 1; m_dbMetaData = null; m_opc = null; m_opc_oc = null; m_osql = null; m_warning = null; m_readOnly = false; m_startTime = 0L; statementCache = null; m_stmtClearMetaData = false; m_process_escapes = true; m_defaultAutoRefetch = true; m_occ = null; m_privData = null; defaultFixedString = false; m_svptStmt = null; wrapper = null; connectionProperties = null; m_sessionTimeZone = null; m_dbTzCalendar = null; String s4 = null; Object obj = null; boolean flag = true; if(properties != null) { s4 = (String)properties.get("protocol"); String s6 = properties.getProperty("processEscapes"); if(s6 != null && s6.equalsIgnoreCase("false")) m_process_escapes = false; connectionProperties = (Properties)properties.clone(); connectionProperties.remove("password"); } initialize(s, s1, s4, dbaccess, null, null, null, s3); logicalHandle = false; try { needLine(); conversion = db_access.logon(s1, s2, s3, properties); m_warning = DBError.addSqlWarning(m_warning, db_access.getWarnings()); if(properties == null || properties.getProperty("connection_pool") != "connection_pool") { default_row_prefetch = db_access.getDefaultPrefetch(); if(properties != null) { String s5 = properties.getProperty("autoCommit"); if(s5 != null && s5.equalsIgnoreCase("false")) flag = false; } setAutoCommit(flag); db_access.initNls(this); } } catch(IOException ioexception) { DBError.throwSqlException(ioexception); } catch(SQLException sqlexception) { try { db_access.logoff(); } catch(IOException _ex) { } catch(SQLException _ex) { } throw sqlexception; } m_txn_mode = 0; }
其实只有一句最关键
conversion = db_access.logon(s1, s2, s3, properties);
这里就是用的TTC7Protocal的实例啊 的logon方法
接着
public synchronized DBConversion logon(String s, String s1, String s2, Properties properties) throws SQLException, IOException { try { if(state > 0) DBError.check_error(428); if(s == null || s1 == null) DBError.check_error(433); if(s.length() == 0 || s1.length() == 0) DBError.check_error(443); if(s2 == null) s2 = "localhost:1521:orcl"; connect(s2, properties); all7 = new Oall7(MEngine); commoncall = new Ocommoncall(MEngine); opencall = new Oopen(MEngine); close = new Oclose(MEngine); TTCTypeRep _tmp = MEngine.types; describe = (Odscrarr)MEngine.types.newTTIFunObject((byte)1, MEngine); bfileMsg = new v8TTIBfile(MEngine); blobMsg = new v8TTIBlob(MEngine); clobMsg = new v8TTIClob(MEngine); TTCTypeRep _tmp1 = MEngine.types; dty = (TTIdty)MEngine.types.newTTCMsgObject((byte)2, MEngine); dty.marshal(); dty.receive(); String s3 = (String)properties.get("internal_logon"); if(s3 != null) if(s3.equalsIgnoreCase("sysoper")) LOGON_MODE = 16L; else if(s3.equalsIgnoreCase("sysdba")) LOGON_MODE = 8L; log1 = new O3log(MEngine, s, properties, LOGON_MODE); log1.marshal(); log1.receive1st(); log2 = new O3log(MEngine, s, s1, log1.encryptedSK, properties, LOGON_MODE); log2.marshal(); do try { log2.receive2nd(); break; } catch(SQLWarning sqlwarning) { setWarnings(DBError.addSqlWarning(getWarnings(), sqlwarning)); } while(true); net.setO3logSessionKey(log2.getSessionKey()); ver = new Oversion(MEngine); ver.marshal(); ver.receive(); short word0 = ver.getVersionNumber(); if(word0 < 7230) DBError.check_error(441); MEngine.types.setVersion(word0); state = 1; return MEngine.conv; } catch(SQLException sqlexception) { try { net.disconnect(); } catch(Exception exception) { } state = 0; throw sqlexception; } }
看到有一句
connect(s2, properties);
private void connect(String s, Properties properties) throws IOException, SQLException { if(s == null || properties == null) DBError.check_error(433); net = new NSProtocol(); try { net.connect(s, properties); } catch(NetException netexception) { throw new IOException(netexception.getMessage()); } MEngine = new MAREngine(net); pro = new v8TTIpro(MEngine); pro.marshal(); pro.receive(); short word0 = pro.getOracleVersion(); short word1 = pro.getCharacterSet(); short word2 = TTCConversion.findAccessCharSet(word1, word0); TTCConversion ttcconversion = new TTCConversion(word1, word2, word0, pro.getncharCHARSET()); MEngine.types.setServerConversion(word2 != word1); MEngine.types.setVersion(word0); if(DBConversion.isCharSetMultibyte(word2)) { if(DBConversion.isCharSetMultibyte(pro.getCharacterSet())) MEngine.types.setFlags((byte)1); else MEngine.types.setFlags((byte)2); } else { MEngine.types.setFlags(pro.getFlags()); } MEngine.conv = ttcconversion; }
进行连接的部分
net = new NSProtocol(); try { net.connect(s, properties); }
嘎嘎 接着找
NSProtocal的connect
public void connect(String s, Properties properties) throws IOException, NetException { if(sAtts.connected) throw new NetException(201); if(s == null) throw new NetException(208); addrRes = new AddrResolution(s, properties); if(addrRes.connection_revised) { s = addrRes.getTNSAddress(); properties = addrRes.getUp(); } if(addrRes.jndi) sAtts.profile = new ClientProfile(properties, addrRes.getJndi()); else sAtts.profile = new ClientProfile(properties); establishConnection(s); Object obj4 = null; try { obj4 = Class.forName("oracle.net.ano.Ano").newInstance(); sAtts.anoEnabled = true; } catch(Exception _ex) { sAtts.anoEnabled = false; } if(obj4 != null) { ((Ano)obj4).init(sAtts); sAtts.ano = (Ano)obj4; sAtts.anoEnabled = true; } label0: do { ConnectPacket connectpacket = new ConnectPacket(sAtts); connectpacket.send(); packet = new Packet(sAtts); packet.receive(); switch(packet.type) { case 2: // '\002' AcceptPacket acceptpacket = new AcceptPacket(packet); break label0; case 11: // '\013' break; case 5: // '\005' RedirectPacket redirectpacket = new RedirectPacket(packet); sAtts.cOption.nt.disconnect(); sAtts = establishConnection(redirectpacket.getData()); break; case 4: // '\004' RefusePacket refusepacket = new RefusePacket(packet); throw new NetException(206, refusepacket.getData()); case 3: // '\003' case 6: // '\006' case 7: // '\007' case 8: // '\b' case 9: // '\t' case 10: // '\n' default: sAtts.cOption.nt.disconnect(); throw new NetException(205); } } while(true); setNetStreams(); sAtts.connected = true; if(sAtts.ano != null) sAtts.ano.negotiation(); packet = null; Object obj = null; Object obj1 = null; Object obj3 = null; Object obj2 = null; }
establishConnection(s);
private SessionAtts establishConnection(String s) throws NetException, IOException { sAtts.cOption = addrRes.resolveAndExecute(s); sAtts.ntInputStream = sAtts.cOption.nt.getInputStream(); sAtts.ntOutputStream = sAtts.cOption.nt.getOutputStream(); sAtts.setTDU(sAtts.cOption.tdu); sAtts.setSDU(sAtts.cOption.sdu); sAtts.nsOutputStream = new NetOutputStream(sAtts, 255); sAtts.nsInputStream = new NetInputStream(sAtts); return sAtts; }哈哈 看到这 可能就迷糊了 啊 接下去怎么看呢 嘎嘎 关键在第一句
sAtts.cOption = addrRes.resolveAndExecute(s);
public ConnOption resolveAndExecute(String s) throws NetException, IOException { cs = new ConnStrategy(); if(s.indexOf("//") != -1) resolveUrl(s); else if(s.indexOf(':') != -1 && s.indexOf(')') == -1) resolveSimple(s); else if(newSyntax) resolveAddrTree(s); else resolveAddr(s); if(!cs.optAvailable()) return cs.execute(); else return cs.getOption(); }
返回一个ConnOptions 是干什么的呢 看看就知道了
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) package oracle.net.nt; import java.io.IOException; import oracle.net.nl.NLException; import oracle.net.ns.NetException; // Referenced classes of package oracle.net.nt: // NTAdapter, TcpNTAdapter public class ConnOption { public ConnOption() { conn_data = new StringBuffer(); } public void connect() throws IOException { nt = getNT(); nt.connect(); } private NTAdapter getNT() throws NetException { try { nt = new TcpNTAdapter(addr); } catch(NLException _ex) { throw new NetException(501); } return nt; } public NTAdapter nt; public int port; public int tdu; public int sdu; public String protocol; public String host; public String sid; public String addr; public String service_name; public String instance_name; public StringBuffer conn_data; public boolean done; }
看到了吧 ConnOptions封装了一个TcpNTAdapter 嘎嘎 看看是干什么的
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) package oracle.net.nt; import java.io.*; import java.net.Socket; import oracle.net.nl.*; // Referenced classes of package oracle.net.nt: // NTAdapter public class TcpNTAdapter implements NTAdapter { public TcpNTAdapter(String s) throws NLException { NVNavigator nvnavigator = new NVNavigator(); NVPair nvpair = (new NVFactory()).createNVPair(s); if(nvpair == null) throw new NLException((short)100); NVPair nvpair1 = nvnavigator.findNVPair(nvpair, "PROTOCOL"); NVPair nvpair2 = nvnavigator.findNVPair(nvpair, "HOST"); NVPair nvpair3 = nvnavigator.findNVPair(nvpair, "PORT"); if(nvpair1 == null || nvpair2 == null || nvpair3 == null) throw new NLException((short)100); prot = nvpair1.getAtom(); host = nvpair2.getAtom(); port = Integer.parseInt(nvpair3.getAtom()); if(!prot.equals("TCP") && !prot.equals("tcp")) throw new NLException((short)100); else return; } public void connect() throws IOException { socket = new Socket(host, port); } public void disconnect() throws IOException { socket.close(); socket = null; } public InputStream getInputStream() throws IOException { return socket.getInputStream(); } public OutputStream getOutputStream() throws IOException { return socket.getOutputStream(); } static final boolean DEBUG = false; int port; String host; String prot; private Socket socket; }
嘎嘎 看到了吧 TcpNTAdapter有一个connect 方法
public void connect() throws IOException { socket = new Socket(host, port); }就建立了一个socket进行连接啊
以上即是 oracle的jdbc的连接过程 没多讲 这个领会多少 就看个人悟性了 噶
oracle的jdbc源码 http://download.csdn.net/detail/hao_lee1014/4221569 其实自己下个jad 就可以得到ojdbc6的源码了
以上即是oracle的 jdbc的几种方式 记录学习的脚步
posted on 2014-09-18 23:42 liangxinzhi 阅读(420) 评论(0) 编辑 收藏 举报