若是用户每次请求都向数据库得到链接,而数据库建立链接一般须要消耗相对较大的资源,建立时间也较长。假设网站一天10万访问量,数据库服务器就须要建立10万次链接,极大的浪费数据库的资源,而且极易形成数据库服务器内存溢出、拓机。以下图所示:html
数据库链接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤其突出.对数据库链接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库链接池正式针对这个问题提出来的。java
数据库链接池负责分配,管理和释放数据库链接,它容许应用程序重复使用一个现有的数据库链接,而不是从新创建一个。以下图所示:mysql
有了池,咱们就不用本身来建立Connection,而是经过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就能够再利用这个Connection对象了。sql
数据库链接池在初始化时将建立必定数量的数据库链接放到链接池中, 这些数据库链接的数量是由最小数据库链接数来设定的.不管这些数据库链接是否被使用,链接池都将一直保证至少拥有这么多的链接数量.链接池的最大数据库链接数量限定了这个链接池能占有的最大链接数,当应用程序向链接池请求的链接数超过最大链接数量时,这些请求将被加入到等待队列中.数据库
数据库链接池的最小链接数和最大链接数的设置要考虑到如下几个因素:apache
【实现思路】 设计模式
【代码编写】缓存
public class MyDataSource implements DataSource { //1.建立1个容器用于存储Connection对象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5个链接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection connection = JdbcUtils.getConnection(); pool.add(connection); } } /** * 重写获取链接的方法 */ @Override public Connection getConnection() throws SQLException { Connection connection = null; //3.使用前先判断 if (pool.size() == 0) { //4.池子里面没有,咱们再建立一些 for (int i = 0; i < 5; i++) { connection = JdbcUtils.getConnection(); pool.add(connection); } } //5.从池子里面获取一个链接对象Connection connection = pool.remove(0); return connection; } /** * 归还链接对象到链接池中去 */ public void backConnection(Connection connection) { pool.add(connection); } ... }
【测试】tomcat
@Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定义链接池对象 MyDataSource dataSource = new MyDataSource(); try { // 2.从池子中获取链接 conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 3); pstmt.setString(2, "张三"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失败"); } } catch (Exception e) { e.printStackTrace(); }finally { dataSource.backConnection(conn); } }
【需求】服务器
上述自定义链接池中存在严重问题,用户调用getConnection()得到链接后,必须使用backConnection()方法进行链接的归还,若是用户调用conn.close()将链接真正的释放,链接池中将出现无链接可用。
此时咱们但愿,即便用户调用了close()方法,链接仍归还给链接池。close()方法原有功能是释放资源,咱们指望的功能是将链接归还给链接池。说明close()方法没有咱们但愿的功能,咱们将对close()方法进行加强,从而实现将链接归还给链接池的功能。
【方法加强总结】
1.继承:
2.装饰者模式:
3.动态代理:
【装饰者设计模式】
设计模式:专门为解决某一类问题,而编写的固定格式的代码。
固定结构:接口A,已知实现类C,须要装饰者建立代理类B
实现步骤:
A a = ...C; B b = new B(a); class B implements A{ private A a; public B(A,a){ this.a = a; } // 加强的方法 public void close(){ } // 不须要加强的方法 public void commit(){ this.a.commit(); } }
【装饰类】
//1.实现同一个接口Connection public class MyConnection implements Connection { //3.定义一个变量 private Connection conn; private LinkedList<Connection> pool; //2.编写一个构造方法(参数使用了面向对象的多态特性) public MyConnection(Connection conn,LinkedList<Connection> pool) { this.conn = conn; this.pool = pool; } //4.书写须要加强的方法 @Override public void close() throws SQLException { pool.add(conn); } /** * 5.实现不须要加强的方法 * 注意:此方法必须覆盖!不然会出现空指针异常!!! */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public void commit() throws SQLException { }
...
}
【使用装饰类】
public class MyDataSource1 implements DataSource { //1.建立1个容器用于存储Connection对象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5个链接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection conn = JdbcUtils.getConnection(); //放入池子中connection对象已经通过改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } /** * 重写获取链接的方法 */ @Override public Connection getConnection() throws SQLException { Connection conn = null; //3.使用前先判断 if (pool.size() == 0) { //4.池子里面没有,咱们再建立一些 for (int i = 0; i < 5; i++) { conn = JdbcUtils.getConnection(); //放入池子中connection对象已经通过改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } //5.从池子里面获取一个链接对象Connection conn = pool.remove(0); return conn; } /** * 归还链接对象到链接池中去 */ public void backConnection(Connection connection) { pool.add(connection); } ... }
【测试】
/** * 使用改造过的connection */ @Test public void testAddUser1() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定义链接池对象 DataSource dataSource = new MyDataSource1(); try { // 2.从池子中获取链接 conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; //3.必须在自定义的connection类中重写prepareStatement(sql)方法 pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 6); pstmt.setString(2, "王五"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失败"); } } catch (Exception e) { throw new RuntimeException(e); } finally { //这里的connection是已经通过加强的,加强后的conn.close()就是将链接归还给链接池 JdbcUtils.release(conn, pstmt, null); } }
DBCP 是 Apache 软件基金组织下的开源链接池实现,在企业开发中也比较常见,它是tomcat内置的链接池
#基本配置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb1 username=root password=123 #初始化池大小,即一开始池中就会有10个链接对象 默认值为0 initialSize=0 #最大链接数,若是设置maxActive=50时,池中最多能够有50个链接,固然这50个链接中包含被使用的和没被使用的(空闲) #你是一个包工头,你一共有50个工人,但这50个工人有的当前正在工做,有的正在空闲 #默认值为8,若是设置为非正数,表示没有限制!即无限大 maxActive=8 #最大空闲链接 #当设置maxIdle=30时,你是包工头,你容许最多有20个工人空闲,若是如今有30个空闲工人,那么要开除10个 #默认值为8,若是设置为负数,表示没有限制!即无限大 maxIdle=8 #最小空闲链接 #若是设置minIdel=5时,若是你的工人只有3个空闲,那么你须要再去招2个回来,保证有5个空闲工人 #默认值为0 minIdle=0 #最大等待时间 #当设置maxWait=5000时,如今你的工做都出去工做了,又来了一个工做,须要一个工人。 #这时就要等待有工人回来,若是等待5000毫秒还没回来,那就抛出异常 #没有工人的缘由:最多工人数为50,已经有50个工人了,不能再招了,但50人都出去工做了。 #默认值为-1,表示无限期等待,不会抛出异常。 maxWait=-1 #链接属性 #就是原来放在url后面的参数,可使用connectionProperties来指定 #若是已经在url后面指定了,那么就不用在这里指定了。 #useServerPrepStmts=true,MySQL开启预编译功能 #cachePrepStmts=true,MySQL开启缓存PreparedStatement功能, #prepStmtCacheSize=50,缓存PreparedStatement的上限 #prepStmtCacheSqlLimit=300,当SQL模板长度大于300时,就再也不缓存它 connectionProperties=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300 #链接的默认提交方式 #默认值为true defaultAutoCommit=true #链接是否为只读链接 #Connection有一对方法:setReadOnly(boolean)和isReadOnly() #若是是只读链接,那么你只能用这个链接来作查询 #指定链接为只读是为了优化!这个优化与并发事务相关! #若是两个并发事务,对同一行记录作增、删、改操做,是否是必定要隔离它们啊? #若是两个并发事务,对同一行记录只作查询操做,那么是否是就不用隔离它们了? #若是没有指定这个属性值,那么是否为只读链接,这就由驱动本身来决定了。即Connection的实现类本身来决定! defaultReadOnly=false #指定事务的事务隔离级别 #可选值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE #若是没有指定,那么由驱动中的Connection实现类本身来决定 defaultTransactionIsolation=REPEATABLE_READ
public class DBCPUtils { /** * 在java中,编写数据库链接池需实现java.sql.DataSource接口,每一种数据库链接池都是DataSource接口的实现 * DBCP链接池就是java.sql.DataSource接口的一个具体实现 */ private static DataSource dataSource = null; //在静态代码块中建立数据库链接池 static{ try { //加载dbcp.properties配置文件 InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); //建立数据源 dataSource = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); } } /** * 从数据源中获取数据库链接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 释放资源 * 释放的资源包括Connection数据库链接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象 */ public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
public class DBCPTest { @Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 获取数据库链接 conn = DBCPUtils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 8); pstmt.setString(2, "刘备"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } } }
C3P0是一个开源的JDBC链接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0数据源在项目开发中使用得比较多。
若是操做的是Oracle数据库,那么还须要导入c3p0-oracle-thin-extras-0.9.2-pre1.jar
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--这是默认配置信息--> <default-config> <!--链接四大参数配置--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">root</property> <!--池参数配置--> <!--若是池中数据链接不够时一次增加多少个--> <property name="acquireIncrement">3</property> <!--初始化链接数--> <property name="initialPoolSize">10</property> <!--最小链接数--> <property name="minPoolSize">2</property> <!--最大链接数--> <property name="maxPoolSize">10</property> </default-config> <!--专门为Oracle提供的配置信息--> <named-config name="oracle-config"> <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">123</property> <property name="acquireIncrement">3</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">2</property> <property name="maxPoolSize">10</property> </named-config> </c3p0-config>
public class C3P0Utils { private static ComboPooledDataSource dataSource = null; //在静态代码块中建立数据库链接池 static{ try { // 经过代码建立C3P0链接池 /*dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setInitialPoolSize(10); dataSource.setMinPoolSize(5); dataSource.setMaxPoolSize(20);*/ //经过读取C3P0的xml配置文件建立数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下 //使用C3P0的默认配置来建立数据源 dataSource = new ComboPooledDataSource(); //使用名为oracle-config的配置来建立数据源 // dataSource = new ComboPooledDataSource("oracle-config"); } catch (Exception e) { e.printStackTrace(); } } /** * 从数据源中获取数据库链接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 释放资源 * 释放的资源包括Connection数据库链接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象 */ public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
@Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 获取数据库链接 conn = C3P0Utils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 9); pstmt.setString(2, "关羽"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } }
在实际开发中,咱们有时候还会使用服务器提供给咱们的数据库链接池,好比咱们但愿Tomcat服务器在启动的时候能够帮咱们建立一个数据库链接池,那么咱们在应用程序中就不须要手动去建立数据库链接池,直接使用Tomcat服务器建立好的数据库链接池便可。要想让Tomcat服务器在启动的时候帮咱们建立一个数据库链接池,那么须要简单配置一下Tomcat服务器。
JNDI(Java Naming and Directory Interface),Java命名和目录接口,它对应于J2SE中的javax.naming包,
这套API的主要做用在于:它能够把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,之后程序想得到Java对象,只需 经过名称检索便可。其核心API为Context,它表明JNDI容器,其lookup方法为检索容器中对应名称的对象。
Tomcat服务器建立的数据源是以JNDI资源的形式发布的,因此说在Tomat服务器中配置一个数据源实际上就是在配置一个JNDI资源,经过查看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/test" maxActive="8" maxIdle="4"/> </Context>
服务器建立好数据源以后,咱们的应用程序又该怎么样获得这个数据源呢,Tomcat服务器建立好数据源以后是以JNDI的形式绑定到一个JNDI容器中的,咱们能够把JNDI想象成一个大大的容器,咱们能够往这个容器中存放一些对象,一些资源,JNDI容器中存放的对象和资源都会有一个独一无二的名称,应用程序想从JNDI容器中获取资源时,只须要告诉JNDI容器要获取的资源的名称,JNDI根据名称去找到对应的资源后返回给应用程序。咱们平时作javaEE开发时,服务器会为咱们的应用程序建立不少资源,好比request对象,response对象,服务器建立的这些资源有两种方式提供给咱们的应用程序使用:第一种是经过方法参数的形式传递进来,好比咱们在Servlet中写的doPost和doGet方法中使用到的request对象和response对象就是服务器以参数的形式传递给咱们的。第二种就是JNDI的方式,服务器把建立好的资源绑定到JNDI容器中去,应用程序想要使用资源时,就直接从JNDI容器中获取相应的资源便可。
对于上面的name="jdbc/datasource"数据源资源,在应用程序中能够用以下的代码去获取
Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此种配置下,数据库的驱动jar文件需放置在tomcat的lib下
<Context> <!--name:指定资源的名称 factory:用来建立资源的工厂,这个值基本上是固定的,不用修改 type:资源的类型 其余的东西都是资源的参数 --> <Resource name="jdbc/datasource" factory="org.apache.naming.factory.BeanFactory" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" maxActive="8" maxIdle="4"/> </Context>
/** * 获取JNDI的资源 * */ public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /* * 1. 建立JNDI的上下文对象 */ try { Context cxt = new InitialContext(); // 2查询出入口 // Context envContext = (Context)cxt.lookup("java:comp/env"); // 3. 再进行二次查询,找到咱们的资源 // 使用的是名称与<Resource>元素的name对应 // DataSource dataSource = (DataSource)envContext.lookup("jdbc/dataSource"); DataSource dataSource = (DataSource)cxt.lookup("java:comp/env/jdbc/dataSource"); Connection con = dataSource.getConnection(); System.out.println(con); con.close(); } catch (Exception e) { throw new RuntimeException(e); } } }
参考:https://www.cnblogs.com/xdp-gacl/p/4002804.html