文章目录
- 前言
- 2.3 Connnection
- 2.3.1 JDBC驱动程序的类型
- 2.3.1.1 JDBC-ODBC Bridge Driver
- 2.3.1.2 Native API Driver
- 2.3.1.3 HDBC-Net Driver
- 2.3.1.4 Native Protocol Driver
- 2.3.2 java.sql.Driver
- 2.3.2.1 静态代码块加载驱动类
- 2.3.2.2 SPI机制加载驱动类
- 2.3.3 DriverManager
- 2.3.3.1 registerDriver&deregisterDriver
- 2.3.3.2 getConnection
- 2.3.4 javax.sql.DataSource
- 2.3.4.1 DataSource接口介绍
- 2.3.4.2 使用JNDI提高可移植性
- 2.3.6 关闭Connection
前言
本节将详细研究Connection接口的相关内容,例如JDBC驱动程序的类型、DriverManager类、Driver接口以及DataSource接口等。
2.3 Connnection
一个Connection对象表示通过JDBC驱动程序与数据源建立的连接。
这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据源。
使用JDBC API的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。
获取JDBC中的Connection对象有两种方式:
(1)通过JDBC API中提供的DriverManager类获取;
(2)通过DataSource接口的实现类获取(推荐使用)。
目前使用比较广泛的数据库连接池,如C3P0、DBCP、Druid等都是javax.sql.DataSource接口的具体实现。
2.3.1 JDBC驱动程序的类型
2.3.1.1 JDBC-ODBC Bridge Driver
SUN公司发布JDBC规范时,市面上并没有成熟的JDBC驱动程序可用,但微软的ODBC(Open-DataBase-Connectivity,开放式数据库互连)技术已经非常成熟,通过ODBC驱动程序可以连接所有类型的数据源。
因此,SUN公司发布了JDBC-ODBC桥接驱动,在JDBC和ODBC之间建立一个桥连接,利用现成的ODBC框架将JDBC调用转换为ODBC调用。
但是,由于桥连接的限制,并非所有功能都能直接转换并正常调用,同时多层调用转换对性能也有一定的影响,在JDBC规范中一般不推荐采用该方式。
2.3.1.2 Native API Driver
这类驱动程序会直接调用数据库提供的原生链接库或客户端,因为没有中间过程,访问速度通常表现良好。
但由于驱动程序和链接库或客户端绑定,无法达到JDBC跨平台的基本目的,因此在JDBC规范中也不推荐该类驱动程序。
2.3.1.3 HDBC-Net Driver
这类驱动程序会将JDBC调用转换为独立于数据库的协议,然后通过特定的中间件或服务器转换为数据库通信协议,主要目的是获取更好的架构灵活性。
然而,通过中间服务器转换会对性能造成一定影响。这类驱动程序并不常见,微软的ADO.NET属于这种架构。
2.3.1.4 Native Protocol Driver
这是最常见的JDBC驱动程序,开发中使用的驱动包基本都属于此类,通常由数据库厂商直接提供,例如mysql-connector-java。
这类驱动程序会把JDBC调用转换为数据库特定的网络通信协议,而使用网络通信,驱动程序可以纯Java实现,支持跨平台部署,性能也较好。
2.3.2 java.sql.Driver
2.3.2.1 静态代码块加载驱动类
所有的JDBC驱动都必须实现Driver接口,而且实现类必须包含一个静态代码块,在该静态代码块中向DriverManager注册自己的一个实例。如MyBatis内置的HSQLDB数据库的驱动程序实现类如下:
源码1:org.hsqldb.jdbc.JDBCDriver
public JDBCDriver() {
}
public static final JDBCDriver driverInstance = new JDBCDriver();
static {
try {
DriverManager.registerDriver(driverInstance);
} catch (Exception e) {
}
}
由 源码1 可知,当加载驱动类JDBCDriver时,上面的静态代码块就会执行,向DriverManager注册一个JDBCDriver驱动类的实例。
这也是为什么在使用JDBC操作数据库时需要先使用Class.forName()
方法加载驱动类。而为了确保驱动类可以使用Class.forName()
方法进行加载,它必须提供一个无参数的构造方法。
2.3.2.2 SPI机制加载驱动类
在JDBC 4.0版本之前,使用DriverManager获取Connection对象都需要通过代码显式地加载驱动类,如:
Class.forName("org.hsqldb.jdbcDriver");
JDBC 4.0及以上的版本,对DriverManager的getConnection
方法做了增强,可以通过Java的SPI机制加载驱动。
符合JDBC 4.0及以上版本的驱动程序的jar包中必须存在一个 META-INF\services\java.sql.Driver 文件,并在该文件中指定Driver接口的实现类。
如hsqldb-2.4.0.jar中:
在DriverManager类中,定义了一段静态代码块:
源码2:DriverManager.java
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
// 读取jdbc.drivers属性指定的驱动类
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 通过SPI机制加载Driver接口的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} // catch ...
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 依次加载jdbc.drivers属性指定的驱动类
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
由 源码2 可知,静态代码块会调用loadInitialDrivers
方法,该方法通过两种方式加载驱动实现类。
在loadInitialDrivers
方法中,首先会读取jdbc.drivers属性指定的驱动类(只是读取属性,还没加载),例如在使用命令启动项目时使用jdbc.drivers属性指定驱动类:
java -Djdbc.drivers=org.hsqldb.jdbcDriver -jar xxx.jar
随后,通过SPI机制,借助ServiceLoader读取 META-INF\services\java.sql.Driver 文件中配置的Driver接口的实现类,完成驱动实现类的加载。
最后,如果jdbc.drivers属性不为空,则使用Class.forName
依次加载jdbc.drivers属性指定的驱动类。
通过一个简单示例来调试一下:
- 自定义一个驱动实现类,并配置到VM参数中
public class MyDriver implements Driver {
static {
try {
DriverManager.registerDriver(new MyDriver());
} catch (SQLException e) {
e.printStackTrace();
}
}
public MyDriver() {
}
// 重写Driver接口的方法 ...
}
- 编写测试类
public class Example01 {
@Test
public void testSPI() {
try {
DriverManager.getConnection("jdbc:hsqldb:mem:mybatis", "sa", "");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
以Debug方式启动项目,可以发现DriverManager中注册了2个驱动类,恰好就是自定义的MyDriver以及 META-INF\services\java.sql.Driver 文件中定义的org.hsqldb.jdbc.JDBCDriver,说明上述的两种配置方式都生效了。
2.3.3 DriverManager
由类名可知,这是一个驱动管理器,为JDBC客户端管理一组可用的驱动实现。
源码3:DriverManager.java
// 注册驱动实现类
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {...}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da) throws SQLException {...}
// 解除驱动注册
public static synchronized void deregisterDriver(Driver driver)
throws SQLException {...}
// 获取数据库连接
public static Connection getConnection(String url)
throws SQLException {...}
public static Connection getConnection(String url,
String user, String password) throws SQLException {...}
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {...}
2.3.3.1 registerDriver&deregisterDriver
由 源码3 可知,registerDriver
方法用于将驱动实现类注册到DriverManager中,这个方法会在驱动实现类加载时通过静态代码块隐式调用。registerDriver
方法有2个重载方法:
registerDriver(java.sql.Driver driver)
:直接注册驱动实现类;registerDriver(java.sql.Driver driver, DriverAction da)
:注册一个驱动实现类的同时,并注册一个DriverAction的实现类,以监听DriverManager使用deregisterDriver
方法解除该监听器的注册时回调。
2.3.3.2 getConnection
这个方法是提供给JDBC客户端调用的,可以接收一个JDBC URL作为参数,DriverManager会对所有注册的驱动实现类进行遍历,找到能够识别该URL的驱动实现,并调用connect
方法与数据库建立连接,然后返回Connection对象。
由 源码3 可知,getConnection
方法有3个重载方法:
getConnection(String url)
:当连接数据库不需要用户名和密码时使用;getConnection(String url, String user, String password)
:当连接数据库只需要用户名、密码时使用;getConnection(String url, java.util.Properties info)
:当连接数据库除了需要用户名、密码,还需要其他额外的参数时使用。
JDBC URL的格式为:jdbc:<subprotocol>:<subname>
。其中subprotocol用于指定数据库连接机制由一个或者多个驱动程序提供支持;subname的内容取决于subprotocol。
常用的数据库驱动程序的驱动实现类的类名及JDBC URL如下:
(1)Oracle
驱动程序类名:oracle.jdbc.driver.OracleDriver
JDBC URL:jdbc:oracle:thin:@//<host>:<port>/<ServiceName>
或jdbc:oracle:thin:@//<host>:<port>/<SID>
(2)MySql
驱动程序类名:com.mysql.jdbc.Driver
JDBC URL:jdbc:mysql://<host>:<port>/<database>
(3)IBM DB2
驱动程序类名:com.ibm.db2.jcc.DB2Driver
JDBC URL:jdbc:db2://<host>:<port>/<database>
2.3.4 javax.sql.DataSource
2.3.4.1 DataSource接口介绍
DataSource是比较推荐的获取数据源连接的一种方式。JDBC驱动程序都会实现DataSource接口,通过DataSource接口实现类的实例,返回一个Connection对象的实例。
DataSource对象表示能够提供数据源连接的数据源对象。如果数据库相关信息发生了变化,则可以简单地修改DataSource对象的属性来反映这种变化,而不用修改应用程序的任何代码。
JDBC API中有3种类型的DataSource对象,分别是DataSource、XADataSource(支持分布式事务)、ConnectionPoolDataSource(提供连接池提高系统性能和伸缩性)。
而具体有哪些DataSource对象的属性,却决于具体的实现类,不同的实现类的属性略有差异。例如在MySQL驱动的实现类MysqlDataSource对象中具有以下属性(列举部分):
- databaseName:数据库名称;
- user:用户名;
- password:密码;
- hostName:主机名;
- port:监听端口;
- url:JDBC URL;
- encoding:编码。
DataSource对象的实现类会为这些属性提供对应的getter和setter方法,并在创建对象时初始化这些属性。
2.3.4.2 使用JNDI提高可移植性
使用JNDI(Java Naming and Directory Interface,Java命名和目录接口)可以提高应用程序的可移植性。在应用程序中,使用JNDI API,就可以仅通过一个逻辑名称来获取DataSource对象。当数据源的属性发生变化时,不会影响JDBC客户端的代码。
JNDI为应用程序提供了一种通过网络访问远程服务的方式,可以把一个逻辑名称和数据源建立映射关系。
下面是使用JNDI的一个示例:
public class Example02 {
@Before
public void before() throws IOException {
// 创建MyBatis提供的数据源工厂
DataSourceFactory dataSourceFactory = new PooledDataSourceFactory();
Properties dsProps = new Properties();
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
dsProps.load(inputStream);
dataSourceFactory.setProperties(dsProps);
// 获取数据源对象
DataSource dataSource = dataSourceFactory.getDataSource();
try {
// Apache Tomcat提供的JNDI实现
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ctx = new InitialContext(jndiProps);
// 将逻辑名称"java:testJNDI"和数据源DataSource绑定起来
ctx.bind("java:testJNDI", dataSource);
} catch (NamingException e) {
e.printStackTrace();
}
}
@Test
public void testJNDI() {
try {
// Apache Tomcat提供的JNDI实现
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ctx = new InitialContext(jndiProps);
// 通过逻辑名称"java:testJNDI"获取数据源DataSource
DataSource dataSource = (DataSource)ctx.lookup("java:testJNDI");
Connection connection = dataSource.getConnection();
Assert.assertNotNull(connection);
System.out.println("成功获取到Connection对象...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在示例的代码中,创建了一个javax.naming.InitialContext实例,然后调用该实例的bind
方法将字符串形式的逻辑名称 java:testJNDI 和数据源DataSource对象绑定在一起。
在testJNDI
方法中,调用InitialContext实例的lookup
方法,传入逻辑名称 java:testJNDI,获取已经绑定的数据源DataSource对象,如果成功获取到,说明JNDI确实生效了。
需要注意的是,JDK只是提供了JNDI的规范,具体的实现由不同厂商来完成。当前示例使用的是Apache Tomcat中提供的JNDI实现,因此需要在项目中的pom.xml文件中添加相关依赖:
<dependency>
<groupId>tomcat</groupId>
<artifactId>naming-java</artifactId>
<version>5.0.28</version>
</dependency>
<dependency>
<groupId>tomcat</groupId>
<artifactId>naming-common</artifactId>
<version>5.0.28</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
在实际的Java EE项目中,JNDI命名服务的创建通常由应用服务器(如Tomcat、WebLogic等)来创建,开发者只需要查找命名服务并使用即可。例如,在Tomcat服务器中,通过配置其 context.xml 配置文件就可以配置JNDI数据源:
<Resource name="java:testJNDI"
scope="Shareable"
type="javax.sql.DataSource"
factory="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory"
username="sa"
password=""
driverClassName="org.hsqldb.jdbcDriver"
url="jdbc:hsqldb:mem:mybatis"/>
2.3.6 关闭Connection
源码4:Connection.java
public interface Connection extends Wrapper, AutoCloseable {
void close() throws SQLException;
boolean isClosed() throws SQLException;
boolean isValid(int timeout) throws SQLException;
}
当使用完Connection对象后,需要显式地关闭该对象。由 源码4 可知,Connection接口提供了几个方法处理Connection对象地关闭。
- close:关闭Connection对象。当调用该方法时,由该Connection对象创建的所有Statement对象都会被关闭;
- isClosed:判断Connection对象是否已被关闭;
- isValid:判断Connection对象是否有效。
…
本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析