-------前篇:DAO框架-开发前的最后准备(二)---------
前言
上一篇主要是温习了一下基础知识,然后将整个项目按照模块进行了划分。因为是个人项目,一个人开发,本人采用了自底向上的开发。
本篇会介绍连接层的代码,包括两个模块:数据库连接的创建和连接池的编写。
数据库连接
数据库连接方法主要就是为了获取数据库连接,这一层不关注连接的使用、关闭等,只提供获取连接。
书上、网上都说,要面向接口编程。
本来我是定义了一个接口的,但是最终实现的时候没有使用上。现在再看这段代码,感觉用上接口之后,依赖关系有点复杂,所以就没改了。
而且,这里的获取连接,定位的是一个工具类,所以直接写了静态方法。具体如下:
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 }
同样为了支持多个数据库实例,所以没有静态变量。
为了保存连接,这里定义了两个容器,分别是Map<Integer, Connection> CONNECTION_MAP_POOL和 Queue<Integer> CONNECTION_KEY_POOL。为了并发安全,所以使用了ConcurrentHashMap和ConcurrentLinkedQueue来存储。
其中,队列是用于取出连接和返回连接。
MAP是为了保存对所有连接的引用,防止一些连接没有归还而系统却不知道。也有一点考虑,是为了以后可以有单独线程来定时判断哪些线程应该关闭。主要是为了以后考虑,具体怎么做,目前还没有想清楚。
写这个之前,我想把连接池做成那种,获取连接的时候,如果没有连接,就等待一段时间之后再试。查了一下对应的技术栈,感觉实现起来有点复杂。后来参考了一下其他连接池的实现,最终做成目前的这样:获取连接的时候,没有连接,看能不能创建,能创建就创建,不能创建就返回失败。逻辑简单,容易实现,快速错误。