数据访问接口

自己实现了一个简易的MySQL数据操作中间层,经过近一年的线上使用和维护,功能已比较完善,性能方面也没有发现大的问题。诚然类似的开源工具有很多,但对于想快速了解其实现原理的同学来说,本文可以成为你的一个切入口。

ORM实体关系映射

import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@DataBase(name = DBName.ZHU_ZHAN)
@Table(name = "position")
public class Position {

    private static Log logger=LogFactory.getLog(Position.class);
    @Id
    private int id;
    private int companyId;
    @Column("createTime")
    private Date refreshTime;//最后一次的刷新时间
    @NotColumn
    private String refreshTimeStr;
}

类注解@DataBase和@Table分别注明该类跟哪个库哪张表对应。

  • 可增。refreshTimeStr是数据库中不存在的字段,加上@NotColumn注解。static成员变量不在数据库中,不需要加@NotColumn。从DB中select出数据后不会给实体的static变量和@NotColumn赋值。
  • 可减。数据库position表中还有其他很多字段,这里Position中都可以没有。当“select *”时实际上提交的请求是“select id,companyId,refreshTime”。
  • 可不同。数据库中的字段名是createTime,但我们在代码中用refreshTime更好理解。
  • 必须注明@Id。在进行update时需要知道主键。

数据访问

   1 import java.io.Serializable;
   2 import java.lang.reflect.Field;
   3 import java.lang.reflect.Modifier;
   4 import java.lang.reflect.ParameterizedType;
   5 import java.sql.ResultSet;
   6 import java.sql.SQLException;
   7 import java.sql.Statement;
   8 import java.sql.Timestamp;
   9 import java.text.SimpleDateFormat;
  10 import java.util.ArrayList;
  11 import java.util.Date;
  12 import java.util.HashMap;
  13 import java.util.HashSet;
  14 import java.util.List;
  15 import java.util.Map;
  16 import java.util.TimeZone;
  17 import java.util.Map.Entry;
  18 import java.util.Set;
  19 import java.util.concurrent.Callable;
  20 import java.util.concurrent.ExecutionException;
  21 import java.util.concurrent.ExecutorService;
  22 import java.util.concurrent.Executors;
  23 import java.util.concurrent.Future;
  24 import java.util.concurrent.TimeUnit;
  25 import java.util.concurrent.TimeoutException;
  26 
  27 import org.apache.commons.lang.StringUtils;
  28 import org.apache.commons.logging.Log;
  29 import org.apache.commons.logging.LogFactory;
  30 
  35 
  36 /**
  37  * 
  38  * @Author:orisun
  39  * @Since:2015-9-29
  40  * @Version:1.0
  41  */
  42 public class BaseDao<T, PK extends Serializable> {
  43 
  44     private static Log logger = LogFactory.getLog(BaseDao.class);
  45     protected final Class<T> aclass;
  46     protected final String TABLE;
  47     // SQL中的关键字,防SQL攻击
  48     private static Set<String> sqlKeywords = new HashSet<String>();
  49     private Map<String, Field> column2Field = new HashMap<String, Field>();// DB字段名=》类属性名
  50     private String allColumns = "";
  51     private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
  52     private static Set<Class<?>> validType = new HashSet<Class<?>>(); // 若要和DB类型对应,合法的java类型
  53     private static ExecutorService exec = Executors.newCachedThreadPool();
  54     private KVreport kvReporter = KVreport.getReporter();
  55     private int dbErrorKey = SystemConfig.getIntValue("db_error_key", -1);// 每次DB操作发生异常时上报
  56     private int dbTimeKey = SystemConfig.getIntValue("db_time_key", -1);// 每次的DB操作耗时都上报
  57 
  58     static {
  59         sqlKeywords.add("and");
  60         sqlKeywords.add("or");
  61         sqlKeywords.add("insert");
  62         sqlKeywords.add("select");
  63         sqlKeywords.add("delete");
  64         sqlKeywords.add("update");
  65         sqlKeywords.add("count");
  66         sqlKeywords.add("chr");
  67         sqlKeywords.add("mid");
  68         sqlKeywords.add("truncate");
  69         sqlKeywords.add("trunc");
  70         sqlKeywords.add("char");
  71         sqlKeywords.add("declare");
  72         sqlKeywords.add("like");
  73         sqlKeywords.add("%");
  74         sqlKeywords.add("<");
  75         sqlKeywords.add(">");
  76         sqlKeywords.add("=");
  77         sqlKeywords.add("\"");
  78         sqlKeywords.add("'");
  79         sqlKeywords.add(")");
  80         sqlKeywords.add("(");
  81         // 防止Xss攻击
  82         sqlKeywords.add("script");
  83         sqlKeywords.add("alert");
  84 
  85         validType.add(int.class);
  86         validType.add(Integer.class);
  87         validType.add(byte.class);
  88         validType.add(Byte.class);
  89         validType.add(Float.class);
  90         validType.add(float.class);
  91         validType.add(Short.class);
  92         validType.add(short.class);
  93         validType.add(Long.class);
  94         validType.add(long.class);
  95         validType.add(String.class);
  96         validType.add(Double.class);
  97         validType.add(double.class);
  98         validType.add(Date.class);
  99         validType.add(Timestamp.class);
 100     }
 101 
 102     /**
 103      * 判断str中是否包含SQL关键字
 104      * 
 105      * @param str
 106      * @return
 107      */
 108     protected boolean containSql(String str) {
 109         String[] arr = str.split("\\s+");
 110         for (String ele : arr) {
 111             if (sqlKeywords.contains(ele)) {
 112                 return true;
 113             }
 114         }
 115         return false;
 116     }
 117 
 118     @SuppressWarnings("unchecked")
 119     public BaseDao() throws Exception {
 120         // 获得超类的泛型参数(即T和PK)的首元素的实际类型(即T在运行时对应的实际类型)
 121         this.aclass = (Class<T>) ((ParameterizedType) getClass()
 122                 .getGenericSuperclass()).getActualTypeArguments()[0];
 123         if (aclass.isAnnotationPresent(Table.class)) {
 124             Table table = (Table) aclass.getAnnotation(Table.class);
 125             String name = table.name();
 126             if (name != null) {
 127                 this.TABLE = name;
 128             } else {
 129                 this.TABLE = "";
 130             }
 131         } else {
 132             this.TABLE = "";
 133         }
 134 
 135         if (this.TABLE == null || "".equals(this.TABLE)) {
 136             throw new Exception("have not specify the table name for "
 137                     + aclass.getCanonicalName());
 138         }
 139         Field[] fileds = aclass.getDeclaredFields();
 140         for (int i = 0; i < fileds.length; i++) {
 141             Field field = fileds[i];
 142             field.setAccessible(true);
 143             String columnName = field.getName();
 144             // 丢弃2种成员变量:静态和带NotColumn注解的
 145             if (!field.isAnnotationPresent(NotColumn.class)
 146                     && (field.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
 147                 if (field.isAnnotationPresent(Column.class)) {
 148                     columnName = field.getAnnotation(Column.class).value();
 149                 }
 150                 if (field.isAnnotationPresent(Id.class)
 151                         && field.getAnnotation(Id.class).auto_increment() == true) {
 152                     assert field.getType() == Integer.class
 153                             || field.getType() == Long.class;
 154                 }
 155                 column2Field.put(columnName.toLowerCase(), field);
 156             }
 157         }
 158         allColumns = StringUtils.join(column2Field.keySet(), ",");
 159     }
 160 
 161     /**
 162      * 获取一个主库连接
 163      * 
 164      * @return
 165      * @throws SQLException
 166      */
 167     public PooledConnection getMasterConn() throws SQLException {
 168         ConnectionPools pools = DaoHelperPool.getConnPool(aclass);
 169         if (pools != null) {
 170             PooledConnection conn = pools.getMasterPool().getConnection();
 171             return conn;
 172         }
 173         return null;
 174     }
 175 
 176     /**
 177      * 获取一个从库连接。从库连接没有时获取主库连接
 178      * 
 179      * @return
 180      * @throws SQLException
 181      */
 182     public PooledConnection getSlaveConn() throws SQLException {
 183         ConnectionPools pools = DaoHelperPool.getConnPool(aclass);
 184         if (pools != null) {
 185             PooledConnection conn = pools.getSlavePool().getConnection();
 186             return conn;
 187         }
 188         return null;
 189     }
 190 
 191     /**
 192      * 关闭一个从库的物理连接
 193      * 
 194      * @param conn
 195      */
 196     private void closeSlaveConnection(PooledConnection conn) {
 197         ConnectionPools pools = DaoHelperPool.getConnPool(aclass);
 198         if (pools != null) {
 199             ConnectionPool pool = pools.getSlavePool();
 200             pool.closeConnection(conn.getConnection());
 201         }
 202     }
 203 
 204     /**
 205      * 把主库连接返回连接池
 206      * 
 207      * @param conn
 208      */
 209     public void retrunMasterConn(PooledConnection conn) {
 210         ConnectionPools pools = DaoHelperPool.getConnPool(aclass);
 211         if (pools != null) {
 212             ConnectionPool pool = pools.getMasterPool();
 213             pool.returnConnection(conn);
 214         }
 215     }
 216 
 217     /**
 218      * 把从库连接返回连接池
 219      * 
 220      * @param conn
 221      */
 222     public void retrunSlaveConn(PooledConnection conn) {
 223         ConnectionPools pools = DaoHelperPool.getConnPool(aclass);
 224         if (pools != null) {
 225             ConnectionPool pool = pools.getSlavePool();
 226             pool.returnConnection(conn);
 227         }
 228     }
 229 
 230     /**
 231      * 分页读取数据<br>
 232      * 注意:使用完ResultSet后一定要调用ResultSet.close()
 233      * 
 234      * @param columns
 235      *            各列用逗号分隔,不区分大小写,允许使用"*"
 236      * @param where
 237      * @param pageNo
 238      *            页数,编号从1始
 239      * @param pageSize
 240      *            每页的大小,即使数据库中有充足的数据,返回的量也可能略少于pageSize
 241      * @return
 242      */
 243     @Deprecated
 244     public ResultSet getListByPage(String columns, String where, int pageNo,
 245             int pageSize) {
 246         if (columns == null || columns.length() == 0) {
 247             return null;
 248         }
 249         if (columns.contains("*")) {
 250             columns = allColumns;
 251         }
 252         columns = columns.toLowerCase();
 253         if (pageNo * pageSize > 5000) {
 254             logger.error("pageNo*pageSize  can't more than 5000");
 255             return null;
 256         }
 257         PooledConnection conn = null;
 258         ResultSet resultSet = null;
 259         // 当数据库设置了主键自增时,select出的结果默认就是按主键递增排序好的
 260         StringBuilder sql = new StringBuilder();
 261         sql.append("select ");
 262         sql.append(columns);
 263         sql.append(" from ");
 264         sql.append(TABLE);
 265         if (where != null && where.length() > 0) {
 266             sql.append(" where ");
 267             sql.append(where);
 268         }
 269         sql.append(" limit ");
 270         sql.append(pageSize * (pageNo - 1));
 271         sql.append(",");
 272         sql.append(pageSize);
 273         int timeout = 0;
 274         long begin = System.currentTimeMillis();
 275         try {
 276             conn = this.getSlaveConn();
 277             timeout = conn.getQueryTimeOut();
 278             final Statement statement = conn.getConnection().createStatement();
 279             final String sqlF = sql.toString();
 280             Future<ResultSet> futureResult = exec
 281                     .submit(new Callable<ResultSet>() {
 282                         @Override
 283                         public ResultSet call() throws Exception {
 284                             return statement.executeQuery(sqlF);
 285                         }
 286                     });
 287             resultSet = futureResult.get(timeout, TimeUnit.MILLISECONDS);
 288             // 如果返回结果数为0,则返回的ResultSet为null
 289             resultSet.last();
 290             if (resultSet.getRow() == 0) {
 291                 resultSet = null;
 292             } else {
 293                 resultSet.beforeFirst();
 294             }
 295         } catch (SQLException | InterruptedException | ExecutionException e) {
 296             logger.error("read data from " + TABLE + " failed", e);
 297             kvReporter.send(dbErrorKey, 1);
 298         } catch (TimeoutException e) {
 299             if (conn != null) {
 300                 // 超时,则直接关闭物理连接
 301                 this.closeSlaveConnection(conn);
 302             }
 303             logger.error("sql query timeout, SQL=" + sql.toString()
 304                     + ", time limit is " + timeout);
 305             kvReporter.send(dbErrorKey, 1);
 306         } finally {
 307             if (conn != null) {
 308                 // 正常使用完,返还连接
 309                 this.retrunSlaveConn(conn);
 310             }
 311             long end = System.currentTimeMillis();
 312             kvReporter.send(dbTimeKey, end - begin);
 313         }
 314         return resultSet;
 315     }
 316 
 317     /**
 318      * 分页读取数据
 319      * 
 320      * @param columns
 321      *            各列用逗号分隔,不区分大小写,允许使用"*"
 322      * @param where
 323      * @param pageNo
 324      *            页数,编号从1始
 325      * @param pageSize
 326      *            每页的大小,即使数据库中有充足的数据,返回的量也可能略少于pageSize
 327      * @return 发生异常时返回null,通常是TimeoutException或SQLException
 328      */
 329     public List<T> getDataByPage(String columns, String where, int pageNo,
 330             int pageSize) {
 331         return getDataByPage(columns, where, pageNo, pageSize, null);
 332     }
 333 
 334     /**
 335      * 分页读取数据
 336      * 
 337      * @param columns
 338      *            各列用逗号分隔,不区分大小写,允许使用"*"
 339      * @param where
 340      * @param pageNo
 341      *            页数,编号从1始
 342      * @param pageSize
 343      *            每页的大小,即使数据库中有充足的数据,返回的量也可能略少于pageSize
 344      * @param forceIndex
 345      *            显式指定要使用的索引名称
 346      * @return 发生异常时返回null,通常是TimeoutException或SQLException
 347      */
 348     public List<T> getDataByPage(String columns, String where, int pageNo,
 349             int pageSize, String forceIndex) {
 350         List<T> rect = new ArrayList<T>();
 351         if (columns == null || columns.length() == 0) {
 352             return rect;
 353         }
 354         if (columns.contains("*")) {
 355             columns = allColumns;
 356         }
 357         columns = columns.toLowerCase();
 358         if (pageNo * pageSize > 5000) {
 359             logger.error("pageNo*pageSize  can't more than 5000");
 360             return rect;
 361         }
 362         PooledConnection conn = null;
 363         ResultSet resultSet = null;
 364         Set<String> columnSet = new HashSet<String>();
 365         String[] arr = columns.split(",");
 366         for (String col : arr) {
 367             if (col.length() > 0) {
 368                 columnSet.add(col);
 369             }
 370         }
 371         // 当数据库设置了主键自增时,select出的结果默认就是按主键递增排序好的
 372         StringBuilder sql = new StringBuilder();
 373         sql.append("select ");
 374         sql.append(columns);
 375         sql.append(" from ");
 376         sql.append(TABLE);
 377         if (forceIndex != null && forceIndex.length() > 0) {
 378             sql.append(" force index(");
 379             sql.append(forceIndex);
 380             sql.append(")");
 381         }
 382         if (where != null && where.length() > 0) {
 383             sql.append(" where ");
 384             sql.append(where);
 385         }
 386         sql.append(" limit ");
 387         sql.append(pageSize * (pageNo - 1));
 388         sql.append(",");
 389         sql.append(pageSize);
 390         int timeout = 0;
 391         long begin = System.currentTimeMillis();
 392         try {
 393             conn = this.getSlaveConn();
 394             final Statement statement = conn.getConnection().createStatement();
 395             timeout = conn.getQueryTimeOut();
 396             final String sqlF = sql.toString();
 397             Future<ResultSet> futureResult = exec
 398                     .submit(new Callable<ResultSet>() {
 399                         @Override
 400                         public ResultSet call() throws Exception {
 401                             return statement.executeQuery(sqlF);
 402                         }
 403                     });
 404             resultSet = futureResult.get(timeout, TimeUnit.MILLISECONDS);
 405             while (resultSet.next()) {
 406                 T inst = po2Vo(resultSet, columnSet);
 407                 rect.add(inst);
 408             }
 409         } catch (SQLException | InterruptedException | ExecutionException e) {
 410             rect = null;
 411             logger.error("read data from " + TABLE + " failed", e);
 412             kvReporter.send(dbErrorKey, 1);
 413         } catch (TimeoutException e) {
 414             rect = null;
 415             if (conn != null) {
 416                 // 超时则关闭DB连接,这样就会造成返回连接池中的有无效连接,从连接池中获取连接时需要判断一下连接是否可用。
 417                 this.closeSlaveConnection(conn);
 418             }
 419             logger.error("sql query timeout, SQL=" + sql.toString()
 420                     + ", time limit is " + timeout);
 421             kvReporter.send(dbErrorKey, 1);
 422         } finally {
 423             try {
 424                 if (resultSet != null) {
 425                     resultSet.close();
 426                 }
 427             } catch (SQLException e) {
 428                 logger.error("close ResultSet failed", e);
 429                 kvReporter.send(dbErrorKey, 1);
 430             }
 431             if (conn != null) {
 432                 // 正常使用完,返还连接
 433                 this.retrunSlaveConn(conn);
 434             }
 435             long end = System.currentTimeMillis();
 436             kvReporter.send(dbTimeKey, end - begin);
 437         }
 438         return rect;
 439     }
 440 
 441     /**
 442      * in查询
 443      * 
 444      * @param columns
 445      *            要获取哪几列
 446      * @param collections
 447      * @param targetColumn
 448      *            在哪一列上进行where in查询
 449      * @return 发生异常时返回null
 450      */
 451     public <K extends Number> List<T> getIn(String columns, Set<K> collections,
 452             String targetColumn) {
 453         List<T> rect = new ArrayList<T>();
 454         if (columns == null || columns.length() == 0 || targetColumn == null
 455                 || targetColumn.length() == 0 || collections == null
 456                 || collections.size() == 0) {
 457             return rect;
 458         }
 459         if (columns.contains("*")) {
 460             columns = allColumns;
 461         }
 462         columns = columns.toLowerCase();
 463         PooledConnection conn = null;
 464         ResultSet resultSet = null;
 465         Set<String> columnSet = new HashSet<String>();
 466         String[] arr = columns.split(",");
 467         for (String col : arr) {
 468             if (col.length() > 0) {
 469                 columnSet.add(col);
 470             }
 471         }
 472         // 当数据库设置了主键自增时,select出的结果默认就是按主键递增排序好的
 473         StringBuilder sql = new StringBuilder();
 474         sql.append("select ");
 475         sql.append(columns);
 476         sql.append(" from ");
 477         sql.append(TABLE);
 478         sql.append(" where ");
 479         sql.append(targetColumn);
 480         sql.append(" in (");
 481         for (Number ele : collections) {
 482             sql.append(ele);
 483             sql.append(",");
 484         }
 485         sql.setCharAt(sql.length() - 1, ')');
 486         int timeout = 0;
 487         long begin = System.currentTimeMillis();
 488         try {
 489             conn = this.getSlaveConn();
 490             final Statement statement = conn.getConnection().createStatement();
 491             timeout = conn.getQueryTimeOut();
 492             final String sqlF = sql.toString();
 493             Future<ResultSet> futureResult = exec
 494                     .submit(new Callable<ResultSet>() {
 495                         @Override
 496                         public ResultSet call() throws Exception {
 497                             return statement.executeQuery(sqlF);
 498                         }
 499 
 500                     });
 501             resultSet = futureResult.get(timeout, TimeUnit.MILLISECONDS);
 502             while (resultSet.next()) {
 503                 T inst = po2Vo(resultSet, columnSet);
 504                 rect.add(inst);
 505             }
 506         } catch (SQLException | InterruptedException | ExecutionException e) {
 507             rect = null;
 508             logger.error("read data from " + TABLE + " failed", e);
 509             kvReporter.send(dbErrorKey, 1);
 510         } catch (TimeoutException e) {
 511             rect = null;
 512             if (conn != null) {
 513                 // 超时则关闭DB连接,这样就会造成返回连接池中的有无效连接,从连接池中获取连接时需要判断一下连接是否可用。
 514                 this.closeSlaveConnection(conn);
 515             }
 516             logger.error("sql query timeout, SQL=" + sql.toString()
 517                     + ", time limit is " + timeout);
 518             kvReporter.send(dbErrorKey, 1);
 519         } finally {
 520             try {
 521                 if (resultSet != null) {
 522                     resultSet.close();
 523                 }
 524             } catch (SQLException e) {
 525                 logger.error("close ResultSet failed", e);
 526                 kvReporter.send(dbErrorKey, 1);
 527             }
 528             if (conn != null) {
 529                 // 正常使用完,返还连接
 530                 this.retrunSlaveConn(conn);
 531             }
 532             long end = System.currentTimeMillis();
 533             kvReporter.send(dbTimeKey, end - begin);
 534         }
 535         return rect;
 536     }
 537 
 538     /**
 539      * 从迭代器ResultSet中读出一个实体
 540      * 
 541      * @param resultSet
 542      * @return
 543      */
 544     private T po2Vo(ResultSet resultSet, Set<String> columns) {
 545         try {
 546             @SuppressWarnings("unchecked")
 547             T inst = (T) Class.forName(aclass.getName()).newInstance();
 548             for (Entry<String, Field> entry : column2Field.entrySet()) {
 549                 Field field = entry.getValue();
 550                 String columnName = entry.getKey();
 551                 if (columns.contains("*") || columns.contains(columnName)) {
 552                     if (field.getType() == Integer.class
 553                             || field.getType() == int.class) {
 554                         field.set(inst, resultSet.getInt(columnName));
 555                     } else if (field.getType() == Long.class
 556                             || field.getType() == long.class) {
 557                         field.set(inst, resultSet.getLong(columnName));
 558                     } else if (field.getType() == Double.class
 559                             || field.getType() == double.class) {
 560                         field.set(inst, resultSet.getDouble(columnName));
 561                     } else if (field.getType() == String.class) {
 562                         field.set(inst, resultSet.getString(columnName));
 563                     }
 564                     // MySQL中的datetime和timestamp都只精确到秒
 565                     else if (field.getType() == Date.class) {
 566                         // resultSet.getDate()返回年月日(精确到天),resultSet.getTime()返回时分秒部分(精确到秒)
 567                         field.set(inst, new Date(resultSet.getDate(columnName)
 568                                 .getTime()
 569                                 + resultSet.getTime(columnName).getTime()
 570                                 + TimeZone.getDefault().getRawOffset()));// 注意加上时间偏置
 571                     } else if (field.getType() == Short.class
 572                             || field.getType() == short.class) {
 573                         field.set(inst, resultSet.getShort(columnName));
 574                     } else if (field.getType() == Timestamp.class) {
 575                         field.set(inst, resultSet.getTimestamp(columnName));
 576                     } else if (field.getType() == Byte.class
 577                             || field.getType() == byte.class) {
 578                         field.set(inst, resultSet.getByte(columnName));
 579                     } else if (field.getType() == Float.class
 580                             || field.getType() == float.class) {
 581                         field.set(inst, resultSet.getFloat(columnName));
 582                     }
 583                 }
 584             }
 585             return inst;
 586         } catch (Exception e) {
 587             logger.error("parse column failed", e);
 588             kvReporter.send(dbErrorKey, 1);
 589             return null;
 590         }
 591     }
 592 
 593     /**
 594      * 根据主键获得一个实体
 595      * 
 596      * @param id
 597      * @return
 598      */
 599     public T getById(PK id) {
 600         T rect = null;
 601         PooledConnection conn = null;
 602         ResultSet resultSet = null;
 603         String sql = "select " + allColumns + " from " + TABLE + " where id="
 604                 + id;
 605         long begin = System.currentTimeMillis();
 606         try {
 607             conn = this.getSlaveConn();
 608             Statement statement = conn.getConnection().createStatement();
 609             resultSet = statement.executeQuery(sql);
 610             if (resultSet.next()) {
 611                 rect = po2Vo(resultSet, column2Field.keySet());
 612             }
 613         } catch (SQLException e) {
 614             logger.error("read data from " + TABLE + " failed", e);
 615             kvReporter.send(dbErrorKey, 1);
 616         } finally {
 617             if (resultSet != null) {
 618                 try {
 619                     resultSet.close();
 620                 } catch (SQLException e) {
 621                     logger.error("close ResultSet failed", e);
 622                     kvReporter.send(dbErrorKey, 1);
 623                 }
 624             }
 625             if (conn != null) {
 626                 // 正常使用完,返还连接
 627                 this.retrunSlaveConn(conn);
 628                 ;
 629             }
 630             long end = System.currentTimeMillis();
 631             kvReporter.send(dbTimeKey, end - begin);
 632         }
 633         return rect;
 634     }
 635 
 636     /**
 637      * 根据条件删除一条记录
 638      * 
 639      * @param condition
 640      * @return 成功返回非负数,失败返回-1
 641      */
 642     public int delete(String condition) {
 643         int rect = -1;
 644 
 645         String sql = "delete from " + TABLE + " where " + condition;
 646         PooledConnection conn = null;
 647         long begin = System.currentTimeMillis();
 648         try {
 649             conn = this.getMasterConn();
 650             rect = conn.executeUpdate(sql);
 651         } catch (SQLException e) {
 652             logger.error("delete from " + TABLE + " failed, condition:"
 653                     + condition, e);
 654             kvReporter.send(dbErrorKey, 1);
 655         } finally {
 656             if (conn != null) {
 657                 this.retrunMasterConn(conn);
 658             }
 659             long end = System.currentTimeMillis();
 660             kvReporter.send(dbTimeKey, end - begin);
 661         }
 662         return rect;
 663     }
 664 
 665     /**
 666      * 删除指定column值属于集合ids的行<br>
 667      * 注意:column必须是索引
 668      * 
 669      * @param ids
 670      * @param column
 671      * @return
 672      */
 673     public int deleteIn(List<? extends Number> ids, String column) {
 674         int rect = -1;
 675         StringBuilder sql = new StringBuilder();
 676         sql.append("delete from ");
 677         sql.append(TABLE);
 678         sql.append(" where ");
 679         sql.append(column);
 680         sql.append(" in (");
 681         for (Number ele : ids) {
 682             sql.append(ele);
 683             sql.append(",");
 684         }
 685         sql.setCharAt(sql.length() - 1, ')');
 686         PooledConnection conn = null;
 687         long begin = System.currentTimeMillis();
 688         try {
 689             conn = this.getMasterConn();
 690             rect = conn.executeUpdate(sql.toString());
 691         } catch (SQLException e) {
 692             logger.error("delete from " + TABLE + " failed", e);
 693             kvReporter.send(dbErrorKey, 1);
 694         } finally {
 695             if (conn != null) {
 696                 this.retrunMasterConn(conn);
 697             }
 698             long end = System.currentTimeMillis();
 699             kvReporter.send(dbTimeKey, end - begin);
 700         }
 701         return rect;
 702     }
 703 
 704     /**
 705      * 插入一条新记录
 706      * 
 707      * @param entity
 708      * @return 成功返回受影响的行数,失败返回-1
 709      */
 710     public int insert(T entity) {
 711         int rect = -1;
 712         StringBuilder columnNames = new StringBuilder();
 713         StringBuilder values = new StringBuilder();
 714         try {
 715             for (Entry<String, Field> entry : column2Field.entrySet()) {
 716                 Field field = entry.getValue();
 717                 if ((!field.isAnnotationPresent(Id.class) || field
 718                         .getAnnotation(Id.class).auto_increment() == false)
 719                         && validType.contains(field.getType())) {
 720                     String columnName = entry.getKey();
 721                     columnNames.append(columnName);
 722                     columnNames.append(",");
 723                     Object value = field.get(entity);
 724                     if (value == null) {
 725                         values.append("null");
 726                     } else if (field.getType() == Date.class
 727                             || field.getType() == Timestamp.class) {
 728                         values.append(sdf.format((Date) value));
 729                     } else if (field.getType() == String.class) {
 730                         String sv = (String) value;
 731                         if (containSql(sv)) {
 732                             logger.warn("danger! sql injection:" + sv);
 733                             sv = "";
 734                         }
 735                         values.append("'" + sv + "'");
 736                     } else {
 737                         values.append(value);
 738                     }
 739                     values.append(",");
 740                 }
 741             }
 742 
 743         } catch (Exception e) {
 744             logger.error("reflect " + entity.getClass().getCanonicalName()
 745                     + " entity failed", e);
 746             kvReporter.send(dbErrorKey, 1);
 747         }
 748 
 749         if (columnNames.length() > 0) {
 750             StringBuilder sql = new StringBuilder("insert into ");
 751             sql.append(TABLE);
 752             sql.append(" (");
 753             sql.append(columnNames.subSequence(0, columnNames.length() - 1));
 754             sql.append(") values (");
 755             sql.append(values.subSequence(0, values.length() - 1));
 756             sql.append(")");
 757             PooledConnection conn = null;
 758             long begin = System.currentTimeMillis();
 759             try {
 760                 conn = this.getMasterConn();
 761                 rect = conn.executeUpdate(sql.toString());
 762             } catch (Exception e) {
 763                 // 如果 是因为唯一键值冲突,则不打印日志
 764                 if (!e.getMessage().contains("Duplicate entry")) {
 765                     logger.error("insert data into " + TABLE + " failed", e);
 766                     kvReporter.send(dbErrorKey, 1);
 767                 }
 768                 rect = -1;
 769             } finally {
 770                 if (conn != null) {
 771                     this.retrunMasterConn(conn);
 772                 }
 773                 long end = System.currentTimeMillis();
 774                 kvReporter.send(dbTimeKey, end - begin);
 775             }
 776         }
 777         return rect;
 778     }
 779 
 780     /**
 781      * 批量插入数据
 782      * 
 783      * @param entities
 784      * @return 成功返回受影响的行数,失败返回-1
 785      */
 786     public int batchInsert(List<T> entities) {
 787         if (entities == null || entities.size() == 0) {
 788             return 0;
 789         }
 790         int rect = -1;
 791         StringBuilder sqlBuffer = new StringBuilder("insert into ");
 792         sqlBuffer.append(TABLE);
 793         StringBuilder columnNames = new StringBuilder();
 794         for (Entry<String, Field> entry : column2Field.entrySet()) {
 795             Field field = entry.getValue();
 796             if ((!field.isAnnotationPresent(Id.class) || field.getAnnotation(
 797                     Id.class).auto_increment() == false)
 798                     && validType.contains(field.getType())) {
 799                 String columnName = entry.getKey();
 800                 columnNames.append(columnName);
 801                 columnNames.append(",");
 802             }
 803         }
 804         sqlBuffer.append(" (");
 805         sqlBuffer.append(columnNames.subSequence(0, columnNames.length() - 1));
 806         sqlBuffer.append(") values ");
 807 
 808         for (T entity : entities) {
 809             StringBuilder values = new StringBuilder();
 810             try {
 811                 for (Entry<String, Field> entry : column2Field.entrySet()) {
 812                     Field field = entry.getValue();
 813                     if ((!field.isAnnotationPresent(Id.class) || field
 814                             .getAnnotation(Id.class).auto_increment() == false)
 815                             && validType.contains(field.getType())) {
 816                         Object value = field.get(entity);
 817                         if (value == null) {
 818                             values.append("null");
 819                         } else if (field.getType() == Date.class
 820                                 || field.getType() == Timestamp.class) {
 821                             values.append(sdf.format((Date) value));
 822                         } else if (field.getType() == String.class) {
 823                             String sv = (String) value;
 824                             if (containSql(sv)) {
 825                                 logger.warn("danger! sql injection:" + sv);
 826                                 sv = "";
 827                             }
 828                             values.append("'" + sv + "'");
 829                         } else {
 830                             values.append(value);
 831                         }
 832                         values.append(",");
 833                     }
 834                 }
 835 
 836             } catch (Exception e) {
 837                 logger.error("reflect " + entity.getClass().getCanonicalName()
 838                         + " entity failed", e);
 839                 kvReporter.send(dbErrorKey, 1);
 840             }
 841 
 842             if (values.length() > 0) {
 843                 sqlBuffer.append("(");
 844                 sqlBuffer.append(values.subSequence(0, values.length() - 1));
 845                 sqlBuffer.append("),");
 846             }
 847         }
 848         PooledConnection conn = null;
 849         String sql = sqlBuffer.substring(0, sqlBuffer.length() - 1).toString();
 850         long begin = System.currentTimeMillis();
 851         try {
 852             conn = this.getMasterConn();
 853             rect = conn.executeUpdate(sql);
 854         } catch (Exception e) {
 855             // 如果是因为唯一键(包括主键在内)值冲突,则不打印日志
 856             if (!e.getMessage().contains("Duplicate entry")) {
 857                 logger.error("insert data into " + TABLE + " failed, sql="
 858                         + sql, e);
 859                 kvReporter.send(dbErrorKey, 1);
 860             }
 861             rect = -1;
 862         } finally {
 863             if (conn != null) {
 864                 this.retrunMasterConn(conn);
 865             }
 866             long end = System.currentTimeMillis();
 867             kvReporter.send(dbTimeKey, end - begin);
 868         }
 869         return rect;
 870     }
 871 
 872     /**
 873      * 根据主键更新一条记录
 874      * 
 875      * @param entity
 876      * @return 如果记录不存在则返回0,更新成功返回正数,更新失败返回-1
 877      */
 878     public int update(T entity) {
 879         int rect = -1;
 880         List<String> columnNames = new ArrayList<String>();
 881         List<String> values = new ArrayList<String>();
 882         String condition = null;
 883         try {
 884             for (Entry<String, Field> entry : column2Field.entrySet()) {
 885                 Field field = entry.getValue();
 886                 String columnName = entry.getKey();
 887                 Object value = field.get(entity);
 888                 if (field.isAnnotationPresent(Id.class)) {
 889                     if (value == null) {
 890                         return 0;
 891                     }
 892                     if (field.getType() == Date.class
 893                             || field.getType() == Timestamp.class) {
 894                         condition = columnName + "=" + sdf.format((Date) value);
 895                     } else if (field.getType() == String.class) {
 896                         String sv = (String) value;
 897                         if (containSql(sv)) {
 898                             logger.warn("danger! sql injection:" + sv);
 899                             sv = "";
 900                         }
 901                         condition = columnName + "='" + sv + "'";
 902                     } else {
 903                         condition = columnName + "=" + value;
 904                     }
 905                 } else if (validType.contains(field.getType())) {
 906                     if (value != null) {
 907                         columnNames.add(columnName);
 908                         if (field.getType() == Date.class
 909                                 || field.getType() == Timestamp.class) {
 910                             values.add(sdf.format((Date) value));
 911                         } else if (field.getType() == String.class) {
 912                             String sv = (String) value;
 913                             if (containSql(sv)) {
 914                                 logger.warn("danger! sql injection:" + sv);
 915                                 sv = "";
 916                             }
 917                             values.add("'" + sv + "'");
 918                         } else {
 919                             values.add(value.toString());
 920                         }
 921                     }
 922                 }
 923             }
 924 
 925         } catch (Exception e) {
 926             logger.error("reflect " + entity.getClass().getCanonicalName()
 927                     + " entity failed", e);
 928             kvReporter.send(dbErrorKey, 1);
 929         }
 930 
 931         if (columnNames.size() > 0 && condition != null) {
 932             StringBuilder sql = new StringBuilder("update ");
 933             sql.append(TABLE);
 934             sql.append(" set ");
 935             int i = 0;
 936             for (; i < columnNames.size() - 1; i++) {
 937                 sql.append(columnNames.get(i) + "=" + values.get(i) + ",");
 938             }
 939             sql.append(columnNames.get(i) + "=" + values.get(i));
 940             sql.append(" where " + condition);
 941             PooledConnection conn = null;
 942             long begin = System.currentTimeMillis();
 943             try {
 944                 conn = this.getMasterConn();
 945                 rect = conn.executeUpdate(sql.toString());
 946             } catch (Exception e) {
 947                 logger.error("update " + TABLE + " failed", e);
 948                 kvReporter.send(dbErrorKey, 1);
 949                 rect = -1;
 950             } finally {
 951                 if (conn != null) {
 952                     this.retrunMasterConn(conn);
 953                 }
 954                 long end = System.currentTimeMillis();
 955                 kvReporter.send(dbTimeKey, end - begin);
 956             }
 957         }
 958         return rect;
 959     }
 960 
 961     /**
 962      * 根据主键删除一条记录
 963      * 
 964      * @param id
 965      * @return
 966      */
 967     public int deleteById(PK id) {
 968         String where = "id=" + id;
 969         return delete(where);
 970     }
 971 
 972     /**
 973      * 根据where条件进行count
 974      * 
 975      * @param condition
 976      * @return
 977      */
 978     public int count(String condition) {
 979         int rect = -1;
 980 
 981         String sql = "select count(*) from " + TABLE + " where " + condition;
 982         PooledConnection conn = null;
 983         long begin = System.currentTimeMillis();
 984         try {
 985             conn = this.getSlaveConn();
 986             ResultSet resultSet = conn.executeQuery(sql);
 987             if (resultSet != null) {
 988                 if (resultSet.next()) {
 989                     rect = resultSet.getInt(1); // 编号从1开始
 990                 }
 991                 resultSet.close();
 992             }
 993         } catch (SQLException e) {
 994             logger.error("count(*) from " + TABLE + " failed, condition:"
 995                     + condition, e);
 996             kvReporter.send(dbErrorKey, 1);
 997         } finally {
 998             if (conn != null) {
 999                 this.retrunSlaveConn(conn);
1000             }
1001             long end = System.currentTimeMillis();
1002             kvReporter.send(dbTimeKey, end - begin);
1003         }
1004         return rect;
1005     }
1006 
1007     /**
1008      * 慎用:慢查询,容易造成DB阻塞!<br>
1009      * 删除表里的所有数据
1010      * 
1011      * @return
1012      */
1013     public int deleteAll() {
1014         return delete("1=1");
1015     }
1016 }
View Code

请留意上述代码中有个方法 po2Vo,它实现了把java.sql.ResultSet转化成Java Bean(即负责实现ORM的功能),用的伎俩实际上就是反射。

BaseDao方法概要

方法 

限定符和类型

方法和说明

int

batchInsert(List<T> entities)

批量插入数据

protected boolean

containSql(String str)

判断str中是否包含SQL关键字

int

count(String condition)

根据where条件进行count

int

delete(String condition)

根据条件删除一条记录

int

deleteAll()

慎用:慢查询,容易造成DB阻塞!

int

deleteById(PK id)

根据主键删除一条记录

int

deleteIn(List<? extends Number> ids, String column)

删除指定column值属于集合ids的行
注意:column必须是索引

T

getById(PK id)

根据主键获得一个实体

List<T>

getDataByPage(String columns, String where, int pageNo, int pageSize)

分页读取数据

List<T>

getDataByPage(String columns, String where, int pageNo, int pageSize, String forceIndex)

分页读取数据。至于为什么要forceIndex参见SQL走不了索引的情况

<K extends Number
List<T>

getIn(String columns, Set<K> collections, String targetColumn)

in查询

ResultSet

getListByPage(String columns, String where, int pageNo, int pageSize)

已过时。 

ConnectionPool.PooledConnection

getMasterConn()

获取一个主库连接

ConnectionPool.PooledConnection

getSlaveConn()

获取一个从库连接。

int

insert(T entity)

插入一条新记录

void

retrunMasterConn(ConnectionPool.PooledConnection conn)

把主库连接返回连接池

void

retrunSlaveConn(ConnectionPool.PooledConnection conn)

把从库连接返回连接池

int

update(T entity)

根据主键更新一条记录

通过上面的dao操作数据库有以下好处:

  1. 读写分离。所有读操作使用的都是从库连接,所有写操作使用的都是主库连接。
  2. 避免了低效不合理的查询操作。分页读取的接口不允许一次读取5000行以上的数据。没有提供连表查询的接口。没有提供一次性select all的查询接口。
  3. 安全。insert和update操作都进行了SQL注入和脚本注入的攻击检测。
  4. 运维友好。所有操作的耗时都进行了KV上报。所有操作发生ERROR时都进行了KV上报。

当要大量读取数据时自然会想到分页读取,即使用mysql中的limit start,offset,然而limit命令实际上是每次都头开始读,再截取最后offset个,所以你会发现当start很大时执行一次读操作非常耗时。遍历表的正确姿势是:

final int PAGE_NO = 1;
final int PAGE_SIZE = 500;
int maxid=-1;
DeliveryDao dao = new DeliveryDao();
while (true) {
    List<Delivery> datas = dao.getDataByPage("*", "id>"+maxid, PAGE_NO,PAGE_SIZE);
    if (datas == null) {   //返回null可能是读取超时,并不代表数据已经读完了
        continue;
    }
    if (datas.size() == 0) {
        break;
    }
    for (Delivery ele : datas) {
        int id = ele.getId();
        if(id>maxid){
            maxid=id;
        }
        // use ele
    }
}

我们借助于id这个索引实现快速定位,每次都是读取第1页。另外使用id(主键)而非使用其他索引列的好处在于:主键是聚集索引,即它和原始数据是存放在一起的,当我们要获取原始数据(而非进行一些聚合查询)时,使用主键会更快一些。有时候在你的应用中where条件里会同时包含主键和其他索引列,MySQL不一定会选择代价最小的索引,需要你通过force index告诉它走哪个索引。

 连接管理

数据库配置文件举例:

db_url=slave1.mysql.host,slave2.mysql.host        #支持多个库
db_name=rec        #库名
db_user=rd        #用户名
db_passwd=123    #密码
#下面这些配置是可选的
db_maxconn=100    #最大DB连接数(所有数据库上的连接加起来)。默认值100
query_timeout=30#每次DB操作的超时时间(毫秒)。默认值30
db_minconn=5    #最少保持多少个连接。默认值5
inc_conn=5        #每次新创建多少个连接。默认值5
refresh_interval=10        #每隔几分钟刷新一次连接池。默认值10

 可以看到连接池配置选项比较多,有必填的也有选填的,所以ConnectionPoolConfigure实例支持使用Builder模式创建。

连接池采用双端队列作为容器,新创建的连接放入队尾,使用连接时从队首取,用完连接后放致队尾。当采用多数据库时,每次轮流地向各个数据库讲求连接,结合双端队列机制保证了各个数据库分到的请求数是均匀的。(PS:针对不同的应用场景,什么情况下容器适合用栈、队列?socket连接池、线程池……)

初始时先创建db_minconn个连接放入池中。池中无空闲连接时再创建inc_conn个新连接。创建的连接数达到db_maxconn的80%时,打印warn日志。创建的连接数达到db_maxconn时,打印error日志,并不再创建新连接。每隔一段时间会刷新一次连接池,刷新的主要目的是释放那些已经创建的但是又空闲下来的连接(在网站访问量高时可能创建了大量连接,访问量降下去时要及时地回收这这些连接),次要目的是确保保留下来的连接都是可用的。

注意,每个应用必须限制最大连接数,因为等到MySQL那边连接数满时后果已经很严重了,所有使用该DB的应用都会受到很大的影响。

每次DB操作耗时进过query_timeout时本次请求会失败,并且会打印error日志,KV上报1个DB错误。

每次操作数据库都要从池中获取一个连接,这个连接可能是事先创建好的,也可能是临时新建的。从池中取出一个连接时都会立即验证一下连接是否可用。

  1 import java.sql.Connection;
  2 import java.sql.Driver;
  3 import java.sql.DriverManager;
  4 import java.sql.ResultSet;
  5 import java.sql.SQLException;
  6 import java.text.DecimalFormat;
  7 import java.util.concurrent.LinkedBlockingDeque;
  8 import java.util.concurrent.atomic.AtomicInteger;
  9 
 10 import org.apache.commons.logging.Log;
 11 import org.apache.commons.logging.LogFactory;
 12 
 13 /**
 14  * 数据库连接池
 15  * 
 16  * @Author:orisun
 17  * @Since:2015-6-3
 18  * @Version:1.0
 19  */
 20 public class ConnectionPool {
 21 
 22     private static Log logger = LogFactory.getLog(ConnectionPool.class);
 23     private final String jdbcDriver = "com.mysql.jdbc.Driver"; // 数据库驱动
 24     private final LinkedBlockingDeque<String> urlList;// 从队首取出元素后,再把它放到队尾,这样多数据源被均匀的使用
 25     private final String dbName;
 26     private final String dbUsername; // 数据库用户名
 27     private final String dbPassword; // 数据库用户密码
 28     private final int minConnections; // 连接池的初始大小,亦最少连接数
 29     private final int incrementalConnections;// 连接池自动增加的大小
 30     private final int maxConnections; // 连接池最大的大小。如果
 31     private final int queryTimeOut;// 查询超时,单位毫秒。通常mysql查询几毫秒就可以完成
 32     private LinkedBlockingDeque<PooledConnection> connections; // 存放所有连接的容器,必须是线程安全的。采用先进先出策略,保证每个连接被均匀地使用
 33     private AtomicInteger connectHaveCreate = new AtomicInteger();
 34 
 35     /**
 36      * DB配置采用Builder模式
 37      * 
 38      * @Author:orisun
 39      * @Since:2016-3-15
 40      * @Version:1.0
 41      */
 42     public static class ConnectionPoolConfigure {
 43         private final String urls;// 有多个库时用逗号分隔
 44         private final String dbName;
 45         private final String userName;
 46         private final String passwd;
 47         private int minConnections = 5; // 连接池的初始大小,亦最少连接数
 48         private int incrementalConnections = 5;// 每次新增多少个连接
 49         private int maxConnections = 100; // 连接池最大的大小。如果maxConnections为0或负数,表示连接数量没有限制
 50         private int queryTimeOut = 30;// 查询超时,单位毫秒。通常mysql查询几毫秒就可以完成
 51 
 52         public ConnectionPoolConfigure(String urls, String dbName,
 53                 String userName, String passwd) {
 54             this.urls = urls;
 55             this.dbName = dbName;
 56             this.userName = userName;
 57             this.passwd = passwd;
 58         }
 59 
 60         public ConnectionPoolConfigure minConnections(int value) {
 61             this.minConnections = value;
 62             return this;
 63         }
 64 
 65         public ConnectionPoolConfigure incrementalConnections(int value) {
 66             this.incrementalConnections = value;
 67             return this;
 68         }
 69 
 70         public ConnectionPoolConfigure maxConnections(int value) {
 71             this.maxConnections = value;
 72             return this;
 73         }
 74 
 75         public ConnectionPoolConfigure queryTimeOut(int value) {
 76             this.queryTimeOut = value;
 77             return this;
 78         }
 79 
 80         public ConnectionPool build() {
 81             return new ConnectionPool(this);
 82         }
 83     }
 84 
 85     public ConnectionPool(ConnectionPoolConfigure configure) {
 86         this.dbName = configure.dbName;
 87         String[] dbUrls = configure.urls.split(",");
 88         this.urlList = new LinkedBlockingDeque<String>();
 89         for (String ele : dbUrls) {
 90             this.urlList.add(ele.trim());
 91         }
 92         this.dbUsername = configure.userName;
 93         this.dbPassword = configure.passwd;
 94         this.minConnections = configure.minConnections;
 95         this.incrementalConnections = configure.incrementalConnections;
 96         this.maxConnections = configure.maxConnections;
 97         this.queryTimeOut = configure.queryTimeOut;
 98         try {
 99             createPool();
100         } catch (Exception e) {
101             logger.error("create db connbection pool failed.", e);
102         }
103     }
104 
105     /**
106      * 创建一个数据库连接池
107      * 
108      * @throws Exception
109      */
110     public synchronized void createPool() throws Exception {
111         if (connections != null) {
112             return;
113         }
114         // 实例化 JDBC Driver 中指定的驱动类实例
115         Driver driver = (Driver) (Class.forName(this.jdbcDriver).newInstance());
116         // 注册 JDBC 驱动程序
117         DriverManager.registerDriver(driver);
118         // 保存连接的容器,用一个双端队列
119         this.connections = new LinkedBlockingDeque<PooledConnection>();
120         // 根据 initialConnections 中设置的值,创建连接
121         createConnections(this.minConnections);
122         logger.info("database connection pool have created.");
123     }
124 
125     /**
126      * 创建由 numConnections 指定数目的数据库连接,并把这些连接放入connections向量中
127      * 
128      * @param numConnections
129      *            要创建的数据库连接的数目
130      */
131     private void createConnections(int numConnections) throws SQLException {
132         final double WARN_THRESH = 0.8;
133         for (int x = 0; x < numConnections; x++) {
134             if (this.maxConnections > 0) {
135                 // 连接池达到最大限制时不再创建连接
136                 if (connectHaveCreate.get() >= this.maxConnections) {
137                     logger.error("connection have reach the maximum, can not create more");
138                     break;
139                 }
140                 // 连接池达到上限的80%时打印warn日志
141                 else if (connectHaveCreate.get() >= this.maxConnections
142                         * WARN_THRESH) {
143                     DecimalFormat df = new DecimalFormat("##.##%");
144                     logger.warn("pool size(" + connectHaveCreate.get()
145                             + ") have reach " + df.format(WARN_THRESH)
146                             + " of the maximum capacity(" + this.maxConnections
147                             + ")");
148                 }
149             }
150 
151             try {
152                 // 新创建的连接放到队尾
153                 Connection newconn = newConnection();
154                 if (newconn != null) {
155                     connections.add(new PooledConnection(newconn,
156                             this.queryTimeOut));
157                 } else {
158                     x--;
159                 }
160             } catch (SQLException e) {
161                 logger.error("create db connection failed", e);
162                 throw new SQLException();
163             }
164         }
165 
166     }
167 
168     /**
169      * 创建一个新的数据库连接并返回它
170      * 
171      * @return 返回一个新创建的数据库连接
172      */
173     private Connection newConnection() throws SQLException {
174         String url = this.urlList.pollFirst();// 从队首取出
175         Connection conn = null;
176         if (url != null) {
177             this.urlList.add(url);// 插入到队尾
178             conn = DriverManager.getConnection("jdbc:mysql://" + url + "/"
179                     + dbName, dbUsername, dbPassword);
180         }
181         if (conn != null) {
182             int n = connectHaveCreate.incrementAndGet();
183             logger.info("have create " + n + " connections for db "
184                     + this.dbName);
185         }
186         return conn;
187     }
188 
189     /**
190      * 获取一个可用的数据库连接<br>
191      * 如果当前没有可用的数据库连接,并且更多的数据库连接不能创建( 如连接池大小的限制),此函数等待一会再尝试获取。
192      * 
193      * @return 返回一个可用的数据库连接对象,如果没有可用的连接则返回null
194      */
195 
196     public PooledConnection getConnection() throws SQLException {
197         // 确保连接池己被创建
198         if (connections == null) {
199             return null;
200         }
201         PooledConnection conn = getFreeConnection(); // 获得一个可用的数据库连接
202         // 如果目前没有可以使用的连接,即所有的连接都在使用中
203         while (conn == null) {
204             // 等一会再试
205             wait(250);
206             conn = getFreeConnection(); // 重新再试,直到获得可用的连接,如果
207         }
208         return conn;
209     }
210 
211     private PooledConnection getFreeConnection() throws SQLException {
212         // 从连接池中获得一个可用的数据库连接
213         PooledConnection conn = findFreeConnection();
214         if (conn == null) {
215             // 如果目前连接池中没有可用的连接创建一些连接
216             createConnections(incrementalConnections);
217             // 重新从池中查找是否有可用连接
218             conn = findFreeConnection();
219         }
220         return conn;
221     }
222 
223     /**
224      * 查找连接池中所有的连接,查找一个可用的数据库连接,如果没有可用的连接,返回 null
225      * 
226      * @return 返回一个可用的数据库连接
227      */
228     private PooledConnection findFreeConnection() throws SQLException {
229         // 从队首取
230         PooledConnection pc = this.connections.pollFirst();
231         if (pc != null) {
232             Connection conn = pc.getConnection();
233             if (!isValid(conn)) {
234                 // 如果此连接不可再用了,则创建一个新的连接,并替换此不可用的连接对象
235 //                int n=connectHaveCreate.decrementAndGet();
236 //                logger.info("detect 1 invalid connection, now have " + n + " valid connections for db "
237 //                        + this.dbName);
238                 try {
239                     conn = newConnection();
240                     if (conn == null) {
241                         pc = null;
242                     } else {
243                         pc.setConnection(conn);
244                     }
245                 } catch (SQLException e) {
246                     logger.error("create new db connection failed.", e);
247                     return null;
248                 }
249             }
250         }
251         return pc;
252     }
253 
254     /**
255      * 
256      * 测试一个连接是否可用,如果不可用,关掉它并返回 false,否则可用返回 true
257      * 
258      * @param conn
259      *            需要测试的数据库连接
260      * 
261      * @return 返回 true 表示此连接可用, false 表示不可用
262      */
263     private boolean isValid(Connection conn) {
264         try {
265             return conn.isValid(3000);
266         } catch (SQLException e) {
267             logger.error("test connection validation failed", e);
268             return false;
269         }
270     }
271 
272     /**
273      * 此函数返回一个数据库连接到连接池中<br>
274      * 所有使用连接池获得的数据库连接均应在不使用此连接时返回它。
275      * 
276      * @param 需返回到连接池中的连接对象
277      */
278     public void returnConnection(PooledConnection conn) {
279         // 确保连接池存在,如果连接没有创建(不存在),直接返回
280         if (connections == null) {
281             logger.error("no connection pool for put back");
282             return;
283         }
284         // 放回时放到队尾
285         this.connections.add(conn);
286     }
287 
288     /**
289      * 
290      * 关闭连接池中所有的连接,并清空连接池。当系统要退出时才可调用此函数
291      */
292     public synchronized void closeConnectionPool() throws SQLException {
293         // 确保连接池存在,如果不存在,返回
294         if (connections == null) {
295             logger.error("no connection pool to close");
296             return;
297         }
298 
299         PooledConnection pc = null;
300         while ((pc = this.connections.poll()) != null) {
301             closeConnection(pc.getConnection());
302         }
303         // 置连接池为空
304         connections = null;
305         logger.info("release db connection pool for " + this.dbName);
306     }
307 
308     /**
309      * 关闭一个数据库连接
310      * 
311      * @param 需要关闭的数据库连接
312      */
313     public void closeConnection(Connection conn) {
314         try {
315             conn.close();
316         } catch (SQLException e) {
317             logger.error("close db connection failed ", e);
318         }
319     }
320 
321     /**
322      * 回收连接池中空闲的连接
323      */
324     public synchronized void recycle() throws SQLException {
325         // 确保连接池己创新存在
326         if (connections == null) {
327             logger.error("no connection pool for refresh");
328             return;
329         }
330         int closeNum = 0;
331         // 回收时要保证池中的最少连接数
332         while (this.connections.size() > this.minConnections) {
333             // 从连接池中移除。从队尾取
334             PooledConnection pc = this.connections.pollLast();
335             if (pc != null) {
336                 connectHaveCreate.decrementAndGet();
337                 closeNum++;
338                 // 把物理连接关闭掉
339                 Connection conn = pc.getConnection();
340                 closeConnection(conn);
341             }
342         }
343         logger.info("recycle " + closeNum + " connections, now have "
344                 + connections.size() + " connections in " + this.dbName
345                 + " pool");
346 
347     }
348 
349     /**
350      * 使程序等待给定的毫秒数
351      * 
352      * @param 给定的毫秒数
353      */
354 
355     private void wait(int mSeconds) {
356         try {
357             Thread.sleep(mSeconds);
358         } catch (InterruptedException e) {
359         }
360     }
361 
362     /**
363      * 内部使用的用于保存连接池中连接对象的类。<br>
364      * 此类中有两个成员,一个是数据库的连接,另一个是指示此连接是否 正在使用的标志。
365      */
366     public class PooledConnection {
367         private Connection connection = null;// 数据库连接
368         private int queryTimeOut;
369 
370         private PooledConnection(Connection connection, int queryTimeOut) {
371             this.connection = connection;
372             this.queryTimeOut = queryTimeOut;
373         }
374 
375         public ResultSet executeQuery(String sql) throws SQLException {
376             return connection.createStatement().executeQuery(sql);
377         }
378 
379         public int executeUpdate(String sql) throws SQLException {
380             return connection.createStatement().executeUpdate(sql);
381         }
382 
383         public Connection getConnection() {
384             return connection;
385         }
386 
387         private void setConnection(Connection connection) {
388             this.connection = connection;
389         }
390 
391         public int getQueryTimeOut() {
392             return queryTimeOut;
393         }
394 
395         public void setQueryTimeOut(int queryTimeOut) {
396             this.queryTimeOut = queryTimeOut;
397         }
398     }
399 
400     public String getDbName() {
401         return dbName;
402     }
403 
404 }
View Code

 

posted @ 2016-05-14 15:21  张朝阳  阅读(824)  评论(0编辑  收藏