使用数据库连接池优化程序性能
应用程序直接获取链接的缺点
缺点:用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天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类
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; } }
- 测试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文件夹下
结果: