数据库连接池原理
什么是连接?
连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。
有人接受这种说法,却不知道它的真正含义。因此,下面我将解释它究竟是什么。[如果你已经知道了,你可以跳到它的工作原理部分]
创建连接的代码片段:
1 2 3 | String connUrl = "jdbc:mysql://your.database.domain/yourDBname" ; Class.forName( "com.mysql.jdbc.Driver" ); Connection con = DriverManager.getConnection (connUrl); |
当我们创建了一个Connection对象,它在内部都执行了什么:
1.“DriverManager”检查并注册驱动程序,
2.“com.mysql.jdbc.Driver”就是我们注册了的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根据我们请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
4.创建的Socket连接将被用来查询我们指定的数据库,并最终让程序返回得到一个结果。
为什么昂贵?
现在让我们谈谈为什么说它“昂贵“。
如果创建Socket连接花费的时间比实际的执行查询的操作所花费的时间还要更长。
这就是我们所说的“数据库连接很昂贵”,因为连接资源数是1,它需要每次创建一个Socket连接来访问DB。
因此,我们将使用连接池。
连接池初始化时创建一定数量的连接,然后从连接池中重用连接,而不是每次创建一个新的。
怎样工作?
接下来我们来看看它是如何工作,以及如何管理或重用现有的连接。
我们使用的连接池供应者,它的内部有一个连接池管理器,当它被初始化:
1.它创建连接池的默认大小,比如指定创建5个连接对象,并把它存放在“可用”状态的任何集合或数组中。
编写连接池需实现java.sql.DataSource接口,
DataSource接口中定义了两个重载的getConnection方法:
Connection getConnection()
Connection getConnection(String username, String password)
- public class JdbcPool implements DataSource {
- private static String driver;
- private static String url;
- private static String username;
- private static String password;
- static{
- try {
- Properties prop = new Properties();
- InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
- prop.load(in);
- driver = prop.getProperty("driver");
- url = prop.getProperty("url");
- username = prop.getProperty("username");
- password = prop.getProperty("password");
- Class.forName(driver);
- } catch (Exception e) {
- throw new ExceptionInInitializerError(e);
- }
- }
- private static LinkedList<Connection> pool = new LinkedList<Connection>();
- private static int poolsize = 10;
- //问题:每次newJdbcPoll都会建立10个链接,可使用单态设计模式解决此类问题
- public JdbcPool(){
- for(int i=0;i<poolsize;i++){
- try {
- Connection conn = DriverManager.getConnection(url,username,password);
- pool.add(conn);
- System.out.println(conn + "被加到池里面了!!!");
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- /*
- * 这里使用动态代理技术返回一个假的Connection,当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法
- * 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,否则,调用真connection的对应方法。
- * (non-Javadoc)
- * @see javax.sql.DataSource#getConnection()
- */
- public Connection getConnection() throws SQLException { //spring aop
- if(pool.size()>0){
- final Connection conn = pool.removeFirst();
- System.out.println(conn + "从池里面取出去了!!!");
- return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(),conn.getClass().getInterfaces(), new InvocationHandler(){
- //proxy为代理对象 method为要调用的方法 args为方法的参数
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- if(method.getName().equals("close")){
- pool.addFirst(conn);
- System.out.println(conn + "被还到池里面了!!");
- return null;
- }else{
- return method.invoke(conn, args);
- }
- }
- });
- }
- throw new RuntimeException("对不起,池里没有资源了!!!");
- }
- public Connection getConnection(String arg0, String arg1)
- 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 arg0) throws SQLException {
- // TODO Auto-generated method stub
- }
- public void setLoginTimeout(int arg0) throws SQLException {
- // TODO Auto-generated method stub
- }
- //public Jdbcpool
- }
public class JdbcPool implements DataSource { private static String driver; private static String url; private static String username; private static String password; static{ try { Properties prop = new Properties(); InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties"); prop.load(in); driver = prop.getProperty("driver"); url = prop.getProperty("url"); username = prop.getProperty("username"); password = prop.getProperty("password"); Class.forName(driver); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } private static LinkedList<Connection> pool = new LinkedList<Connection>(); private static int poolsize = 10; //问题:每次newJdbcPoll都会建立10个链接,可使用单态设计模式解决此类问题 public JdbcPool(){ for(int i=0;i<poolsize;i++){ try { Connection conn = DriverManager.getConnection(url,username,password); pool.add(conn); System.out.println(conn + "被加到池里面了!!!"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /* * 这里使用动态代理技术返回一个假的Connection,当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法 * 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,否则,调用真connection的对应方法。 * (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws SQLException { //spring aop if(pool.size()>0){ final Connection conn = pool.removeFirst(); System.out.println(conn + "从池里面取出去了!!!"); return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(),conn.getClass().getInterfaces(), new InvocationHandler(){ //proxy为代理对象 method为要调用的方法 args为方法的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("close")){ pool.addFirst(conn); System.out.println(conn + "被还到池里面了!!"); return null; }else{ return method.invoke(conn, args); } } }); } throw new RuntimeException("对不起,池里没有资源了!!!"); } public Connection getConnection(String arg0, String arg1) 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 arg0) throws SQLException { // TODO Auto-generated method stub } public void setLoginTimeout(int arg0) throws SQLException { // TODO Auto-generated method stub } //public Jdbcpool }
一般实现第一个就可了,数据库的信息都 是通过配置文件来实现的,
通过加载配置文件就可以,取得与数据库的连接
实现DataSource接口,并实现连接池功能的步骤:
1.在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中
因为LinkedList 是用链表实现的,对于增删实现起来比较容易
因为每从池中取出一个连接,都要将这个对象从池不删除,当返回时就要添加回去
2.实现getConnection方法,让getConnection方法每次调用时,
从LinkedList中取一个Connection返回给用户(即要从池中删除一个对象)
3.当用户使用完Connection,调用Connection.close()方法时,
Collection对象应保证将自己返回到LinkedList中
(用户一般都会调用Connection的close方法来关闭连接,从而不能将这个连接返回给池,
所以这里采用了动态代理技术:赤获取用户关闭连接的操作,当获取到时而不是将其给关闭,
而是将这个连接(对象)添加到池中)
注:
** 这里使用动态代理技术返回一个假的Connection,
当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法
** 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,
否则,调用真connection的对应方法。
开源的数据库连接池(数据源)
都提供DataSoruce的实现,即连接池的实现
DBCP 数据库连接池 C3P0 数据库连接池
DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
它用到了一个配置文件dbcpconfig.properties(放在类路径下)
用BaseDataSourceFactory 对象 装载这个配置文件
- private static DataSource dataSource;
- static {
- try {
- InputStream in = JdbcUtil.class.getClassLoader()
- .getResourceAsStream("dbcpconfig.properties");
- Properties prop = new Properties();
- prop.load(in);
- BasicDataSourceFactory factory = new BasicDataSourceFactory();
- dataSource = factory.createDataSource(prop);
- } catch (Exception e) {
- throw new ExceptionInInitializerError(e);
- }
- }
- public static Connection getConnection() throws SQLException {
- return dataSource.getConnection();
- }
private static DataSource dataSource; static { try { InputStream in = JdbcUtil.class.getClassLoader() .getResourceAsStream("dbcpconfig.properties"); Properties prop = new Properties(); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory(); dataSource = factory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }
在Tomcat 下实现数据库连接池
1.要在META-INF目录下建一个context.xml文件
- <?xml version="1.0" encoding="UTF-8"?>
- <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>
<?xml version="1.0" encoding="UTF-8"?> <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>
(这是将数据库连接对象绑定到一个名字中去,要用到时就通过这个名字来找这个对象)JNDI
2.还要将数据库驱动的JAR复制到Tomcat的LIB目录下,因为Tomcat起动时就会去初始化连接池
所以就会去找相应的JAR文件,其实这个配置是可以配在Tomcat conf目录下server.xml文件中的
, 为了不改变它原来的配置,所以配置有那也是一样有效的
3.取得连接,通过命名空间
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/dataSource");