使用数据库连接池优化程序性能
应用程序直接获取链接的缺点
缺点:用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
【案例】传统方法连接数据库
- 配置文件db.properties
- driver=com.mysql.jdbc.Driver
- url=jdbc:mysql://localhost:3306/java
- username=root
- password=123456
- 加载数据库驱动
- 连接数据库java代码 DBConn.java
- package com.hbsi.util;
- import java.io.IOException;
- import java.io.InputStream;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.util.Properties;
- public class DBConn {
- static String driver;
- static String url;
- static String username;
- static String password;
- static{
- InputStream in = DBConn.class.getClassLoader().getResourceAsStream("db.properties");
- Properties pro = new Properties();
- try {
- pro.load(in);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- driver = pro.getProperty("driver");
- url = pro.getProperty("url");
- username = pro.getProperty("username");
- password = pro.getProperty("password");
- }
- public static Connection getConnection(){
- Connection conn = null;
- try {
- Class.forName(driver);
- conn = DriverManager.getConnection(url,username,password);
- } catch (ClassNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return conn;
- }
- public static void close(ResultSet rs, PreparedStatement ps, Connection conn){
- if(rs != null){
- try {
- rs.close();
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if(ps != null){
- try {
- ps.close();
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if(conn != null){
- try {
- conn.close();
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
- 测试 DBConnTest.java
- package com.hbsi.test;
- import org.junit.Test;
- import com.hbsi.util.DBConn;
- import junit.framework.TestCase;
- public class DBConnTest extends TestCase {
- @Test
- public void testGetConn(){
- System.out.print(DBConn.getConnection());
- }
- }
- java应用类
- package com.hbsi.demo;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import com.hbsi.util.DBConn;
- public class ChuanTong {
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement ps =null;
- ResultSet rs = null;
- try{
- conn = DBConn.getConnection();
- //.....
- System.out.println(conn);
- //.....
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- DBConn.close(rs, ps, conn);
- }
- }
- }
使用连接池:
编写数据库连接池
- 编写连接池需实现javax.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
- Connection getConnection()
- Connection getConnection(String username, String password)
- 实现DataSource接口,并实现连接池功能的步骤:
- 在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。
- 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。
- 当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。
- Collection保证将自己返回到LinkedList中是此处编程的难点。
使用动态代理技术构建连接池中的connection--自己编写
动态代理
- 明确两个概念:
- 代理对象存在的价值:主要用于拦截对真实业务对象的访问。
- 代理对象有什么方法
- Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
- 生成代理对象使用哪个类装载器
- 生成哪个对象的代理对象,通过接口指定
- 生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
- 初学者必须理解,或不理解必须记住的2件事情:
- Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
- 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
建立了一个代理类,传入代理对象、方法、方法的参数
如果想访问真是对象的方法只能通过invoke访问
【案例】自编写连接池
- java类
- <pre name="code" class="java">package com.hbsi.util;
- import java.io.InputStream;
- import java.io.PrintWriter;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.SQLException;
- import java.util.LinkedList;
- import java.util.Properties;
- import javax.sql.DataSource;
- public class MyJdbcPool implements DataSource {
- // 创建的连接放在linklist里
- private static LinkedList<Connection> list = new LinkedList<Connection>();
- private static String driver;
- private static String url;
- private static String username;
- private static String password;
- // 通过静态代码块创建连接
- static {
- try {
- // 类加载器
- InputStream in = MyJdbcPool.class.getClassLoader()
- .getResourceAsStream("db.properties");
- //属性对象
- Properties prop = new Properties();
- prop.load(in);
- driver = prop.getProperty("driver");
- url = prop.getProperty("url");
- username = prop.getProperty("username");
- password = prop.getProperty("password");
- //连接数据库,加载驱动
- Class.forName(driver);
- //创建一批连接
- for (int i = 0; i < 10; i++) {
- Connection conn = DriverManager.getConnection(url, username,
- password);
- System.out.println("向池中加入了:" + conn);
- list.add(conn);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public Connection getConnection() throws SQLException {
- if (list.size() > 0) {
- //不用get方法,get方法会出现重复利用
- final Connection conn = list.removeFirst();
- System.out.println("用户从池中取走了:" + conn);
- System.out.println("池的大小为:" + list.size());
- //动态代理,那个类,哪个对象,干什么事
- return (Connection) Proxy.newProxyInstance(MyJdbcPool.class
- .getClassLoader(), new Class[] { Connection.class },
- new InvocationHandler() {
- //做什么事情,放回连接池里
- public Object invoke(Object proxy, Method method,
- Object[] args) throws Throwable {
- if (!method.getName().equalsIgnoreCase("close")) {
- return method.invoke(conn, args);
- }
- System.out.println(conn + "被还了");
- list.add(conn);
- System.out.println("池的大小为:" + list.size());
- return null;
- }
- });
- } else {
- throw new RuntimeException("对不起,请稍等!");
- }
- }
- 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;
- }
- }
- </pre><br>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- 测试java类
- package com.hbsi.demo;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import com.hbsi.util.DBConn;
- import com.hbsi.util.MyJdbcPool;
- public class MyJdbcPoolDemo {
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement ps =null;
- ResultSet rs = null;
- try{
- MyJdbcPool pool = new MyJdbcPool();
- conn =pool.getConnection();
- //.....
- System.out.println(conn);
- //.....
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- DBConn.close(rs, ps, conn);
- }
- }
- }
开源数据库连接池
- 现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSource的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
- 也有一些开源组织提供了数据源的独立实现:
- DBCP 数据库连接池
- C3P0 数据库连接池
- 实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接。程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能。
DBCP数据源
- DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
- Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
【案例】dbcp连接数据库
- 考jar包
- 写配置文件
- #Á¬½ÓÉèÖÃ
- driverClassName=com.mysql.jdbc.Driver
- url=jdbc\:mysql\://localhost\:3306/java
- username=root
- password=123456
- #<!-- ³õʼ»¯Á¬½Ó -->
- 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 Ö¸¶¨ÓÉÁ¬½Ó³ØËù´´½¨µÄÁ¬½ÓµÄÖ»¶Á£¨read-only£©×´Ì¬¡£
- #Èç¹ûûÓÐÉèÖøÃÖµ£¬Ôò¡°setReadOnly¡±·½·¨½«²»±»µ÷Óᣣ¨Ä³Ð©Çý¶¯²¢²»Ö§³ÖÖ»¶Áģʽ£¬È磺Informix£©
- defaultReadOnly=
- #driver default Ö¸¶¨ÓÉÁ¬½Ó³ØËù´´½¨µÄÁ¬½ÓµÄÊÂÎñ¼¶±ð£¨TransactionIsolation£©¡£
- #¿ÉÓÃֵΪÏÂÁÐÖ®Ò»£º£¨ÏêÇé¿É¼ûjavadoc¡££©NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
- defaultTransactionIsolation=READ_UNCOMMITTED
-
- #连接设置
- driverClassName=com.mysql.jdbc.Driver
- url=jdbc\:mysql\://localhost\:3306/java
- username=root
- password=123456
- #<!-- 初始化连接 -->
- 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 指定由连接池所创建的连接的只读(read-only)状态。
- #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
- defaultReadOnly=
- #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
- #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
- defaultTransactionIsolation=READ_UNCOMMITTED
- 编写java工具类 DBManger_dbcp.java
- package com.hbsi.util;
- import java.io.InputStream;
- import java.sql.Connection;
- import java.sql.SQLException;
- import java.util.Properties;
- import javax.sql.DataSource;
- import org.apache.commons.dbcp.BasicDataSourceFactory;
- public class DBManger_dbcp {
- private static DataSource ds ;
- static{
- try{
- InputStream in = DBManger_dbcp.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
- Properties prop = new Properties();
- prop.load(in);
- ds = BasicDataSourceFactory.createDataSource(prop);
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- public static Connection getConnction() throws SQLException{
- return ds.getConnection();
- }
- }
- 提供一个方法,从池里去连接 DBManger_dbcpDemo.java
- package com.hbsi.demo;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import com.hbsi.util.DBConn;
- import com.hbsi.util.DBManger_dbcp;
- public class DBManger_dbcpDemo {
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement ps =null;
- ResultSet rs = null;
- try{
- conn = DBManger_dbcp.getConnction();
- //.....
- System.out.println(conn);
- //.....
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- DBConn.close(rs, ps, conn);
- }
- }
- }
C3P0 数据源
【案例】c3p0连接数据库
- 加jar包
- 编写工具类DBManager_c3p0.java
- package com.hbsi.util;
- import java.sql.Connection;
- import java.sql.SQLException;
- import com.mchange.v2.c3p0.ComboPooledDataSource;
- public class DBManager_c3p0 {
- private static ComboPooledDataSource ds = null;
- static{
- try{
- //创建连接池
- ds = new ComboPooledDataSource("mysql");
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- public static Connection getConnection() throws SQLException{
- return ds.getConnection();
- }
- }
- 编写连接配置文件 c3p0-config.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <c3p0-config>
- <default-config>
- <property name="driverClass">com.mysql.jdbc.Driver</property>
- <property name="jdbcUrl">jdbc:mysql://localhost:3306/java</property>
- <property name="user">root</property>
- <property name="password">123456</property>
- <property name="acquireIncrement">5</property>
- <property name="initialPoolSize">10</property>
- <property name="minPoolSize">5</property>
- <property name="maxPoolSize">20</property>
- </default-config>
- <named-config name="mysql">
- <property name="driverClass">com.mysql.jdbc.Driver</property>
- <property name="jdbcUrl">jdbc:mysql://localhost:3306/java</property>
- <property name="user">root</property>
- <property name="password">123456</property>
- <property name="acquireIncrement">5</property>
- <property name="initialPoolSize">10</property>
- <property name="minPoolSize">5</property>
- <property name="maxPoolSize">20</property>
- </named-config>
- </c3p0-config>
- 相应实现DBManager_c3p0Demo.java
- package com.hbsi.demo;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import com.hbsi.util.DBConn;
- import com.hbsi.util.DBManager_c3p0;
- public class DBManager_c3p0Demo {
- public static void main(String[] args) {
- Connection conn = null;
- PreparedStatement ps =null;
- ResultSet rs = null;
- try{
- conn = DBManager_c3p0.getConnection();
- //.....
- System.out.println(conn);
- //.....
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- DBConn.close(rs, ps, conn);
- }
- }
- }
配置Tomcat数据源
特别提醒: 此种配置下,驱动jar文件需放置在tomcat的lib下JNDI技术简介
- JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包,
- 这套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可。
- 其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
【实例】利用Tomcat实现数据库连接
在META-INF下新建context.xml
context.xml
- <Context>
- <Resourse name="jdbc/javaDB"
- auth="Container"
- type="javax.sql.DataSource"
- username="root"
- password="123456"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/java"
- maxActive="8"
- maxIdle="4"/>
- </Context>
做一个servlet,读取xml文件
- package com.hbsi.util;
- import java.io.IOException;
- import java.sql.Connection;
- import java.sql.SQLException;
- import javax.naming.Context;
- import javax.naming.InitialContext;
- import javax.naming.NamingException;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import javax.sql.DataSource;
- public class TomcatPool extends HttpServlet {
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- try {
- //初始化jndi容器
- Context initCxt = new InitialContext();
- //获取容器,检索出web服务器中的jndi容器
- Context envCxt = (Context)initCxt.lookup("java:comp/env");
- //从jndi容器中检索出连接池
- DataSource ds = (DataSource)envCxt.lookup("jdbc/javaDB");
- //获取连接
- Connection conn = ds.getConnection();
- System.out.println(conn);
- } catch (NamingException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
- }
把jar包考到Tomcat的lib文件夹下
结果: