能够说出mybatis的核心组件

能够说出mybatis的配置解析流程

能够说出mybatis一次查询的源码流程

Mybatis源码解析

2.1 三层架构持久层说明

1.项目准备

准备Sql

  1. CREATE TABLE `user` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `username` varchar(100) DEFAULT NULL,
  4. `birthday` datetime DEFAULT NULL,
  5. `sex` varchar(10) DEFAULT NULL,
  6. `address` varchar(255) DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  9. INSERT INTO `user` VALUES ('1', '刘备', '2019-11-26 15:31:09', '男', '蜀国');
  10. INSERT INTO `user` VALUES ('2', '关羽', '2019-11-26 15:31:09', '男', '蜀国');
  11. INSERT INTO `user` VALUES ('3', '孙尚香', '2019-11-05 15:32:00', '女', '吴国');
  12. INSERT INTO `user` VALUES ('4', '杨贵妃', '2019-11-01 15:32:25', '女', '大唐');

pom依赖

<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <!-- 淘淘项目 分页组件 -->
        <dependency>
            <groupId>com.github.miemiedev</groupId>
            <artifactId>mybatis-paginator</artifactId>
            <version>1.2.15</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

Properties配置文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

mybatis核心配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置properties
        可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
        resource属性: 常用的
            用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
        url属性:
            是要求按照Url的写法来写地址
            URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
            它的写法:
                http://localhost:8080/mybatisserver/demo1Servlet
                协议      主机     端口       URI
            URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
    -->
    <properties url="file:///E:\lession\itcast\work_plus\mybatis\mybatis_xml2\src\main\resources\jdbc.properties">
        <!-- <property name="driver" value="com.mysql.jdbc.Driver"></property>
         <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"></property>
         <property name="username" value="root"></property>
         <property name="password" value="1234"></property>-->
    </properties>
    <!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
    <typeAliases>
        <!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就再区分大小写
        <typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
        <!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
        <package name="com.itcast.entity"></package>
    </typeAliases>
    <!-- 配置分页插件 -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
    <!--配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务 -->
            <transactionManager type="JDBC"></transactionManager>

            <!--配置连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置映射文件的位置 -->
    <mappers>
        <!--<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>-->
        <!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class了 -->
        <package name="com.itcast.mapper"></package>
    </mappers>
</configuration>

User实体类

public class User{
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

IUserDao接口定义

public interface IUserDao {
    List<User> findAll();
    void update(User user);
    void insert(User user);
    void delete(Integer id);
    User findById(Integer id);
}

IUserDao.xml定义

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itcast.mapper.IUserDao">
    <!-- <resultMap id="BaseResultMap" type="com.itcast.domain.User">-->
    <resultMap id="userMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="birthday" property="birthday"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
    </resultMap>

    <select id="findAll"  resultMap="userMap" >
        select * from USER
    </select>
    <select id="findById" resultMap="userMap">
        select * from USER
        <where>
            id = #{id}
        </where>
    </select>
    <update id="update" parameterType="User" >
        update USER SET
        username=#{username},
        address=#{address},
        birthday=#{birthday},
        sex=#{sex},
        address=#{address}
        <where>
            id=#{id}
        </where>
    </update>
    <insert id="insert" parameterType="User" >
        INSERT INTO USER
        (id,username,birthday,sex,address)
        VALUES
        (null,#{username},#{birthday},#{sex},#{address})
    </insert>
    <delete id="delete" parameterType="int">
        delete FROM USER where id=#{id}
    </delete>
</mapper>

2.Mybatis核心组件

2.1 代码演示

案例

    /**
     * 测试查询全部账户列表数据
     */
    @Test
    public void findAllAccountTest() throws Exception{
        // 1.加载mybatis框架主配置文件sqlMapConfig.xml
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");

        // 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(inputStream);

        // 3.获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 4.获取接口代理对象
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

        // 5.执行数据库操作
        List<Account> list = accountDao.findAllAccounts();
        for (Account ac:list){
            System.out.println(ac);
        }

        // 6.释放资源
        sqlSession.close();

    }

1569307732235

2.2 核心组件演示

| 序号 | 组件名称 | 组件描述 | | —- | —- | —- |

| 1 | Configuration | 用于封装、维护mybatis框架的所有配置信息 |

| 2 | MappedStatement | 每个MappedStatement,封装维护了一个节点 |

| 3 | SqlSessionFactoryBuilder | 构建器,用于解析封装主配置文件内容,构建SqlSessionFactory核心对象 |

| 4 | SqlSessionFactory | 工厂类接口,通过工厂方法openSession创建SqlSession对象 |

| 5 | SqlSession | mybatis框架的顶层API,表示和数据库交互的会话,提供了完成数据库增删改查操作的接口方法 |

| 6 | Executor | mybatis框架执行器,是框架的调度核心,负责sql语句的生成和查询缓存维护 |

| 7 | StatementHandler | 封装jdbc Statement操作,比如设置参数、将Statement结果集转换成List |

| 8 | ParameterHandler | 将用户传递的参数,转换成jdbc Statement 所需要的参数 |

| 9 | SqlSource | 根据用户传递的parameterObject,动态生成sql语句,将信息封装到BoundSql对象中 |

| 10 | BoundSql | 动态生成的sql语句、和参数信息 |

| 11 | ResultSetHandler | 将jdbc返回的ResultSet结果集对象,进行结果集封装,转换成List类型的集合 |

| 12 | TypeHandler | 类型处理器,完成java数据类型和jdbc数据类型之间的映射和转换 |

002

#mybatis框架运行流程:

    【1】.加载配置文件:
        主配置文件:sqlMapConfig.xml。主配置文件用于配置框架的运行时环境
        sql语句映射文件:Account.xml、AccountDao.xml。sql语句映射文件用于描述要执行的数据库操作

    【2】.读取解析配置文件内容:
        将配置文件内容,封装到Configuration对象中
        根据主配置文件内容,获取框架的核心对象SqlSessionFactory

    【3】.获取SqlSession对象:
        通过SqlSessionFactory对象,获取到操作数据库的会话对象SqlSession

    【4】.通过SqlSession对象,执行数据库操作
        SqlSession直接通过相关API方法,执行数据库操作
        SqlSession获取mapper接口代理对象,执行数据库操作(注意:底层还是SqlSession调用API方法)

    【5】.通过Executor执行器,执行数据库操作

    【6】.通过MappedStatement语句对象,完成数据库操作:
        根据传递进来的Statement Id、和参数,动态组装成完整的sql语句,发送到数据库执行
        将执行的结果,封装到结果集返回

3.mybatis运行原理(源码)

3.1 解析封装配置文件

#1.在mybatis框架中,配置文件主要有两类:
    1.1.主配置文件:sqlMapConfig.xml
    1.2.接口映射文件:mapper.xml,mapper1.xml......
#2.在使用mybatis框架中,引导启动加载配置文件信息代码如下:
     // 1.加载mybatis框架主配置文件sqlMapConfig.xml
     InputStream inputStream =
                Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
#3.在mybatis框架中,解析封装配置文件信息,提供了如下API:
    SqlSessionFactoryBuilder:
        构建器设计模式应用,用于构建SqlSessionFactory对象。该对象作用于建议是方法级别(局部变量),一旦创建好SqlSessionFactory,即不再需要。
    Configuration:
        配置信息对象,根据主配置文件sqlMapConfig.xml配置内容,进行封装。包括:属性资源文件、别名、数据源、mapper接口映射信息
    MappedStatement:
        每个MappedStatement,封装维护了一个<select|update|delete|insert>节点配置信息
    BoundSql:
        封装动态生成的sql语句、和参数信息

1569291562951

SqlSessionFactoryBuilder
#构建器设计模式应用,用于构建SqlSessionFactory对象。该对象作用于建议是方法级别(局部变量),一旦创建好SqlSessionFactory,即不再需要

图一:
1569296866694
图二:
1569296982435
图三:
1569297097849

Configuration
#配置信息对象,根据主配置文件sqlMapConfig.xml配置内容,进行封装。包括:属性资源文件、别名、数据源、mapper接口映射信息

源代码:

public Configuration() {
        this.safeResultHandlerEnabled = true;
        this.multipleResultSetsEnabled = true;
        this.useColumnLabel = true;
        this.cacheEnabled = true;//启用缓存
        this.useActualParamName = true;
        this.localCacheScope = LocalCacheScope.SESSION;
        this.jdbcTypeForNull = JdbcType.OTHER;
        this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));// 触发延迟加载的方法
        this.defaultExecutorType = ExecutorType.SIMPLE;//默认执行器
        this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;// 自动映射列到字段或属性策略
        this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;// 未知列的处理策略
        this.variables = new Properties();
        this.reflectorFactory = new DefaultReflectorFactory();
        this.objectFactory = new DefaultObjectFactory();// 对象工厂
        this.objectWrapperFactory = new DefaultObjectWrapperFactory();
        this.lazyLoadingEnabled = false;//延迟加载
        this.proxyFactory = new JavassistProxyFactory();
        this.mapperRegistry = new MapperRegistry(this);// mapper接口注册器
        this.interceptorChain = new InterceptorChain();// 插件链
        this.typeHandlerRegistry = new TypeHandlerRegistry();// 类型处理器注册器
        this.typeAliasRegistry = new TypeAliasRegistry();// 别名注册器
        this.languageRegistry = new LanguageDriverRegistry();
        this.mappedStatements = (new Configuration.StrictMap("Mapped Statements collection")).conflictMessageProducer((savedValue, targetValue) -> {
            return ". please check " + savedValue.getResource() + " and " + targetValue.getResource();
        });// mapper映射文件中<select|insert|update|delete>标签配置
        this.caches = new Configuration.StrictMap("Caches collection");// 缓存集合
        this.resultMaps = new Configuration.StrictMap("Result Maps collection");// ResultMap集合
        this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");// 参数集合
        this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
        this.loadedResources = new HashSet();
        this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");// sql片段
        this.incompleteStatements = new LinkedList();
        this.incompleteCacheRefs = new LinkedList();
        this.incompleteResultMaps = new LinkedList();
        this.incompleteMethods = new LinkedList();
        this.cacheRefMap = new HashMap();
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
        this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
        this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
        this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        this.languageRegistry.register(RawLanguageDriver.class);
    }
MappedStatement
#每个MappedStatement,封装维护了一个<select|update|delete|insert>节点

小面试题:
id的组成方式
同一个mapper文件中,可不可以出现重载方法
不同mapper文件中,可不可以出现重载方法

图一:
1569307287848
2.3 解析MappedStatement

BoundSql
#动态生成的sql语句、和参数信息

图一:
1569307422443

3.2一次查询过程源码分析

第一步:获取SqlSession
【1】/* 应用入口代码:MybatisDemo*/
    // 3.获取SqlSession对象
     SqlSession sqlSession = sqlSessionFactory.openSession();

---------------------------------------------------------------------------
【2】/*工厂接口实现类:DefaultSqlSessionFactory*/
    // 第一步:获取一个操作数据库会话SqlSession对象
        public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
        }

---------------------------------------------------------------------------

    【3】// 第二步:获取SqlSession对象详细过程
        private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 重点 创建执行器  执行器的分类
            //<!--取值范围 SIMPLE, REUSE, BATCH -->
        //<setting name="defaultExecutorType" value="SIMPLE"/>
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx); 
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }
Executor(执行器)接口,
主要存在于SqlSession实例中,用于处理和数据库的交互。
有两个实现类:BaseExecutor 和 CachingExecutor  
有三个继承BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器)。以上三个就是主要的Executor。Mybatis在Executor的设计上面使用了装饰者模式,我们可以用CachingExecutor来装饰前面的三个执行器目的就是用来实现缓存。

图一:
1569308402522
图二:
1569308480974
图三:
1569308576135

第二步:获取Mapper接口代理对象
【1】/*应用入口代码:*/
    // 4.获取接口代理对象
     AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

【2】/*DefaultSqlSession*/
    // 获取mapper接口代理对象
     public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
     }

【3】/*Configuration*/
    // 获取mapper接口代理对象
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

【4】/*MapperRegistry*/
    // 通过动态代理技术,创建mapper接口代理对象
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

图一:
1569309157655
图二:
1569309213467
图三:
1569310069790
图四:
1569310166912
图五:
1569310313370

第三步:查询用户列表数据
【1】/*应用程序执行入口*/
     // 5.执行数据库操作
     List<Account> list = accountDao.findAllAccounts();
【2】/*MapperProxy,实现了InvocationHandler接口*/
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        // mapper接口代理方法对象
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }    
【3】/*MapperMethod*/
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:// 添加,对应<insert>标签
            param = this.method.convertArgsToSqlCommandParam(args);
                // 可以看到,mapper接口代理实现方式,最终底层也是SqlSession.insert|update|select|delete方式执行
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:// 更新,对应<update>标签
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:// 删除,对应<delete>标签
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:// 查询,对应<select>标签
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
【4】/*BaseExecutor*/
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                // 优先从缓存取数据
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    // 如果缓存数据为空,即没有从缓存中取到数据,则从数据库查询数据
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

【5】/*BaseExecutor*/
     private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            // 从数据库中查询获取数据
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

         // 将数据放入本地缓存
        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }
【6】/*SimpleExecutor*/
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            // 获取StatementHandler
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 获取jdbc Statement对象
            stmt = this.prepareStatement(handler, ms.getStatementLog());

            // 执行查询,封装结果集
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

【7】/*PreparedStatementHandler*/
     public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
    }    

【8】/*DefaultResultSetHandler*/
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
        List<Object> multipleResults = new ArrayList();
        int resultSetCount = 0;
        ResultSetWrapper rsw = this.getFirstResultSet(stmt);
        List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        this.validateResultMapsCount(rsw, resultMapCount);

        // ResultMap结果集映射
        while(rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
            this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
            rsw = this.getNextResultSet(stmt);
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }

        // ResultSet结果集映射
        String[] resultSets = this.mappedStatement.getResultSets();
        if (resultSets != null) {
            while(rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                    this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
                }

                rsw = this.getNextResultSet(stmt);
                this.cleanUpAfterHandlingResultSet();
                ++resultSetCount;
            }
        }

        return this.collapseSingleResultList(multipleResults);
    }

【9】/*DefaultResultSetHandler*/
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
            if (parentMapping != null) {
                this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);
            } else if (this.resultHandler == null) {
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
                this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);
            }
        } finally {
            this.closeResultSet(rsw.getResultSet());
        }

    }

图一:
1569312168473
图二:
1569315108096
图三:
1569315256061
图四:
1569315413422
图五:
1569315509808
图六:
1569315669211
图七:
1569315817276
图八:
1569315994952
图九:
1569316093050
图十:
1569316288240
图十一:
1569316334875
图十二:
1569316478391

流程小结:
-- 代码: SqlSession sqlSession = sqlSessionFactory.openSession();
源码:通过【DefaultSqlSessionFactory】的openSession方法获取SqlSession对象
      1. 获取环境变量
      2. 构建事务处理器
      3. 构建Executor执行器 
      4. 设置是否自动提交事务
      5. 根据上面内容构建SqlSession对象【DefaultSqlSession】 
-- 代码:IUserDao mapper = sqlSession.getMapper(IUserDao.class);
源码:【DefaultSqlSession】的getMapper方法被调用
      内部直接调用 configuration 的getMapper方法
      configuration使用jdk动态代理,为IUserDao的接口创建了代理对象。
      代理对象的处理在MapperProxy类中

-- 代码:List<User> all = mapper.findAll();
源码:实际上触发了代理类【MapperProxy】中invoke方法的调用
         invoke方法中获取mapperMethod对象,这个对象记录的信息:
         1.    本次查询的ID -> 类的全限定类名+方法名 如:com.itcast.mapper.IUserDao.findAll
         2.  本次查询的类型 -> select insert update delete
     【MapperMethod】的execute方法被调用
         根据查询的类型,触发sqlSession的增删改查方法的执行
         如:findAll方法 类型为select 触发 sqlSession.<E>selectList
     【DefaultSqlSession】 的selectList方法被调用
         根据查询的ID 在configuration的mappedStatements map集合中查询对应的MappedStatment对象
         将查询到的ms作为参数,调用SqlSession的内部执行器对象Executor去执行
     【CachingExecutor】的query方法被调用
         根据ms对象获取对应的 BoundSql 
        查询缓存,缓存不存在调用【BaseExecutor】的query方法去数据库查询
     【BaseExecutor】的queryFromDatabase方法被调用
          对本次查询进行缓存处理
          调用配置的Executor继续执行
     【SimpleExecutor】的doQuery方法被调用
         根据ms,boundSql等参数创建【StatementHandler】的对象
         通过【StatementHandler】调用jdbc进行查询
         通过【ResultHandler】进行结果封装,返回集合

Mybatis面试热点

Mybatis

mybatis的动态sql

MyBatis的一个强大特性之一通常是它的动态SQL能力。如果你有使用JDBC或其他相似框架的经验,你就明白条件串联SQL字符串在一起是多么地痛苦,确保不能忘了空格或者在列表的最后的省略逗号,动态SQL可以彻底处理这种痛苦。
通常使用动态SQL不可能是独立的一部分,MyBatis当然使用一种强大的动态SQL语言来改进这种情形,这种语言可以被用在任意映射的SQL语句中。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itcast.dao.IUserMapper">
<!--    <cache />-->
   <!-- <resultMap id="BaseResultMap" type="com.itcast.domain.User">-->
    <resultMap id="userMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="birthday" property="birthday"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>

        <collection property="list" ofType="com.itcast.domain.Account" fetchType="lazy" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
            <id column="id" property="id"></id>
            <result column="uid" property="uid"></result>
            <result column="money" property="money"></result>
        </collection>
    </resultMap>
    <select id="findById" resultMap="userMap">
        <include refid="selectSql"></include>
        <where>
            id = #{id}
        </where>
    </select>


    <select id="findAll" resultMap="userMap">
      <include refid="selectSql"></include>
    </select>


    <!-- 使用动态标签 实现模糊查询 -->
    <select id="findByCondition" parameterType="User" resultMap="userMap">
      <include refid="selectSql"></include>
      <where>
        <if test="username !=null and username != ''">
            AND username like #{username}
        </if>
        <if test="address !=null and address != ''">
            AND address like #{address}
        </if>
      </where>
    </select>
    <update id="update" parameterType="User" >
        update user SET
        username=#{username},
        address=#{address},
        birthday=#{birthday},
        sex=#{sex},
        address=#{address}
        <where>
            id=#{id}
        </where>
    </update>
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user
        (id,username,birthday,sex,address)
        VALUES
        (null,#{username},#{birthday},#{sex},#{address})
    </insert>
    <delete id="delete" parameterType="int">
        delete FROM user where id=#{id}
    </delete>
    <insert id="insertBatchs">
        insert INTO user
        (username,birthday,sex,address)
        VALUES
        <foreach collection="list" item="u" separator=",">
            (#{u.username},#{u.birthday},#{u.sex},#{u.address})
        </foreach>
    </insert>
    <sql id="selectSql">
        select * from user
    </sql>
</mapper>

mybatis的多表查询

多对一、一对一

1对1,多对1 都是实体类里面关联另一个实体类

public class Account {
    private Integer id;
    private Integer uid;
    private double money;
    // 每个账户都关联一个User
    User user;
}

在xml中定义映射关系, 使用association代表关联一个实体类, property映射的属性名,javaType该属性的实体类类型,fetchType是否延迟加载,column用于执行select方法所传递的参数,select需要执行的mapper方法的全限定名称

<resultMap id="BaseResultMap" type="com.itcast.domain.Account">
    <id column="id" property="id"/>
    <result column="uid" property="uid"/>
    <result column="money" property="money"/>
    <!-- account中 有一个属性为实体类user 以及user对应的映射 -->
    <association property="user" javaType="User" fetchType="eager" column="uid" select="com.itcast.dao.IUserMapper.findById">
        <id column="id" property="id"></id>
        <result column="sex" property="sex"></result>
        <result column="address" property="address"></result>
        <result column="birthday" property="birthday"></result>
        <result column="username" property="username"></result>
    </association>
</resultMap>

一对多,多对多

对多,实体类中会有一个集合属性

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    // 对多,实体类中会有一个集合属性
    private List<Account> list;
}

在xml中定义映射关系,因为是一个集合 所以使用collection标签,ofType代表集合中实体类的类型,其他属性和上面对一的情况一致

<resultMap id="userMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="birthday" property="birthday"/>
    <result column="address" property="address"/>
    <result column="sex" property="sex"/>
    <collection property="list" ofType="Account" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
        <id column="id" property="id"></id>
        <result column="uid" property="uid"></result>
        <result column="money" property="money"></result>
    </collection>
</resultMap>

延迟加载的问题

要使用延迟加载,需要在配置文件中开启延迟加载开关

<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

在关联查询中,通过fetchType设置是否延迟加载, lazy表示延迟加载 eager表示立即加载

<association property="user" javaType="User" fetchType="lazy" column="uid" select="com.itcast.dao.IUserMapper.findById">
    <id column="id" property="id"></id>
    <result column="sex" property="sex"></result>
    <result column="address" property="address"></result>
    <result column="birthday" property="birthday"></result>
    <result column="username" property="username"></result>
</association>
<collection property="list" ofType="Account" fetchType="lazy" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
    <id column="id" property="id"></id>
    <result column="uid" property="uid"></result>
    <result column="money" property="money"></result>
</collection>

mybatis的缓存机制

一级缓存
一级缓存的范围是同一个SqlSession对象,当我们使用SqlSession对象进行查询时mybatis会帮我们把查询的数据存入到内存中,当我们在这个SqlSession中再一次执行同样的查询操作时,我们就可以直接去缓存中获取数据。
一级缓存是默认开启的
img
一级缓存的生命周期

a. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;

c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;

d.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;

二级缓存
二级缓存是mapper级别的缓存,多个SqlSession共享,其作用域是mapper的同一个namespace,不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
img二级缓存的配置
MyBatis二级缓存的划分
MyBatis并不是简单地对整个二级缓存分配整个Cache缓存对象,而是将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象,具体如下:
a.为每一个Mapper分配一个Cache缓存对象(使用节点配置);
b.多个Mapper共用一个Cache缓存对象(使用节点配置);
mybatis的二级缓存配置

MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true

该select语句所在的Mapper,配置了 或节点,并且有效

该select语句的参数 useCache=true

一级缓存和二级缓存的使用顺序
如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:

           二级缓存    ———>    一级缓存    ——>    数据库

二级缓存的实现
MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成
总之,使用MyBatis的二级缓存有三个选择:

    1.MyBatis自身提供的缓存实现;

    2.用户自定义的Cache接口实现;

    3.跟第三方内存缓存库的集成;

mybatis的批量操作

1.方式1, 通过mybatis的动态标签,已批量新增为例 比较常用

<!-- 批量新增-->
<insert id="batchSave" parameterType="java.util.List">
    INSERT INTO lp_user_test_batch
    (
    id,
    user_id,
    user_name
    )
    VALUES
    <foreach collection="list" item="item" index="index" separator=",">
        (
        #{item.id,jdbcType=BIGINT},
        #{item.userId,jdbcType=VARCHAR},
        #{item.userName,jdbcType=VARCHAR}
        )
    </foreach>
</insert>

2.方式2,通过mybatis的批处理特性,批量提交 ,比方式一略慢一些

定义一条sql语句

<!-- 批量新增-->
<insert id="batchSave" parameterType="java.util.List">
    INSERT INTO lp_user_test_batch
    (
    id,
    user_id,
    user_name
    )
    VALUES
        (
        #{item.id,jdbcType=BIGINT},
        #{item.userId,jdbcType=VARCHAR},
        #{item.userName,jdbcType=VARCHAR}
        )

</insert>

利用 MyBatis 批处理特性,批量提交(ExecutorType.BATCH)
SqlSession
Executor
simple (默认)
JDBC:
PreparedStatement pstat = new sql
reuse
做缓存处理:
PreparedStatement = new sql
batch
addBatch ()

/**
 * 利用 MyBatis 批处理特性,批量提交
 */
public void batchInsert(List<UserTestBatchDO> testBatchDAOList) {
    //集合非空
    if (CollectionUtils.isEmpty(testBatchDAOList)) {
        return;
    }
    //批处理方式 SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    //获得对应的Mapper
    UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
    try {
        for (UserTestBatchDO testBatchDO : testBatchDAOList) {
            userTestBatchDOMapper.insert(testBatchDO);
        }
        //统一提交
        sqlSession.commit();
    } catch (Exception e) {
        //没有提交的数据可以回滚
        sqlSession.rollback();
    } finally {
        //关闭 sqlSession
        sqlSession.close();
    }
}

方式三,方便单条控制事务, 但非常耗时,每循环一次都需要与数据库做一次交互

java程序循环调用单条修改语句
执行方式:一条sql ,程序循环执行

``` for (UserTestBatchDO userTestBatch : testBatchDAOList) { userTestBatchDOMapper.updateByUserId(userTestBatch); }

```

mybatis的分页插件实现原理