一、 连接池概述
概念
在Java开发中,使用JDBC操作数据库的四个步骤如下:
①加载数据库驱动程序(Class.forName("数据库驱动类");)
②连接数据库
(Connection con = DriverManager.getConnection();)
③操作数据库
(PreparedStatement stat = con.prepareStatement(sql);stat.executeQuery();)
④关闭数据库,释放连接(con.close();)
也就是说,所有的用户都需要经过此四步进行操作,但是这四步之中有三步(①加载数据库驱动程序、②连接数据库、④关闭数据库,释放连接)对所有人都是一样的,而所有人只有在操作数据库上是不一样,那么这就造成了性能的损耗。
那么最好的做法是,准备出一个空间,此空间里专门保存着全部的数据库连接,以后用户用数据库操作的时候不用再重新加载驱动、连接数据库之类的,而直接从此空间中取走连接,关闭的时候直接把连接放回到此空间之中。那么此空间就可以称为连接池(保存所有的数据库连接。
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用;简单理解为,当一辆汽车搬运东西,如果使用jdbc连接,(jdbc连接:与数据库建立连接、发送操作数据库的语句并处理结果)那么每一次都要去打开数据库,获得连接,关闭数据库。假设汽车搬运的东西是连接,那么我可不可以每一次将连接搬运多个呢?而不是jdbc那样,一次只搬运一个连接,然后就把汽车扔掉?这时候,使用连接池。
原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
规范
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。
常见的连接池:DBCP、C3P0。
二、C3P0连接池的使用
1. 导入jar包
MySQL:
mysql-connector-java-5.1.30-bin.jar
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jarOracle:
c3p0-0.9.1.2.jar
c3p0-oracle-thin-extras-0.9.1.2.jar
mchange-commons-java-0.2.11.jar
2. 配置c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 第一种配置方法 获取方法DataSource ds = new ComboPooledDataSource(); <default-config>
</default-config> 第二种配置方法可以指定数据库 获取方法 DataSource ds = new ComboPooledDataSource("mysql");
<named-config name="mysql"></named-config> -->
<named-config name="mysql">
<!--JDBC驱动 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!--数据库地址 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/scott?useUnicode=true&characterEncoding=utf8
</property>
<!--用户名。Default: null -->
<property name="user">root</property>
<!--密码。Default: null -->
<property name="password">123456</property>
<!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize">3</property>
<!--最大空闲时间,30秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">30</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">50</property>
<!--连接池中保留的最少连接数。Default: 15 -->
<property name="minPoolSize">10</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
<property name="maxStatements">200</property>
</named-config>
<named-config name="oracle">
<!--JDBC驱动 -->
<property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
<!--数据库地址 -->
<property name="jdbcUrl">jdbc:oracle:thin:@localhost:1521:orcl
</property>
<!--用户名。Default: null -->
<property name="user">scott</property>
<!--密码。Default: null -->
<property name="password">123456</property>
<!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize">3</property>
<!--最大空闲时间,30秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">30</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">50</property>
<!--连接池中保留的最少连接数。Default: 15 -->
<property name="minPoolSize">10</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
<property name="maxStatements">200</property>
</named-config>
</c3p0-config>
3. 简单使用
public class Test {
static ComboPooledDataSource cpds = null;
static {
// 这里有个优点,写好配置文件,想换数据库,简单
// cpds = new ComboPooledDataSource("oracle");//这是oracle数据库
cpds = new ComboPooledDataSource("mysql");// 这是mysql数据库
}
/**
* 获得数据库连接
*
* @return Connection
*/
public static Connection getConnection() {
try {
return cpds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
/**
* 数据库关闭操作
*
* @param conn
* @param st
* @param pst
* @param rs
*/
public static void close(Connection conn, PreparedStatement pst,
ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pst != null) {
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
Connection conn = getConnection();
System.out.println(conn.getClass().getName());
Statement stmt;
ResultSet rs = null;
try {
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from emp");
while (rs.next()) {
System.out.println(rs.getInt(1));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(conn, null, null);
}
}
注意:使用C3P0之后调用conn.close()方法并没有关闭连接。而是把链接放到线程池里面去了.
4.C3P0的自定义DBUtils
/**
* 数据库连接工具
*
* @author Administrator
*
*/
public class C3P0DBUtils {
// 饿汉式
private static DataSource ds = new ComboPooledDataSource("mysql");
/**
* 它为null表示没有事务 它不为null表示有事务 当开启事务时,需要给它赋值 当结束事务时,需要给它赋值为null
* 并且在开启事务时,让dao的多个方法共享这个Connection
*/
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public static DataSource getDataSource() {
return ds;
}
/**
* dao使用本方法来获取连接
*
* @return
* @throws SQLException
*/
public static Connection getConnection() {
/*
* 如果有事务,返回当前事务的con 如果没有事务,通过连接池返回新的con
*/
Connection con = tl.get();// 获取当前线程的事务连接
if (con != null)
return con;
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 开启事务
*
* @throws SQLException
*/
public static void beginTransaction() {
Connection con = tl.get();// 获取当前线程的事务连接
if (con != null) {
System.out.println("已经开启了事务,不能重复开启!");
}
try {
con = ds.getConnection();
con.setAutoCommit(false);// 设置为手动提交
} catch (SQLException e) {
e.printStackTrace();
}
// 给con赋值,表示开启了事务
tl.set(con);// 把当前事务连接放到tl中
}
/**
* 提交事务
*
* @throws SQLException
*/
public static void commitTransaction(){
Connection con = tl.get();// 获取当前线程的事务连接
if (con == null){
System.out.println("没有事务不能提交!");
}
try {
con.commit();// 提交事务
con.close();// 关闭连接
} catch (SQLException e) {
e.printStackTrace();
}
con = null;// 表示事务结束!
tl.remove();
}
/**
* 回滚事务
*
* @throws SQLException
*/
public static void rollbackTransaction() {
Connection con = tl.get();// 获取当前线程的事务连接
if (con == null){
System.out.println("没有事务不能回滚!");
}
try {
con.rollback();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
tl.remove();
}
/**
* 释放Connection
*
* @param con
* @throws SQLException
*/
public static void releaseConnection(Connection connection)
throws SQLException {
Connection con = tl.get();// 获取当前线程的事务连接
if (connection != con) {
// 如果参数连接,与当前事务连接不同,说明这个连接不是当前事务,可以关闭!
// 如果参数连接没有关闭,关闭之!
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}
/**
* 封裝得到數據庫發送對象的方法
*/
public static PreparedStatement preparedStatement(Connection conn,
String sql, Object[] obj) {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
if (null != obj && obj.length > 0) {
for (int i = 0; i < obj.length; i++) {
pstmt.setObject(i + 1, obj[i]);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return pstmt;
}
/**
* 執行SQL的方法 添加 删除 修改
*/
public static void executeUpdate(PreparedStatement pstmt) {
try {
int x = pstmt.executeUpdate();
if (x > 0) {
System.out.println("操作数据库成功");
} else {
System.out.println("操作数据库失败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询的方法
*/
public static ResultSet executeQuery(PreparedStatement pstmt) {
ResultSet rs = null;
try {
rs = pstmt.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
return rs;
}
/**
* 关闭的方法
*/
public static void close(AutoCloseable closeable) {
try {
if (null != closeable) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭的方法
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
close(rs);
close(stmt);
close(conn);
}
/**
* 关闭的方法
*/
public static void close(Statement stmt, Connection conn) {
close(stmt);
close(conn);
}
}
三、DBCP连接池的使用
DBCP也是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池。
1.导包
commons-dbcp-1.2.1.jar
commons-pool-1.3.jar
对应的数据库驱动包
2.配置dbcp-config.properties
#配置文件
dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=utf8;
username=root
password=root
#初始资源
initialSize=10
#活动资源
maxActive=50
#最大空闲资源
maxIdle=20
#最小空闲资源
minIdle=5
#资源没有的时候会让用户等待,等待一段时间后,它会填充资源;当空闲资源小于最小空闲资源时,它会往里添加资源并判断是否小于最大空闲资源
maxWait=60000
#编码字符集
characterEncoding=UTF-8
#自动提交
defaultAutoCommit=true
3.简单使用
public class DbcpJdbcUtil {
private static DataSource ds=null;
static{
try{
InputStream in=DbcpJdbcUtil.class.getResourceAsStream("dbcp.properties");
Properties prop=new Properties();
prop.load(in);//以上与1同
ds=BasicDataSourceFactory.createDataSource(prop);
//工厂,创建Source
}catch(Exception e){
e.printStackTrac e();
}
}
public static Connection getConnection() throws SQLException{
return ds.getConnection();
}
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
rs.close();
}catch(Exception e){
e.printStackTrace();
}
rs=null;
}
if(st!=null){
try{
st.close();
}catch(Exception e){
e.printStackTrace();
}
st=null;
}
if(conn!=null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
conn=null;
}
}
//查询功能,将结果集中第一条记录封装到一个指定的javaBean中。
public static void main(String[] args){
try{
QueryRunner qr = new QueryRunner(ds);//此处需传入一个datasource对象
String sql="SELECT * FROM user";
Object[] params = {};
User u = qr.query(sql, new BeanHandler<User>(User.class), params);
//结果集处理
System.out.println(u);
}catch(SQLException e){
throw new RuntimeException(e);
}
}
}
输出:四、自定义连接池
【连接池的原理】
在javax.sql.DataSource接口--连接池的接口
- 功能:初始化多个连接.把多个连接放入到内存中.
- 归还:将连接对象放回到内存中.
public class MyDataSource implements DataSource{
// 创建一个集合List集合.
List<Connection> list = new ArrayList<Connection>();
// 初始化连接池的时候,初始化5个连接.
public MyDataSource() {
for (int i = 1; i <= 5; i++) {
Connection conn = JDBCUtils.getConnection();
list.add(conn);
}
}
@Override
// 从连接池中获得连接
public Connection getConnection() throws SQLException {
if(list.size()==0){
for (int i = 1; i <= 3; i++) {
Connection conn = JDBCUtils.getConnection();
list.add(conn);
}
}
Connection conn = list.remove(0);
return conn;
}
// 归还连接:
public void addBack(Connection conn){
list.add(conn);
}
...
}