[关闭]
@Otokaze 2018-12-05T14:51:42.000000Z 字数 6699 阅读 777

DataSource 介绍

Java

DriverManager vs DataSource

使用 DriverManager 获取数据库连接:

  1. Connection connection = DriverManager.getConnection(url, user, pass)

使用 DataSource 获取数据库连接:

  1. DataSource dataSource = new DataSourceImpl();
  2. // set some property for dataSource instance ...
  3. Connection connection = dataSource.getConnection();

我们知道 JDBC 的核心原理是 java.sql.Driver 驱动接口和各个数据库厂商提供的 java.sql.Driver 接口实现类,比如 MySQL 实现类为 com.mysql.jdbc.Driver。通过 DriverManager 获取数据库连接的一般步骤为:

  1. Class.forName("com.mysql.jdbc.Driver"),注册数据库厂商的 JDBC 驱动(static 初始化块)。
  2. 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 接口的定义:

  1. Connection getConnection() throws SQLException
  2. 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 功能。

MySQL DriverManager 例子

  1. package com.zfl9;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. import java.sql.Statement;
  7. public class DriverManagerMain {
  8. public static void main(String[] args) {
  9. Connection connection = null;
  10. try {
  11. connection = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "123456");
  12. } catch (SQLException e) {
  13. e.printStackTrace();
  14. System.exit(1);
  15. }
  16. Statement statement = null;
  17. ResultSet resultSet = null;
  18. try {
  19. statement = connection.createStatement();
  20. resultSet = statement.executeQuery("select id, name, site from test0");
  21. System.out.printf("id\tname\tsite\n");
  22. while (resultSet.next()) {
  23. System.out.printf("%d\t%s\t\t%s\n",
  24. resultSet.getInt(1),
  25. resultSet.getString(2),
  26. resultSet.getString(3));
  27. }
  28. } catch (SQLException e) {
  29. e.printStackTrace();
  30. } finally {
  31. if (resultSet != null) {
  32. try {
  33. resultSet.close();
  34. } catch (SQLException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. if (statement != null) {
  39. try {
  40. statement.close();
  41. } catch (SQLException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. if (connection != null) {
  46. try {
  47. connection.close();
  48. } catch (SQLException e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. }
  53. }
  54. }

MySQL DataSource 例子

  1. package com.zfl9;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. import com.mysql.cj.jdbc.MysqlDataSource;
  7. public class DataSourceMain {
  8. public static void main(String[] args) {
  9. MysqlDataSource dataSource = new MysqlDataSource();
  10. dataSource.setServerName("localhost");
  11. dataSource.setDatabaseName("test");
  12. try { dataSource.setServerTimezone("UTC"); } catch (SQLException e) { e.printStackTrace(); }
  13. dataSource.setUser("root");
  14. dataSource.setPassword("123456");
  15. Connection connection = null;
  16. try {
  17. connection = dataSource.getConnection();
  18. } catch (SQLException e) {
  19. e.printStackTrace();
  20. System.exit(1);
  21. }
  22. Statement statement = null;
  23. ResultSet resultSet = null;
  24. try {
  25. statement = connection.createStatement();
  26. resultSet = statement.executeQuery("select id, name, site from test0");
  27. System.out.printf("id\tname\tsite\n");
  28. while (resultSet.next()) {
  29. System.out.printf("%d\t%s\t\t%s\n",
  30. resultSet.getInt(1),
  31. resultSet.getString(2),
  32. resultSet.getString(3));
  33. }
  34. } catch (SQLException e) {
  35. e.printStackTrace();
  36. } finally {
  37. if (resultSet != null) {
  38. try {
  39. resultSet.close();
  40. } catch (SQLException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. if (statement != null) {
  45. try {
  46. statement.close();
  47. } catch (SQLException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. if (connection != null) {
  52. try {
  53. connection.close();
  54. } catch (SQLException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }
  60. }

MySQL Spring-JDBC-DataSource 例子

  1. package com.zfl9;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. import org.springframework.jdbc.datasource.DriverManagerDataSource;
  7. public class SpringJdbcDataSourceMain {
  8. public static void main(String[] args) {
  9. DriverManagerDataSource dataSource = new DriverManagerDataSource();
  10. dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
  11. dataSource.setUrl("jdbc:mysql://localhost/test?serverTimezone=UTC");
  12. dataSource.setUsername("root");
  13. dataSource.setPassword("123456");
  14. Connection connection = null;
  15. try {
  16. connection = dataSource.getConnection();
  17. } catch (SQLException e) {
  18. e.printStackTrace();
  19. System.exit(1);
  20. }
  21. Statement statement = null;
  22. ResultSet resultSet = null;
  23. try {
  24. statement = connection.createStatement();
  25. resultSet = statement.executeQuery("select id, name, site from test0");
  26. System.out.printf("id\tname\tsite\n");
  27. while (resultSet.next()) {
  28. System.out.printf("%d\t%s\t\t%s\n",
  29. resultSet.getInt(1),
  30. resultSet.getString(2),
  31. resultSet.getString(3));
  32. }
  33. } catch (SQLException e) {
  34. e.printStackTrace();
  35. } finally {
  36. if (resultSet != null) {
  37. try {
  38. resultSet.close();
  39. } catch (SQLException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. if (statement != null) {
  44. try {
  45. statement.close();
  46. } catch (SQLException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. if (connection != null) {
  51. try {
  52. connection.close();
  53. } catch (SQLException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. }
  58. }
  59. }

DataSource 总结

其实通过上面三个 MySQL 的例子,可以知道,使用数据库厂商提供的 DataSource 实现的耦合度其实并没有降低,反而有点增高了,而使用开源实现的通用 DataSource 实现(上面的 DriverManagerDataSource 是 spring-jdbc 模块自带的一个简单 DriverManager 封装实现)的耦合度其实是最低的,而且通常这些 DataSource 实现都有连接池功能,建议大家使用开源的 DataSource 实现,如 DBCP、HikariCP、druid。注意,Spring-JDBC 模块里面的 DriverManager 仅仅是 DriverManager 接口的封装而已,并没有实现数据库连接池,每次调用 getConnection() 方法获取的数据库连接也都是新创建的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注