人生需要总结

手写DAO框架(三)-数据库连接

-------前篇:手写DAO框架(二)-开发前的最后准备---------

前言

上一篇主要是温习了一下基础知识,然后将整个项目按照模块进行了划分。因为是个人项目,一个人开发,本人采用了自底向上的开发。

本篇会介绍连接层的代码,包括三部分:pom配置、数据库连接和数据库连接池。

pom配置

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 3     <modelVersion>4.0.0</modelVersion>
 4 
 5     <groupId>me.lovegao</groupId>
 6     <artifactId>gdao</artifactId>
 7     <version>0.0.2-SNAPSHOT</version>
 8     <packaging>jar</packaging>
 9 
10     <name>gdao</name>
11     <url>http://maven.apache.org</url>
12 
13     <properties>
14         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15         <junit.version>4.12</junit.version>
16     </properties>
17 
18     <dependencies>
19         <dependency>
20             <groupId>junit</groupId>
21             <artifactId>junit</artifactId>
22             <version>${junit.version}</version>
23             <scope>test</scope>
24         </dependency>
25         <dependency>
26             <groupId>org.slf4j</groupId>
27             <artifactId>slf4j-api</artifactId>
28             <version>1.7.12</version>
29         </dependency>
30         <dependency>
31             <groupId>com.alibaba</groupId>
32             <artifactId>fastjson</artifactId>
33             <version>1.2.31</version>
34         </dependency>
35         <dependency>
36             <groupId>org.apache.commons</groupId>
37             <artifactId>commons-lang3</artifactId>
38             <version>3.2.1</version>
39         </dependency>
40         <dependency>
41             <groupId>mysql</groupId>
42             <artifactId>mysql-connector-java</artifactId>
43             <version>5.1.31</version>
44         </dependency>
45     </dependencies>
46     <build>
47         <finalName>me_lovegao_gdao</finalName>
48         <plugins>
49             <plugin>
50                 <groupId>org.apache.maven.plugins</groupId>
51                 <artifactId>maven-compiler-plugin</artifactId>
52                 <version>3.7.0</version>
53                 <configuration>
54                     <target>1.8</target>
55                     <source>1.8</source>
56                 </configuration>
57             </plugin>
58         </plugins>
59     </build>
60 </project>

 maven定义了jdk的版本,免得导入IDE的时候变成1.5,又得修改。

数据库连接

数据库连接主要就是为了获取数据库连接,这个模块不关注连接的使用、关闭等,只提供获取连接,其他逻辑由调用方负责。

书上、网上都说,要面向接口编程。

本来我是定义了一个接口的,但是最终实现的时候没有用上。回头看这段代码,感觉用上接口之后,依赖关系有点复杂,所以就没改了。

而且,这里的获取连接,定位的是一个工具类,所以直接写了静态方法。

1、连接获取类:ConnectionGetor

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.util.Properties;
 6 
 7 import org.slf4j.Logger;
 8 import org.slf4j.LoggerFactory;
 9 
10 import me.lovegao.gdao.bean.SystemConstant;
11 
12 public class ConnectionGetor {
13     private final static Logger log = LoggerFactory.getLogger(ConnectionGetor.class);
14 
15     public static Connection createConnection(Properties properties) throws Exception {
16         Connection conn = null;
17         String driverName = properties.getProperty(SystemConstant.STR_DRIVER_NAME);
18         String url = properties.getProperty(SystemConstant.STR_CONNECTION_URL);
19         String userName = properties.getProperty(SystemConstant.STR_USER_NAME);
20         String passwd = properties.getProperty(SystemConstant.STR_USER_PASSWORD);
21         try {
22             Class.forName(driverName);
23             conn = DriverManager.getConnection(url, userName, passwd);
24         } catch (Exception e) {
25             log.error("createConnectionException", e);
26         }
27         return conn;
28     }
29 
30 }

为了保证通用性,入参我选择了Properties类型。这样,配置既可以从本地加载,也可以从流加载,确保了灵活性。

为了能够支持多数据源,所以这个方法里没有定义和数据库连接相关的局部变量或者常量。

 

2、数据库实例配置:Properties

说到了配置,我就把配置的字段先列一下。毕竟框架再好用,不教人怎么配置就是扯。

##驱动名称
driverName=com.mysql.jdbc.Driver
##连接url
connectionUrl=jdbc:mysql://localhost:3306/simple?useServerPrepStmts=false&rewriteBatchedStatements=true&connectTimeout=1000&useUnicode=true&characterEncoding=utf-8
##用户名
userName=name
##用户密码
userPassword=123456
##初始化连接数
initConnectionNum=10
##最大连接数
maxConnectionNum=50
##最大查询等待时间-秒
maxQueryTime=3

配置这块就是这样的。为什么定义了这几个配置,在前两篇文章里也有涉及,这里不再赘述。

数据库连接池

因为数据库连接的创建是需要时间的,我们可以在项目初始化的时候先创建一批连接,在需要使用连接的时候可以节省创建连接的等待时间,从而提高程序响应速度。而且,连接是可以复用的,所以,项目中使用连接池是有意义的。

要面向接口编程,所以这里先定义接口。

1、连接池接口:IConnectionPool

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 
 5 /**
 6  * 连接池
 7  * @author simple
 8  *
 9  */
10 public interface IConnectionPool {
11     /**
12      * 获取连接
13      * @return
14      */
15     Connection getConnection() throws Exception;
16     /**
17      * 归还连接
18      * @param conn
19      */
20     void returnConnection(Connection conn);
21     
22     /**
23      * 获取查询超时时间
24      * @return
25      */
26     int getQueryTimeoutSecond();
27 }

 从代码可以看到,连接池的主要功能包括:获取连接,归还连接。

“获取查询超时时间”这个接口是我后来加的,作为一个框架,应该保证不因为一个查询耗时超过正常区间仍然义无反顾的等待。只不过加在这里,感觉不太优雅,暂时也没有想到更好的写法,所以还是暂时放在这里吧。(有建议欢迎提出)

2、连接池的简单实现:SimpleConnectionPool

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 import java.util.Map;
 5 import java.util.Properties;
 6 import java.util.Queue;
 7 import java.util.concurrent.ConcurrentHashMap;
 8 import java.util.concurrent.ConcurrentLinkedQueue;
 9 import java.util.concurrent.atomic.AtomicInteger;
10 
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 
14 import me.lovegao.gdao.bean.SystemConstant;
15 
16 public class SimpleConnectionPool implements IConnectionPool {
17     private final static Logger log = LoggerFactory.getLogger(SimpleConnectionPool.class);
18     /**配置**/
19     private Properties properties;
20     /**保存连接的map**/
21     private final Map<Integer, Connection> CONNECTION_MAP_POOL = new ConcurrentHashMap();
22     /**连接池的连接索引**/
23     private final Queue<Integer> CONNECTION_KEY_POOL = new ConcurrentLinkedQueue();
24     /**连接池初始连接数量**/
25     private int POOL_INIT_NUM;
26     /**连接池最大连接数量**/
27     private int POOL_MAX_NUM;
28     /**已创建连接数量**/
29     private AtomicInteger POOL_CREATE_NUM = new AtomicInteger(0);
30     /**查询超时时间-秒**/
31     private int QUERY_TIMEOUT_SECONDS;
32     
33     public SimpleConnectionPool(Properties properties) throws Exception {
34         this.properties = properties;
35         this.POOL_INIT_NUM = Integer.parseInt(properties.getProperty(SystemConstant.STR_INIT_CONNECTION_NUM));
36         this.POOL_MAX_NUM = Integer.parseInt(properties.getProperty(SystemConstant.STR_MAX_CONNECTION_NUM));
37         this.QUERY_TIMEOUT_SECONDS = Integer.parseInt(properties.getProperty(SystemConstant.STR_QUERY_TIME));
38         for(int i=0; i<POOL_INIT_NUM; i++) {
39             POOL_CREATE_NUM.incrementAndGet();
40             Connection conn = ConnectionGetor.createConnection(properties);
41             CONNECTION_MAP_POOL.put(conn.hashCode(), conn);
42             CONNECTION_KEY_POOL.add(conn.hashCode());
43         }
44     }
45     
46     @Override
47     public Connection getConnection() throws Exception {
48         Connection conn = null;
49         Integer connKey = CONNECTION_KEY_POOL.poll();
50         if(connKey == null) {
51             if(POOL_CREATE_NUM.intValue() < POOL_MAX_NUM) {
52                 int poolNum = POOL_CREATE_NUM.incrementAndGet();
53                 if(poolNum <= POOL_MAX_NUM) {
54                     conn = ConnectionGetor.createConnection(properties);
55                     CONNECTION_MAP_POOL.put(conn.hashCode(), conn);
56                 } else {
57                     POOL_CREATE_NUM.decrementAndGet();
58                 }
59             }
60         } else {
61             conn = CONNECTION_MAP_POOL.get(connKey);
62         }
63         //没有获取到连接
64         if(conn == null) {
65             throw new NullPointerException("连接池连接用完");
66         }
67         return conn;
68     }
69     
70     @Override
71     public void returnConnection(Connection conn) {
72         if(conn != null) {
73             try {
74                 if(conn.isClosed()) {
75                     CONNECTION_MAP_POOL.remove(conn.hashCode());
76                     POOL_CREATE_NUM.decrementAndGet();
77                 } else {
78                     CONNECTION_KEY_POOL.add(conn.hashCode());
79                 }
80             } catch (Exception e) {
81                 log.error("returnConnectionException", e);
82             }
83         }
84     }
85 
86     @Override
87     public int getQueryTimeoutSecond() {
88         return QUERY_TIMEOUT_SECONDS;
89     }
90 
91 }

逻辑简介:初始化的时候,先创建指定数量的连接存起来。在获取连接的时候,如果还有连接,就返回;如果没有连接了,就判断连接数是否到了最大上限,没有到就创建并返回,到上限了就返回空。

其他说明:

    为了保证并发安全,这里是通过POOL_CREATE_NUM变量来保证的。

    为了支持多数据源,这里同样没有静态变量。

    为了保存连接,这里定义了两个容器,分别是Map<Integer, Connection> CONNECTION_MAP_POOL和 Queue<Integer> CONNECTION_KEY_POOL。为了并发安全,使用了ConcurrentHashMap和ConcurrentLinkedQueue来存储。

        其中,队列是用于取出连接和返回连接,这里队列存放的是连接对应的key。MAP是为了保存对所有连接的引用,防止一些连接没有归还而系统却不知道,同时也可以知道连接的总数。也有一点考虑,是为了以后可以有单独线程来定时判断哪些线程应该关闭,主要是为了以后扩展用。

 

最初,我想把连接池做成那种:获取连接的时候,如果没有连接,就等待一段时间(指定时间,即获取连接等待时间)之后再试。查了一下对应的技术栈,综合考虑了一下,感觉有些不足之处,比如:如果连接用完了,可能出现大量线程等待的现象,反而会影响性能。

后来参考了一下其他连接池的实现,最终做成目前的这样:获取连接的时候,没有连接,看能不能创建,能创建就创建,不能创建就返回失败。逻辑简单,容易实现,快速错误。大概这就是fast-fail吧!

 

--本文内容到这里就结束了,欢迎指导交流--

 

下篇内容:手写DAO框架(四)-SQL执行

posted @ 2019-06-18 21:30  水木桶  阅读(...)  评论(... 编辑 收藏