• 2. Mybatis流程


    2. Mybatis流程

    private SqlSessionFactory sqlSessionFactory;
    
    @Before
    public void init() throws IOException {
        //--------------------第一阶段---------------------------
        // 1.读取mybatis配置文件创SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 1.读取mybatis配置文件创SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }
    
    @Test
    // 快速入门
    public void quickStart() throws IOException {
        //--------------------第二阶段---------------------------
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应 被代理的 mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    
        //--------------------第三阶段---------------------------
        // 4.执行查询语句并返回单条数据
        TUser user = mapper.selectByPrimaryKey(2);
        System.out.println(user);
    
        System.out.println("----------------------------------");
    
        // 5.执行查询语句并返回多条数据
        List<TUser> users = mapper.selectAll();
        for (TUser tUser : users) {
            System.out.println(tUser);
        }
    }
    
    • 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
    1. 读取配置文件mybatis-config.xml(回去加载我们的mapper.xml文件), 创建sqlSessionFactory
    2. 获取sqlSession
    3. 获取对应 被代理的 mapper
    4. 执行查询语句并返回单条数据
    5. 执行查询语句并返回多条数据

    mybatis核心流程三大阶段

    image-20220626230202206

    初始阶段,代理阶段, 数据读取阶段

    1.Mybatis的第一阶段: 初始化阶段

    建造者模式

    类图

    image-20220626230338721

    Director其实就是客户端, 理解成客户端, 发现类图理解变简单了, , 其实也是一种流式编程(链式编程)

    Builder接口

    public interface RedPacketBuilder {
    
        RedPacketBuilder setPublisherName(String publishName);
    
        RedPacketBuilder setAcceptName(String acceptName);
    
        RedPacketBuilder setPacketAmount(BigDecimal packetAmount);
    
        RedPacketBuilder setPacketType(int packetType);
    
        RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime);
    
        RedPacketBuilder setOpenPacketTime(Date openPacketTime);
    
        RedPacket build();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    具体的建造者, 如果不要接口, 简化的类图其实就是建造者和产品

    public class RedPacketBuilderImpl implements RedPacketBuilder {
    
        private String publisherName;
    
        private String acceptName;
    
        private BigDecimal packetAmount;
    
        private int packetType;
    
        private Date pulishPacketTime;
    
        private Date openPacketTime;
    
        public static RedPacketBuilderImpl getBulider() {
            return new RedPacketBuilderImpl();
        }
    
        @Override
        public RedPacketBuilder setPublisherName(String publishName) {
            this.publisherName = publishName;
            return this;
        }
    
        @Override
        public RedPacketBuilder setAcceptName(String acceptName) {
            this.acceptName = acceptName;
            return this;
        }
    
        @Override
        public RedPacketBuilder setPacketAmount(BigDecimal packetAmount) {
            this.packetAmount = packetAmount;
            return this;
        }
    
        @Override
        public RedPacketBuilder setPacketType(int packetType) {
            this.packetType = packetType;
            return this;
        }
    
        @Override
        public RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime) {
            this.pulishPacketTime = pushlishPacketTime;
            return this;
        }
    
        @Override
        public RedPacketBuilder setOpenPacketTime(Date openPacketTime) {
            this.openPacketTime = openPacketTime;
            return this;
        }
    
        public RedPacket build() {
            return new RedPacket(publisherName, acceptName, packetAmount, packetType, pulishPacketTime, openPacketTime);
        }
    }
    
    • 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

    产品

    public class RedPacket {
    
        private String publisherName; //发包人
    
        private String acceptName; //收包人
    
        private BigDecimal packetAmount; //红包金额
    
        private int packetType; //红包类型
    
        private Date pulishPacketTime; //发包时间
    
        private Date openPacketTime; //抢包时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Mybatis建造者类图

    image-20220627012506080

    • XMLConfigBuilder: 主要负责解析mybatis-config.xml;
    • XMLMapperBuilder: 主要负责解析映射配置文件;
    • XMLStatementBuilder: 主要负责解析映射配置文件中的SQL节点;

    映射器的关键类

    • Configuration : Mybatis启动初始化的核心就是将所有xml配置文件信息加载到Configuration对象中, Configuration是单例的,生命周期是应用级的;
    • MapperRegistry:mapper接口动态代理工厂类的注册中心。在MyBatis中,通过mapperProxy实现InvocationHandler接口,MapperProxyFactory用于生成动态代理的实例对象;
    • ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id,result等子元素;
    • MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点的很多重要属性;
    • SqlSource:mapper.xml文件中的sql语句会被解析成SqlSource对象,经过解析SqlSource包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;

    SqlSessionFactoryBuilder

    没有建造接口的具体建造类

    public class SqlSessionFactoryBuilder {
    
        public SqlSessionFactory build(Reader reader) {
            return build(reader, null, null);
        }
    
        public SqlSessionFactory build(Reader reader, String environment) {
            return build(reader, environment, null);
        }
    
        public SqlSessionFactory build(Reader reader, Properties properties) {
            return build(reader, null, properties);
        }
    
        public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
            try {
                //读取配置文件
                XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
                return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
            } catch (Exception e) {
                throw ExceptionFactory.wrapException("Error building SqlSession.", e);
            } finally {
                ErrorContext.instance().reset();
                try {
                    reader.close();
                } catch (IOException e) {
                    // Intentionally ignore. Prefer previous error.
                }
            }
        }
    
        public SqlSessionFactory build(InputStream inputStream) {
            return build(inputStream, null, null);
        }
    
        public SqlSessionFactory build(InputStream inputStream, String environment) {
            return build(inputStream, environment, null);
        }
    
        public SqlSessionFactory build(InputStream inputStream, Properties properties) {
            return build(inputStream, null, properties);
        }
    
        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            try {
                XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                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.
                }
            }
        }
    
        public SqlSessionFactory build(Configuration config) {
            return new DefaultSqlSessionFactory(config);
        }
    
    }
    
    • 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

    它的产品接口是SqlSessionFactory, 具体产品DefaultSqlSessionFactory

    DefaultSqlSessionFactory

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
        private final Configuration configuration; // 配置总管家, 其实例化是一个非常复杂的过程
    
    • 1
    • 2
    • 3

    Configuration

    Configuration类图解

    image-20220627012928424

    三个核心容器:

    ​ mapperRegistry: 注册接口的容器

    ​ mappedStatements:

    Mybatis启动初始化的核心就是读取xml,初始化Configuration类, Configuration类是单例

    image-20220701215511579

    为什么XMLConfigBuilder没有配一个Assistant? 因为相对比较简单, 解析类容固定

    数据来源非常多

    public class Configuration {
    
        protected Environment environment;
    
        /* 是否启用行内嵌套语句**/
        protected boolean safeRowBoundsEnabled;
        protected boolean safeResultHandlerEnabled = true;
        /* 是否启用数据组A_column自动映射到Java类中的驼峰命名的属性**/
        protected boolean mapUnderscoreToCamelCase;
    
        /*当对象使用延迟加载时 属性的加载取决于能被引用到的那些延迟属性,否则,按需加载(需要的是时候才去加载)**/
        protected boolean aggressiveLazyLoading;
    
        /*是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true **/
        protected boolean multipleResultSetsEnabled = true;
    
        /*-允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false**/
        protected boolean useGeneratedKeys;
    
        /* 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。**/
        protected boolean useColumnLabel = true;
    
        /*配置全局性的cache开关,默认为true**/
        protected boolean cacheEnabled = true;
        protected boolean callSettersOnNulls;
        protected boolean useActualParamName = true;
        protected boolean returnInstanceForEmptyRow;
    
        /* 日志打印所有的前缀 **/
        protected String logPrefix;
    
        /* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找**/
        protected Class<? extends Log> logImpl;
        protected Class<? extends VFS> vfsImpl;
        /* 设置本地缓存范围,session:就会有数据的共享,statement:语句范围,这样不会有数据的共享**/
        protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
        /* 设置但JDBC类型为空时,某些驱动程序 要指定值**/
        protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
    
        /* 设置触发延迟加载的方法**/
        protected Set<String> lazyLoadTriggerMethods =
            new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
    
        /* 设置驱动等待数据响应超时数**/
        protected Integer defaultStatementTimeout;
    
        /* 设置驱动返回结果数的大小**/
        protected Integer defaultFetchSize;
    
        /* 执行类型,有simple、resue及batch**/
        protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    
        /*指定 MyBatis 应如何自动映射列到字段或属性*/
        protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    
        protected Properties variables = new Properties();
    
        protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    
        /*MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO*/
        protected ObjectFactory objectFactory = new DefaultObjectFactory();
        protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    
        /*延迟加载的全局开关*/
        protected boolean lazyLoadingEnabled = false;
    
        /*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
        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 InterceptorChain interceptorChain = new InterceptorChain();
    
        /*TypeHandler注册中心*/
        protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    
        /*TypeAlias注册中心*/
        protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
        //-------------------------------------------------------------
    
        /*mapper接口的动态代理注册中心*/
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        /*mapper文件中增删改查操作的注册中心*/
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    
        /*mapper文件中配置cache节点的 二级缓存*/
        protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
    
        /*mapper文件中配置的所有resultMap对象  key为命名空间+ID*/
        protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
        protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    
        /*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
        protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
    
        /*加载到的所有*mapper.xml文件*/
        protected final Set<String> loadedResources = new HashSet<>();
    
        /*mapper文件中配置的sql元素,key为命名空间+ID*/
        protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
    
        protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
        protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
        protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
        protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
    
        /*
         * 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<>();
    
        public Configuration(Environment environment) {
            this();
            this.environment = environment;
        }
    
        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
    • 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
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 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
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162

    mybatis-config.xml的元素都可以在这个类中找到对应的属性

    
    DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        
        <settings>
            
            <setting name="cacheEnabled" value="true"/>
            
            <setting name="lazyLoadingEnabled" value="true"/>
            
            <setting name="aggressiveLazyLoading" value="true"/>
            
            <setting name="multipleResultSetsEnabled" value="true"/>
            
            <setting name="useColumnLabel" value="true"/>
            
            <setting name="useGeneratedKeys" value="true"/>
            
            <setting name="autoMappingBehavior" value="PARTIAL"/>
            
            <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
            
            <setting name="defaultExecutorType" value="REUSE"/>
            
            <setting name="defaultStatementTimeout" value="25000"/>
            
            <setting name="defaultFetchSize" value="100"/>
            
            <setting name="safeRowBoundsEnabled" value="false"/>
            
            <setting name="mapUnderscoreToCamelCase" value="false"/>
            
            <setting name="localCacheScope" value="SESSION"/>
            
            <setting name="jdbcTypeForNull" value="OTHER"/>
            
            <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
        settings>
    
        
        <typeAliases>
            <typeAlias alias="pageAccessURL" type="com.lgm.mybatis.model.PageAccessURL"/>
        typeAliases>
    
        
        <typeHandlers>
            
            
            <package name="com.xhm.util"/>
        typeHandlers>
    
        
        <plugins>
            <plugin interceptor="com.xhm.util.PageInterceptor">plugin>
        plugins>
    
        
        <environments default="development">
            
            <environment id="development1">
                
                <transactionManager type="JDBC"/>
                
                
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/xhm"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                    
                dataSource>
            environment>
    
            
            <environment id="development2">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/xhm"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                    
                    <property name="poolMaximumActiveConnections" value="10"/>
                    
                    <property name="poolMaximumIdleConnections" value="5"/>
                    
                    <property name="poolMaximumCheckoutTime" value="20000"/>
                    
                    <property name="poolTimeToWait" value="20000"/>
                    
                    <property name="poolPingQuery" value="NO PING QUERY SET"/>
                    
                    <property name="poolPingEnabled" value="false"/>
                    
                    <property name="poolPingConnectionsNotUsedFor" value="0"/>
                dataSource>
            environment>
    
            
            <environment id="development3">
                <transactionManager type="JDBC"/>
                <dataSource type="JNDI">
                    <property name="data_source" value="java:comp/env/jndi/mybatis"/>
                    <property name="env.encoding" value="UTF8"/>
                    
                dataSource>
            environment>
        environments>
    
        
        <mappers>
            
            <mapper resource="com/xhm/mapper/UserMapper.xml"/>
            
            <package name="com.xhm.mapper"/>
        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
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 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
    • 127
    • 128
    • 129
    • 130
    • 131

    所以Configuration类的实例化核心就是读取xml, 然后填充属性

    代码入口

    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 {
            // inputStream是文件流, parser去解析xml
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            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

    只有这一个位置在new Configuration对象, 注意单例模式和单例类实例不是一个概念, 业务可以保证一个不是单例模式的类创建的是单实例

    XMLConfigBuilder

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration()); // 只有这个位置在new Configuration对象
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用XPathParser解析

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration")); // 取到根元素
        return configuration;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    顺序解析元素节点

    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            //解析节点
            propertiesElement(root.evalNode("properties"));
            //解析节点
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            //解析节点
            typeAliasesElement(root.evalNode("typeAliases"));
            //解析节点
            pluginElement(root.evalNode("plugins"));
            //解析节点
            objectFactoryElement(root.evalNode("objectFactory"));
            //解析节点
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            //解析节点
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);//将settings填充到configuration
            // read it after objectFactory and objectWrapperFactory issue #631
            //解析节点
            environmentsElement(root.evalNode("environments"));
            //解析节点
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            //解析节点
            typeHandlerElement(root.evalNode("typeHandlers"));
            //解析节点
            mapperElement(root.evalNode("mappers")); // 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

    propertiesElement

    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
            //解析的子节点(),读取name和value属性,并记录到Properties中
            Properties defaults = context.getChildrenAsProperties();
            //解析resource属性
            String resource = context.getStringAttribute("resource");
            //解析url属性
            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) {//根据resource加载配置文件
                defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {//根据url加载配置文件
                defaults.putAll(Resources.getUrlAsProperties(url));
            }
    
            //将配置文件的信息与configuration中的Variables合并
            Properties vars = configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }
            //更新解析器parser和configuration中的Variables
            parser.setVariables(defaults);
            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
    • 30

    XMLMapperBuilder

    mappers元素解析: 核心重点

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {//处理mapper子节点
                if ("package".equals(child.getName())) {//package子节点
                    String mapperPackage = child.getStringAttribute("name");
                    configuration.addMappers(mapperPackage);
                } else {//获取节点的resource、url或mClass属性这三个属性互斥
                    String resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {//如果resource不为空
                        ErrorContext.instance().resource(resource);
                        InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
                        //实例化XMLMapperBuilder解析mapper映射文件
                        XMLMapperBuilder mapperParser =
                            new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {//如果url不为空
                        ErrorContext.instance().resource(url);
                        InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
                        //实例化XMLMapperBuilder解析mapper映射文件
                        XMLMapperBuilder mapperParser =
                            new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url == null && mapperClass != null) {//如果class不为空
                        Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
                        configuration.addMapper(mapperInterface);//向代理中心注册mapper
                    } else {
                        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

    parse方法

    public void parse() {
        //判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));//处理mapper节点
            configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
            bindMapperForNamespace();//注册mapper接口
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    configurationElement

    private void configurationElement(XNode context) {
        try {
            //获取mapper节点的namespace属性
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            //设置builderAssistant的namespace属性
            builderAssistant.setCurrentNamespace(namespace);
            //解析cache-ref节点
            cacheRefElement(context.evalNode("cache-ref"));
            //重点分析 :解析cache节点----------------1-------------------
            cacheElement(context.evalNode("cache"));
            //解析parameterMap节点(已废弃)
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            //解析sql节点
            sqlElement(context.evalNodes("/mapper/sql"));
            //重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
            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

    builderAssistant: builder助手帮完成注册到Configuration任务

    private void cacheElement(XNode context) throws Exception {
        if (context != null) {
            //获取cache节点的type属性,默认为PERPETUAL
            String type = context.getStringAttribute("type", "PERPETUAL");
            //根据type对应的cache接口的实现
            Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
            //读取eviction属性,既缓存的淘汰策略,默认LRU
            String eviction = context.getStringAttribute("eviction", "LRU");
            //根据eviction属性,找到装饰器
            Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
            //读取flushInterval属性,既缓存的刷新周期
            Long flushInterval = context.getLongAttribute("flushInterval");
            //读取size属性,既缓存的容量大小
            Integer size = context.getIntAttribute("size");
            //读取readOnly属性,既缓存的是否只读
            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
            //读取blocking属性,既缓存的是否阻塞
            boolean blocking = context.getBooleanAttribute("blocking", false);
            Properties props = context.getChildrenAsProperties();
            //通过builderAssistant创建缓存对象,并添加至configuration
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    useNewCache

    //通过builderAssistant创建缓存对象,并添加至configuration
    public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval,
        Integer size, boolean readWrite, boolean blocking, Properties props) {
        //经典的建造起模式,创建一个cache对象
        Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
            .addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
            .readWrite(readWrite).blocking(blocking).properties(props).build();
        //将缓存添加至configuration,注意二级缓存以命名空间为单位进行划分
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    build中添加了默认的装饰器

    public Cache build() {
        //设置缓存的主实现类为PerpetualCache
        setDefaultImplementations();
        //通过反射实例化PerpetualCache对象
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);//根据cache节点下的信息,初始化cache
        // issue #352, do not apply decorators to custom caches
    
        if (PerpetualCache.class.equals(cache.getClass())) {//如果cache是PerpetualCache的实现,则为其添加标准的装饰器
            for (Class<? extends Cache> decorator : decorators) {//为cache对象添加装饰器,这里主要处理缓存清空策略的装饰器
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            //通过一些属性为cache对象添加装饰器,这里主要logger、Synchronized、定时调度等功能
            cache = setStandardDecorators(cache); // 添加了默认的标准装饰器
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            //如果cache不是PerpetualCache的实现,则为其添加日志的能力
            cache = new LoggingCache(cache);
        }
        return cache;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    resultMap解析

    ResultMap封装外面的大元素

    public class ResultMap {
        private Configuration configuration;//configuration对象
    
        private String id;//resultMap的id属性
        private Class<?> type;//resultMap的type属性
        private List<ResultMapping> resultMappings;//除discriminator节点之外的映射关系
        private List<ResultMapping> idResultMappings;//记录ID或者中idArg的映射关系
        private List<ResultMapping> constructorResultMappings;记录标志的映射关系
        private List<ResultMapping> propertyResultMappings;//记录非标志的映射关系
        private Set<String> mappedColumns;//记录所有有映射关系的columns字段
        private Set<String> mappedProperties;//记录所有有映射关系的property字段
        private Discriminator discriminator;//鉴别器,对应discriminator节点
        private boolean hasNestedResultMaps;//是否有嵌套结果映射
        private boolean hasNestedQueries;是否有嵌套查询
        private Boolean autoMapping;//是否开启了自动映射
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ResultMapping封装里面的子元素

    public class ResultMapping {
    
        private Configuration configuration;//引用的configuration对象
        private String property;//对应节点的property属性
        private String column;//对应节点的column属性
        private Class<?> javaType;//对应节点的javaType属性
        private JdbcType jdbcType;//对应节点的jdbcType属性
        private TypeHandler<?> typeHandler;//对应节点的typeHandler属性
    
        private String nestedResultMapId;对应节点的resultMap属性,嵌套结果时使用
        private String nestedQueryId;对应节点的select属性,嵌套查询时使用
        private Set<String> notNullColumns;//对应节点的notNullColumn属性
        private String columnPrefix;//对应节点的columnPrefix属性
        private List<ResultFlag> flags;//标志,id 或者 constructor
        private List<ResultMapping> composites;
        private String resultSet;//对应节点的resultSet属性
        private String foreignColumn;//对应节点的foreignColumn属性
        private boolean lazy;//对应节点的fetchType属性,是否延迟加载
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    入口: resultMapElements

    //解析resultMap节点,实际就是解析sql查询的字段与pojo属性之间的转化规则
    private void resultMapElements(List<XNode> list) throws Exception {
        //遍历所有的resultmap节点
        for (XNode resultMapNode : list) {
            try {
                //解析具体某一个resultMap节点
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
                // ignore, it will be retried
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    核心逻辑:

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings)
        throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        //获取resultmap节点的id属性
        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
        //获取resultmap节点的type属性
        String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
        //获取resultmap节点的extends属性,描述继承关系
        String extend = resultMapNode.getStringAttribute("extends");
        //获取resultmap节点的autoMapping属性,是否开启自动映射
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        //从别名注册中心获取entity的class对象
        Class<?> typeClass = resolveClass(type);
        Discriminator discriminator = null;
        //记录子节点中的映射结果集合
        List<ResultMapping> resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);
        //从xml文件中获取当前resultmap中的所有子节点,并开始遍历
        List<XNode> resultChildren = resultMapNode.getChildren();
        for (XNode resultChild : resultChildren) {
            if ("constructor".equals(resultChild.getName())) {//处理节点
                processConstructorElement(resultChild, typeClass, resultMappings);
            } else if ("discriminator".equals(resultChild.getName())) {//处理节点
                discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
            } else {//处理   节点
                List<ResultFlag> flags = new ArrayList<>();
                if ("id".equals(resultChild.getName())) {
                    flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
                }
                //创建ResultMapping对象并加入resultMappings集合中
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
            }
        }
        //实例化resultMap解析器
        ResultMapResolver resultMapResolver =
            new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
            //通过resultMap解析器实例化resultMap并将其注册到configuration对象
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw 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

    XMLStatementBuilder

    //处理所有的sql语句节点并注册至configuration对象
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            //创建XMLStatementBuilder 专门用于解析sql语句节点
            final XMLStatementBuilder statementParser =
                new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                //解析sql语句节点
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    MappedStatement: 对应的select/insert/update/delete元素

    public final class MappedStatement {
    
        private String resource;//节点的完整的id属性,包括命名空间
        private Configuration configuration;
        private String id;//节点的id属性
        private Integer fetchSize;//节点的fetchSize属性,查询数据的条数
        private Integer timeout;//节点的timeout属性,超时时间
        private StatementType statementType;//节点的statementType属性,默认值:StatementType.PREPARED;
        private ResultSetType resultSetType;//节点的resultSetType属性,jdbc知识
        private SqlSource sqlSource;//节点中sql语句信息-和BoundSql一起使用, 解析去BoundSql中的sql
        private Cache cache;//对应的二级缓存
        private ParameterMap parameterMap;//已废弃
        private List<ResultMap> resultMaps;//节点的resultMaps属性
        private boolean flushCacheRequired;//节点的flushCache属性是否刷新缓存
        private boolean useCache;//节点的useCache属性是否使用二级缓存
        private boolean resultOrdered;
        private SqlCommandType sqlCommandType;//sql语句的类型,包括:INSERT, UPDATE, DELETE, SELECT
        private KeyGenerator keyGenerator;//节点keyGenerator属性
        private String[] keyProperties;
        private String[] keyColumns;
        private boolean hasNestedResultMaps;//是否有嵌套resultMap
        private String databaseId;
        private Log statementLog;
        private LanguageDriver lang;
        private String[] resultSets;//多结果集使用
    
    • 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

    BoundSql: 解析sql语句的数据结构

    public class BoundSql {
    
        private final String sql;//sql语句,进行 #{ } 和 ${ } 替换完毕之后的结果sql, 注意每个 #{ }替换完之后就是一个 ?
        //这里的parameterMappings列表参数里的item个数, 以及每个item的属性名称等等, 都是和上面的sql中的 ? 完全一一对应的.
        private final List<ParameterMapping> parameterMappings;
        private final Object parameterObject;//用户传入的参数
        private final Map<String, Object> additionalParameters;
        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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ResultMap类图解

    image-20220627013033214

    mappedStatment类图解

    image-20220627013112632

    第一阶段: 初始化的时序图

    如何记住自己的源码, 调式,让后自己画出时序图

    image-20220701224114018

    2. Mybatis的第二阶段: 代理阶段

    Mybatis的接口层

    configuration如何保证全局唯一的(对于同一个工厂):

    DefaultSqlSessionFactory会持有一个configuration对象, 每次openSession时候都会传入同一个configuration

    SqlSession使用的是门面模式, 所有的数据库访问的api都通过sqlSession访问的

    使用原生方法访问数据库

    // 2.获取sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3.执行查询语句并返回结果
    TUser user = sqlSession.selectOne("com.hgy.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2);
    System.out.println(user.toString());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Executor是mybatis访问数据的核心组件

    SqlSession和Excutor体现了单一职责原则

    策略模式

    image-20220701225137683

    策略模式的使用场景:

    • 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时;
    • 出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。

    Context:算法调用者,使用setStrategy方法灵活的选择策略(strategy)

    Strategy:算法的统一接口;

    ConcreteStrategy:算法的具体实现;

    经典例子:

    spring容器的面向接口的注入就是一个策略模式, 接口用来承接实例, 当我们的接口的实现变化(不考虑多个的情况, 例如修改了类名, 也可以使用@Primary注解), 我们的类不用修改, Context就是这个类, 客户可以理解成容器

    何为事务? 对connection和dataSource的封装

    public class JdbcTransaction implements Transaction {
    
        private static final Log log = LogFactory.getLog(JdbcTransaction.class);
    
        protected Connection connection;
        protected DataSource dataSource;
        protected TransactionIsolationLevel level;
        protected boolean autoCommit;
    
        public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, 
                               boolean desiredAutoCommit) {
            dataSource = ds;
            level = desiredLevel;
            autoCommit = desiredAutoCommit;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    mybatis中如何使用策略模式

    PooledDataSource和UnpooledDataSource都继承同一个接口DataSource, 而他对应的上下文就是配置文件

    
    <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
    • 12
    • 13

    所以我们只有使用了一个接口的多个实现方式, 基本就可以认为是一种策略模式

    SqlSession相关类UML

    SqlSession是MyBaits对外提供的最关键的核心接口,通过它可以执行数据库读写命令、获取映射器、管理事务等;

    image-20220702005306522

    public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    
        //底层封装的
        private final SqlSessionFactory sqlSessionFactory;
        private final SqlSession sqlSessionProxy;
    	// 使用了ThreadLocal
        private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
    
        private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
            this.sqlSessionFactory = sqlSessionFactory;
            this.sqlSessionProxy =
                (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] {SqlSession.class},
                    new SqlSessionInterceptor());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    mybatis没有使用这个类, 通过ThreadLocal提供了在一个线程中复用sqlSession的能力

    MyBatis两种编程模型

    • 使用mapper接口编程,就可以访问数据库;

    • 使用sqlsession对外提供数据库的访问;

      ibatis之前使用的编程模型, 后买被google收购后, 使用了mapper的编程模型

    Binding模块

    **阿里面试题:**为什么使用mapper接口就能对数据库进行访问?

    通过binding模块动态代理生成了实现类

    核心就是借助xml的配置把接口mapper接口的调用改成sqlSession调用模块

    用例代码:

    public void quickStart() throws IOException {
        //--------------------第二阶段---------------------------
        // 2.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.获取对应mapper被代理
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    
        //--------------------第三阶段---------------------------
        // 4.执行查询语句并返回单条数据
        TUser user = mapper.selectByPrimaryKey(2);
        System.out.println(user);
    
        System.out.println("----------------------------------");
    
        // 5.执行查询语句并返回多条数据
        List<TUser> users = mapper.selectAll();
        for (TUser tUser : users) {
            System.out.println(tUser);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    session.defaults.DefaultSqlSession#getMapper是入口核心

    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }
    
    • 1
    • 2
    • 3

    org.apache.ibatis.session.Configuration#getMapper

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
    • 1
    • 2
    • 3

    发现是从mapperRegistry中获取到的

    binding.MapperRegistry#getMapper

    public class MapperRegistry {
    
        private final Configuration config;//config对象,mybatis全局唯一的
        //记录了mapper接口与对应MapperProxyFactory之间的关系
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
        
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            // 这里是从容器中和获取一个工厂
            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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

    /**
     *
     * 用于生成mapper接口动态代理的实例对象;
     * @author Lasse Voss
     */
    public class MapperProxyFactory<T> {
    
        //mapper接口的class对象
        private final Class<T> mapperInterface;
        //key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
        private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
    
        public MapperProxyFactory(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
    
        public Class<T> getMapperInterface() {
            return mapperInterface;
        }
    
        public Map<Method, MapperMethod> getMethodCache() {
            return methodCache;
        }
    
        @SuppressWarnings("unchecked")
        protected T newInstance(MapperProxy<T> mapperProxy) {
            //创建实现了mapper接口的动态代理对象
            return (T)Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {mapperInterface}, mapperProxy);
        }
    
        public T newInstance(SqlSession sqlSession) {
            //每次调用都会创建新的MapperProxy对象
            final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
            return newInstance(mapperProxy);
        }
    
    }
    
    • 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

    最终invoke方法

    org.apache.ibatis.binding.MapperProxy#invoke

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增强
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        //从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //调用execute方法执行sql
        return mapperMethod.execute(sqlSession, args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最核心的事实现类是MapperMethod

    public class MapperMethod {
        //从configuration中获取方法的命名空间.方法名以及SQL语句的类型
        private final SqlCommand command;
        //封装mapper接口方法的相关信息(入参,返回类型);
        private final MethodSignature method;
    
        public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
            this.command = new SqlCommand(config, mapperInterface, method);
            this.method = new MethodSignature(config, mapperInterface, method);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    binding.MapperMethod#execute

    //三步翻译在此完成
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //第一步 根据sql语句类型以及接口返回的参数选择调用不同的方法
        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()) {//返回值为void
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {//返回值为集合或者数组
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {//返回值为map
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {//返回值为游标
                    result = executeForCursor(sqlSession, args);
                } else {//处理返回为单一对象的情况
                    //通过参数解析器解析解析参数
                    Object param = method.convertArgsToSqlCommandParam(args);//第三步翻译,讲入参转化成Map
                    // 最终还是调用了sqlSession的方法
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() && (result == null || !method.getReturnType()
                        .equals(result.getClass()))) {
                        result = OptionalUtil.ofNullable(result);
                    }
                }
                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;
    }
    
    • 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

    三部翻译图解

    image-20220702014325779

    1. 中岛session中对应的方法
    2. 中岛命名空间和方法名
    3. 传递参数

    binding模块分析

    image-20220702014638725

    • MapperRegistry : mapper接口和对应的代理对象工厂的注册中心;
    • MapperProxyFactory:用于生成mapper接口动态代理的实例对象;
    • MapperProxy:实现了InvocationHandler接口,它是增强mapper接口的实现;
    • MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁;(核心)

    多次调用getMaper会返回同一个对象吗?

    不会, 但是获取的MapperProxyFactory是同一个, 但是每次MapperProxyFactory都会创建对象

    MapperRegistry是在哪儿被初始化的?

    XMLMapperBuilder中初始化的

    builder.xml.XMLMapperBuilder#parse

    public void parse() {
        //判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));//处理mapper节点
            configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
            bindMapperForNamespace();//注册mapper接口
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    bindMapperForNamespace

    //注册mapper接口
    private void bindMapperForNamespace() {
        //获取命名空间
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                //通过命名空间获取mapper接口的class对象
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                //ignore, bound type is not required
            }
            if (boundType != null) {
                if (!configuration.hasMapper(boundType)) {//是否已经注册过该mapper接口?
                    // Spring may not know the real resource name so we set a flag
                    // to prevent loading again this resource from the mapper interface
                    // look at MapperAnnotationBuilder#loadXmlResource
                    //将命名空间添加至configuration.loadedResource集合中
                    configuration.addLoadedResource("namespace:" + namespace);
                    //将mapper接口添加到mapper注册中心
                    configuration.addMapper(boundType);
                }
            }
        }
    }
    
    • 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

    第二阶段: 代理阶段的时序图

    代码入口: sqlSession的getMapper方法

    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    
    • 1

    image-20220703175343608

    3. Mybatis的第三阶段: 读写阶段

    1+3核心组件 一主三从, 1执行, 3处理

    Excutor, ParameterHandler,StatementHandler, ResultSetHandler

    Excutor

    SqlSession接到的请求都转发给Executor, 所以可以认为Exector是核心中的核心

    image-20220702020158248

    CachingExecutor: 使用装饰器模式, 缓存的日志模块也是用了装饰器模式

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
        ResultHandler resultHandler) throws SQLException {
        //获取sql语句信息,包括占位符,参数等信息
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        //拼装缓存的key值: 一级缓存
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
        ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        //从MappedStatement中获取二级缓存
        Cache cache = ms.getCache();
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked") List<E> list = (List<E>)tcm.getObject(cache, key);//从二级缓存中获取数据
                if (list == null) {
                    //二级缓存为空,才会调用BaseExecutor.query
                    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    模板方法模式

    BaseExecutor是一个模板类(源码中使用base或者abstract声明的类一般都是用了模板方法模式)

    public abstract class BaseExecutor implements Executor {
        // 四个待实现的抽象方法
        protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
    
        protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
    
        protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
            ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
    
        protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds 
                                                       rowBounds,BoundSql boundSql) throws SQLException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    常用的显然是doUpdate 和doQuery

    查询流程

    image-20220702021028523

    Mybatis的分页组件: 基本不会使用

    public class RowBounds {
    
        public static final int NO_ROW_OFFSET = 0; // 其实偏移量
        public static final int NO_ROW_LIMIT = Integer.MAX_VALUE; // 默认最大数据量
        public static final RowBounds DEFAULT = new RowBounds();
    
        private final int offset;
        private final int limit;
    
        public RowBounds() {
            this.offset = NO_ROW_OFFSET;
            this.limit = NO_ROW_LIMIT;
        }
    
        public RowBounds(int offset, int limit) {
            this.offset = offset;
            this.limit = limit;
        }
    
        public int getOffset() {
            return offset;
        }
    
        public int getLimit() {
            return limit;
        }
    
    }
    
    • 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

    Executor典型的三个实现类解读

    • SimpleExecutor:默认配置,使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象;

    • ReuseExecutor:使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;

      这种可能性能好些, 但是可能不安全, 如果数据的索引或者表结构变化就会出现问题

    • BatchExecutor:实现批量执行多条SQL语句的能力;

    org.apache.ibatis.executor.SimpleExecutor#prepareStatement

    @Override
    //查询的实现
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
        BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();//获取configuration对象
            //创建StatementHandler对象,
            StatementHandler handler =
                configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
            stmt = prepareStatement(handler, ms.getStatementLog());
            //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    //创建Statement
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //获取connection对象的动态代理,添加日志能力;
        Connection connection = getConnection(statementLog);
        //通过不同的StatementHandler,利用connection创建(prepare)Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        //使用parameterHandler处理占位符
        handler.parameterize(stmt);
        return stmt;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Executor的三个重要处理器

    通过对SimpleExecutor doQuery()方法的解读发现,Executor是个指挥官,它在调度三个小弟工作:

    • StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用;
    • ParameterHandler:对预编译的SQL语句进行参数设置,SQL语句中的的占位符“?”都对应BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
    • ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型

    Executor内部运作过程: 重要

    image-20220702022730543

    StatementHandler

    StatementHandler完成Mybatis最核心的工作,也是Executor实现的基础;功能包括:

    • 创建statement对象,
    • 为sql语句绑定参数,
    • 执行增删改查等SQL语句,
    • 将结果映射集进行转化

    image-20220702131701728

    • BaseStatementHandler:所有子类的抽象父类,定义了初始化statement的操作顺序,由子类实现具体的实例化不同的statement(模板模式);
    • RoutingStatementHandler:Excutor组件真正实例化的子类,使用静态代理模式,根据上下文决定创建哪个具体实体类;
    • SimpleStatmentHandler :使用statement对象访问数据库,无须参数化;
    • PreparedStatmentHandler :使用预编译PrepareStatement对象访问数据库;
    • CallableStatmentHandler :调用存储过程;

    RoutingStatementHandler: 静态代理

    public class RoutingStatementHandler implements StatementHandler {
    
        private final StatementHandler delegate;//底层封装的真正的StatementHandler对象
    
        public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
            ResultHandler resultHandler, BoundSql boundSql) {
            //RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
            switch (ms.getStatementType()) {
                case STATEMENT:
                    delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    break;
                case PREPARED:
                    delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    break;
                case CALLABLE:
                    delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    break;
                default:
                    throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    org.apache.ibatis.executor.SimpleExecutor#doQuery: 入口

    @Override
    //查询的实现
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
        BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();//获取configuration对象
            //创建StatementHandler对象,
            StatementHandler handler =
                configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
            stmt = prepareStatement(handler, ms.getStatementLog());
            //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    //创建Statement
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //获取connection对象的动态代理,添加日志能力;
        Connection connection = getConnection(statementLog);
        //通过不同的StatementHandler,利用connection创建(prepare)Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        //使用parameterHandler处理占位符
        handler.parameterize(stmt);
        return stmt;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    org.apache.ibatis.executor.statement.BaseStatementHandler#prepare

    image-20220703162530750

    image-20220703162655864

    ParameterHandler

    参数设置的核心逻辑

    org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

    public class DefaultParameterHandler implements ParameterHandler {
    
        //typeHandler注册中心
        private final TypeHandlerRegistry typeHandlerRegistry;
        //对应的sql节点的信息
        private final MappedStatement mappedStatement;
        //用户传入的参数
        private final Object parameterObject;
        //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());
            //从boundSql中获取sql语句的占位符对应的参数信息
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            //遍历这个参数列表,把参数设置到PreparedStatement中
            if (parameterMappings != null) {
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);//获得入参的形参名字,javatype,jdbctype等信息
                    if (parameterMapping.getMode() != ParameterMode.OUT) {//对于存储过程中的参数不处理
                        Object value;//绑定的实参值
                        String propertyName = parameterMapping.getProperty();//参数的名字
                        // 获取对应的实参值
                        if (boundSql.hasAdditionalParameter(propertyName)) { //是否为附加参数
                            value = boundSql.getAdditionalParameter(propertyName);
                        } else if (parameterObject == null) {//是否为空
                            value = null;
                        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//是否要进行指定的类型转换
                            value = parameterObject;
                        } else {//大部分情况走这段
                            MetaObject metaObject = configuration.newMetaObject(parameterObject);
                            value = metaObject.getValue(propertyName);
                        }
                        TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取typeHandler对象
                        JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的jdbcType
                        if (value == null && jdbcType == null) {
                            jdbcType = configuration.getJdbcTypeForNull();
                        }
                        try {
                            //为statment中的占位符绑定参数
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);
                        } catch (TypeException e) {
                            throw new TypeException(
                                "Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                        } catch (SQLException e) {
                            throw new TypeException(
                                "Could not set parameters for mapping: " + parameterMapping + ". 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
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    ResultSetHandler

    ResultSetHandler分析

    nResultSetHandler将从数据库查询得到的结果按照映射配置文件的映射规则,映射成相应的结果集对象;

    image-20220703164149551

    image-20220703164122905

    @Override
    //查询的实现
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
        BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();//获取configuration对象
            //创建StatementHandler对象,
            StatementHandler handler =
                configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
            stmt = prepareStatement(handler, ms.getStatementLog());
            //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();//获取sql语句
        statement.execute(sql);//执行sql语句
        return resultSetHandler.<E>handleResultSets(statement);//使用resultSetHandler处理查询结果
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
        //用于保存结果集对象
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        //statment可能返回多个结果集对象,这里先取出第一个结果集
        ResultSetWrapper rsw = getFirstResultSet(stmt);
        //获取结果集对应resultMap,本质就是获取字段与java属性的映射规则
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);//结果集和resultMap不能为空,为空抛出异常
        while (rsw != null && resultMapCount > resultSetCount) {
            //获取当前结果集对应的resultMap
            ResultMap resultMap = resultMaps.get(resultSetCount);
            //根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
            handleResultSet(rsw, resultMap, multipleResults, null);
            rsw = getNextResultSet(stmt);//获取下一个结果集
            cleanUpAfterHandlingResultSet();//清空nestedResultObjects对象
            resultSetCount++;
        }
        //获取多结果集。多结果集一般出现在存储过程的执行,存储过程返回多个resultset,
        //mappedStatement.resultSets属性列出多个结果集的名称,用逗号分割;
        //多结果集的处理不是重点,暂时不分析
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
            while (rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }
    
        return collapseSingleResultList(multipleResults);
    }
    
    • 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

    第三阶段: 查询结果的时序图

    当然还有插入更新删除的时序图, 仅仅以最复杂的查询为例

    入口:

    TUser user = mapper.selectByPrimaryKey(2);
    
    • 1

    image-20220703185436124

  • 相关阅读:
    SpringBoot笔记:SpringBoot集成JWT、Shiro实战
    数据结构之单链表
    制造商面临的常见网络威胁是什么,他们如何保护自己
    AcWing 3. 完全背包问题 学习笔记
    VMware搭载linux出现的bugs
    Redis-核心数据结构
    spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
    一种基于端到端结构化网络的高效火灾和烟雾检测算法
    数据结构与算法(Java篇)笔记--选择排序
    X64汇编 shellcode
  • 原文地址:https://blog.csdn.net/yin18827152962/article/details/126066193