zoukankan      html  css  js  c++  java
  • 手写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执行

  • 相关阅读:
    CSS概念
    CSS概念
    javascript 操作符小结
    jquery插件-自由拖拽
    MySQL随手记
    intellij 引入本地库并war打包
    Spring学习笔记3——消息队列(rabbitmq), 发送邮件
    RabbitMQ在mac上的安装
    Spring学习笔记2——表单数据验证、文件上传
    Spring学习笔记1——IOC: 尽量使用注解以及java代码
  • 原文地址:https://www.cnblogs.com/shuimutong/p/11043348.html
Copyright © 2011-2022 走看看