为什么使用数据库连接池
在JDBC中,一个Connection对象表示一个对数据库的连接。建立一个连接耗时又耗资源,如果所有客户共享一组已经打开的连接会节约很多时间。
1.所有用户通过一个通用的数据库帐号访问数据库。
2.数据库连接只用于单个请求的持续时间,用完即归还连接。
连接池是存储、管理数据库连接的容器,应用程序把获取数据库连接的功能委托给连接池。每个连接池都有一个上限,如果连接池达到上限,应用程序线程申请连接时被堵塞,等待其他线程释放连接。使用连接池首先要获得对连接池对象的引用,这里的对象是唯一的。无论有多少个进程访问连接池,连接池对象只有一个实例,在整个程序运行期间,连接池对象要常驻内存。
最小连接数 | 连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。 |
---|---|
最大连接数 | 连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。 |
最小连接数与最大连接数差距 | 最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。 |
注意:如果一个进程需要使用多个连接对象,那么连接池的最大连接数应大于单个进程需要的最大连接数。
int minConn | 连接池最少连接数 |
---|---|
int maxConn | 连接池最多连接数 |
Stack |
使用Stack保存数据库连接, 也可以使用数组, Vector等保存 |
private ConnManager() | 构造函数功能: 1.初始化 2.打开Log文件 3.注册驱动程序 4.根据最小连接数生成连接 |
public static synchronized ConnManager getInstance() | 返回连接池对象唯一实例。如果是第一次调用此方法,则调用创建该实例 |
private Connection newConnection() | 创建新的连接 |
public synchronized Connection getConnection() | 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接数限制, 则创建新连接 |
public synchronized void freeConnection(Connection con) | 将不再使用的连接返回给连接池 |
ConnManager.java
package org.DBCP.sql;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Date;
import java.util.Stack;
/**
* @author Cqh_i 2018年7月19日20:49:19
* SQL数据库连接池
*/
public class ConnManager {
int minConn = 2; // 连接池最少连接数
int maxConn = 5;// 连接池最多连接数
String user = "sa"; // 连接数据库使用的用户名
String password = "abc123!"; // 连接数据库使用的用户名密码
String url = "jdbc:sqlserver://localhost:1433;DatabaseName=DBNAME";// 所连接的数据库的URL(连接字符串)
String logFile = "E:\\Program Files (x86)\\Sql_log_file\\dbpool.log";// 日志文件路径
PrintWriter loger = null; // 记录日志Log的对象
int connAmount = 0; // 当前现有的连接的个数
Stack connStack = new Stack<>(); // 使用Stack保存数据库连接, 也可以使用数组, Vector等保存
/**
* 构造函数 功能: 1.初始化 2.打开Log文件 3.注册驱动程序 4.根据最小连接数生成连接
*/
private ConnManager() {
try {
loger = new PrintWriter(new FileWriter(logFile, true), true);
} catch (IOException ioe) {
loger = new PrintWriter(System.err);
}
// 注册驱动程序
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
log("成功注册驱动程序");
} catch (Exception e) {
log("无法注册驱动程序");
}
// 根据最少连接数生成连接
for (int i = 0; i < minConn; i++) {
Connection con = newConnection();
if (con != null)
connStack.push(con);
}
}
// 将文本信息msg写入日志文件
private void log(String msg) {
loger.println(new Date() + ":" + msg);
}
private static ConnManager instance; // 数据库连接池ConnManager的实例
public static synchronized ConnManager getInstance() {
// 返回唯一实例。如果是第一次调用此方法,则创建该实例
if (instance == null) {
instance = new ConnManager();
}
return instance;
}
// 创建新的连接
private Connection newConnection() {
Connection con = null;
try {
con = DriverManager.getConnection(url, user, password);
connAmount++;
log("连接池创建一个新的连接");
} catch (SQLException e) {
log("无法创建下列url的连接: " + url);
return null;
}
return con;
}
public synchronized Connection getConnection() {
/**
* 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接数限制, 则创建新连接
*/
Connection con = null;
log("从连接池申请一个连接");
log("现在可用的连接总数为: " + connStack.size());
if (!connStack.empty()) {
con = (Connection) connStack.pop();
} else if (connAmount < maxConn) {
con = newConnection();
} else {
try {
log("等待连接");
wait(100000);// 等待其他进程释放连接,单位是毫秒
return getConnection();
} catch (InterruptedException ie) {
}
}
return con;
}
public synchronized void freeConnection(Connection con) {
// 将不再使用的连接返回给连接池
connStack.push(con);
notifyAll();// 唤醒在此对象监视器上等待的所有线程
log("归还一个连接到连接池");
}
}
对以上数据池进行简单测试,建立Test_DBCP.jsp文件
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page language="java" import="org.DBCP.sql.ConnManager, java.sql.*"%>
Test_DBCP
<%
//1.得到连接池的唯一实例
ConnManager pool = ConnManager.getInstance();
//2.从连接池中获得一个连接
Connection conn = pool.getConnection();
/*
3.对获得的连接进行操作使用
*/
//4.操作完成,归还连接。
if (conn != null)
pool.freeConnection(conn);
%>
运行结果查看log日志文件
MySQL的官方JDBC驱动程序MySQL Connector / J官网下载地址:https://dev.mysql.com/downloads/connector/j/5.1.html
这里用为tomcat配置数据源的方法。在web应用的META-INF文件夹下创建一个context.xml配置文件,并在其中添加
context.xml注释版(无注释版见下)
path: 即要建立的虚拟目录, 注意是/DBCP_test, 它指定访问Web应用的上下文根,
如http://localhost:8080/DBCP_test/****
docBase: 为应用程序的路径或WAR文件存放的路径, 可以是绝对路径, 也可是相
对路径, 相对路径是相对于
reloadable:如果这个属性设为true, Tomcat服务器在运行状态下会监视在
WEB-INF/classes和Web-INF/lib目录CLASS文件的改变,
如果监视到有class文件被更新,服务器自动重新加载Web应用,
这样我们可以在不重起tomcat的情况下改变应用程序
debug:为设定debug的等级, 程序异常时写入日志文件里的详细程度,
0提供最少的信息, 9提供最多的信息,
,这个属性是必需的,
如果使用,这个属性是可选的。
type="javax.sql.DataSource"
对象类型固定为DataSource即可
maxActive="100"
连接池支持的最大连接数, 设0为没有限制
maxIdle="30"
连接池中最多可空闲maxIdle个连接, 设0为没有限制
maxWait="10000"
连接池中连接用完时,新的请求等待时间,毫秒
username="root"
数据库连接的用户名
password="123456"
数据库用户的密码
driverClassName="com.mysql.jdbc.Driver"
JDBC的驱动程序
url="jdbc:mysql://localhost:3306/DateBase_Name?autoReconnect=true" />
数据库URL地址,mysql5以上的,设置autoReconnect=true 是无效的 只有4.x版本,起作用
autoReconnect=true含义:当数据库连接异常中断时,是否自动重新连接,数据库的url还可
以携带其他的参数,如:
url="jdbc:mysql://localhost:3306/DateBase_Name?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8"
后面两个参数的作用是:指定字符的编码、解码格式。在MySQL中,数据库用的是gbk编码,而项目数据库用的是utf-8编码,这两个参数的作用如下:
1.存数据时:
数据库在存放项目数据的时候会先用UTF-8格式将数据解码成字节码,然后再将解码后的字节码重新使用GBK编码存放到数据库中。
2.取数据时:
在从数据库中取数据的时候,数据库会先将数据库中的数据按GBK格式解码成字节码,然后再将解码后的字节码重新按UTF-8格式编码数据,最后再将数据返回给客户端。
注意在xml文件中,参数用&隔开
context.xml无注释版
在tomcat启动时,会逐个加载其中的web应用,当其加载到当前应用时,会加载到META-INF文件夹下该context.xml配置文件,此时tomcat会根据其中的配置信息为当前web应用创建一个数据库连接池,注该数据库连接池只能用于当前web应用,tomcat下的其他web应用无法使用该数据库连接池。Tomcat中有一个共有的容器:jndi,当Tomcat为web应用创建数据源之后会将数据源以键值对的形式存放到jndi容器当中,其中键名就是数据源配置时给定的名字。而Tomcat会自动将jndi容器放到初始化容器InitalContext中,且jndi以固定的名字(java:comp/env)存储在InitalContext容器中。
获取数据库连接池实例步骤(通常将这部分设置为静态块:类加载的时候会执行一次,且只会执行一次(类似与单例模式)):
获取InitalContext容器实例(JNDI的接口)
Context initContext = new InitialContext();
在InitalContext容器中根据字符串java:comp/env搜寻,以及获取jndi容器实例
Context envContext = (Context)initContext.lookup("java:/comp/env");
根据数据源(数据库连接池)的名称从jndi容器获取到数据源(数据库连接池)实例,通过JNDI中的核心api(Context),调用该对象的lookup方法,去jdni容器中根据资源的名称来检索一个对象
ds = (DataSource)envContext.lookup("jdbc/mysql");
进行简单的测试,建立DBCP_Test.jsp文件
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ page import="java.sql.*, javax.sql.DataSource,javax.naming.*"%>
<%@ page contentType="text/html; charset=utf-8"%>
Test
<%
try {
Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:/comp/env");
DataSource ds = (DataSource) envContext.lookup("jdbc/mysql");
Connection conn = ds.getConnection();
out.print("连接成功");
conn.close();
} catch (Exception e) {
out.print(e.toString());
}
%>
运行结果:
注意:context.xml的存放位置不同,它的作用范围也不同。如在tomcat安装目录/conf/context.xml,它的作用范围配置在这个位置的信息将会被所有的web应用所共享。
开发中可使用C3P0、DBCP等开源的数据库连接池以及开源的DBUtils完成增删查改工作,BeanUtils获取Bean减少代码量。