@Otokaze
2018-12-05T14:51:42.000000Z
字数 6699
阅读 777
Java
使用 DriverManager 获取数据库连接:
Connection connection = DriverManager.getConnection(url, user, pass)
使用 DataSource 获取数据库连接:
DataSource dataSource = new DataSourceImpl();
// set some property for dataSource instance ...
Connection connection = dataSource.getConnection();
我们知道 JDBC 的核心原理是 java.sql.Driver 驱动接口和各个数据库厂商提供的 java.sql.Driver 接口实现类,比如 MySQL 实现类为 com.mysql.jdbc.Driver。通过 DriverManager 获取数据库连接的一般步骤为:
Class.forName("com.mysql.jdbc.Driver")
,注册数据库厂商的 JDBC 驱动(static 初始化块)。DriverManager.getConnection(url, user, pass)
,根据 url、user、pass 信息获取数据库连接。url 的格式与具体的数据库厂商有关,而 user 是用来认证的用户名,pass 则是用来认证的用户名密码。
DataSource 只是一个接口,DataSource 是数据源的意思,我更喜欢将 DataSource 理解为数据库的抽象表示,我们可以从这个 DataSource 中获取数据库连接,这和数据库系统很相似。因为 DataSource 是数据库的抽象表示(其本身也是一个接口),所以我们需要一个 DataSource 实现类才能使用 DataSource 来获取数据库连接,那么这个实现类由谁提供呢?当然是数据库厂商了(MySQL、Oracle 等)。
DataSource 是 DriverManager 的更高层次的抽象。一个 DataSource 实例就表示一个数据库,我们可以从一个 DataSource 对象中获取数据库连接,即 dataSource.getConnection()。因为 DataSource 实现类是由数据库厂商提供的,所以数据库厂商可以自由的在实现类中加入数据库连接池、分布式事务等功能,而我们使用者只需要简单的调用 dataSource.getConnection() 来获取数据库连接,也因为如此,DataSource 的性能要比 DriverManager.getConnection() 获取数据库连接的方式好得多,因为 DataSource 实现类内部完全可以加入自己的数据库连接池逻辑,但这一点都不影响 dataSource 实例的使用方法。
DataSource 实现类必须提供一个无参构造函数,这么规定的原因是为了可以方便的与 JNDI 一起使用,因为通常情况下,我们都会在 JNDI 容器中注册对应的 DataSource 实现类,这样我们在程序中只需要像获取 Spring IoC 容器中的 bean 实例一样简单,如 DataSource dataSource = context.lookup("name")
,是不是感觉和 IoC 容器差不多?当然将 dataSource 放到 JNDI 中的目的和使用 IoC 容器的目的也是一样的,就是为了降低不同对象之间的耦合度。使用 JNDI 后,我们如果要更换 DataSource 实现,只需要修改容器的配置文件,然后重启容器就行了,不用更改任何代码(Tomcat 容器就有 JNDI 功能,这个以后再了解)。
javax.sql.DataSource 接口的定义:
Connection getConnection() throws SQLException
Connection getConnection(String username, String password) throws SQLException
有必要声明一点,DataSource 并不能取代 DriverManager,它只是 DriverManager 的封装,底层可能仍然是使用 DriverManager 来获取连接的。之所以要封装是因为 DriverManager 获取连接的方式太直接、太底层了,无法透明的实现数据库连接池,因为每次调用 DriverManager.getConnection() 返回的数据库连接都是新创建的,不能复用。而 DataSource 就不一样了,DataSource 这时候一个接口,接口暴露的 API 为 dataSource.getConnection(),当我们调用 dataSource.getConnection() 获取数据库连接时,实现类内部完全可以进行别的操作,比如实现数据库连接池,又比如分布式事务支持等。
还有一点就是,DataSource 实现类不一定要数据库厂商支持才能做,我们自己也是能够实现 DataSource 接口的,并且还能够在里面实现数据库连接池等,同理,DataSource 也有很多开源实现,比如 Apache 的 DBCP、号称性能无敌的 HikariCP、功能全面的 druid(阿里巴巴开发)。DBCP 目前分为两个版本,1.x 和 2.x(有没有感觉和 Log4J 很相似,1.x 和 2.x),DBCP 1.x 性能听说不行,是单线程同步模型。而 DBCP 2.x 推出比较晚,当它推出时,很多项目早就转为了其他数据库连接池实现了。而 druid 是阿里开源的一个数据库连接池实现,听说性能和 HikariCP 不相上下,不过因为 druid 很多功能是为了方便运维而开发的,所以目前使用比较普遍的数据库连接池实现是 HikariCP。
注意,DataSource 实现类并不都是带有数据库连接池功能的,请不要混淆了。Spring JDBC 框架里面也有一个 DriverManagerDataSource 实现,不带有数据库连接池功能,它只是 DriverManager 的简单封装而已。
最后说下 JNDI,JNDI 全称为 Java Naming and Directory Interface,中文为“Java 命名和目录接口”。千万不要以为这是个什么很高级很复杂的协议/接口,你完全可以将它看作是 Spring IoC 容器的“JDK 官方版本”,本质上,它们都是一个“容器”,都是存储对象的容器,而且都支持在 xml 配置文件中配置“对象”,然后在程序中可以使用对应的 name 来获取对应的对象,仅此而已。常见的 Tomcat 容器就支持 JNDI 功能。
package com.zfl9;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DriverManagerMain {
public static void main(String[] args) {
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
System.exit(1);
}
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("select id, name, site from test0");
System.out.printf("id\tname\tsite\n");
while (resultSet.next()) {
System.out.printf("%d\t%s\t\t%s\n",
resultSet.getInt(1),
resultSet.getString(2),
resultSet.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
package com.zfl9;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.cj.jdbc.MysqlDataSource;
public class DataSourceMain {
public static void main(String[] args) {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("localhost");
dataSource.setDatabaseName("test");
try { dataSource.setServerTimezone("UTC"); } catch (SQLException e) { e.printStackTrace(); }
dataSource.setUser("root");
dataSource.setPassword("123456");
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
System.exit(1);
}
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("select id, name, site from test0");
System.out.printf("id\tname\tsite\n");
while (resultSet.next()) {
System.out.printf("%d\t%s\t\t%s\n",
resultSet.getInt(1),
resultSet.getString(2),
resultSet.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
package com.zfl9;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
public class SpringJdbcDataSourceMain {
public static void main(String[] args) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/test?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
System.exit(1);
}
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("select id, name, site from test0");
System.out.printf("id\tname\tsite\n");
while (resultSet.next()) {
System.out.printf("%d\t%s\t\t%s\n",
resultSet.getInt(1),
resultSet.getString(2),
resultSet.getString(3));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
其实通过上面三个 MySQL 的例子,可以知道,使用数据库厂商提供的 DataSource 实现的耦合度其实并没有降低,反而有点增高了,而使用开源实现的通用 DataSource 实现(上面的 DriverManagerDataSource 是 spring-jdbc 模块自带的一个简单 DriverManager 封装实现)的耦合度其实是最低的,而且通常这些 DataSource 实现都有连接池功能,建议大家使用开源的 DataSource 实现,如 DBCP、HikariCP、druid。注意,Spring-JDBC 模块里面的 DriverManager 仅仅是 DriverManager 接口的封装而已,并没有实现数据库连接池,每次调用 getConnection() 方法获取的数据库连接也都是新创建的。