浅谈Mybatis连接原理

       众所周知数据库连接的过程,但是最近面试的人 (菜面菜),都说用的 SSM 框架,但是我问了一下,mybatis 是怎么连接上 mysql 的,基本上都会说:配置好的,直接用了,今天我来抛砖引玉一下,欢迎拍砖!

       什么是 JDBC?

       Java 语言访问数据库的一种规范, 是一套 API。JDBC (Java Database Connectivity) API,即 Java 数据库编程接口,是一组标准的 Java 语言中的接口和类,使用这些接口和类,Java 客户端程序可以访问各种不同类型的数据库。JDBC 规范采用接口和实现分离的思想设计了 Java 数据库编程的框架。接口包含在 java.sql 及 javax.sql 包中,其中 java.sql 属于 JavaSE,javax.sql 属于 JavaEE。为了使客户端程序独立于特定的数据库驱动程序,JDBC 规范建议开发者使用基于接口的编程方式,即尽量使应用仅依赖 java.sql 及 javax.sql 中的接口和类。

        JAVA 使用 JDBC 访问数据库的步骤:

        1. 得到数据库驱动程序

        2. 创建数据库连接

        3. 执行 SQL 语句

        4. 得到结果集

        5. 对结果集做相应的处理 (增, 删, 改, 查)

        6. 关闭资源: 这里释放的是 DB 中的资源

        mysql 的驱动包提供了 java.sql.Driver 这个 SPI 的实现,实现类是 com.mysql.jdbc.Driver,在 mysql-connector-java-5.1.6.jar 中,我们可以看到有一个 META-INF/services 目录,目录下有一个文件名为 java.sql.Driver 的文件,其中的内容是 com.mysql.jdbc.Driver。
在运行 DriverManager.getDriver 并传入参数“com.mysql.jdbc.Driver”时,DriverManager 会从 mysql-connector-java-5.1.6.jar 中找到 com.mysql.jdbc.Driver 并实例化返回一个 com.mysql.jdbc.Driver 的实例。而 SPI(Service Provider Interface)是指一些提供给你继承、扩展,完成自定义功能的类、接口或者方法。

       SPI 是一种回调的思想,回调是指我们在使用 api 时,我们可以向 API 传入一个类或者方法,API 在合适的时间调用类或者方法。SPI 是在一些通用的标准中,为标准的实现产商提供的扩展点。标准在上层提供 API,API 内部使用了 SPI,当 API 被客户使用时,会动态得从当前运行的 classpath 中寻找该 SPI 的实现,然后使用该 SPI 的实现来完成 API 的功能。
       SPI 的实现方式是:提供实现的实现类打包成 Jar 文件,这个 Jar 文件里面必须有 META-INF 目录,其下又有 services 目录,其下有一个文本文件,文件名即为 SPI 接口的全名,文件的内容该 jar 包中提供的 SPI 接口的实现类名。
       大家看项目中 Mybaits 的 jar 包会发现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

      sqlSessionTemplate.SqlSessionInterceptor 源码,有没有一种很熟悉的感觉?至于 getConnection 自己去看;用过 ElasticSearch 和 Redis 的童鞋,细心的童鞋会发现连接字符串都大同小异,连接都是类似的,标准的连接方式,提高效率,有效控制连接;

      ElasticSearch 的连接字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected SearchResponse getSearchResponse(String fieldName, String indexName) {
    client = null;
    SearchResponse response = null;
    try {
        getClient();
        MaxAggregationBuilder aggregation =
                AggregationBuilders
                        .max("agg")
                        .field(fieldName);
        SearchRequestBuilder request = client.prepareSearch(indexName).addAggregation(aggregation);
        response = request.execute().actionGet();
    } catch (Exception ex) {
        logger.error("getSearchResponse", ex);
    } finally {
        if (client != null) {
            client.close();
        }
        return response;
    }
}

     Jedis 连接字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
执行命令如下:
Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
    //具体的命令
    jedis.executeCommand()
} catch (Exception e) {
    logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
    //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
    if (jedis != null)
        jedis.close();
}

       拦截器的实现都是基于代理的设计模式实现的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并在方法之前执行拦截器代码,拦截器一般有登陆拦截器——验证会话信息,权限拦截器——验证权限信息,那么 SqlSessionInterceptor 是干什么的?

       Mybatis 拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动 Mybatis 固有的逻辑。打个比方,对于 Executor,Mybatis 中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor 和 CachingExecutor。
这个时候如果你觉得这几种实现对于 Executor 接口的 query 方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个 Mybatis 拦截器用于拦截 Executor 接口的 query 方法,在拦截之后实现自己的 query 方法逻辑,之后可以选择是否继续执行原来的 query 方法。允许你在已映射语句执行过程中的某一点进行拦截调用。有的用 Mybatis 拦截器统封装分页,有的用它实现读写分离等,如果读写分离还是建议配置多数据源;

       spring 整合 mybatis 之后,通过动态代理的方式,使用 SqlSessionTemplate 持有的 sqlSessionProxy 属性来代理执行 sql 操作,由 spring 管理的 sqlSeesion 在 sql 方法 (增删改查等操作) 执行完毕后就自行关闭了 sqlSession,不需要我们对其进行手动关闭。

       愿你有情人终成眷属,愿你有个有趣的灵魂,愿你拍我一砖!