• MyBatis 源码分析之 SqlSessionFactory 创建


    • 三哥

    内容来自【自学星球】

    欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。

    想要了解更多,欢迎访问👉:自学星球

    --------------SSM系列源码文章及视频导航--------------

    创作不易,望三连支持!

    SSM源码解析视频

    👉点我

    Spring

    1. Spring 中注入 Bean 的各种骚操作做
    2. Spring 中Bean的生命周期及后置处理器使用
    3. Spring 中容器启动分析之refresh方法执行之前
    4. Spring refresh 方法分析之一
    5. Spring refresh 方法之二 invokeBeanFactoryPostProcessors 方法解析
    6. Spring refresh 方法分析之三
    7. Spring refresh 方法之四 finishBeanFactoryInitialization 分析
    8. Spring AOP源码分析一
    9. Spring AOP源码分析二
    10. Spring 事务源码分析

    SpringMVC

    1. SpringMVC 启动流程源码分析
    2. SpringMVC 请求流程源码分析

    MyBatis

    1. MyBatis 源码分析之 SqlSessionFactory 创建
    2. MyBatis 源码分析之 SqlSession 创建
    3. MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行
    4. MyBatis 源码分析之 Select 语句执行(上)
    5. MyBatis 源码分析之 Select 语句执行(下)
    6. MyBatis 源码分析一二级缓存

    ---------------------【End】--------------------

    一、MyBatis案例

    源码分析之前,我们还是老套路的来搭建一个 MyBatis 的小案例,方便后续的源码分析。

    1、引入依赖

    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>8.0.25version>
    dependency>
    
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.4.5version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2、添加配置文件:myBatisConfig.xml

    
    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="mapUnderscoreToCamelCase" value="true"/>
        settings>
        
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                
                <dataSource type="POOLED">
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                dataSource>
            environment>
        environments>
    
        
        <mappers>
            <mapper resource="cn/j3code/studyspring/mybatis/helloworld/mapper/UserMapper.xml"/>
            
        mappers>
    configuration>
    
    • 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
    • 35

    3、编写jdbc连接配置:jdbc.properties

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/my_utils?useUnicode=true&useSSL=false&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    jdbc.username=root
    jdbc.password=root
    
    • 1
    • 2
    • 3
    • 4

    4、编写实体类:User

    public class User {
        private String name;
        private Integer age;
    
        // get,set,toString
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5、编写mapper文件

    public interface UserMapper {
        /**
         * 查询所有用户
         *
         * @return
         */
        List<User> findAll();
    
        /**
         * 根据用户id查询用户
         *
         * @param id
         * @return
         */
        User findById(int id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6、编写 UserMapper.java 对应的 UserMapper.xml ,记住两者的包路径名称记得保持一致。

    <mapper namespace="cn.j3code.studyspring.mybatis.helloworld.mapper.UserMapper">
        <select id="findAll" resultType="cn.j3code.studyspring.mybatis.helloworld.User">
            select * from t_user;
        select>
    
        <select id="findById" parameterType="int" resultType="cn.j3code.studyspring.mybatis.helloworld.User">
            select * from t_user where id=#{id};
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7、测试(记得测试前把 SQL 创建好)

    public class MyBatisUserMain {
        public static void main(String[] args) throws Exception {
            //读取配置文件流
            InputStream in = Resources.getResourceAsStream("myBatisConfig.xml");
    
            // sqlsessionfactory对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    
            // sqlsession对象
            SqlSessionFactory sqlSessionFactory = builder.build(in);
    
            // 获得会话
             session = sqlSessionFactory.openSession();
    
            UserMapper userMapper = session.getMapper(UserMapper.class);
    
            System.out.println(userMapper.findAll());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    很显然,MyBatis 的重要功能逻辑都体现在了第七步骤中。首先 MyBatis 通过读取我们编写的配置文件构建出 SqlSessionFactory 对象出来,然后通过 SqlSessionFactory 对象创建出操作 MySQL 的 SqlSession ,最后我们就可以通过 SqlSession 对象操作数据库。

    下面我们就来分析源码把!

    二、SqlSessionFactory 创建

    通过案例可知,SqlSessionFactory 对象是通过 build 方法构建出来的,所以我们的源码分析入口就在这:

    SqlSessionFactory sqlSessionFactory = builder.build(in);
    
    • 1

    org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)

    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    
    • 1
    • 2
    • 3

    org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 创建配置文件解析器
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 调用 parser.parse() 方法解析配置文件,生成 Configuration 对象
            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.
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们的 SqlSessionFactory 对象是通过 SqlSessionFactoryBuilder 的 build 方法创建出来的,而 build 方法内部是通过一个 XMLConfigBuilder 对象解析mybatisConfig.xml 文件生成一个 Configuration 对象,最后调用 DefaultSqlSessionFactory 构造器将 Configuration 对象传入生成我们所要的 SqlSessionFactory 。

    抓住关键点 Configuration 对象,在 MyBatis 的配置文件中我们的所有配置都只能在 标签中配置,所以 标签配置的值在 Configuration 对象中都有对应的属性,即他们是一一对应的。

    那,我们来看下 Configuration 对于的 XML 配置文件示例:

        
    
    DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">    
    
    <configuration>    
    
        
        <properties resource="db.propertis">
            <property name="XXX" value="XXX"/>
        properties>
    
        
        <typeAliases>
            
            <typeAlias type="cn.j3code.codehelp.entity.User" alias="user"/>
        typeAliases>
    
        
        <typeHandlers>
            
        typeHandlers>
    
        
        
        <objectFactory type="">objectFactory>
    
        
        <plugins>
            
            <plugin interceptor="cn.j3code.interceptor.MyPlugin">plugin>
        plugins>
    
        
        <settings>   
            <setting name="cacheEnabled" value="false" />   
            <setting name="useGeneratedKeys" value="true" />
            <setting name="defaultExecutorType" value="REUSE" />   
            <setting name="lazyLoadingEnabled" value="true"/>
            <setting name="aggressiveLazyLoading" value="true"/>
            <setting name="multipleResultSetsEnabled" value="true"/>
            <setting name="useColumnLabel" value="true"/>
            <setting name="autoMappingBehavior" value="PARTIAL"/>
            <setting name="defaultExecutorType" value="SIMPLE"/>
            <setting name="defaultFetchSize" value=""/>
            <setting name="defaultStatementTimeout" value="5"/>
            <setting name="safeRowBoundsEnabled" value="false"/>
            <setting name="safeResultHandlerEnabled" value="true"/>
            <setting name="mapUnderscoreToCamelCase" value="false"/>
            <setting name="localCacheScope" value="SESSION"/>
            <setting name="jdbcTypeForNull" value="OTHER"/>
            <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
            <setting name="callSettersOnNulls" value="false"/>
            <setting name="logPrefix" value="XXXX"/>
            <setting name="logImpl" value="SLF4J"/>
            <setting name="proxyFactory" value="CGLIB"/>
        settings>  
    
        
        <environments default="development">    
            <environment id="development">    
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">    
                    <property name="driver" value="com.mysql.jdbc.Driver" />    
                    <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />    
                    <property name="username" value="root" />    
                    <property name="password" value="root" />    
                dataSource>    
            environment>    
        environments>    
    
        
        <mappers>    
            <mapper resource="cn/j3code/codehelp/mapper/UserMapper.xml"/>    
        mappers>    
    configuration>
    
    • 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
    • 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
    • 72
    • 73
    • 74
    • 75

    Configuration类属性如下:

    public class Configuration {
    
        protected Environment environment;//运行环境
    
        protected boolean safeRowBoundsEnabled;
        protected boolean safeResultHandlerEnabled = true;
        protected boolean mapUnderscoreToCamelCase;
    
        //true:有延迟加载属性的对象被调用时完全加载任意属性;false:每个属性按需要加载
        protected boolean aggressiveLazyLoading;
    
        //是否允许多种结果集从一个单独的语句中返回
        protected boolean multipleResultSetsEnabled = true;
        protected boolean useGeneratedKeys;//是否支持自动生成主键
        protected boolean useColumnLabel = true;//是否使用列标签
        protected boolean cacheEnabled = true;//是否使用缓存标识
        protected boolean callSettersOnNulls;
        protected boolean useActualParamName = true;
        protected boolean returnInstanceForEmptyRow;
    
        protected String logPrefix;
        protected Class <? extends Log> logImpl;
        protected Class <? extends VFS> vfsImpl;
        protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
        protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
        protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
        protected Integer defaultStatementTimeout;
        protected Integer defaultFetchSize;
        protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    
        //指定mybatis如果自动映射列到字段和属性,PARTIAL会自动映射简单的没有嵌套的结果,FULL会自动映射任意复杂的结果
        protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    
        protected Properties variables = new Properties();
        protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
        protected ObjectFactory objectFactory = new DefaultObjectFactory();
        protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    
        //是否延时加载,false则表示所有关联对象即使加载,true表示延时加载
        protected boolean lazyLoadingEnabled = false;
        protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
    
        protected String databaseId;
        /**
        * Configuration factory class.
        * Used to create Configuration for loading deserialized unread properties.
        *
        * @see Issue 300 (google code)
        */
        protected Class<?> configurationFactory;
    
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
        protected final InterceptorChain interceptorChain = new InterceptorChain();
        protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
        protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
        protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
        protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
        protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
        protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
    
        protected final Set<String> loadedResources = new HashSet<String>(); //已经加载过的resource(mapper)
        protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
    
        protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
        protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
        protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
        protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
    
        /*
        * A map holds cache-ref relationship. The key is the namespace that
        * references a cache bound to another namespace and the value is the
        * namespace which the actual cache is bound to.
        */
        protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
    
        //其他方法略
    }
    
    • 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
    • 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
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    Configuration 构造器:

    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    
        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    
        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    
        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    
        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
    }
    
    • 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

    typeAliasRegistry 注册了很多别名,我们看看 TypeAliasRegistry:

    public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
    
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
    
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
    
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
    
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
    
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
    
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
    
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
    
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
    
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
    
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
    
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    
        // mybatis-typehandlers-jsr310
        if (Jdk.dateAndTimeApiExists) {
            Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
        }
    
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
    }
    // register 方法最终会把值放入这个 Map 中
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    
    • 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
    • 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
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    让我们回到 build 方法中的 parser.parse() 方法,看看他是如何生成 Configuration 对象的。

    2.1 parse 解析 XML 生成 Configuration

    org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // 解析配置,/configuration 表达式表示的就是  标签
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 properties 配置
            propertiesElement(root.evalNode("properties"));
    
            // 解析 settings 配置,并将其转换为 Properties 对象
            Properties settings = settingsAsProperties(root.evalNode("settings"));
    
            // 加载 vfs
            loadCustomVfs(settings);
    
            // 解析 typeAliases 配置
            typeAliasesElement(root.evalNode("typeAliases"));
    
            // 解析 plugins 配置
            pluginElement(root.evalNode("plugins"));
    
            // 解析 objectFactory 配置
            objectFactoryElement(root.evalNode("objectFactory"));
    
            // 解析 objectWrapperFactory 配置
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    
            // 解析 reflectorFactory 配置
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
    
            // settings 中的信息设置到 Configuration 对象中
            settingsElement(settings);
    
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 environments 配置
            environmentsElement(root.evalNode("environments"));
    
            // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    
            // 解析 typeHandlers 配置
            typeHandlerElement(root.evalNode("typeHandlers"));
    
            // 解析 mappers 配置
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    parseConfiguration 方法通过 root 节点也就是 标签,来依次解析该标签中配置的各种子标签,解析标签如下:

    ok,下面我们对这些标签的解析依次做分析。

    2.1.1 解析 properties 配置

    先来看一下 properties 节点的配置内容,如下:

    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    properties>
    
    • 1
    • 2
    • 3
    • 4

    下面我们来看看解析方法

    org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement

    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
            // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
            Properties defaults = context.getChildrenAsProperties();
            // 获取 propertis 节点中的 resource 和 url 属性值
            String resource = context.getStringAttribute("resource");
            String url = context.getStringAttribute("url");
            // 两者都不为空,则抛出异常
            if (resource != null && url != null) {
                throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
            }
    
            if (resource != null) {
                // 从文件系统中加载并解析属性文件
                defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {
                // 通过 url 加载并解析属性文件
                defaults.putAll(Resources.getUrlAsProperties(url));
            }
            Properties vars = configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }
            // 将属性值设置到 XPathParser 中 
            parser.setVariables(defaults);
            // 将属性值设置到 configuration 中
            configuration.setVariables(defaults);
        }
    }
    
    • 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

    org.apache.ibatis.parsing.XNode#getChildrenAsProperties

    public Properties getChildrenAsProperties() {
        //创建一个Properties对象
        Properties properties = new Properties();
        // 获取并遍历子节点
        for (XNode child : getChildren()) {
            // 获取 property 节点的 name 和 value 属性
            String name = child.getStringAttribute("name");
            String value = child.getStringAttribute("value");
            if (name != null && value != null) {
                // 设置属性到属性对象中
                properties.setProperty(name, value);
            }
        }
        return properties;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    org.apache.ibatis.parsing.XNode#getChildren

    public List<XNode> getChildren() {
        List<XNode> children = new ArrayList<XNode>();
        // 获取子节点列表
        NodeList nodeList = node.getChildNodes();
        if (nodeList != null) {
            for (int i = 0, n = nodeList.getLength(); i < n; i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    children.add(new XNode(xpathParser, node, variables));
                }
            }
        }
        return children;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    解析 properties 主要分三个步骤:

    • 1、解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。
    • 2、从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
    • 3、将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。

    需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。

    2.1.2 解析 settings 配置

    先来看一下 settings 节点的配置内容,如下:

    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
    settings>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    下面我们来看看解析方法

    org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsAsProperties

    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            return new Properties();
        }
    
        // 获取 settings 子节点中的内容,解析成Properties,getChildrenAsProperties 方法前面已分析过
        Properties props = context.getChildrenAsProperties();
    
        // 创建 Configuration 类的“元信息”对象
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        for (Object key : props.keySet()) {
            // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
            if (!metaConfig.hasSetter(String.valueOf(key))) {
                throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
            }
        }
        return props;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    解析 settings 标签之后会将其中的 name 及 value 都都封装到 Properties 当中,最后校验 Configuration 中是否有相关的属性配置,如果没有则报错。

    下面我们来看看 settings 标签值设置到 Configuration 对象中的方法

    org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement

    private void settingsElement(Properties props) throws Exception {
        // 设置 autoMappingBehavior 属性,默认值为 PARTIAL
        configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    
        // 设置 cacheEnabled 属性,默认值为 true
        configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
        configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
        configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
        configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
        configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
        configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
        configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
        @SuppressWarnings("unchecked")
    
        // 解析默认的枚举处理器
        Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
        // 设置默认枚举处理器
        configuration.setDefaultEnumTypeHandler(typeHandler);
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        configuration.setLogPrefix(props.getProperty("logPrefix"));
        @SuppressWarnings("unchecked")
        Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
        configuration.setLogImpl(logImpl);
        configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38

    2.1.3 解析 typeAliases 配置

    在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。

    第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名。这种方式的配置如下:

    <typeAliases>
        <package name="cn.j3code.mybatis.po"/>
    typeAliases>
    
    • 1
    • 2
    • 3

    第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:

    <typeAliases>
        <typeAlias alias="employe" type="cn.j3code.mybatis.po.Employe" />
        <typeAlias type="cn.j3code.mybatis.po.User" />
    typeAliases>
    
    • 1
    • 2
    • 3
    • 4

    来看一下两种不同的别名配置是怎样解析的。代码如下:

    private void typeAliasesElement(XNode parent) {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                // 从指定的包中解析别名和类型的映射
                if ("package".equals(child.getName())) {
                    String typeAliasPackage = child.getStringAttribute("name");
                    configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
    
                    // 从 typeAlias 节点中解析别名和类型的映射
                } else {
                    // 获取 alias 和 type 属性值,alias 不是必填项,可为空
                    String alias = child.getStringAttribute("alias");
                    String type = child.getStringAttribute("type");
                    try {
                        // 加载 type 对应的类型
                        Class<?> clazz = Resources.classForName(type);
    
                        // 注册别名到类型的映射   
                        if (alias == null) {
                            typeAliasRegistry.registerAlias(clazz);
                        } else {
                            typeAliasRegistry.registerAlias(alias, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                    }
                }
            }
        }
    }
    
    • 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

    显然,子标签名字为 package 则是通过指定的包解析别名和类型的映射反之则是从 typeAlias 节点中解析别名和类型的映射。

    1)从指定的包中解析并注册别名

    org.apache.ibatis.type.TypeAliasRegistry#registerAliases(java.lang.String)

    public void registerAliases(String packageName){
      registerAliases(packageName, Object.class);
    }
    
    • 1
    • 2
    • 3

    org.apache.ibatis.type.TypeAliasRegistry#registerAliases(java.lang.String, java.lang.Class)

    public void registerAliases(String packageName, Class<?> superType){
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    
        /*
         * 查找包下的父类为 Object.class 的类。
         * 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。
         */ 
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    
        // 获取查找结果
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for(Class<?> type : typeSet){
            // Ignore inner classes and interfaces (including package-info.java)
            // Skip also inner classes. See issue #6
            // 忽略匿名类,接口,内部类
            if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                // 为类型注册别名 
                registerAlias(type);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    主要分为两个步骤:

    • 1、查找指定包下的所有类。
    • 2、遍历查找到的类型集合,为每个类型注册别名。

    2)从 typeAlias 节点中解析并注册别名

    在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。

    org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.Class)

    public void registerAlias(Class<?> type) {
        // 获取全路径类名的简称
        String alias = type.getSimpleName();
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
            // 从注解中取出别名
            alias = aliasAnnotation.value();
        } 
        // 调用重载方法注册别名和类型映射
        registerAlias(alias, type);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.String, java.lang.Class)

    public void registerAlias(String alias, Class<?> value) {
        if (alias == null) {
            throw new TypeException("The parameter alias cannot be null");
        }
    
        // issue #748
        // 将别名转成小写
        String key = alias.toLowerCase(Locale.ENGLISH);
    
        /*
             * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
             * 不一致则抛出异常,不允许一个别名对应两种类型
             */
        if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
            throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
        }
    
        // 缓存别名到类型映射
        TYPE_ALIASES.put(key, value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    若用户为明确配置 alias 属性,MyBatis 会使用类名的小写形式作为别名。比如,全限定类名cn.j3code.mybatis.po.User 的别名为 user。若类中有 @Alias 注解,则从注解中取值作为别名。

    2.1.4 解析 plugins 配置

    插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比如分页插件,在SQL执行之前动态拼接语句,对于插件机制,后面会进行分析,先来看看插件的配置,如下:

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        plugin>
    plugins>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    方法源码:

    org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement

    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                String interceptor = child.getStringAttribute("interceptor");
                // 获取配置信息
                Properties properties = child.getChildrenAsProperties();
                // 解析拦截器的类型,并创建拦截器
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
                // 设置属性
                interceptorInstance.setProperties(properties);
                // 添加拦截器到 Configuration 中
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    org.apache.ibatis.session.Configuration#addInterceptor

    public void addInterceptor(Interceptor interceptor) {
      interceptorChain.addInterceptor(interceptor);
    }
    
    • 1
    • 2
    • 3
    public class InterceptorChain {
        private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4

    首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到 Configuration 中。

    InterceptorChain 实际上就是一个 interceptors 集合。

    2.1.5 解析 environments 配置

    在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            dataSource>
        environment>
    environments>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    解析源码:

    org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement

    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            if (environment == null) {
                // 获取 default 属性
                environment = context.getStringAttribute("default");
            }
            for (XNode child : context.getChildren()) {
                // 获取 id 属性
                String id = child.getStringAttribute("id");
    
                /*
                 * 检测当前 environment 节点的 id 与其父节点 environments 的属性 default 
                 * 内容是否一致,一致则返回 true,否则返回 false
                 * 将其default属性值与子元素environment的id属性值相等的子元素设置为当前使用的Environment对象
                 */
                if (isSpecifiedEnvironment(id)) {
                    // 将environment中的transactionManager标签转换为TransactionFactory对象
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    // 将environment中的dataSource标签转换为DataSourceFactory对象
                    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    // 创建 DataSource 对象
                    DataSource dataSource = dsFactory.getDataSource();
                    Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);
                    // 构建 Environment 对象,并设置到 configuration 中
                    configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }
    
    • 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

    environmentsElement 方法主要是遍历其子节点,将 Transaction 和 DataSource 创建出来,下面分别来看看如何解析:

    org.apache.ibatis.builder.xml.XMLConfigBuilder#transactionManagerElement

    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            // 通过别名获取Class,并实例化
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement

    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            // 通过别名获取Class,并实例化
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    中type有"JDBC"、"MANAGED"这两种配置,而我们前面Configuration中默认注册的别名中有对应的JdbcTransactionFactory.class、ManagedTransactionFactory.class这两个TransactionFactory

    中type有"JNDI"、“POOLED”、"UNPOOLED"这三种配置,默认注册的别名中有对应的JndiDataSourceFactory.class、PooledDataSourceFactory.class、UnpooledDataSourceFactory.class这三个DataSourceFactory

    而我们的environment配置中transactionManager type="JDBC"和dataSource type=“POOLED”,则生成的transactionManager为JdbcTransactionFactory,DataSourceFactory为PooledDataSourceFactory

    我们来看看 PooledDataSourceFactory 和 UnpooledDataSourceFactory

    public class UnpooledDataSourceFactory implements DataSourceFactory {
    
        private static final String DRIVER_PROPERTY_PREFIX = "driver.";
        private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
    
        protected DataSource dataSource;
    
        public UnpooledDataSourceFactory() {
            //创建UnpooledDataSource实例
            this.dataSource = new UnpooledDataSource();
        }
        
        @Override
        public DataSource getDataSource() {
            return dataSource;
        }
        
        ......
    }
    
    //继承UnpooledDataSourceFactory
    public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
    
        public PooledDataSourceFactory() {
            //创建PooledDataSource实例
            this.dataSource = new PooledDataSource();
        }
    
    }
    
    • 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

    我们发现 UnpooledDataSourceFactory 创建的dataSource是 UnpooledDataSource,PooledDataSourceFactory创建的 dataSource是PooledDataSource

    2.1.6 解析 mappers 配置

    mapper.xml 文件的配置方式

    <mappers>
        
        <mapper resource="cn/j3code/studyspring/mybatis/helloworld/mapper/UserMapper.xml"/>
        
        <package name="cn.j3code.Dao">package>
        
         <mapper url="file:///var/mappers/UserMapper.xml"/>
        
         <mapper class="org.mybatis.mappers.ManagerMapper"/>
    mappers>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法源码:

    org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {
                    //检测是否是package节点
                    String mapperPackage = child.getStringAttribute("name");
                    configuration.addMappers(mapperPackage);
                } else {
                    //读取中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
                    String resource = child.getStringAttribute("resource");
                    //读取mapper节点的url属性
                    String url = child.getStringAttribute("url");
                    //读取mapper节点的class属性
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {
                        //根据rusource加载mapper文件
                        ErrorContext.instance().resource(resource);
                        //读取文件字节流
                        InputStream inputStream = Resources.getResourceAsStream(resource);
                        //实例化mapper解析器
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                        //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        //从网络url资源加载mapper文件
                        ErrorContext.instance().resource(url);
                        InputStream inputStream = Resources.getUrlAsStream(url);
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url == null && mapperClass != null) {
                        //使用mapperClass加载文件
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        configuration.addMapper(mapperInterface);
                    } else {
                        //resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                    }
                }
            }
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    该方法对 Mapper 不同的配置方式做了不同的解析流程,后面我单独拿出一节来分析,这里,我们仅简单的看一下这个方法就好了。

    2.2 创建 DefaultSqlSessionFactory

    2.1 节我们吧 XMLConfigBuilder 的 parse 方法中的重要步骤都过了一遍了,然后返回的就是一个完整的 Configuration 对象了,最后通过 SqlSessionFactoryBuilder 的 build 的重载方法创建了一个 SqlSessionFactory 实例DefaultSqlSessionFactory,我们来看看。

    public SqlSessionFactory build(Configuration config) {
        //创建DefaultSqlSessionFactory实例
        return new DefaultSqlSessionFactory(config);
    }
    
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
        private final Configuration configuration;
    
        //只是将configuration设置为其属性
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
    
        //略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    三、mapper.xml 的解析

    在 2.1.6 节中,我们就是提了一下该方法,而没有做具体的分析,本就就是对它做具体分析的。

    下面我们来看看 mapper 配置的方式吧!

    1)接口信息进行配置

    <mappers>
        <mapper class="org.mybatis.mappers.UserMapper"/>
        <mapper class="org.mybatis.mappers.ProductMapper"/>
        <mapper class="org.mybatis.mappers.ManagerMapper"/>
    </mappers>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:这种方式必须保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。因为是通过获取mapper中的class属性,拼接上.xml来读取UserMapper.xml,如果xml文件名不同或者不在同一个包中是无法读取到xml的。

    2)相对路径进行配置

    <mappers>
        <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
        <mapper resource="org/mybatis/mappers/ProductMapper.xml"/>
        <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>
    mappers>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:这种方式不用保证同接口同包同名。但是要保证xml中的namespase和对应的接口名相同。

    3)绝对路径进行配置

    <mappers>
        <mapper url="file:///var/mappers/UserMapper.xml"/>
        <mapper url="file:///var/mappers/ProductMapper.xml"/>
        <mapper url="file:///var/mappers/ManagerMapper.xml"/>
    mappers>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4)接口所在包进行配置

    <mappers>
        <package name="org.mybatis.mappers"/>
    mappers>
    
    • 1
    • 2
    • 3

    这种方式和第一种方式要求一致,保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。

    注意:以上所有的配置都要保证 xml 中的 namespase 和对应的接口名相同。

    下面就以 packae 属性为例,具体来分析一下。

    3.1 mappers解析入口方法

    org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {
                    //检测是否是package节点
                    String mapperPackage = child.getStringAttribute("name");
                    configuration.addMappers(mapperPackage);
                } else {
                    //读取中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
                    String resource = child.getStringAttribute("resource");
                    //读取mapper节点的url属性
                    String url = child.getStringAttribute("url");
                    //读取mapper节点的class属性
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {
                        //根据rusource加载mapper文件
                        ErrorContext.instance().resource(resource);
                        //读取文件字节流
                        InputStream inputStream = Resources.getResourceAsStream(resource);
                        //实例化mapper解析器
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                        //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        //从网络url资源加载mapper文件
                        ErrorContext.instance().resource(url);
                        InputStream inputStream = Resources.getUrlAsStream(url);
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url == null && mapperClass != null) {
                        //使用mapperClass加载文件
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        configuration.addMapper(mapperInterface);
                    } else {
                        //resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                    }
                }
            }
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    显然,该方法就是根据我们上面分析的四种加载映射文件的方式进行处理:

    1. 从文件系统中加载映射文件
    2. 通过 URL 的方式加载和解析映射文件
    3. 通过 mapper 接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中
    4. 最后一种是通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息

    上面代码对 Mapper 文件的解析主要体现在下面两个代码:

    • configuration.addMappers(mapperPackage)
    • mapperParser.parse()

    那,我们先来看看 packae 扫描的形式,及 configuration.addMappers(mapperPackage) 方法。

    org.apache.ibatis.session.Configuration#addMappers(java.lang.String)

    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }
    
    • 1
    • 2
    • 3

    org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String)

    public void addMappers(String packageName) {
        // 传入包名和Object.class类型
        addMappers(packageName, Object.class);
    }
    
    • 1
    • 2
    • 3
    • 4

    org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String, java.lang.Class)

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    
        /*
         * 查找包下的父类为 Object.class 的类。
         * 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。上一篇文章我们已经看过这部分的源码,不再累述了
         */ 
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // 获取查找结果
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
            // 解析mapper类
            addMapper(mapperClass);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    该方法先找到传入的包名下所有的 class 文件,然后挨个遍历解析对应的 mapper 及方法信息,源码如下:

    org.apache.ibatis.binding.MapperRegistry#addMapper

    public <T> void addMapper(Class<T> type) {
        // 该类必须为接口
        if (type.isInterface()) {
            // 检测该类是否已经被加载过
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                /*
                 * 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
                 */
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // 构造一个MapperAnnotationBuilder去解析这个类
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                // 解析注解中的信息
                parser.parse();
                loadCompleted = true;
            } finally {
                // 若上面加载时出错,则将该类移除
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    
    • 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

    该方法对传入的 class 做一些基本的判断,如果不是接口和未解析的 class 则跳过,反之则调用 parser.parse() 方法解析 class ,我们来看看该方法源码。

    org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse

    public void parse() {
        String resource = type.toString();
        // 是否已经加载过该resource(值形式类似于为:interface org.tree.study.mybatis.dao.UserDao)
        if (!configuration.isResourceLoaded(resource)) {
            // 加载类对应的xml文件
            loadXmlResource();
            // 将该resource加入已经加载过的Set中
            configuration.addLoadedResource(resource);
            // 设置命名空间
            assistant.setCurrentNamespace(type.getName());
            // 解析 Mapper 接口的 @CacheNamespace 注解,创建缓存
            parseCache();
            // 解析 Mapper 接口的 @CacheNamespaceRef 注解,引用其他命名空间
            parseCacheRef();
            // 加载类中所有的方法
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                try {
                    // 解析方法上面的注解
                    parseStatement(method);
                } catch (IncompleteElementException e) {
                    // 将异常解析的方法加入一个List中
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        // 重新处理异常解析的方法
        parsePendingMethods();
    }
    
    • 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

    该方法解析的一个重点就是 loadXmlResource() ,解析对应的 xml 文件,紧接着会对 class 中的缓存注解及方法注解做一些解析处理。

    下面我们来看看 loadXmlResource() 方法源码,对于上述方法的其他代码不做分析,因为我们会在 XMLMapperBuilder#parse 中做分析(MapperAnnotationBuilder#parse 为注解版解析,XMLMapperBuilder#parse 为 xml 版解析)。

    org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource

    private void loadXmlResource() {
        // 看是否已经加载过该xml文件
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
            // 从这里我们可以看出,若是要使用加载类的方法,xml文件的路径需要和类的包名路径一致,并且xml文件名要和类名一致(如:org.tree.study.mybatis.dao.UserDao对应org/tree/study/mybatis/dao/UserDao.xml)
            String xmlResource = type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            } catch (IOException e) {
                // ignore, resource is not required
            }
            if (inputStream != null) {
                // 从这里我们看出,加载xml文件时,使用了XMLMapperBuilder,这正是我们要讲的另一种方式,这里的详细过程,我们待会再分析
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
                xmlParser.parse();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    由上述方法可知为啥 Mapper 类要和 Mapper.xml 文件目录一致了把!

    紧接着该方法就会调用 XMLMapperBuilder 中的 parse 方法去解析我们编写的 xml 文件,那么也就来到了我们上面所说的第二个方法。

    org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

    public void parse() {
        // 检测映射文件是否已经被解析过
        if (!configuration.isResourceLoaded(resource)) {
            // 解析 mapper 节点
            configurationElement(parser.evalNode("/mapper"));
            // 添加资源路径到“已解析资源集合”中
            configuration.addLoadedResource(resource);
            // 通过命名空间绑定 Mapper 接口
            bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    该方法我们会分析两部分:

    1. 解析 mapper 节点
    2. 通过命名空间绑定 Mapper 接口

    先来看看解析 mapper 节点

    3.2 解析映射文件

    在 MyBatis 映射文件中,可以配置多种节点。比如 ,, 以及 节点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }

    • 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

    该方法也比较好理解,主要就是解析 mapper.xml 文件中所有的可配置节点,解析步骤:

    1. 获取命名空间并设置到 builderAssistant 中
    2. 解析缓存节点,
    3. 解析  节点
    4. 解析 节点
    5. 解析  节点
    6. 解析