JDBC(Java Database Connectivity)代表Java编程语言与数据库连接的标准API,然而JDBC只是接口,JDBC驱动才是真正的接口实现,没有驱动无法完成数据库连接. 每个数据库厂商都有自己的驱动,用来连接自己公司的数据库(如Oricle, MySQL, DB2, MS SQLServer).
下面我们以MySQL为例,JDBC编程大致步骤如下:
/**
* @author jifang
* @since 16/2/18 上午9:02.
*/
public class SQLClient {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/* 加载数据库驱动 */
Class.forName("com.mysql.jdbc.Driver");
/* 通过 DriverManager 获取数据库连接 */
Connection connection = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");
/* 通过 Connection 创建 Statement */
Statement statement = connection.createStatement();
/* 通过 Statement 执行SQL */
ResultSet users = statement.executeQuery("SELECT * FROM user");
/* 操作 ResultSet 结果集 */
int columnCount = users.getMetaData().getColumnCount();
while (users.next()) {
for (int i = 1; i <= columnCount; ++i) {
System.out.printf("%s\t", users.getObject(i));
}
System.out.println();
}
/* 回收数据库资源(推荐使用Java1.7提供的 可以自动关闭资源的try) */
users.close();
statement.close();
connection.close();
}
}
注意: 需要在pom.xml中添加如下MySQL驱动:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
注: ResultSet参数columnIndex索引从1开始,而不是0!
ConnectionManger
DriverManger
JDBC规定: 驱动类在被加载时,需要主动把自己注册到DriverManger中:
com.mysql.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
代码显示:只要去加载com.mysql.jdbc.Driver类那么就会执行static块, 从而把com.mysql.jdbc.Driver注册到DriverManager中.
java.sql.DriverManager是用于管理JDBC驱动的服务类,其主要功能是获取Connection对象:
1. static Connection getConnection(String url, Properties info)
2. static Connection getConnection(String url, String user, String password)
另: 还可以在获取Connection的URL中设置参数,如: jdbc:mysql://host:port/database?useUnicode=true&characterEncoding=UTF8
useUnicode=true&characterEncoding=UTF8指定连接数据库的过程中使用Unicode字符集/UTF-8编码;
Connection
java.sql.Connection代表数据库连接,每个Connection代表一个物理连接会话, 该接口提供如下创建Statement的方法, 只有获取Statement之后才可执行SQL语句:
Creates a Statement object for sending SQL statements to the database.
其中Connection还提供了如下控制事务/保存点的方法:
Creates a savepoint with the given name in the current transaction and returns the new Savepoint object that represents it.
以上方法还存在不同的重载形式, 详细可参考JDK文档.
ConnectionManger
由于获取Connection的步骤单一,每次可能只是加载的参数不同,因此我们可以将获取Connection的操作封装成一个方法,并使其从配置文件中加载配置:
配置文件形式
## Data Source
mysql.driver.class=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://host:port/database
mysql.user=admin
mysql.password=admin
ConnectionManger
/**
* @author jifang
* @since 16/2/19 上午10:40.
*/
public class ConnectionManger {
/*获取原生Connection*/
public static Connection getConnection(String file) {
Properties config = SQLUtil.loadConfig(file);
try {
Class.forName(config.getProperty("mysql.driver.class"));
String url = config.getProperty("mysql.url");
String username = config.getProperty("mysql.user");
String password = config.getProperty("mysql.password");
return DriverManager.getConnection(url, username, password);
} catch (SQLException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
SQLUtil
/**
* @author jifang
* @since 16/2/18 上午8:24.
*/
public class SQLUtil {
/**
* 加载.properties配置文件
*
* @param file
* @return
*/
public static Properties loadConfig(String file) {
Properties properties = new Properties();
try {
properties.load(ClassLoader.getSystemResourceAsStream(file));
return properties;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
数据库连接池
前面通过DriverManger获得Connection, 一个Connection对应一个实际的物理连接,每次操作都需要打开物理连接, 使用完后立即关闭;这样频繁的打开/关闭连接会造成不必要的数据库系统性能消耗.
数据库连接池提供的解决方案是:当应用启动时,主动建立足够的数据库连接,并将这些连接组织成连接池,每次请求连接时,无须重新打开连接,而是从池中取出已有连接,使用完后并不实际关闭连接,而是归还给池.
JDBC数据库连接池使用javax.sql.DataSource表示, DataSource只是一个接口, 其实现通常由服务器提供商(如WebLogic, WebShere)或开源组织(如DBCP,C3P0和HikariCP)提供.
数据库连接池的常用参数如下:
数据库初始连接数;
连接池最大连接数;
连接池最小连接数;
连接池每次增加的容量;
C3P0
Tomcat默认使用的是DBCP连接池,但相比之下,C3P0则比DBCP更胜一筹(hibernate推荐使用C3P0),C3P0不仅可以自动清理不再使用的Connection, 还可以自动清理Statement/ResultSet, 使用C3P0需要在pom.xml中添加如下依赖:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.11</version>
</dependency>
ConnectionManger
public class ConnectionManger {
/*双重检测锁保证DataSource单例*/
private static DataSource dataSource;
/*获取DataSource*/
public static DataSource getDataSourceC3P0(String file) {
if (dataSource == null) {
synchronized (ConnectionManger.class) {
if (dataSource == null) {
Properties config = SQLUtil.loadConfig(file);
try {
ComboPooledDataSource source = new ComboPooledDataSource();
source.setDriverClass(config.getProperty("mysql.driver.class"));
source.setJdbcUrl(config.getProperty("mysql.url"));
source.setUser(config.getProperty("mysql.user"));
source.setPassword(config.getProperty("mysql.password"));
// 设置连接池最大连接数
source.setMaxPoolSize(Integer.valueOf(config.getProperty("pool.max.size")));
// 设置连接池最小连接数
source.setMinPoolSize(Integer.valueOf(config.getProperty("pool.min.size")));
// 设置连接池初始连接数
source.setInitialPoolSize(Integer.valueOf(config.getProperty("pool.init.size")));
// 设置连接每次增量
source.setAcquireIncrement(Integer.valueOf(config.getProperty("pool.acquire.increment")));
// 设置连接池的缓存Statement的最大数
source.setMaxStatements(Integer.valueOf(config.getProperty("pool.max.statements")));
// 设置最大空闲时间
source.setMaxIdleTime(Integer.valueOf(config.getProperty("pool.max.idle_time")));
dataSource = source;
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
}
return dataSource;
}
/*获取Connection*/
public static Connection getConnectionC3P0(String file) {
return getConnection(getDataSourceC3P0(file));
}
public static Connection getConnection(DataSource dataSource) {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// ...
}
C3P0还可以使用配置文件来初始化连接池(配置文件可以是properties/XML, 在此仅介绍XML),C3P0配置文件名必须为c3p0-config.xml,其放在类路径下:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://host:port/database</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">user</property>
<property name="password">password</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">3</property>
<property name="maxPoolSize">20</property>
</default-config>
<named-config name="mysql-config">
<property name="jdbcUrl">jdbc:mysql://host:port/common</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">user</property>
<property name="password">password</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">3</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
这样, 我们在创建ComboPooledDataSource时就默认加载配置文件中的配置, 无须手动配置:
public static DataSource getDataSourceC3P0(String file) {
if (dataSource == null) {
synchronized (ConnectionManger.class) {
if (dataSource == null) {
dataSource = new ComboPooledDataSource();
}
}
}
return dataSource;
}
C3P0配置文件可以配置多个连接信息, 并为每个配置命名, 这样可以方便的通过配置名称来切换配置信息:
public static DataSource getDataSourceC3P0(String file) {
if (dataSource == null) {
synchronized (ConnectionManger.class) {
if (dataSource == null) {
dataSource = new ComboPooledDataSource("mysql-config");
}
}
}
return dataSource;
}