1、使用数据库连接池优化程序性能
2、数据库连接池编写原理分析
(1)编写连接池需实现javax.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
Connection getConnection()
Connection getConnection(String username, String password)
(2)实现DataSource接口,并实现连接池功能的步骤:
在DataSource构造函数中批量创建与数据库的连接,并把创建的连接保存到一个集合对象中
实现getConnection方法,让getConnection方法每次调用时,从集合对象中取一个Connection返回给用户。
当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到连接池的集合对象中,而不要把conn还给数据库。
package com.itheima.pool;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
public class MyPool implements DataSource {
private static List<Connection> poolList = new LinkedList<Connection>();
static{
try{
Class.forName("com.mysql.jdbc.Driver");
for(int i = 0;i<5; i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
poolList.add(conn);
}
}catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
public Connection getConnection() throws SQLException {
if(poolList.size()<=0){
for(int i = 0;i<5; i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
poolList.add(conn);
}
}
final Connection conn = poolList.remove(0);
Connection procxy = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if("close".equals(method.getName())){
poolList.add(conn);
System.out.println("还回了一个连接,还剩余"+poolList.size());
return null;
}else{
return method.invoke(conn, args);
}
}
});
System.out.println("取走了一个连接,还剩余"+poolList.size());
return procxy;
}
public void retConn(Connection conn){
try {
if(conn == null || conn.isClosed())return;
poolList.add(conn);
System.out.println("还回了一个连接,还剩余"+poolList.size());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Connection getConnection(String username, String password)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
package com.itheima.pool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class PoolDemo1 {
@Test
public void test1(){
MyPool pool = new MyPool();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = pool.getConnection();
ps = conn.prepareStatement("select * from account where id=?");
ps.setInt(1, 1);
rs = ps.executeQuery();
while(rs.next()){
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(name+":"+money);
}
}catch (Exception e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rs = null;
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
ps = null;
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn = null;
}
}
}
}
}
3、编写数据库连接池核心
(1)扩展Connection的close方法
在关闭数据库连接时,将connection存回连接池中,而并非真正的关闭
(2)扩展类的三种方式
基于继承--- 方法覆盖
使用装饰模式包装类,增强原有行为
使用动态代理 --- 基于字节码Class在内存中执行过程
package com.itheima.pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface 动物 {
public void 叫();
public void 吃();
}
interface xx{
public void sss();
}
public class 狗 implements 动物 ,xx {
public void 叫(){
System.out.println("狗在叫");
}
public void 吃(){
System.out.println("狗在吃");
}
//代理设计模式
public static void main(String[] args) {
final 狗 dog = new 狗();
动物 proxy = (动物)Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces()
, new InvocationHandler(){
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
if("叫".equals(method.getName())){
System.out.println("代理狗在旺旺叫~~~~~~~~~~");
return null;
}else{
return method.invoke(dog, args);
}
}
});
proxy.叫();
proxy.吃();
((xx)proxy).sss();
// dog.叫();
// dog.吃();
}
public void sss() {
System.out.println("sss");
}
}
//装饰设计模式
class 装饰狗 implements 动物{
private 狗 dog = null;
public 装饰狗(狗 dog) {
this.dog = dog;
}
public void 叫() {
System.out.println("装饰狗在叫!!!!!");
}
public void 吃() {
dog.吃();
}
}
//继承方式改变方法的功能
class 继承狗 extends 狗{
@Override
public void 叫() {
System.out.println("继承狗在叫");
}
}
4、动态代理增强close示例
使用动态代理技术构建连接池中的connection
proxyConn = (Connection) Proxy.newProxyInstance(this.getClass()
.getClassLoader(), conn.getClass().getInterfaces(),
new InvocationHandler() {
//此处为内部类,当close方法被调用时将conn还回池中,其它方法直接执行
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if (method.getName().equals("close")) {
pool.addLast(conn);
return null;
}
return method.invoke(conn, args);
}
});
5、开源数据库连接池(DataSource)
(1)现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
(2)也有一些开源组织提供了数据源的独立实现:
DBCP 数据库连接池
C3P0 数据库连接池
Apache Tomcat内置的连接池(apache dbcp)
(3)实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接。程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能。
(4)原来由jdbcUtil创建连接,现在由dataSource创建连接,为实现不和具体数据为绑定,因此datasource也应采用配置文件的方法获得连接。
6、DBCP数据源
(1)DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
(2)Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///day13");
dataSource.setUsername("root");
tPassword("root");
Properties prop = new Properties();
prop.load(new FileInputStream("config.properties"));
DataSource dataSource = BasicDataSourceFactory.createDataSource(prop);
(3)使用DBCP示例代码
static{
InputStream in = JdbcUtil.class.getClassLoader().
getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
BasicDataSourceFactory factory = new BasicDataSourceFactory();
dataSource = factory.createDataSource(prop);
}
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///day11
username=root
password=root
package com.itheima.pool;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;
public class DBCPDemo {
@Test
public void test1(){
// BasicDataSource dataSource = new BasicDataSource();
// dataSource.setDriverClassName("com.mysql.jdbc.Driver");
// dataSource.setUrl("jdbc:mysql:///day11");
// dataSource.setUsername("root");
// dataSource.setPassword("root");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
Properties prop = new Properties();
prop.load(new FileReader("dbcpconfig.properties"));
DataSource dataSource = BasicDataSourceFactory.createDataSource(prop);
conn = dataSource.getConnection();
ps = conn.prepareStatement("select * from account where id=?");
ps.setInt(1, 1);
rs = ps.executeQuery();
while(rs.next()){
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(name+":"+money);
}
}catch (Exception e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rs = null;
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
ps = null;
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn = null;
}
}
}
}
}
(4)配置文件写法说明
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
username=root
password=
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
5、C3P0 数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
ComboPooledDataSource dataSource = new ComboPooledDataSource("mySoruce");
<?xml version="1.0"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property >
<property name="jdbcUrl">jdbc:mysql:///Day12</property >
<property name="user">root</property>
<property name="password">root</property>
</default-config>
<!-- This app is massive! -->
<named-config name="mySoruce">
<property name="driverClass">com.mysql.jdbc.Driver</property >
<property name="jdbcUrl">jdbc:mysql:///Day12</property >
<property name="user">root</property>
<property name="password">root</property>
</named-config>
</c3p0-config>
driverClass
jdbcUrl
user
password
acquireIncrement:当连接池中已经没有连接时,连接池自动获取连接时一次获取的连接个数。
initialPoolSize:连接池初始化时,获取连接的个数。
maxPoolSize:连接池可以保有的最大的连接的数量。
maxIdleTime:当连接空闲多久时释放连接。如果该时间值设置问为0,表示从不释放连接。
minPoolSize:连接池应该保有的最小的连接的数量。
package com.itheima.pool;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Demo {
@Test
public void test() throws PropertyVetoException{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
ps = conn.prepareStatement("select * from account where id=?");
ps.setInt(1, 1);
rs = ps.executeQuery();
while(rs.next()){
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(name+":"+money);
}
}catch (Exception e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rs = null;
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
ps = null;
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn = null;
}
}
}
}
}
<?xml version="1.0" ?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day11</property>
<property name="user">root</property>
<property name="password">root</property>
</default-config>
<named-config name="c1">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day10</property>
<property name="user">root</property>
<property name="password">root</property>
</named-config>
<named-config name="c2">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day09</property>
<property name="user">root</property>
<property name="password">root</property>
</named-config>
</c3p0-config>
6、配置Tomcat数据源
查看Tomcat文档,示例代码:
<Context>
<Resource name="jdbc/datasource" auth="Container"
type="javax.sql.DataSource" username="root" password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbc"
maxActive="8" maxIdle="4"/>
</Context>
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
特别提醒:此种配置下,驱动jar文件需放置在tomcat的lib下
只有在servlet/jsp中才可以使用jndi机制
操作步骤
1、配置使用tomcat 内置连接池 配置<context> 元素
context元素有三种常见配置位置
1) tomcat/conf/context.xml 所有虚拟主机,所有工程都可以访问该连接池
2) tomcat/conf/Catalina/localhost/context.xml 当前虚拟主机(localhost)下所有工程都可以使用该连接池
3) 当前工程/META-INF/context.xml 只有当前工程可以访问该连接池
<Context>
<Resource name="jdbc/EmployeeDB" auth="Container"
type="javax.sql.DataSource" username="root" password="abc"
driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day14"
maxActive="8" maxIdle="4"/>
</Context>
* 必须先将mysql驱动jar包 复制tomcat/lib下
* 在tomcat启动服务器时,创建连接池对象,绑定 jdbc/EmployeeDB 指定名称上
2、通过运行在JNDI容器内部的程序(Servlet/JSP)去访问tomcat内置连接池
Context context = new InitialContext();
Context envCtx = (Context)context.lookup("java:comp/env"); 固定路径
DataSource datasource = (DataSource) envCtx.lookup("jdbc/EmployeeDB"); 通过绑定名称,查找指定java对象
=====================================================================补充知识:
在Context中配置数据源,有五个位置可以配置,参考tomcat文档
~在conf/context.xml爸爸Context中配置,这个配置将被tomcat的所有web应用共享
In the $CATALINA_BASE/conf/context.xml file: the Context element information will be loaded by all webapps.
~在conf/Catalina/localhost/context.xml,这个配置将被当前虚拟主机所共享
In the $CATALINA_BASE/conf/[enginename]/[hostname]/context.xml.default file: the Context element information will be loaded by all webapps of that host.
~在conf/Catalina/localhost/XXXXXX.xml,这是配置web应用的对外访问路径,这个配置只对当前web应用起作用
In individual files (with a ".xml" extension) in the $CATALINA_BASE/conf/[enginename]/[hostname]/ directory. The name of the file (less the .xml extension) will be used as the context path. Multi-level context paths may be defined using #, e.g. foo#bar.xml for a context path of /foo/bar. The default web application may be defined by using a file called ROOT.xml.
~在web应用个的META-INF目录下创建context.xml这个配置只对当前web应用起作用
Only if a context file does not exist for the application in the $CATALINA_BASE/conf/[enginename]/[hostname]/, in an individual file at /META-INF/context.xml inside the application files. If the web application is packaged as a WAR then /META-INF/context.xml will be copied to $CATALINA_BASE/conf/[enginename]/[hostname]/ and renamed to match the application's context path. Once this file exists, it will not be replaced if a new WAR with a newer /META-INF/context.xml is placed in the host's appBase.
~在conf/servler.xml的Host标签下配置Context标签
Inside a Host element in the main conf/server.xml.
4、在Cotext中配置:
<Resource name="mySource" ---在数据源创建好以后绑定到jndi容器中时使用的名字
auth="Container"
type="javax.sql.DataSource" ---当前对象的类型,默认就是数据源
username="root" --- 数据库用户名
password="root" --- 数据库密码
driverClassName="com.mysql.jdbc.Driver" ---数据库驱动名
url="jdbc:mysql:///day12" --- 数据库连接信息
maxActive="8" --- 最大连接数
maxIdle="4"/> --- 最大空闲连接数
5、在程序中获取数据源:
Context context = new InitialContext();
Context envCtx = (Context)context.lookup("java:comp/env");
DataSource datasource = (DataSource) envCtx.lookup("mySource");
7、JNDI技术简介
(1)JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包,
(2)这套API的主要作用在于:它可以把Java对象放在一个容器中(支持JNDI容器 Tomcat),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可。
(3)其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
(4)原来由jdbcUtil创建连接,现在由dataSource创建连接,为实现不和具体数据为绑定,因此datasource也应采用配置文件的方法获得连接。