0%

mybatis

面试常问:

  • 延迟加载提高加载效率:

Mybatis的缓存

  • 一级缓存:也即是本地缓存,是SqlSession级别的,读个sql语句之间不会共享缓存,使用@Transactional来生效,也就是说在一次事务中多次查询会使用到一级缓存
    • 默认开启,可以在配置中关闭
      1. 当我们执行查询操作时,MyBatis会先去一级缓存中查找是否有之前查询过的数据。如果有,直接返回缓存中的数据;如果没有,去数据库查询数据,并将查询结果放入一级缓存中。
      2. 当我们执行更新操作(包括insert、update、delete)时,MyBatis会清空一级缓存。这是为了保证缓存中的数据和数据库中的数据是一致的。
      3. 当SqlSession结束或关闭时,一级缓存也就清空了。
    • 二级缓存:
      • 在mapper.xml上方加上<Cache></Cache> 即可
      • 根据mapper.xml中命名空间来区分,是mapper级别的,只用当执行同一个mapper中的增改删语句时才会失效,增删改频繁时二级缓存基本失效,并且,微服务中多台服务中只有被调用的那一台的二级缓存才会删除,其余的不删除,造成不一致。
  • Springboot Cache,会缓存方法的返回值,但是同样也只能在一个节点生效,并且,@Cacheable不会主动刷新缓存,但是@CachePut会强制刷新缓存,并把新的缓存放入
  • 共享的缓存!Redis 可以在配置文件中将springboot的缓存类型设置为Redis

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

  • #{} 是Propeties文件中的变量占位符,会被原样替换
  • ${}是sql的参数占位符,Mybatis会把他替换成 ? 后续通过反射进行替换数据

xml 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?

resultMap 定义查询结果的映射规则

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name" />
  <result property="password" column="user_password" />
  <result property="email" column="user_email" />
  <result property="bio" column="user_bio" />
</resultMap>

sql:定义可复用的SQL代码段

<sql id="userColumns"> ${alias}.id, ${alias}.username, ${alias}.password, ${alias}.email, ${alias}.bio </sql>

<select id="selectUsers" resultMap="userResultMap" >
  select
    <include refid="userColumns"><property name="alias" value="user"/></include>
  from some_table user
</select>

parameterType 定义SQL语句的输入参数类型

<insert id="insertUser" parameterType="User">
  insert into users (username, password, email, bio)
  values (#{username}, #{password}, #{email}, #{bio})
</insert>

resultType:定义 SQL 语句的输出结果类型。

<select id="selectUsernames" resultType="string">
  select username from users
</select>

association:定义一对一的关联关系。

<resultMap id="userResultMap" type="User">
  <!-- ... -->
  <association property="address" javaType="Address">
    <id property="id" column="address_id" />
    <result property="street" column="address_street" />
    <result property="city" column="address_city" />
    <result property="state" column="address_state" />
    <result property="zip" column="address_zip" />
    <result property="country" column="address_country" />
  </association>
</resultMap>

collection:定义一对多的关联关系。

<resultMap id="userResultMap" type="User">
  <!-- ... -->
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id" />
    <result property="subject" column="post_subject" />
    <result property="body" column="post_body" />
  </collection>
</resultMap>

dynamic 标签如 ifchoosewhenotherwisetrimwhereset:用于构建动态 SQL。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE' 
  <if test="title != null">
    AND title like #{title}
  </if>
</select>
  • 执行过程
    • java程序加载config文件,创建SqlSessionFactory对象,之后通过SqlSessionFactory创建SqlSession对象,之后通过SqlSession对象执行映射配置文件中定义的SQL语句,最后通过SqlSession对象提交事务,关闭SqlSession对象

Dao接口(Mapper接口)的原理

Dao中的方法,参数不同时,可以重载吗

Mapper中的方法可以 重载,使用的是全限定名 + 方法名 拼接的字符串作为key去匹配。 但是xml文件中的id只能指定一个,也即是重载的所有方法都是用一个sql语句,而这个sql语句我们可以使用动态sql来实现

<select id="getAllStu" resultType="com.pojo.Student">
  select * from student
  <where>
    <if test="id != null">
      id = #{id}
    </if>
  </where>
</select>

Dao接口的原理:
MyBatis运行时会使用JDK动态代理来为Dao生成代理proxy对象,代理对象会拦截接口方法,转而执行MappedStatement中的sql

MyBatis的分页

nihao

原理

答:**(1)** MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;**(2)** 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,**(3)** 也可以使用分页插件来完成物理分页。

分页插件的原理

  1. 插件设置一个ThreadLocal变量来存储分页参数
  2. 当执行查询时,MyBatis会调用所有注册的拦截器。
  3. PageHelper 首先会保存原始的查询SQL,然后生成一个新的SQL,这个新的SQL在原始的SQL基础上添加了LIMIT和OFFSET
  4. PageHelper 将新的SQL替换成原始的SQL然后执行
  5. 最后PageHelper会清除ThreadLocal中变量,避免内存泄漏

MyBatis动态sql是什么的,有哪些,原理

常用的动态sql标签

  • <if></if>
  • <where></where>(trim,set)
  • <choose></choose>(when, otherwise)
  • <foreach></foreach>
  • <bind/>

执行原理

OGNL (Object-Graph Navigation Language)表达式,通过他可以在XML配置文件中引用Java对象和方法,在动态SQL中,常常使用其进行判断条件

  • 常见的OGNL语法:
    person.name #访问对象的属性
    person.getName() # 调用对象的方法
    persons.{name} # 获取persons对象的name属性
    person.age > 18 ? 'adult' : 'child' # 条件表达式
    person.agge + 1 # 算术表达式
    person.age > 18 && person.gender == 'male' # 逻辑表达式

MyBatis如何将sql执行结果封装为目标对象并返回

  1. 使用<resultMap> 标签去映射列表名和对象属性名之间的映射关系
  2. 使用sql列别名方式,将列名书写为对象属性名,例如:T_NAME AS NAME 对应的属性名是name,会忽略大小写
    当映射关系建立之后,MyBatis会通过反射创建对象,然后给对象的属性一一赋值

MyBatis延迟加载的原理 待写

MyBatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的是1v1,collection是1v多

Executor执行器

  • SimpleExecutor 每执行一次 update或select,就开启一个Statement对象,用完立刻关闭Statement对象
  • ReuseExecutor 执行update/select 以sql为key查找Statement对象,不存在就创建,使用后放在Map中,供下一次使用
  • BatchExecutor 执行update 时,将所有的sql都添加到批处理中,之后统一执行,缓存多个Statement对象,每个Statement对象都是等待sql添加之后,等待逐一执行

深入解读

基础支持层

  • 解析器模块
    1. DOM解析将xml的标签组织成一颗DOM树,将整个xml文档加载进内存
    2. SAX基于时间模型的xml解析方式。加载一部分到内存中,并且当程序处理过程中满足条件时,会结束解析,不必解析剩余的xml内容。但是不支持层次关系和父子关系的保存
    • XPathParser:MyBatis提供的XPathParser类封装了XPath、Document、EntityResolver

MyBatis Plus

一些插件

自定义填充字段

@Component
public class MyBatisPlusDateHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}

分页插件

@Configuration(value = "dataBaseConfigurationByAdmin")
public class DataBaseConfiguration {

    /**
     * 分页插件
     */
    @Bean
    @ConditionalOnMissingBean
    public MybatisPlusInterceptor mybatisPlusInterceptorByAdmin() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

使用时,只需要在自己定义的方法上加上一个Page类型的变量就可以进行分页查询了

Page<User> page = new Page<>(1, 10); // 第1页,每页10条记录
List<User> users = userService.selectUsers(page);