为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
(1)资源重用:由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
(2)更快的系统反应速度:数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
(3)新的资源分配手段对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置实现某一应用最大可用数据库连接数的限制避免某一应用独占所有的数据库资源.
(4)统一的连接管理,避免数据库连接泄露在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
Properties ps = new Properties(); ps.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"));
所谓数据源也就是数据的来源。它存储了所有建立数据库连接需要的信息。算是对数据库的一个抽象映射,即一个数据源对于一个数据库。
数据源有以下属性
DBCP(DataBase Connection Pool)是Java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开。Connection的创建和销毁非常的消耗资源
DBCP通过
BasicDataSourceFactory.createDataSource(properties)
获取数据源
除去本身的commons-dbcp2-2.6.0.jar(下载的最新)
另外还需要除此之外还需要comms-logging包和commons-pool2包。附上链接http://commons.apache.org/proper/
记得首先到如连接数据库的jar包链接地址:https://dev.mysql.com/downloads/connector/j/
DBCP现在有四个不同的版本,以支持不同版本的JDBC。下面是它的工作原理:
DBCP 2.6.0仅在Java 8下编译和运行(JDBC 4.2)
DBCP 2.5.0仅在Java 8下编译和运行(JDBC 4.2)
DBCP 2.4.0仅在Java 7下编译和运行(JDBC 4.1)
DBCP 1.4仅在Java 6下编译和运行(JDBC 4)
DBCP 1.3仅在Java 1.4-5.0下编译和运行(JDBC 3)
通常都是dbcp.properties进行命名,将其放在src下面。
下面给出大致的(根据自己的需要进行补充)dbcp.properties代码:
########DBCP配置文件##########
#驱动名
driverClassName=com.mysql.jdbc.Driver
#url
url=jdbc:mysql:///mydb
#用户名
username=root
#密码
password=root
#初试连接数
initialSize=30
#最大活跃数
maxTotal=30
#最大idle数
maxIdle=10
#最小idle数
minIdle=5
#最长等待时间(毫秒)
maxWaitMillis=1000
#程序中的连接不使用后是否被连接池回收(该版本要使用removeAbandonedOnMaintenance和removeAbandonedOnBorrow)
#removeAbandoned=true
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
#连接在所指定的秒数内未使用才会被删除(秒)(为配合测试程序才配置为1秒)
removeAbandonedTimeout=1
将DBCP抽取成一个工具类命名为
DBCPUtils.java:
public class DBCPUtils {
//声明数据源
private static DataSource ds;
//静态代码块首先加载,对数据库进行连接
static {
try {
//加载配置文件
Properties ps = new Properties();
ps.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"));
//给数据源进行赋值
ds = BasicDataSourceFactory.createDataSource(ps);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//获取数据源对象
public static DataSource getDataSource() throws SQLException {
return ds;
}
//关闭资源
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Statement stmt, Connection conn) {
close(null, stmt, conn);
}
}
编写相应的测试代码,进行简单的CRUD(运行效果就不展示了亲测有效):
public class Demo1 {
private static Connection conn;
private static PreparedStatement pstmt;
private static ResultSet rs;
@Test
//查询
public void test1() {
try {
conn = DBCPUtils.getConnection();
String sql = "select * from employee";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()){
System.out.print(rs.getInt("id"));
System.out.print(rs.getString("username"));
System.out.println(rs.getInt("salary"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBCPUtils.close(rs,pstmt,conn);
}
}
@Test
//插入
public void test2() {
try {
conn = DBCPUtils.getConnection();
String sql = "insert into employee (username , salary) values (?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "白骨精");
pstmt.setInt(2, 6000);
int rows = pstmt.executeUpdate();
if (rows > 0) {
System.out.println("插入成功!");
} else {
System.out.println("插入失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
DBCPUtils.close(pstmt,conn);
}
}
@Test
//更新
public void test3() {
try {
conn = DBCPUtils.getConnection();
String sql = "update employee set username=? where id=?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "豬八戒");
pstmt.setInt(2, 4);
int rows = pstmt.executeUpdate();
if (rows > 0) {
System.out.println("更新成功!");
} else {
System.out.println("更新失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
DBCPUtils.close(pstmt,conn);
}
}
@Test
//删除
public void test4() {
try {
conn = DBCPUtils.getConnection();
String sql = "delete from employee where id=?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 4);
int rows = pstmt.executeUpdate();
if (rows > 0) {
System.out.println("删除成功!");
} else {
System.out.println("删除失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
DBCPUtils.close(pstmt,conn);
}
}
}
疑问解答:
1.如何将使用完毕的Connection对象归还给数据库连接池?
首先肯定要对Conecntion的close()的方法进行增强,将真正的关闭改变为归还给数据库连接池,有三种方法:
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0中池类是:ComboPooledDataSource。
通过下面的方式获取数据源:
DataSource ds = new ComboPooledDataSource();
c3p0相应的jar包地址:https://sourceforge.net/projects/c3p0/
数据库的连接jar包地址:https://dev.mysql.com/downloads/connector/j/
c3p0-config.xml必须这样命名:c3p0连接池会自动的加载
c3p0-config.xml代码:
com.mysql.jdbc.Driver
jdbc:mysql:///mydb
root
root
5
10
3000
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/day25
root
root
5
8
1000
将C3P0抽取成一个工具类,命名为C3P0Utils.java:
功能:
1、获取数据库连接对象
2、获取数据源对象
3、关闭资源连接
C3P0Utils.java
public class C3P0Utils {
private static DataSource ds = new ComboPooledDataSource();
//1.获取数据库的连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//2.获取数据源
public static DataSource getDataSource() {
return ds;
}
//3.关闭资源
public static void release(Connection conn, Statement st, ResultSet rs) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void release(Connection conn, Statement st) {
release(conn, st, null);
}
}
编写测试方法进行CRUD操作:
public class C3P0Demo {
static Connection conn = null;
static PreparedStatement pstm = null;
static ResultSet rs = null;
@Test
//查询
public void test1() {
try {
conn = C3P0Utils.getConnection();
String sql = "select * from employee ";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while (rs.next()) {
System.out.print(rs.getInt("id"));
System.out.print(rs.getString("username"));
System.out.println(rs.getString("salary"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
C3P0Utils.release(conn, pstm, rs);
}
}
@Test
//更新
public void test2() {
try {
conn = C3P0Utils.getConnection();
String sql = "update employee set username = ? where id=?";
pstm = conn.prepareStatement(sql);
pstm.setString(1, "张三丰");
pstm.setInt(2, 4);
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("更新成功");
} else {
System.out.println("更新失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
C3P0Utils.release(conn, pstm);
}
}
@Test
//插入
public void test3() {
try {
conn = C3P0Utils.getConnection();
String sql = "insert into employee (username,salary) values (?,?)";
pstm = conn.prepareStatement(sql);
pstm.setString(1, "孙悟空");
pstm.setInt(2,9000);
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("插入成功");
} else {
System.out.println("插入失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
C3P0Utils.release(conn, pstm);
}
}
@Test
//刪除
public void test4() {
try {
conn = C3P0Utils.getConnection();
String sql = "delete from employee where id=?";
pstm = conn.prepareStatement(sql);
pstm.setInt(1,5);
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("刪除成功");
} else {
System.out.println("刪除失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
C3P0Utils.release(conn, pstm);
}
}
}
注意事项及重点总结:
1、获取C3P0数据库连接的方法有三种:
2、第一就自己在获取到DataSource对象之后手动进行设置(数据库的连接DriverClass、JdbcUrl、username、password数据库连接最大值等等)
3、通过properties配置文件,必须是c3p0.properties
4、通过properties配置文件,必须是c3p0-config.xml(推荐)
Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Druid是一个JDBC组件,它包括三部分:
Druid可以做什么?
可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
步骤:
//加载配置文件
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
//获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//获取连接
Connection conn = ds.getConnection();
相应的jar包,druid-1.0.9.jar,附链接地址:http://central.maven.org/maven2/com/alibaba/druid/
连接数据库的jar包链接地址mysql-connector-java-5.1.47-bin.jar:[https://dev.mysql.com/downloads/connector/j/]
建议的命名:druid.properties
#可以根据需要进行扩展配置
#数据源驱动类
driverClassName=com.mysql.jdbc.Driver
#基本属性 url、user、password
url=jdbc:mysql://127.0.0.1:3306/mydb
username=root
password=root
#配置初始化大小
initialSize=5
# 最大并发连接数
maxActive=10
#配置获取连接等待超时的时间
maxWait=3000
对Druid进行抽取Utils类,和C3P0、DBCP一样(都是基本的抽取方式):
DruidUtils .java
public class DruidUtils {
private static DataSource ds;
static {
try {
//加载配置文件
Properties ps = new Properties();
//参数传入配置文件druid.properties
ps.load(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(ps);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//获取数据源
public static DataSource getDataSource() {
return ds;
}
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Statement stmt,Connection conn){
close(null,stmt,conn);
}
}
编写测试代码进行简单的CRUD操作,感受Druid
//Druid的简单使用
public class DruidDemo {
private Connection conn = null;
private PreparedStatement pstm = null;
private ResultSet rs = null;
@Test
//查詢
public void tets1() {
try {
conn = DruidUtils.getConnection();
String sql = "select * from employee";
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while (rs.next()) {
System.out.print(rs.getInt("id"));
System.out.print(rs.getString("username"));
System.out.println(rs.getString("salary"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtils.close(rs, pstm, conn);
}
}
@Test
//插入
public void tets2() {
try {
conn = DruidUtils.getConnection();
String sql = "insert into employee (username , salary) values (?,?)";
pstm = conn.prepareStatement(sql);
pstm.setString(1, "唐僧");
pstm.setInt(2, 10000);
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("插入成功");
} else {
System.out.println("插入失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtils.close(pstm, conn);
}
}
@Test
//更新
public void tets3() {
try {
conn = DruidUtils.getConnection();
String sql = "update employee set salary = ? where username = ?";
pstm = conn.prepareStatement(sql);
pstm.setInt(1, 10000);
pstm.setString(2, "孙悟空");
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("更新成功");
} else {
System.out.println("更新失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtils.close(pstm, conn);
}
}
@Test
//删除
public void tets4() {
try {
conn = DruidUtils.getConnection();
String sql = "delete from employee where username = ? ";
pstm = conn.prepareStatement(sql);
pstm.setString(1, "高圆圆");
int count = pstm.executeUpdate();
if (count > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtils.close(pstm, conn);
}
}
}
数据库连接池间的比较:
1:性能方面 hikariCP>druid>tomcat-jdbc>dbcp>c3p0 。hikariCP的高性能得益于最大限度的避免锁竞争。
2:druid功能最为全面,sql拦截等功能,统计数据较为全面,具有良好的扩展性。
3:综合性能,扩展性等方面,可考虑使用druid或者hikariCP连接池。
4:可开启prepareStatement缓存,对性能会有大概20%的提升。
DBCP2
代表的也就是2.X版本,相比较于1.x版本,性能有了较大提升,支持JMX和一些新特性;DBCP2.X和DBCP1.x是不兼容的,包括具体配置上面的参数也有些不同;项目引用的类名称、maven仓库的位置坐标也不一样。
C3P0
支持JDBC3规范和JDBC2的标准扩展; 在hibernate项目中使用的比较多;
Druid
是阿里巴巴开源平台上一个数据库连接池实现,除了数据库连接池外,还提供了一系列内置的jdbc组件,可以监控DB连接池和sql执行情况。
主要区别
1、c3p0提供最大空闲时间,当连接超过最大空闲连接时间,当前连接就会被断掉。
2、DBCP提供最大连接数,当连接数超过最大连接数时候,所有连接都会被断开。
3、Druid结合了C3P0、DBCP的优点,同时针对不同的数据库,在实现上有针对性的优化。
4、c3p0有自动回收空闲连接功能。 dbcp没有自动回收空闲连接功能。
5、C3P0的底层运行机制?
6、DBCP的底层运行机制?
通常情况,采用的是createDataSource方法读取数据库连接参数,连接事务参数 数据池连接参数等。
使用建议
1、Druid除了连接池,监控功能也不错,便于优化,一般情况下推荐使用;
2、如果使用的是oracle数据库,不建议使用c3p0,因为c3p0不遵循LRU;
相应数据库及表的创建
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`salary` int(12) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8