Mybatis工作流程及其原理与解析

Mybatis简介:
    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。本文将通过debug的方式来了解其工作原理。



Mybatis核心类:
    SqlSessionFactory:每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或通过Java的方式构建出 SqlSessionFactory 的实例。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。一个SqlSessionFactory对应配置文件中的一个环境(environment),如果你要使用多个数据库就配置多个环境分别对应一个SqlSessionFactory。

    SqlSession:SqlSession是一个接口,它有2个实现类,分别是DefaultSqlSession(默认使用)以及SqlSessionManager。SqlSession通过内部存放的执行器(Executor)来对数据进行CRUD。此外SqlSession不是线程安全的,因为每一次操作完数据库后都要调用close对其进行关闭,官方建议通过try-finally来保证总是关闭SqlSession。

    Executor:Executor(执行器)接口有两个实现类,其中BaseExecutor有三个继承类分别是BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器)。以上三个就是主要的Executor。通过下图可以看到Mybatis在Executor的设计上面使用了装饰者模式,我们可以用CachingExecutor来装饰前面的三个执行器目的就是用来实现缓存。

    MappedStatement:MappedStatement就是用来存放我们SQL映射文件中的信息包括sql语句,输入参数,输出参数等等。一个SQL节点对应一个MappedStatement对象。


Mybatis工作流程:下面将通过debug方式对Mybatis进行一步步解析。首先贴出我的mybatis-config.xml文件以及Mapper.xml文件。

[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"></properties>
  <settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="defaultExecutorType" value="REUSE"/>
  </settings>
  <typeAliases>
   <typeAlias alias="User" type="com.ctc.model.User"/>
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <package name="com.ctc.mapper"/>
  </mappers>
</configuration>

 

[XML] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ctc.mapper.UserMapper">
 <cache readOnly="true" size="200" eviction="FIFO"></cache>
 <sql id="select"> select * from user </sql>
  <select id="selectUser" resultMap="selectUserMap" useCache="true">
    <include refid="select"></include> User where id = #{id}
  </select>
  <resultMap type="User" id="selectUserMap">
  <result property="name" column="username"/>
  </resultMap>
  <insert id="insertUser" useGeneratedKeys="true"  keyProperty="id" keyColumn="id">
   insert into User (username,birthday,sex,address)
  values (#{name},#{birthday},#{sex},#{address})
  </insert>
  <update id="updateUser">
    update User set username = #{username},birthday = #{birthday},
    sex = #{sex},address = #{address} where id = #{id}
  </update>
  <delete id="deleteUser" >
  delete from User where id = #{id}
  </delete>
  <select id="selectUserByName"  resultMap="selectUserMap">
  select * from User where sex = #{param1}
  <choose>
  <when test="{param2} != null">
  and username like #{param2}
  </when>
  <otherwise>and address = #{parma3}</otherwise>
  </choose>
  
  </select>
  <select id="selectUserCount" resultType="int" >
  select count(*) from user where username like #{username}
  </select>
   
  <select id="selectUserNew" resultMap="selectUserMap">
  <bind name="pattern" value="'%' + name + '%'" />
  <include refid="select"></include>
   
  <where>
  <if test="pattern !=null">
  username like #{pattern}
  </if>
  <if test="sex !=null">
  and sex = #{sex}
  </if>
  <if test="address != null">
  and address = #{address}
  </if>
  </where>
  </select>
   
  <select id="selectUserByIds" resultMap="selectUserMap">
  <include refid="select"></include>
  where id in
  <foreach collection="list" item="id" index="0" open="(" close=")" separator="," >
  #{id}
  </foreach>
  </select>
</mapper>



第一步通过SqlSessionFactoryBuilder创建SqlSessionFactory:

    首先在SqlSessionFactoryBuilder的build()方法中可以看到MyBatis内部定义了一个类XMLConfigBuilder用来解析配置文件mybatis-config.xml。针对配置文件中的每一个节点进行解析并将数据存放到Configuration这个对象中,紧接着使用带有Configuration的构造方法发返回一个DefautSqlSessionFactory。

[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public SqlSessionFactory build(InputStream inputStream) {
   return build(inputStream, null, null);
 }
 
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
   try {
     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     //解析mybatis-config.xml
     return build(parser.parse());
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error building SqlSession.", e);
   } finally {
     ErrorContext.instance().reset();
     try {
       inputStream.close();
     } catch (IOException e) {
       // Intentionally ignore. Prefer previous error.
     }
   }
 }
    
 //返回SqlSessionFactory,默认使用的是实现类DefaultSqlSessionFactory
 public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }
 
 public Configuration parse() {
   if (parsed) {
     throw new BuilderException("Each XMLConfigBuilder can only be used once.");
   }
   parsed = true;
   //获取根节点configuration
   parseConfiguration(parser.evalNode("/configuration"));
   return configuration;
 }
 
 //开始解析mybatis-config.xml,并把解析后的数据存放到configuration中
 private void parseConfiguration(XNode root) {
   try {
     //保存mybatis-config.xml中的标签setting,本例中开启全局缓存cacheEnabled,设置默认执行器defaultExecutorType=REUSE
     Properties settings = settingsAsPropertiess(root.evalNode("settings"));
     //issue #117 read properties first
     //解析是否配置了外部properties,例如本例中配置的jdbc.propertis
     propertiesElement(root.evalNode("properties"));
     //查看是否配置了VFS,默认没有,本例也没有使用
     loadCustomVfs(settings);
     //查看是否用了类型别名,减少完全限定名的冗余,本例中使用了别名User代替了com.ctc.Model.User
     typeAliasesElement(root.evalNode("typeAliases"));
     //查看是否配置插件来拦截映射语句的执行,例如拦截Executor的Update方法,本例没有使用
     pluginElement(root.evalNode("plugins"))
     //查看是否配置了ObjectFactory,默认情况下使用对象的无参构造方法或者是带有参数的构造方法,本例没有使用
     objectFactoryElement(root.evalNode("objectFactory"));
     //查看是否配置了objectWrapperFatory,这个用来或者ObjectWapper,可以访问:对象,Collection,Map属性。本例没有使用
     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
     //查看是否配置了reflectorFactory,mybatis的反射工具,提供了很多反射方法。本例没有使用
     reflectorFactoryElement(root.evalNode("reflectorFactory"));
     //放入参数到configuration对象中
     settingsElement(settings);
     // read it after objectFactory and objectWrapperFactory issue #631
     //查看数据库环境配置
     environmentsElement(root.evalNode("environments"));
     //查看是否使用多种数据库,本例没有使用
     databaseIdProviderElement(root.evalNode("databaseIdProvider"));
     //查看是否配置了新的类型处理器,如果跟处理的类型跟默认的一致就会覆盖。本例没有使用
     typeHandlerElement(root.evalNode("typeHandlers"));
     //查看是否配置SQL映射文件,有四种配置方式,resource,url,class以及自动扫包package。本例使用package
     mapperElement(root.evalNode("mappers"));
   } catch (Exception e) {
     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
   }
 }



第二步通过SqlSessionFactory创建SqlSession:

[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
35
36
37
38
39
40
41
42
43
44
@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  
  
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //拿到前文从mybatis中解析到的数据库环境配置
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //拿到jdbc的事务管理器,有两种一种是jbc,一种的managed。本例使用的是JdbcTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //从mybatis配置文件可以看到本例使用了REUSE,因此返回的是ReuseExecutor并把事务传入对象中
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
}


第三步通过SqlSession拿到Mapper对象的代理:

[Java] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
35
36
37
38
39
40
41
42
@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
  
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //前文解析Mybatis-config.xml的时候,在解析标签mapper就是用configuration对象的mapperRegistry存放数据
    return mapperRegistry.getMapper(type, sqlSession);
  }
  
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //knownMapper是一个HashMap在存放mapperRegistry的过程中,以每个Mapper对象的类型为Key, MapperProxyFactory 为value保存。
    //例如本例中保存的就是Key:com.ctc.mapper.UserMapper,value就是保存了key的MapperProxyFactory对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  
  public T newInstance(SqlSession sqlSession) {
    //生成一个mapperProxy对象,这个对象实现了InvocationHandler, Serializable。就是JDK动态代理中的方法调用处理器
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //通过JDK动态代理生成一个Mapper的代理,在本例中的就是UserMapper的代理类,它实现了UserMapper接口
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }


第四步通过MapperProxy调用Maper中相应的方法:

[Java] 纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //判断当前调用的method是不是Object中声明的方法,如果是的话直接执行。
   if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  
  //把当前请求放入一个HashMap中,一旦下次还是同样的方法进来直接返回。
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          //本次案例会执行selectOne
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
  
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
  
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  //这边调用的是CachingExecutor类的query,还记得前文解析mybatis-config.xml的时候我们指定了REUSE但是因为在配置文件中开启了缓存
  //所以ReuseExecutor被CachingExecotur装饰,新增了缓存的判断,最后还是会调用ReuseExecutor
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //如果缓存中没有数据则查询数据库
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //结果集放入缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }


转载:https://blog.csdn.net/u010890358/article/details/80665753

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis 是一种基于 Java 的持久化框架,它的主要作用是简化与数据库的交互。MyBatis 工作原理如下: 1. 配置文件解析MyBatis 配置文件是一个 XML 文件,它包含了数据库连接信息、Mapper 映射文件信息等。在 MyBatis 启动时,会将配置文件读取到内存中,并解析成相应的对象。 2. SqlSession 的创建:SqlSession 是 MyBatis 中用于与数据库进行交互的核心类,它封装了 JDBC 操作。在应用程序中需要执行 SQL 语句时,首先需要创建 SqlSession 对象。 3. Mapper 映射文件解析:Mapper 映射文件是 MyBatis 中用于定义 SQL 语句的 XML 文件,它包含了 SQL 语句、参数信息、返回值信息等。在 SqlSession 中执行 SQL 语句时,会根据 Mapper 映射文件中定义的 SQL 语句进行操作。 4. SQL 语句执行:当 SqlSession 接收到应用程序传递的 SQL 语句后,会根据 Mapper 映射文件中定义的 SQL 语句进行操作,包括 SQL 语句的解析、参数绑定、SQL 执行等。 5. 结果集映射:当 SQL 语句执行完毕后,MyBatis 会将查询结果映射成 Java 对象,并返回给应用程序。MyBatis 支持将结果集映射为单个 Java 对象、Java 对象列表、Map 等。 6. 事务管理:在 MyBatis 中,事务是通过 SqlSession 进行管理的。当应用程序需要执行一系列 SQL 语句时,可以通过 SqlSession 对象开启事务,执行完毕后再提交或回滚事务。 总的来说,MyBatis工作原理主要包括配置文件解析、SqlSession 的创建、Mapper 映射文件解析、SQL 语句执行、结果集映射和事务管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值