该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释( Mybatis源码分析 GitHub 地址 Mybatis-Spring 源码分析 GitHub 地址 Spring-Boot-Starter 源码分析 GitHub 地址 )进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

该系列其他文档请查看: 《精尽 MyBatis 源码分析 - 文章导读》

MyBatis的初始化

在MyBatis初始化过程中,大致会有以下几个步骤:

创建 Configuration 全局配置对象,会往 TypeAliasRegistry 别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为 XMLLanguageDriver

加载 mybatis-config.xml 配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中

构建 DefaultSqlSessionFactory 对象,通过它可以创建 DefaultSqlSession 对象,MyBatis中 SqlSession 的默认实现类

因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:

  • 《MyBatis初始化(一)之加载mybatis-config.xml》
  • 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》
  • 《MyBatis初始化(三)之SQL初始化(上)》
  • 《MyBatis初始化(四)之SQL初始化(下)》
  • 由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解

    初始化(四)之SQL初始化(下)

    在上一篇文档中详细地讲述了MyBatis在解析 <select /> <insert /> <update /> <delete /> 节点的过程中,是如何解析SQL语句的,如何实现动态SQL语句的,最终会生成一个 org.apache.ibatis.mapping.SqlSource 对象的,那么接下来我们来看看 SqlSource 到底是什么

    主要包路径:org.apache.ibatis.mapping、org.apache.ibatis.builder

    主要涉及到的类:

  • org.apache.ibatis.builder.SqlSourceBuilder :继承了BaseBuilder抽象类, SqlSource 构建器,负责将SQL语句中的 #{} 替换成相应的 ? 占位符,并获取该 ? 占位符对应的 ParameterMapping 对象
  • org.apache.ibatis.builder.ParameterExpression :继承了 HashMap<String, String> ,参数表达式处理器,在 SqlSourceBuilder 处理 #{} 的内容时,需要通过其解析成key-value键值对
  • org.apache.ibatis.mapping.ParameterMapping :保存 #{} 中配置的属性参数信息
  • org.apache.ibatis.mapping.SqlSource :SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息)
  • org.apache.ibatis.mapping.BoundSql :用于数据库可执行的SQL语句的最终封装对象
  • org.apache.ibatis.scripting.defaults.DefaultParameterHandler :实现了ParameterHandler接口,用于将入参设置到 java.sql.PreparedStatement 预编译对象中
  • 用于将入参设置到 java.sql.PreparedStatement 预编译对象中

    我们先来回顾一下 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder parseScriptNode() 方法,将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象

    代码如下:

    public SqlSource parseScriptNode() {
        // 解析 XML 或者注解中定义的 SQL
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource;
        if (isDynamic) {
            // 动态语句,使用了 ${} 也算
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        return sqlSource;
    
  • 如果是动态 SQL 语句,使用了 MyBatis 的自定义标签(<if /> <foreach />等)或者使用了 ${} 都是动态 SQL 语句,则会创建DynamicSqlSource对象
  • 否则就是静态 SQL 语句,创建 RawSqlSource 对象
  • SqlSource接口的实现类如下图所示:

    SqlSourceBuilder

    org.apache.ibatis.builder.SqlSourceBuilder:继承了BaseBuilder抽象类,SqlSource构建器,负责将SQL语句中的#{}替换成相应的?占位符,并获取该?占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象

    public class SqlSourceBuilder extends BaseBuilder {
    	private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
    	public SqlSourceBuilder(Configuration configuration) {
    		super(configuration);
    

    其中PARAMETER_PROPERTIES字符串定义了#{}中支持定义哪些属性,在抛异常的时候用到

    parse方法

    解析原始的SQL(仅包含#{}定义的参数),转换成StaticSqlSource对象

    因为在DynamicSqlSource调用该方法前会将MixedSqlNode进行处理,调用其apply方法进行应用,根据DynamicContext上下文对MyBatis的自定义标签或者包含${}的SQL生成的SqlNode进行逻辑处理或者注入值,生成一个SQL(仅包含#{}定义的参数)

    代码如下:

    * 执行解析原始 SQL ,成为 SqlSource 对象 * @param originalSql 原始 SQL * @param parameterType 参数类型 * @param additionalParameters 上下文的参数集合,包含附加参数集合(通过 <bind /> 标签生成的,或者`<foreach />`标签中的集合的元素) * RawSqlSource传入空集合 * DynamicSqlSource传入 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合 * @return SqlSource 对象 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { // <1> 创建 ParameterMappingTokenHandler 对象 ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); // <2> 创建 GenericTokenParser 对象 GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); * <3> 执行解析 * 将我们在 SQL 定义的所有占位符 #{content} 都替换成 ? * 并生成对应的 ParameterMapping 对象保存在 ParameterMappingTokenHandler 中 String sql = parser.parse(originalSql); // <4> 创建 StaticSqlSource 对象 return new StaticSqlSource(configuration, sql, handler.getParameterMappings());

    该方法的入参originalSql为原始的SQL,也就是其所有的SqlNode节点已经应用了,也就是都调用了apply方法

    包含的${}也已经注入了对应的值,所以这里只剩#{}定义的入参了

  • 创建ParameterMappingTokenHandler处理器对象handler
  • 创建GenericTokenParser对象,用于处理#{}中的内容,通过handler将其转换成?占位符,并创建对应的ParameterMapping对象
  • 执行解析,获取最终的 SQL 语句
  • 创建StaticSqlSource对象
  • ParameterMappingTokenHandler

    org.apache.ibatis.builder.SqlSourceBuilder的内部类,用于解析#{}的内容,创建ParameterMapping对象,并将其替换成?占位符

    代码如下:

    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
         * 我们在 SQL 语句中定义的占位符对应的 ParameterMapping 数组,根据顺序来的
        private List<ParameterMapping> parameterMappings = new ArrayList<>();
         * 参数类型
        private Class<?> parameterType;
         * additionalParameters 参数的对应的 MetaObject 对象
        private MetaObject metaParameters;
        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            // 创建 additionalParameters 参数的对应的 MetaObject 对象
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        @Override
        public String handleToken(String content) {
            // <1> 构建 ParameterMapping 对象,并添加到 parameterMappings 中
            parameterMappings.add(buildParameterMapping(content));
            // <2> 返回 ? 占位符
            return "?";
         * 根据内容构建一个 ParameterMapping 对象
         * @param content 我们在 SQL 语句中定义的占位符
         * @return ParameterMapping 对象
        private ParameterMapping buildParameterMapping(String content) {
            // <1> 将字符串解析成 key-value 键值对保存
            // 其中有一个key为"property",value就是对应的属性名称
            Map<String, String> propertiesMap = parseParameterMapping(content);
            // <2> 获得属性的名字和类型
            String property = propertiesMap.get("property"); // 名字
            Class<?> propertyType; // 类型
            if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
                propertyType = metaParameters.getGetterType(property);
            } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { // 有对应的类型处理器,例如java.lang.string
                propertyType = parameterType;
            } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { // 设置的 Jdbc Type 是游标
                propertyType = java.sql.ResultSet.class;
            } else if (property == null || Map.class.isAssignableFrom(parameterType)) { // 是 Map 集合
                propertyType = Object.class;
            } else { // 类对象
                MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
                if (metaClass.hasGetter(property)) {
                  // 通过反射获取到其对应的 Java Type
                    propertyType = metaClass.getGetterType(property);
                } else {
                    propertyType = Object.class;
            // <3> 创建 ParameterMapping.Builder 构建者对象
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
            // <3.1> 初始化 ParameterMapping.Builder 对象的属性
            Class<?> javaType = propertyType;
            String typeHandlerAlias = null;
            // 遍历 SQL 配置的占位符信息,例如这样配置:"name = #{name, jdbcType=VARCHAR}"
            for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                if ("javaType".equals(name)) {
                    javaType = resolveClass(value);
                    builder.javaType(javaType);
                } else if ("jdbcType".equals(name)) {
                    builder.jdbcType(resolveJdbcType(value));
                } else if ("mode".equals(name)) {
                    builder.mode(resolveParameterMode(value));
                } else if ("numericScale".equals(name)) {
                    builder.numericScale(Integer.valueOf(value));
                } else if ("resultMap".equals(name)) {
                    builder.resultMapId(value);
                } else if ("typeHandler".equals(name)) {
                    typeHandlerAlias = value;
                } else if ("jdbcTypeName".equals(name)) {
                    builder.jdbcTypeName(value);
                } else if ("property".equals(name)) {
                    // Do Nothing
                } else if ("expression".equals(name)) {
                    throw new BuilderException("Expression based parameters are not supported yet");
                } else {
                    throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
            // <3.2> 如果 TypeHandler 类型处理器的别名非空
            if (typeHandlerAlias != null) {
                builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
            // <3.3> 创建 ParameterMapping 对象
            return builder.build();
        private Map<String, String> parseParameterMapping(String content) {
            try {
                return new ParameterExpression(content);
            } catch (BuilderException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new BuilderException("Parsing error was found in mapping #{" + content
                        + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
    

    构造方法:创建additionalParameters对应的MetaObject对象,便于操作上下文的参数集合,包含附加参数集合(通过 <bind /> 标签生成的,或者<foreach />标签中的集合的元素)

    handleToken(String content)方法:

    调用buildParameterMapping(content)方法,解析#{}的内容创建ParameterMapping对象

    直接返回?占位符

    buildParameterMapping(content)方法:

  • 将字符串解析成 key-value 键值对,通过org.apache.ibatis.builder.ParameterExpression进行解析,其中有一个key为"property",value就是对应的属性名称
  • 获得属性的名字和类型
  • 创建ParameterMapping.Builder构建者对象,设置参数的名称与Java Type
  • 将上面第1步解析到key-value键值对设置到Builder中
  • 如果TypeHandler类型处理器的别名非空,则尝试获取其对应的类型处理器并设置到Builder中
  • 通过Builder创建ParameterMapping对象,如果没有配置TypeHandler类型处理器,则根据参数Java Type和Jdbc Type从TypeHandlerRegistry注册中心获取并赋值到该对象中
  • ParameterExpression

    org.apache.ibatis.builder.ParameterExpression:继承了HashMap<String, String>,参数表达式处理器,在ParameterMappingTokenHandler处理#{}的内容时需要通过其解析成key-value键值对

    构造方法:

    public class ParameterExpression extends HashMap<String, String> {
    	private static final long serialVersionUID = -2417552199605158680L;
         * 从类的注释中可以看出我们可以这样定义占位符
         * 1. #{propertyName, javaType=string, jdbcType=VARCHAR}
         * 2. #{(expression), javaType=string, jdbcType=VARCHAR}
         * @param expression 我们定义的占位符表达式
    	public ParameterExpression(String expression) {
    		parse(expression);
    

    在构造函数中调用其parse(String expression)方法

    private void parse(String expression) {
        // 跳过前面的非法字符(ASCII 小于33),目的是去除空格,还有非法的字符,可以参照 ASCII 字符代码表看看
        int p = skipWS(expression, 0);
        if (expression.charAt(p) == '(') {
            // 属于第二种方式,我在官方没有看到介绍,这里也不做介绍了
            expression(expression, p + 1);
        } else {
            // 将整个字符串转换成 key-value 保存至 Map.Entry
            property(expression, p);
    

    先出去前面的空格或者非法字符,然后调用property(String expression, int left)方法

    // #{propertyName, javaType=string, jdbcType=VARCHAR}
    private void property(String expression, int left) {
        if (left < expression.length()) {
            // 获取到逗号或者冒号第一个位置,也就是分隔符
            int right = skipUntil(expression, left, ",:");
        	// 从内容中截取第一个逗号前面的字符串,也上面第 1 种方式的 "name"
        	put("property", trimmedStr(expression, left, right));
        	// 解析字符串一个逗号后面的字符串,也就是该属性的相关配置
        	jdbcTypeOpt(expression, right);
    

    如果left开始位置小于字符串的长度,那么开始解析

    调用skipUntil方法,获取从left开始,或者:第一个位置,也就是分隔符的位置

    这里第一次进入的话就会先获取第一个,的位置,那么调用trimmedStr方法截取前面的字符串,也就是属性名称,然后存放一个键值对(key为property,value为属性名称)

    调用jdbcTypeOpt(String expression, int p)方法,继续解析后面的字符串,也就是该属性的相关配置

    private void jdbcTypeOpt(String expression, int p) {
        p = skipWS(expression, p);
        if (p < expression.length()) {
            if (expression.charAt(p) == ':') { // 属于上面第 2 种方式,不做分析
                jdbcType(expression, p + 1);
            } else if (expression.charAt(p) == ',') {
                // 将第一个 , 后面的字符串解析成 key-value 保存
                option(expression, p + 1);
            } else {
                throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
    

    如果p(第一个,的位置)后面还有字符串

    则调用option(String expression, int p)方法将一个,后面的字符串解析成key-value键值对保存

    * 将字符串生成转换成key-value的形式 * 例如 expression = "name, jdbcType = VARCHAR, javaType = string" 设置 p = 6 * 这样将会往 Map 中保存两个键值对:"jdbcType"->"VARCHAR" "javaType"->"string" * @param expression 字符串 * @param p 字符串从哪个位置转换 private void option(String expression, int p) { int left = skipWS(expression, p); if (left < expression.length()) { // 获取 = 的位置 int right = skipUntil(expression, left, "="); // 截取 = 前面的字符串,对应的 key String name = trimmedStr(expression, left, right); left = right + 1; // 获取 , 的位置 right = skipUntil(expression, left, ","); // 截取 = 到 , 之间的字符串,也就是对应的 value String value = trimmedStr(expression, left, right); // 将 key-value 保存 put(name, value); // 继续遍历后面的字符串 option(expression, right + 1);

    逐步解析,将字符串解析成key-value键值对保存,这里保存的都是属性的相关配置,例如JdbcType配置

    ParameterMapping

    org.apache.ibatis.mapping.ParameterMapping:保存#{}中配置的属性参数信息,一个普通的实体类,代码如下:

    * SQL 语句中 ? 占位符对应的对象 * @author Clinton Begin public class ParameterMapping { * 全局配置对象 private Configuration configuration; * 属性名称 private String property; * 参数模式 private ParameterMode mode; * 属性的 Java Type * 一般可以直接通过入参对象知道,但是如果入参是 Map,需要显式指定,以确保使用正确的类型处理器 private Class<?> javaType = Object.class; * 属性的 Jdbc Type private JdbcType jdbcType; * 对于数值类型,指定小数点后保留的位数 private Integer numericScale; * 类型处理器 private TypeHandler<?> typeHandler; * 如果 {@link mode} 为 OUT 或者 INOUT,且{@link jdbcType} 为 CURSOR(也就是 Oracle 的 REFCURSOR) * 必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上 private String resultMapId; * Jdbc Type 名称 private String jdbcTypeName; private String expression; private ParameterMapping() {

    SqlSource

    org.apache.ibatis.mapping.SqlSource:SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息),代码如下:

    * Represents the content of a mapped statement read from an XML file or an annotation. * It creates the SQL that will be passed to the database out of the input parameter received from the user. * @author Clinton Begin public interface SqlSource { * 根据传入的参数对象,返回 BoundSql 对象 * @param parameterObject 参数对象 * @return BoundSql 对象 BoundSql getBoundSql(Object parameterObject);

    StaticSqlSource

    org.apache.ibatis.builder.StaticSqlSource:实现 SqlSource 接口,静态的 SqlSource 实现类,代码如下:

    public class StaticSqlSource implements SqlSource {
    	 * 解析后的 SQL 语句,数据库能执行
    	private final String sql;
    	 * 上面 SQL 语句中占位符对应的 ParameterMapping 参数集合
    	private final List<ParameterMapping> parameterMappings;
         * 全局配置对象
    	private final Configuration configuration;
    	public StaticSqlSource(Configuration configuration, String sql) {
    		this(configuration, sql, null);
    	public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    		this.sql = sql;
    		this.parameterMappings = parameterMappings;
    		this.configuration = configuration;
    	@Override
    	public BoundSql getBoundSql(Object parameterObject) {
    		return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    

    SqlSourceBuilder构建的SqlSource类型就是StaticSqlSource,用于获取最终的静态 SQL 语句

    RawSqlSource

    org.apache.ibatis.scripting.defaults.RawSqlSource:实现了SqlSource接口,静态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:

    public class RawSqlSource implements SqlSource {
      private final SqlSource sqlSource;
      public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
         * 因为静态的 SQL 语句可以直接拿来解析,不需要根据入参就可以应用
         * 所以调用 getSql 方法获取静态的 SQL 语句
        this(configuration, getSql(configuration, rootSqlNode), parameterType);
      public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        // 通过 SqlSourceBuilder 将这个静态的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
      private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
        DynamicContext context = new DynamicContext(configuration, null);
        // 调用 StaticTextSqlNode 将 SQL 语句拼接起来
        rootSqlNode.apply(context);
        return context.getSql();
      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        return sqlSource.getBoundSql(parameterObject);
    

    在构造函数中我们可以看到,会先调用getSql方法直接创建SqlSource

    因为静态的 SQL 语句,不需要根据入参来进行逻辑上的判断处理,所以这里在构造函数中就先初始化好 SqlSource,后续需要调用Mapper接口执行SQL的时候就减少了一定的时间

    getSql方法:

  • 创建一个上下文对象DynamicContext,入参信息为null
  • 调用StaticTextSqlNodeapply方法,将所有的SQL拼接在一起
  • 返回拼接好的SQL语句
  • 构造方法:

  • 创建SqlSourceBuilder构建对象sqlSourceParser
  • 调用sqlSourceParserparse方法对该SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象
  • 2步返回的StaticSqlSource对象设置到自己的sqlSource属性中
  • getBoundSql方法:直接通过StaticSqlSource创建BoundSql对象

    DynamicSqlSource

    org.apache.ibatis.scripting.defaults.DynamicSqlSource:实现了SqlSource接口,动态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:

    public class DynamicSqlSource implements SqlSource {
    	private final Configuration configuration;
    	 * 根 SqlNode 对象
    	private final SqlNode rootSqlNode;
    	public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    		this.configuration = configuration;
    		this.rootSqlNode = rootSqlNode;
    	@Override
    	public BoundSql getBoundSql(Object parameterObject) {
    		// <1> 创建本次解析的动态 SQL 语句的上下文
    		DynamicContext context = new DynamicContext(configuration, parameterObject);
    		// <2> 根据上下文应用整个 SqlNode
    		rootSqlNode.apply(context);
    		// <3> 创建 SqlSourceBuilder 对象
    		SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    		Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
            // <4> 通过 SqlSourceBuilder 将应用后的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合
    		SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    		// <5> 创建 BoundSql 对象
    		BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    		// <6> 添加附加参数到 BoundSql 对象中,因为上一步创建的`BoundSql`对象时候传入的仅是入参信息,没有添加附加参数
    		context.getBindings().forEach(boundSql::setAdditionalParameter);
    		return boundSql;
    

    在构造函数中仅仅是赋值,不像RawSqlSource的构造函数一样直接可创建对应的SqlSource对象,因为动态SQL语句需要根据入参信息,来解析SqlNode节点,所以这里在getBoundSql方法中每次都会创建StaticSqlSource对象

    getBoundSql方法:

  • 创建本次解析的动态 SQL 语句的上下文,设置入参信息
  • 根据上下文应用整个 SqlNode,内部包含的所有SqlNode都会被应用,最终解析后的SQL会保存上下文中
  • 创建 SqlSourceBuilder 构建对象sqlSourceParser
  • 调用sqlSourceParserparse方法对第2步解析后的SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象
  • 通过第4步返回的StaticSqlSource对象创建BoundSql对象
  • 添加附加参数到BoundSql对象中,因为上一步创建的BoundSql对象时候传入的仅是入参信息,没有添加附加参数(通过<bind />标签生成的,或者<foreach />标签中的集合的元素)
  • BoundSql

    org.apache.ibatis.mapping.BoundSql:用于数据库可执行的SQL语句的最终封装对象,一个普通的实体类,代码如下:

    public class BoundSql {
       * SQL 语句
      private final String sql;
       * 占位符 ? 对应的入参信息
      private final List<ParameterMapping> parameterMappings;
       * 入参对象
      private final Object parameterObject;
       * 附加参数集合
      private final Map<String, Object> additionalParameters;
       * 附加参数的 MetaObject 对象,便于操作
      private final MetaObject metaParameters;
      public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
      public String getSql() {
        return sql;
      public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
      public Object getParameterObject() {
        return parameterObject;
      public boolean hasAdditionalParameter(String name) {
        String paramName = new PropertyTokenizer(name).getName();
        return additionalParameters.containsKey(paramName);
      public void setAdditionalParameter(String name, Object value) {
        metaParameters.setValue(name, value);
      public Object getAdditionalParameter(String name) {
        return metaParameters.getValue(name);
    

    DefaultParameterHandler

    org.apache.ibatis.scripting.defaults.DefaultParameterHandler:实现了ParameterHandler接口,默认实现类,仅提供这个实现类,用于将入参设置到java.sql.PreparedStatement预编译对象中

    回看到org.apache.ibatis.scripting.xmltags.XMLLanguageDriver语言驱动类中,实现了createParameterHandler方法,返回的参数处理器就是该对象

    代码如下:

    public class DefaultParameterHandler implements ParameterHandler {
    	private final TypeHandlerRegistry typeHandlerRegistry;
         * MappedStatement 对象
    	private final MappedStatement mappedStatement;
    	private final Object parameterObject;
         * BoundSql 对象,实际的 SQL 语句
    	private final BoundSql boundSql;
         * 全局配置对象
    	private final Configuration configuration;
    	public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    		this.mappedStatement = mappedStatement;
    		this.configuration = mappedStatement.getConfiguration();
    		this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    		this.parameterObject = parameterObject;
    		this.boundSql = boundSql;
    	@Override
    	public Object getParameterObject() {
    		return parameterObject;
    	@Override
    	public void setParameters(PreparedStatement ps) {
    		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    		// 获取 SQL 的参数信息 ParameterMapping 对象
    		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    		if (parameterMappings != null) {
    			// 遍历所有参数
    			for (int i = 0; i < parameterMappings.size(); i++) {
    				ParameterMapping parameterMapping = parameterMappings.get(i);
                     * OUT 表示参数仅作为出参,非 OUT 也就是需要作为入参
    				if (parameterMapping.getMode() != ParameterMode.OUT) {
    					Object value;
    					// 获取入参的属性名
    					String propertyName = parameterMapping.getProperty();
    					 * 获取入参的实际值
    					if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
    					  // 在附加参数集合(<bind />标签生成的)中获取
    						value = boundSql.getAdditionalParameter(propertyName);
    					} else if (parameterObject == null) {
    					  // 入参为 null 则该属性也定义为 null
    						value = null;
    					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    					  // 有类型处理器,则直接获取入参对象
    						value = parameterObject;
    					} else {
    					  // 创建入参对应的 MetaObject 对象并获取该属性的值
    						MetaObject metaObject = configuration.newMetaObject(parameterObject);
    						value = metaObject.getValue(propertyName);
    					// 获取定义的参数类型处理器
    					TypeHandler typeHandler = parameterMapping.getTypeHandler();
    					// 获取定义的 Jdbc Type
    					JdbcType jdbcType = parameterMapping.getJdbcType();
    					if (value == null && jdbcType == null) {
    						// 如果没有则设置成 'OTHER'
    						jdbcType = configuration.getJdbcTypeForNull();
    					try {
    						// 通过定义的 TypeHandler 参数类型处理器将 value 设置到对应的占位符
    						typeHandler.setParameter(ps, i + 1, value, jdbcType);
    					} catch (TypeException | SQLException e) {
    						throw new TypeException(
    								"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    

    PreparedStatement中设置参数的大致逻辑如下:

  • 获取SQL的参数信息ParameterMapping对象的集合,然后对其遍历
  • 如果参数的模式不为ParameterMode.OUT(默认为ParameterMode.IN),也就是说需要作为入参,那么开始接下来的赋值
  • 获取该参数对应的属性名称,并通过其获取到对应的值
  • 获取到TypeHandler类型处理器(在ParameterMapping构建的时候会创建对应的TypeHandler
  • 获取到Jdbc Type
  • 通过TypeHandler类型处理器,根据参数位置和Jdbc Type将属性值设置到PreparedStatement
  • 这样就完成对PreparedStatement的赋值,然后通过它执行SQL语句

    在MyBatis初始化的过程中,会将XML映射文件中的<select /> <insert /> <update /> <delete />节点解析成MappedStatement对象,其中会将节点中定义的SQL语句通过XMLLanguageDriver语言驱动类创建一个SqlSource对象,本文就是对该对象进行分析

    通过SqlSource这个对象根据入参可以获取到对应的BoundSql对象,BoundSql对象中包含了数据库需要执行的SQL语句、ParameterMapping参数信息、入参对象和附加的参数(通过<bind />标签生成的,或者<foreach />标签中的集合的元素等等)

    好了,对于MyBatis的整个初始化过程我们已经全部分析完了,其中肯定有不对或者迷惑的地方,欢迎指正!!!感谢大家的阅读!!!😄😄😄

    参考文章:芋道源码《精尽 MyBatis 源码分析》