MyBatis 源码解析 面试题总结

MyBatis源码学习环境下载

文章目录

1、工作原理

1.1 初始化

1.1.1 系统启动的时候,加载解析全局配置文件和相应的映射文件
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

全局配置文件:mybatis-config.xml

映射文件:mapper/*.xml

// 2.加载解析配置文件并获取 SqlSessionFactory 对象
// SqlSessionFactory 的实例我们没有通过 DefaulSqlSessionFactory 直接来获取
// 而是通过一个 Builder 对象来建造的
// SqlSessionFactory 生产 SqlSession 对象的 SqlSessionFactory 应该是单例
// 全局配置文件和映射文件 也只需要在 系统启动的时候完成加载操作
// 通过建造者模式来 构建复杂的对象: 1.完成配置文件的加载解析  2.完成 SqlSessionFactory 的创建
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

加载解析的相关信息存储在SqlSessionFactory对象的Configuration属性里,

1.1.2 建造者模式帮助我们解决复杂对象的创建:
  1. 完成配置文件的加载解析
  2. 完成 SqlSessionFactory 的创建

1.2 处理SQL请求的流程

通过工厂得到SqlSession对象

// 3.根据 SqlSessionFactory 对象获取 SqlSession 对象
SqlSession sqlSession = factory.openSession();
1.2.1 通过sqlSession中提供的API方法来操作数据库
// 4.通过sqlSession中提供的API方法来操作数据库
List<User> list = sqlSession.selectList("com.boge.mapper.UserMapper.selectUserList");
1.2.2 获取接口的代码对象-得到的其实是 通过JDBC代理模式获取的一个代理对象
// 获取接口的代码对象-得到的其实是 通过JDBC代理模式获取的一个代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
1.2.3 处理完请求之后,需要关闭会话SqlSession
//5.关闭会话
sqlSession.close();//关闭session  清空一级缓存

1.3 底层

全局配置文件的加载解析:Configuration

映射文件的加载解析:Configuration.mappedStatements

/**
     * MappedStatement 映射
     *
     * KEY:`${namespace}.${id}`
     */
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

生产了DefaultSqlsession实例对象,完成了Executor对象的创建,以及二级缓存CachingExecutor的装饰,同时完成了插件逻辑的植入。

selectOne():二级缓存->一级缓存->数据库插入

    // 1. 读取配置文件,读成字节输入流,注意:现在还没解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. 解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 生产了DefaultSqlsession实例对象   设置了事务不自动提交  完成了executor对象的创建
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1)根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
       //(2)将查询任务委派了executor执行器
    User user =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
    System.out.println(user);

    User user2 =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
    System.out.println(user2);

    // 5.释放资源
    sqlSession.close();
1.3.1 原理图:

在这里插入图片描述

1.3.2 源码结构:

在这里插入图片描述

2、MyBatis中的缓存

2.1 缓存的作用

降低数据源的访问频率,从而提高数据源的处理能力,提高服务器的响应速度。

2.2 缓存的设计

2.2.1 架构设计

在这里插入图片描述

通过装饰者模式对Cache接口的工作做增强

在这里插入图片描述

装饰者作用
BlockingCache阻塞的 Cache 实现类
FifoCache基于先进先出的淘汰机制的 Cache 实现类
LoggingCache支持打印日志的 Cache 实现类
LruCache基于最少使用的淘汰机制的 Cache 实现类
ScheduledCache定时清空整个容器的 Cache 实现类
SerializedCache支持序列化值的 Cache 实现类
SoftCache软引用缓存装饰器
SynchronizedCache同步的 Cache 实现类
TransactionalCache支持事务的 Cache 实现类,主要用于二级缓存中
WeakCache弱引用缓存装饰器
2.2.2 一级缓存和二级缓存
一级缓存:session级别,默认开启
<!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
<setting name="localCacheScope" value="STATEMENT"/>

二级缓存:SqlSessionFactory级别(工厂/进程级别)

开启二级缓存:
  • 在mybatis配置文件中配置cacheEnabled为true

    <!-- 控制全局二级缓存,默认ture-->
    <setting name="cacheEnabled" value="true"/>
    <!-- 延迟加载的全局开关。开启时,所有关联对象都会延迟加载。默认false -->
    <setting name="lazyLoadingEnabled" value="true"/>
    
  • 在映射文件中添加cache标签,可以在cache标签中更细致的增加配置

    <!--二级缓存开启-->
    <cache />
    
  • 命名空间下的所有标签放开二级缓存

  • 可以通过在标签中添加 useCache=false 指定api不走二级缓存

mybatis-config.xml

在这里插入图片描述

2.2.3 缓存的处理顺序
  • 获取mapper映射文件中cache标签里的配置MappedStatement.getCache()
  • 如果cache配置不为空,从二级缓存中查找(List) TransactionalCacheManager.getObject(cache, key);
  • 如果没有值,则执行查询, Executor.query()这个查询实际也是先走一级缓存查询,
  • 一级缓存也没有的话,则进行DB查询
  • 先将查询到的结果放入缓存TransactionalCacheManager.putObject(cache, key, list),再返回结果
2.2.3.1 先在二级缓存中查找

原因:找到概率更高,性能角度。

一级缓存二级缓存
作用域SqlSession级别SqlSessionFactory级别
找到概率5%90%
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {

        // 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
        // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
        // 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
        Cache cache = ms.getCache();

        // 如果配置文件中没有配置 <cache>,则 cache 为空
        if (cache != null) {
            //如果需要刷新缓存的话就刷新:flushCache="true"
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                // 暂时忽略,存储过程相关
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                // 从二级缓存中,获取结果
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 缓存查询结果
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                // 如果存在,则直接返回结果
                return list;
            }
        }
        // 不使用缓存,则从数据库中查询(会查一级缓存)
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
2.2.3.2 Executor.query()先走一级缓存查询,一级缓存也没有的话,则进行DB查询
    /**
     * 记录嵌套查询的层级
     */
    protected int queryStack;
    /**
     * 本地缓存,即一级缓存
     */
    protected PerpetualCache localCache;

    @SuppressWarnings("unchecked")
    @Override
    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());
        // 已经关闭,则抛出 ExecutorException 异常
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            queryStack++;
            // 从一级缓存中,获取查询结果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // 获取到,则进行处理
            if (list != null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // 获得不到,则从数据库中查询
            } else {
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // queryStack - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // 执行延迟加载
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

3、如何扩展MyBatis中的缓存

3.1 架构理解

3.2 实际开发

/**
 * 永不过期的 Cache 实现类,基于 HashMap 实现类
 *
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {
    /**
     * 缓存容器
     */
    private Map<Object, Object> cache = new HashMap<>();
        @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }
}
3.2.1 自定义三级缓存

创建Cache接口的实现,重写putObject和getObject方法

在mapper映射文件中的cache标签里增加type属性,关联自定义的Cache接口的实现

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

如果未添加type,会默认读取 PERETUAL (二级缓存)

4、MyBatis中的涉及的设计模式

4.1 从整体架构设计分析

4.1.1 基础模块:
cache缓存模块:装饰器模式

Cache接口 定义了缓存的基本行为

PerpetualCache基于Cache实现,针对于缓存的功能 1.缓存数据淘汰;2.缓存数据的存放机制;3.缓存数据添加是否同步【阻塞】;4.缓存对象是否同步处理…做了增强处理–>代理模式

以及很多装饰类,灵活增强出适用于不同业务场景的Cache实现

logging日志模块:适配器模式、策略模式、代理模式

帮助我们适配不同的日志框架

Log接口针对不同日志框架,有不同的实现类,做增强处理

reflection反射模块:工程模式、装饰器模式
datasource数据源:工程模式
transaction事务模块:工厂模式
SqlSessionFactory:SqlSessionFactoryBuilder建造者模式

5、谈谈你对SqlSessionFactory的理解

  • 目的:创建SqlSession对象
  • 单例,在应用程序(服务)中只保存唯一的一份
  • SqlSessionFactory对象的创建是通过SqlSessionFactoryBuilder,
  • 同时也完成了全局配置文件Configuration和相关映射文件Mapper的加载和解析操作。
  • 涉及到了工厂模式和建造者模式
    /**
     * 构造 SqlSessionFactory 对象
     *
     * @param reader Reader 对象
     * @param environment 环境
     * @param properties Properties 变量
     * @return SqlSessionFactory 对象
     */
    @SuppressWarnings("Duplicates")
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder 对象
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
        } 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.
            }
        }
    }

    /**
    * 1.我们最初调用的build
    */
    public SqlSessionFactory build(InputStream inputStream) {
        //调用了重载方法
        return build(inputStream, null, null);
    }

    /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

6、谈谈你读SqlSession的理解

6.1 SqlSession

  • 作用:通过相关API来实现对应的数据的操作

  • SqlSession对象的获取需要通SqlSessionFactory来实现,

  • 作用域是会话级别,当一个新的会话到来的时候,需要新建一个SqlSession对象;当一个会话结束后,需要关闭相关会话资源

  • 处理请求的方式:

    1.通过相关crud的API直接处理

    2.通过getMapper(xx.xml)来获取相关mapper接口的代理对象来处理

6.2 SqlSession的安全问题

6.2.1 非线程安全:

在这里插入图片描述

6.2.2 Spring中是如何解决DefaultSqlSession的数据安全问题?
  • DefaultSqlSession是非线程安全的,也就意味着我们不能把DefaultSqlSession声明在成员变量中。
  • 每个线程都应该有自己的SqlSession实例。
  • 最佳作用域是请求或方法作用域
  • 决不能将SqlSession实例引用放在一个类的静态域,甚至一个类的实例变量也不行。
  • 应该将SqlSession放在一个和HTTP请求相似的作用域中,每次请求打开一个SqlSession,返回一个响应后就关闭他,关闭操作放在finally块中。

在这里插入图片描述

  • Spring中提供了SqlSessionTemplate来实现SqlSession的相关定义。
  • 其中每一个方法都通过SqlSessionProxy来操作,这是一个动态代理对象。
  • 在动态代理对象中通过方法级别的DefaultSqlSession来实现相关的数据库操作。

7、谈谈你对MyBatis的理解

  • 使用频率最高的ORM框架、持久层框架
  • 提供了非常方便的API实现CRUD
  • 支持灵活的缓存处理方案,一级缓存、二级缓存、三级缓存
  • 支持相关的延迟数据加载处理
  • 还提供了非常多的灵活标签,来实现复杂的业务处理,if forech where trim set bind…
  • 相比Hibernate(全自动化)会更加灵活

8、谈谈MyBatis中分页的理解

8.1 谈谈分页的理解:

  • 数据库层面SQL:

    MySQL:LIMIT

    Oracle:rowid

8.2 分页的实现

8.2.1 逻辑分页:RowBounds
8.2.2 物理分页:拦截器实现,执行分页语句的组装

在这里插入图片描述
分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。

举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)t limit 0,10

9、谈谈MyBatis中的插件原理

9.1 插件设计的目的:

方便开发人员实现对MyBatis功能的增强

设计中MyBatis允许映射语句执行过程中的某一点进行拦截调用,允许使用插件拦截的方法包括:

在这里插入图片描述

  • Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、

  • Executor 这 4 种接口的插件,Mybatis 通过动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是

  • InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

  • 实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

9.2 实现原理:

9.2.1 创建自定义Java类,通过@Interceptor注解来定义相关的方法签名

在这里插入图片描述

9.2.2 在对应的配置文件中通过plugin来注册自定义的拦截器
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
9.2.3 拦截器的作用
  • 检查执行的SQL
  • 对执行SQL的参数做处理
  • 对查询的结果做装饰处理
  • 对查询SQL做分表处理

10、不同Mapper中的id是否可以相同?

可以相同,每一个映射文件的namespace都会设置为对应的mapper接口的全类路径名称

保证了每个Mapper映射文件的namespace是唯一的。

11、谈谈对MyBatis架构设计的理解

11.1 接口层

面向开发者,提供相关API

11.2 核心层

核心功能的实现:增删改查操作

11.3 基础模块

支撑核心层来完成核心的功能

在这里插入图片描述

12、谈谈对传统JDBC开发的不足

JDBCMyBatis
资源频繁的创建和释放数据库的连接对象,造成系统资源的浪费通过全局配置文件设置相关的数据连接池
sql维护sql语句直接写在了代码中,维护成本高,动态性要求较高sql语句写在Mapper映射文件中的标签里
参数向SQL中传递参数麻烦,where条件不一定,占位符和参数需要一一对应自动完成Java对象和SQL中参数的映射
结果集映射麻烦,需要循环封装,SQL本身的变化会导致解析的难度通过ResultHandler自动将结果集映射到Java对象
拓展不支持事务、缓存、延迟加载等功能提供了相关实现
性能运行性能更高开发效率更高

13、MyBatis中数据源模块的设计

在这里插入图片描述

完成相关标签的解析,存储在configuration中

    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            // environment 属性非空,从 default 属性获得
            if (environment == null) {
                environment = context.getStringAttribute("default");
            }
            // 遍历 XNode 节点
            for (XNode child : context.getChildren()) {
                // 判断 environment 是否匹配
                String id = child.getStringAttribute("id");
                if (isSpecifiedEnvironment(id)) {
                    // 解析 `<transactionManager />` 标签,返回 TransactionFactory 对象
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    // 解析 `<dataSource />` 标签,返回 DataSourceFactory 对象
                    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    // 创建 Environment.Builder 对象
                    Environment.Builder environmentBuilder = new Environment.Builder(id)
                            .transactionFactory(txFactory)
                            .dataSource(dataSource);
                    // 构造 Environment 对象,并设置到 configuration 中
                    configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }

在这里插入图片描述

  • UnpooledDataSource:非数据库连接池的实现
  • PooledDataSource:数据库连接池的实现
  • 从连接池中获取连接对象:如果有空闲连接直接返回,活跃连接数是否超过了最大连接数,是否有连接超时的连接
  • 数据库连接池关闭连接,如果空闲连接没有超过最大连接数那么就返回空闲队列中。
  • 否则关闭真实的连接。

连接池工作原理

在这里插入图片描述

14、MyBatis中事务模块的设计

14.1 事务的理解ACID

14.2 MyBatis中的事务

14.2.1 事务的配置

在这里插入图片描述

14.2.2 事务接口的定义:定义了事务的基本行为

在这里插入图片描述

14.2.3 在MyBatis中的事务管理有两个选择

JDBC:在MyBatis中自己处理事务的管理

Managed:在MyBatis中没有处理任何的事务操作,这种情况下事务的处理会交给Spring容器来管理

14.2.4 如何设置事务管理的方式

在这里插入图片描述

14.2.5 在MyBatis中执行DML操作事务的处理逻辑

SqlSession.commit()

15、谈谈你对Mapper接口的设计理解

15.1 接口的对应的规则

名称需要对应

15.2 接口的设计原理

代理模式的使用

在这里插入图片描述

创建动态代理对象

在这里插入图片描述

15.3 代理对象执行的逻辑的本质还是会执行SqlSession中相关的DML操作的方法

在这里插入图片描述

15.4 为何要多包一层代理对象

每调用一个mapper接口都需要在命名空间中指向接口路径

在这里插入图片描述

16、谈谈你对Reflector模块的理解

  • Reflector是MyBatis中提供的一个针对反射封装简化的模块:简化反射的相关操作。
  • 表结构的数据和Java对象中数据的映射,不可避免的会存在非常多的反射操作。
  • Reflector是一个独立的模块,可以把这个模块单独抽取出来,直接使用的。
  • 反射器,每个 Reflector 对应一个类。

在这里插入图片描述

反射模块的具体设计:

在这里插入图片描述

17、MyBatis的类型转换模块

MyBatis是如何解决Java中的类型和数据库中的字段类型的映射

在这里插入图片描述

类型处理器:

  • 写:Java类型 --> JDBC类型

  • 读:JDBC类型 --> Java类型

  • SQL操作:读 写

  • 本质上执行的JDBC操作:

    String sql = "SELECT id,user_name,phone from user where id = ? and user_name = ?";
    ps = conn.prepareStatement(sql);
    ps.setInt(1,2);
    ps.setString(2,"张三");
    

TypeHandler --> BaseTypeHandler —> 具体的TypeHandler

18、整合MyBatis

18.1 单纯Spring整合MyBatis

  • 添加mybatis-spring依赖

            <!-- mybatis 中间件 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.2.2</version>
            </dependency>
    
  • 在spring配置文件spring-context.xml中添加数据源信息和sqlSessionFactory,将sqlSessionFactory注入容器

    	<!-- 数据源配置, 使用 BoneCP 数据库连接池 -->
    	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    	    <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
    	    <property name="driverClassName" value="${jdbc.driver}" />
    		<!-- 基本属性 url、user、password -->
    		<property name="url" value="${jdbc.url}" />
    		<property name="username" value="${jdbc.username}" />
    		<property name="password" value="${jdbc.password}" />
    		<!-- 配置初始化大小、最小、最大 -->
    		<property name="initialSize" value="${jdbc.pool.init}" />
    		<property name="minIdle" value="${jdbc.pool.minIdle}" />
    		<property name="maxActive" value="${jdbc.pool.maxActive}" />
    		<!-- 配置获取连接等待超时的时间 -->
    		<property name="maxWait" value="300000" />
    		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    		<property name="timeBetweenEvictionRunsMillis" value="60000" />
    		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    		<property name="minEvictableIdleTimeMillis" value="300000" />
    	</bean>
     	<!-- MyBatis begin -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--关联数据源-->
            <property name="dataSource" ref="dataSource"/>
            <!--添加别名设置-->
            <property name="typeAliasesPackage" value="com.hlframe"/>
            <property name="typeAliasesSuperType" value="com.hlframe.common.persistence.BaseEntity"/>
            <!--映射文件和接口文件不在同一个目录下的时候,配置映射文件位置-->
            <property name="mapperLocations" value="classpath:/mappings/**/*.xml"/>
            <!--关联MyBatis的配置文件-->
    		<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
        </bean>
    

18.2 SpringBoot整合MyBatis

  • 添加mybatis-plus-boot-starter依赖

            <!--mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
                <scope>provided </scope>
            </dependency>
    
  • 将sqlSessionFactory注入容器

在这里插入图片描述

19、Mybatis 动态 sql 是做什么的?都有哪些动态 sql?

  • 1)Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。

  • 2)Mybatis 提供了 9 种动态 sql 标签:

    trim|where|set|foreach|if|choose|when|otherwise|bind。

  • 3)其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。

20、#{}和${}的区别是什么?

  • 1)#{}是预编译处理,${}是字符串替换。
  • 2)Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
  • 3)Mybatis 在处理 时,就是把 {}时,就是把 时,就是把{}替换成变量的值。
  • 4)使用#{}可以有效的防止 SQL 注入,提高系统安全性。

21、为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

答:Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

22、Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

  • 1) Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
  • 2) 它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。

23、简述 Mybatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系?

答:Mybatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在

Xml 映射文件中,标签会被解析为 ParameterMap 对象,其每个子元素会被解析为 ParameterMapping 对象。标签会被解析为 ResultMap 对象,其每个子

元素会被解析为 ResultMapping 对象。每一个、、、标签均会被解析为 MappedStatement 对象,标签内的 sql 会被解析为 BoundSql 对象。

24、MyBatis 实现一对一有几种方式?具体怎么操作的?

答:有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面配置 association 节点配置一对一的类就可以完成;嵌套查询是先查一个表,根据这个表里面的结果的外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。

25、Mybatis 能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别?

答:能,Mybatis 不仅可以执行一对一、一对多的关联查询,还可以执行多对一,多对多的关联查询,多对一查询,其实就是一对一查询,只需要把 selectOne()修改为 selectList()即可;多对多查询,其实就是一对多查询,只需要把 selectOne()修改为 selectList()即可。

关联对象查询,有两种实现方式,一种是单独发送一个 sql 去查询关联对象,赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用 join 查询,一部分列是 A 对象的属性值,另外一部分列是关联对象 B 的属性值,好处是只发一个 sql 查询,就可以把主对象和其关联对象查出来。

26、MyBatis 里面的动态 Sql 是怎么设定的?用什么语法?

答:MyBatis 里面的动态 Sql 一般是通过 if 节点来实现,通过 OGNL 语法来实现,但是如果要写的完整,必须配合 where,trim 节点,where 节点是判断包含节点有内容就插入 where,否则不插入,trim 节点是用来判断如果动态语句是以 and 或 or 开始,那么会自动把这个 and 或者 or 取掉。

27、通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应, Dao 的工作原理,是否可以重载?

答:不能重载,因为通过 Dao 寻找 Xml 对应的 sql 的时候全限名+方法名的保存和寻找策略。接口工作原理为 jdk 动态代理原理,运行时会为 dao 生成 proxy,代理对象会拦截接口方法,去执行对应的 sql 返回数据。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柚几哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值