一、简介

Java DataSource接口位于javax.sql包中,它只声明了两个重载方法:getConnection()getConnection(String username, String password)

DataSource对象表示特定的DBMS或其他数据源(例如文件),使用DataSource可以获取松耦合的连接,方便切换数据库;除此之外,DataSource还可以提供连接池和分布式事务。

DataSource通常由驱动程序供应商实现,可以通过三种方式:

  • 产生标准Connection对象的DataSource实现(不支持连接池和分布式事务)。
  • 支持连接池的DataSource的实现,产生可回收的Connection对象。
  • 支持分布式事务的DataSource的实现,产生可以在分布式事务中使用的Connection对象(在一个事务中访问两个或多个DBMS服务器);支持分布式事务的DataSource通常也实现了对连接池的支持。

不同的数据库供应商提供了不同的DataSource实现,例如:MySQL使用com.mysql.jdbc.jdbc2.optional.MysqlDataSource提供DataSource接口的基本实现;Oracle使用oracle.jdbc.pool.OracleDataSource提供DataSource接口的基本实现。除了获取数据库连接外,这些JDBC数据源实现类还提供了其他一些常见功能,例如:

  • 缓存PreparedStatement以加快处理速度
  • 连接超时设置
  • 日志记录
  • ResultSet最大阈值设置

二、JDBC数据源

数据库表:

create table employee(
	id int primary key auto_increment,
	age int,
	firstname varchar(30),
	lastname varchar(30)
)
id age firstname lastname
2 36 Michael Stonebridge
3 33 Damien Scott
4 38 Rachel Dalton

数据库配置(使用properties配置方式提供松耦合的数据库配置):

#MySQL
MYSQL_DB_DRIVERCLASS=com.mysql.jdbc.Driver
MYSQL_DB_URL=jdbc:mysql://localhost:3306/test
MYSQL_DB_USERNAME=root
MYSQL_DB_PASSWORD=123456

#Oracle
ORACLE_DB_DRIVERCLASS=oracle.jdbc.driver.OracleDriver
ORACLE_DB_URL=jdbc:oracle:thin:@localhost:1521:orcl
ORACLE_DB_USERNAME=admin
ORACLE_DB_PASSWORD=123456

还需要在工程中引入对应数据库的驱动jar。

1、样例一

数据源工厂MyDataSourceFactory:

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import oracle.jdbc.pool.OracleDataSource;

public class MyDataSourceFactory {

	public static DataSource getDataSource(String type){
		DataSource ds = null;
		if("MySQL".equals(type)){
			ds = getMySQLDataSource();
		}else if("Oracle".equals(type)){
			ds = getOracleDataSource();
		}else{
			System.out.println("invalid db type");
		}
		return ds;
	}
	
	private static DataSource getMySQLDataSource(){
		MysqlDataSource dataSource = new MysqlDataSource();
		try {
			Properties props = new Properties();
			props.load(new FileInputStream("src/db.properties"));
			dataSource.setURL(props.getProperty("MYSQL_DB_URL"));
			dataSource.setUser(props.getProperty("MYSQL_DB_USERNAME"));
			dataSource.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
		}catch (IOException e) {
			e.printStackTrace();
		}
		return dataSource;
	}
	
	private static DataSource getOracleDataSource(){
		OracleDataSource dataSource = null;
		try {
			dataSource = new OracleDataSource();
			Properties props = new Properties();
			props.load(new FileInputStream("db.properties"));
			dataSource.setURL(props.getProperty("ORACLE_DB_URL"));
			dataSource.setUser(props.getProperty("ORACLE_DB_USERNAME"));
			dataSource.setPassword(props.getProperty("ORACLE_DB_PASSWORD"));
		}catch (IOException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return dataSource;
	}
}

测试类:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

public class DataSourceTest {

	public static void main(String[] args) {
		DataSource ds = MyDataSourceFactory.getDataSource("MySQL");
		try(Connection connection = ds.getConnection()){
			try(Statement stmt = connection.createStatement()){
				String sql = "select id,age,firstname,lastname from employee";
				try(ResultSet rs = stmt.executeQuery(sql)){
					while(rs.next()){
						String format = "ID: %s, Age: %s, FirstName: %s, LastName: %s.";
						System.out.println(String.format(format, rs.getInt("id"), rs.getInt("age"), rs.getString("firstname"), rs.getString("lastname")));
					}
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
}

样例中的测试类完全独立于任何特定的数据库类,这样可以隐藏底层细节,实现松耦合。

2、样例二

样例一中的数据源工厂类存在以下问题:

  • 用于创建MySQL和Oracle数据源的工厂类与各数据库的驱动程序API紧耦合;
  • 获取MySQL和Oracle数据源的大部分代码是相似的,唯一不同的是使用的实现类。

此样例中使用Apache Commons DBCP API提供的Java DataSource实现来处理这些问题(Apache DBCP库依赖于Commons Pool库,需要引入相关的jar包)。

数据库配置文件:

mysql.properties

#MySQL
DB_DRIVERCLASS=com.mysql.jdbc.Driver
DB_URL=jdbc:mysql://localhost:3306/test
DB_USERNAME=root
DB_PASSWORD=123456

oracle.properties

#Oracle
DB_DRIVERCLASS=oracle.jdbc.driver.OracleDriver
DB_URL=jdbc:oracle:thin:@localhost:1521:orcl
DB_USERNAME=admin
DB_PASSWORD=123456

数据源工厂DBCPDataSourceFactory:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;

public class DBCPDataSourceFactory {

	public static DataSource getDataSource(String type){
		BasicDataSource dataSource = new BasicDataSource();
		String filename = null;
		if("MySQL".equals(type)){
			filename = "mysql.properties";
		}else if("Oracle".equals(type)){
			filename = "oracle.properties";
		}else{
			System.out.println("invalid db type");
		}
		if(filename != null){
			try {
				Properties props = new Properties();
				props.load(new FileInputStream(filename));
				dataSource.setDriverClassName(props.getProperty("DB_DRIVERCLASS"));
				dataSource.setUrl(props.getProperty("DB_URL"));
				dataSource.setUsername(props.getProperty("DB_USERNAME"));
				dataSource.setPassword(props.getProperty("DB_PASSWORD"));
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		return dataSource;
	}
}

测试类只需要替换获取DataSource的代码:

DataSource ds = DBCPDataSourceFactory.getDataSource("MySQL");

从上面的例子中可以看到Apache DBCP提供抽象的关键点是setDriverClassName()方法。

三、Tomcat容器中的数据源

DataSource的主要好处是当它在上下文中使用并与JNDI一起使用时,可以通过简单的配置创建一个由容器本身维护的数据库连接池。大多数的Servlet容器(例如Tomcat和JBoss)都提供了自己的Java DataSource实现,我们所需要的只是通过简单的XML配置并使用JNDI上下文查找来获取Java DataSource。

Tomcat提供了三种在JNDI上下文中配置DataSource的方法:

  • 应用程序配置context.xml

    这是配置DataSource的最简单方法,只需要在META-INF目录下的context.xml文件中配置Resource,容器将负责加载。这种方式有以下缺点:

    • context.xml与war包捆绑在一起,修改时需要重新构建部署新的war包。
    • 数据源仅供应用程序使用,不能全局使用。
    • 如果(在server.xml中)定义了同名的全局数据源,则会忽略该应用程序数据源。
  • 服务器配置context.xml

如果服务器中有多个应用程序,并且希望在它们之间共享DataSource,那么可以在服务器的context.xml文件中配置数据源(此文件位于Tomcat的conf目录下)。

由于服务器context.xml文件的作用域是应用程序,将为每个应用程序创建数据源,因此,如果定义了一个包含100个连接的DataSource连接池,而且服务器有20个应用程序,这将会有2000个连接,这样显然会消耗数据库服务器资源并降低应用程序性能。

  • 配置server.xmlcontext.xml

可以通过server.xml中的GlobalNamingResources元素中定义全局DataSource,使服务器上运行的多个应用程序之间共享公共资源池。

server.xml中定义了一个名为jdbc/MyDB的数据源,供Web应用通过JNDI查找使用:

<GlobalNamingResources>
    <Resource name="jdbc/MyDB" 
      global="jdbc/MyDB" 
      auth="Container" 
      type="javax.sql.DataSource" 
      driverClassName="com.mysql.jdbc.Driver" 
      url="jdbc:mysql://localhost:3306/test" 
      username="root" 
      password="123456" 
      
      maxActive="100" 
      maxIdle="20" 
      minIdle="5" 
      maxWait="10000"/>
</GlobalNamingResources>

Web应用若需访问全局资源,需要在context.xml中引用:

<Context>
    <ResourceLink name="jdbc/MyLocalDB"
                global="jdbc/MyDB"
                auth="Container"
                type="javax.sql.DataSource" />
</Context>

使用示例:

Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/MyLocalDB");
Connection con = ds.getConnection();
参考资料: