Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

上次就比较简单的讲了使用,这块也比较简单,因为封装得不是很复杂,首先我们从 select 作为入口来看看,这个具体的实现,

String selectSql = new SQL() {{
            SELECT("id", "name");
            FROM("student");
            WHERE("id = #{id}");
        }}.toString();

SELECT 方法的实现,

public T SELECT(String... columns) {
  sql().statementType = SQLStatement.StatementType.SELECT;
  sql().select.addAll(Arrays.asList(columns));
  return getSelf();
}

statementType是个枚举

public enum StatementType {
  DELETE, INSERT, SELECT, UPDATE
}

那这个就是个 select 语句,然后会把参数转成 list 添加到 select 变量里,
然后是 from 语句,这个大概也能猜到就是设置下表名,

public T FROM(String table) {
  sql().tables.add(table);
  return getSelf();
}

往 tables 里添加了 table,这个 tables 是什么呢
这里也可以看下所有的变量,

StatementType statementType;
List<String> sets = new ArrayList<>();
List<String> select = new ArrayList<>();
List<String> tables = new ArrayList<>();
List<String> join = new ArrayList<>();
List<String> innerJoin = new ArrayList<>();
List<String> outerJoin = new ArrayList<>();
List<String> leftOuterJoin = new ArrayList<>();
List<String> rightOuterJoin = new ArrayList<>();
List<String> where = new ArrayList<>();
List<String> having = new ArrayList<>();
List<String> groupBy = new ArrayList<>();
List<String> orderBy = new ArrayList<>();
List<String> lastList = new ArrayList<>();
List<String> columns = new ArrayList<>();
List<List<String>> valuesList = new ArrayList<>();

可以看到是一堆 List 先暂存这些sql 片段,然后再拼装成 sql 语句,
因为它重写了 toString 方法

@Override
public String toString() {
  StringBuilder sb = new StringBuilder();
  sql().sql(sb);
  return sb.toString();
}

调用的 sql 方法是

public String sql(Appendable a) {
      SafeAppendable builder = new SafeAppendable(a);
      if (statementType == null) {
        return null;
      }

      String answer;

      switch (statementType) {
        case DELETE:
          answer = deleteSQL(builder);
          break;

        case INSERT:
          answer = insertSQL(builder);
          break;

        case SELECT:
          answer = selectSQL(builder);
          break;

        case UPDATE:
          answer = updateSQL(builder);
          break;

        default:
          answer = null;
      }

      return answer;
    }

根据上面的 statementType判断是个什么 sql,我们这个是 selectSQL 就走的 SELECT 这个分支

private String selectSQL(SafeAppendable builder) {
  if (distinct) {
    sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
  } else {
    sqlClause(builder, "SELECT", select, "", "", ", ");
  }

  sqlClause(builder, "FROM", tables, "", "", ", ");
  joins(builder);
  sqlClause(builder, "WHERE", where, "(", ")", " AND ");
  sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
  sqlClause(builder, "HAVING", having, "(", ")", " AND ");
  sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
  limitingRowsStrategy.appendClause(builder, offset, limit);
  return builder.toString();
}

上面的可以看出来就是按我们常规的 sql 理解顺序来处理
就是select ... from ... where ...这样子
再看下 sqlClause 的代码

private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
                           String conjunction) {
      if (!parts.isEmpty()) {
        if (!builder.isEmpty()) {
          builder.append("\n");
        }
        builder.append(keyword);
        builder.append(" ");
        builder.append(open);
        String last = "________";
        for (int i = 0, n = parts.size(); i < n; i++) {
          String part = parts.get(i);
          if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) {
            builder.append(conjunction);
          }
          builder.append(part);
          last = part;
        }
        builder.append(close);
      }
    }

这里的拼接方式还需要判断 AND 和 OR 的判断逻辑,其他就没什么特别的了,只是where 语句中的 lastList 不知道是干嘛的,好像只有添加跟赋值的操作,有知道的大神也可以评论指导下

mybatis 还有个比较有趣的功能,就是使用 SQL 类生成 sql,有点类似于 hibernate 或者像 php 的 laravel 框架等的,就是把sql 这种放在 xml 里或者代码里直接写 sql 用对象的形式

select语句

比如这样

public static void main(String[] args) {
    String selectSql = new SQL() {{
        SELECT("id", "name");
        FROM("student");
        WHERE("id = #{id}");
    }}.toString();
    System.out.println(selectSql);
}

打印出来就是

SELECT id, name
FROM student
WHERE (id = #{id})

应付简单的 sql 查询基本都可以这么解决,如果习惯这种模式,还是不错的,
其实以面向对象的编程模式来说,这样是比较符合面向对象的,先不深入的解析这块的源码,先从使用角度讲一下

比如 update 语句

String updateSql = new SQL() {{
            UPDATE("student");
            SET("name = #{name}");
            WHERE("id = #{id}");
        }}.toString();

打印输出就是

UPDATE student
SET name = #{name}
WHERE (id = #{id})

insert 语句

String insertSql = new SQL() {{
            INSERT_INTO("student");
            VALUES("name", "#{name}");
            VALUES("age", "#{age}");
        }}.toString();
        System.out.println(insertSql);

打印输出

INSERT INTO student
 (name, age)
VALUES (#{name}, #{age})

delete语句

String deleteSql = new SQL() {{
            DELETE_FROM("student");
            WHERE("id = #{id}");
        }}.toString();
        System.out.println(deleteSql);

打印输出

DELETE FROM student
WHERE (id = #{id})

开始修老房子又可以更新这个系列了,比较无聊,就是帮着干点零活的记录,这次过去起的比较早,前几天是在翻新瓦片,到这次周六是收尾了,到了的时候先是继续筛了沙子,上周也筛了,就是只筛了一点点,筛沙子的那个像纱窗一样的还是用一扇中空的中间有一根竖档的门钉上铁丝网做的,就是沙子一直放在外面,原来是有袋子装好的,后来是风吹雨打在上面的都已经破掉,还夹杂了很多树叶什么的,需要过下筛,并且前面都是下雨天,沙子都是湿的,不太像我以前看村里有人造房子筛沙子那样,用铲子铲上去就自己都下去的,湿的就是会在一坨,所以需要铲得比较少,然后撒的比较开,这个需要一点经验,然后如果有人一起的话就可以用扫把按住扫一下,这样就会筛得比较有效,不至于都滑下去,沙子本来大部分是可以筛出来的,还有一点就是这种情况网筛需要放得坡度小一点,不然就更容易直接往下调,袋子没破的就不用过筛了,只是湿掉了的是真的重,筛完了那些破掉了的袋子里的沙子,就没有特别的事情要做了,看到大工在那打墙,有一些敲下来的好的砖头就留着,需要削一下上面的混凝土,据大工说现在砖头要七八毛一块了,后面能够重新利用还是挺值钱的,我跟 LD 就用泥刀和铁锹的在那慢慢削,砖头上的泥灰有的比较牢固有的就像直接是沙子,泥刀刮一下就下来了,有的就结合得比较牢固,不过据说以前的砖头工艺上还比较落后,这个房子差不多是三十年前了的,砖头表面都是有点不平,甚至变形,那时候可能砖头是手工烧制的,现在的砖头比较工艺可好多了,不过可能也贵了很多,后来老丈人也过来了,指导了我们拌泥灰,就是水泥和黄沙混合,以前小时候可喜欢玩这个了,可是就也搞不清楚这个是怎么搅拌的,只看见是水泥跟黄沙围城一圈,中间放水,然后一点点搬进去,首先需要先把干的水泥跟黄沙进行混合,具体的比例是老丈人说的,拌的方式有点像堆沙堆,把水泥黄沙铲起来堆起来,就一直要往这个混合堆的尖尖上堆,这样子它自己滑下来能更好地混合,来回两趟就基本混合均匀了,然后就是跟以前看过的,中间扒拉出一个空间放水,然后慢慢把周围的混合好的泥沙推进去,需要注意不要太着急,旁边的推进去太多太快就会漏水出来,一个是会把旁边的地给弄脏,另一个也铲不回水,然后就是推完所有的都混合水了,就铲起来倒一下,再将铲子翻过来捣几下,后面我们就去吃饭了,去了一家叫金记的,又贵又不太好吃的店,就是离得比较近,六七个人只有六七个菜,吃完要四百多,也是有点离谱了。
下午是重头戏,其实本来倒没啥事,就说帮忙搞下靠背(就是踢脚线上面到窗台附近的用木头还有其他材料的装饰性的),都撬撬掉,但是真的是有点离谱了,首先是撬棒真的很重,20 斤的重量(网上查的,没有真的查过),抡起来还要用力地铲进去,因为就是破坏性的要把整个都撬掉,对于我这种又没技巧又没力气的非专业选手,抡起撬棍铲两下手就开始痛了,只是也比较犟,不想刚开始弄就说太重了要休息,后面都完全靠的一点倔强劲撑着,看着里面的工艺感觉也是不容易的,直着横着的木条有好多,竖的一整条,每隔三五十公分,横着的就是三五十公分,每根都要用钉子钉起来,然后外层好像是贴上去,在同一个面的开了头之后就能靠着蛮力往下撬,但是到了转角就又要重新开头,而且最上面一根横条跟紧邻的那一块,大概十几公分,是横着的三条钉在一起,真的是大力都出不了奇迹了,用撬棍的一头用力地敲打都很震手,要从下面往上铲进去撬开一点,然后再从上面往下敲打,这里比较重要的是要小心钉子,我这次运气比较好,踩下去已经扎到了,不过正好在脚趾缝里,没有扎到脚,还是要小心的,做完这个我的手真的是差不多废了,上臂的疼痛已经动一下就受不了了,后面有撬下了最下面当踢脚线的小瓷砖,这个房子估计中间修过一次,两批水泥糊的,新的那批粘的特别牢,敲敲打打了半天才下来一点点,锤子敲上去跟一整块石头一样,震手又没有进展,整个搞完,楼上又在敲墙了,下面的灰尘也是从没见过,我一直在那洒水都完全没有缓解,就上去跟 LD 一起拣砖头,手痛到只能抬两块砖头都会痛了。
回到家里开始越来越痛,两个手就完全没法动了,应该也是肌肉拉伤了,我这样是没足够的力气也不会什么技巧,像大工说的,他们也累也难,只是为了赚钱,不过他们有了经验跟技巧,会注意怎么使力不容易受伤,怎么样比较省力,还有一点就是即使这么累,他们一般也下午五点半就下班了,真的很累了,至少还有不少时间可以回家休息,而我们的职业呢,就像 LD 说的回家就像住酒店,就只是来洗澡睡个觉,希望能改善吧

上周因为一些事情没回去,买好了内存条这周才回去用,结果不知道是 U 盘的问题还是什么其他原因原来装的那个系统起不来,然后想用之前一直用的 LD 的笔记本,结果 LD 的老笔记本也起不来了,一直卡在启动界面,猜测可能是本身也比较老了,用的机械硬盘会不会被我一直动而损坏了,所以就想搞个老毛桃跟新搞一个启动盘,结果这周最大的坑就在这了,在我的 15 寸 mbp 上用etcher做了个 win10 的启动盘,可能是分区表格式是 GUID,不是 MBR 的,老电脑不能识别,就像装个虚拟机来做启动盘,结果像老毛桃不支持这种用虚拟机的,需要本地磁盘,然后我就有用 bootcamp 安装了个 win10,又没网络,查了下网络需要用 bootcamp 下载 Windows Support 软件,下了好久一直没下完,后来又提示连不上苹果服务器,好不容易下完了,开启 win10 后,安装到一半就蓝屏了,真的是够操蛋的,在这个时候真的很需要有一个 Windows 机器的支持,LD 的那个笔记本也跟我差不多老了,虽然比较慢但之前好歹也还可以用,结果这下也被我弄坏了,家里就没有其他机器可以支持了,有点巧妇难为无米之炊的赶脚,而且还是想再折腾下我的老电脑,后面看看打算做几个 U 盘,分工职责明确,一个老毛桃的PE 盘,老毛桃的虽然不是很纯净的,但是是我一直以来用过内置工具比较全,修复启动功能比较强大的一个 pe,没用过 winpe,后续可以考虑使用下,一个 win7 的启动盘,因为是老电脑,如果后面是新的电脑可以考虑是 win10,一个 win to go 盘,这样我的 mbp 就可以直接用 win10 了,不过想来也还是要有一台 win 机器才比较好,不然很多时候都是在折腾工具,工欲善其事必先利其器,后面也可以考虑能够更深入的去了解主板的一些问题,我原来的那个主板可能是一些小的电阻等问题,如果能自己学习解决就更好了,有时候研究下还是有意思的。

连接池主要是两个逻辑,首先是获取连接的逻辑,结合代码来讲一讲

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    // 循环获取连接
    while (conn == null) {
      // 加锁
      lock.lock();
      try {
        // 如果闲置的连接列表不为空
        if (!state.idleConnections.isEmpty()) {
          // Pool has available connection
          // 连接池有可用的连接
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // Pool does not have available connection
          // 进入这个分支表示没有空闲连接,但是活跃连接数还没达到最大活跃连接数上限,那么这时候就可以创建一个新连接
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            // 这里创建连接我们之前讲过,
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // Cannot create new connection
            // 进到这个分支了就表示没法创建新连接了,那么怎么办呢,这里引入了一个 poolMaximumCheckoutTime,这代表了我去控制连接一次被使用的最长时间,如果超过这个时间了,我就要去关闭失效它
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              // 所有超时连接从池中被借出的次数+1
              state.claimedOverdueConnectionCount++;
              // 所有超时连接从池中被借出并归还的时间总和 + 当前连接借出时间
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              // 所有连接从池中被借出并归还的时间总和 + 当前连接借出时间
              state.accumulatedCheckoutTime += longestCheckoutTime;
              // 从活跃连接数中移除此连接
              state.activeConnections.remove(oldestActiveConnection);
              // 如果该连接不是自动提交的,则尝试回滚
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happened.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not interrupt current executing thread and give current thread a
                     chance to join the next competition for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  log.debug("Bad connection. Could not roll back");
                }
              }
              // 用此连接的真实连接再创建一个连接,并设置时间
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // Must wait
              // 这样还是获取不到连接就只能等待了
              try {
                // 标记状态,然后把等待计数+1
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                // 等待 poolTimeToWait 时间
                condition.await(poolTimeToWait, TimeUnit.MILLISECONDS);
                // 记录等待时间
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                // set interrupt flag
                Thread.currentThread().interrupt();
                break;
              }
            }
          }
        }
        // 如果连接不为空
        if (conn != null) {
          // ping to server and check the connection is valid or not
          // 判断是否有效
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              // 回滚未提交的
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            // 设置时间
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            // 添加进活跃连接
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            // 连接无效,坏连接+1
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            // 如果坏连接已经超过了容忍上限,就抛异常
            if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      } finally {
        // 释放锁
        lock.unlock();
      }

    }

    if (conn == null) {
      // 连接仍为空
      if (log.isDebugEnabled()) {
        log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
      }
      // 抛出异常
      throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    }
    // fanhui 
    return conn;
  }

然后是还回连接

protected void pushConnection(PooledConnection conn) throws SQLException {
    // 加锁
    lock.lock();
    try {
      // 从活跃连接中移除当前连接
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        // 当前的空闲连接数小于连接池中允许的最大空闲连接数
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          // 记录借出时间
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            // 同样是做回滚
            conn.getRealConnection().rollback();
          }
          // 新建一个连接
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          // 加入到空闲连接列表中
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          // 原连接失效
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          // 提醒前面等待的
          condition.signal();
        } else {
          // 上面是相同的,就是这里是空闲连接数已经超过上限
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    } finally {
      lock.unlock();
    }
  }
0%