谈谈你对 MyBatis 的理解?

  1. Mybatis是⼀个半ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需 要花费精⼒去处理加载驱动、创建连接、创建 Statement 等繁杂的过程。程序员直接编写原⽣态 SQL,可以 严格控制 SQL 执⾏性能,灵活度⾼。
  2. MyBatis 可以使⽤ XML 或注解来配置和映射原⽣信息,将 POJO 映射成数据库中的记录,避免了⼏乎所有的 JDBC 代码和⼿动设置参数以及获取结果集。
  3. 通过 XML ⽂件或注解的⽅式将要执⾏的各种 Statement 配置起来,并通过 Java 对象和 Statement 中 SQL 的 动态参数进⾏映射⽣成最终执⾏的 SQL 语句,最后由 MyBatis 框架执⾏ SQL并将结果映射为 Java 对象并返 回。(从执⾏ SQL到返回 Result 的过程)

MyBatis 编程步骤

  1. 创建 SqlSessionFactory 对象。
  2. 通过 SqlSessionFactory 获取 SqlSession 对象。
  3. 通过 SqlSession 获得 Mapper 代理对象。
  4. 通过 Mapper 代理对象,执行数据库操作。
  5. 执行成功,则使用 SqlSession 提交事务。
  6. 执行失败,则使用 SqlSession 回滚事务。
  7. 最终,关闭会话。

介绍 MyBatis 的一级缓存和二级缓存的概念和实现原理?

一级缓存 是 SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结 构用于存储缓存数据。 不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不 同的sqlSession中的缓存是互相不能读取的。

一级缓存的工作原理:

image-20210911202945606

用户发起查询请求,查找某条数据,sqlSession 先去缓存中查找,是否有该数据,如果有,读取; 如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。 但 sqlSession 执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。

如果commit不清空缓存,会有以下场景:

A查询了某商品库存为10件,并将10件库存的数据存入缓存中,之后被客户买走了10件,数据被delete 了,但是下次查询这件商品时,并不从数据库中查询,而是从缓存中查询,就会出现错误。

二级缓存原理:

image-20210911203104032

二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个Mapper的sql语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。 UserMapper有一个二级缓存区域(按 namespace 划分),每个 mapper 也有自己的二级缓存区域 (按namespace分)。 每一个 namespace 的 mapper 都有一个二级缓存区域,如果相同两个 mapper 的 namespace ,这两 个mapper执行sql查询到数据将存在相同的二级缓存区域中。

开启二级缓存:

1、打开总开关 在MyBatis的全局配置文件中加入:

<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>

2,在需要开启二级缓存的 mapper.xml 中加入 cache 标签 :

<cache/>

3,让使用二级缓存的 POJO 类实现 Serializable 接口

既然有了一级缓存,那么为什么要提供二级缓存呢?

二级缓存是 mapper 级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个 SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用范围更大。

还有一个原因,实际开发中,MyBatis 通常和 Spring 进行整合开发。 Spring 将事务放到 Service 中管理,对于每一个 service 中的 sqlsession 是不同的, 这是通过 mybatis-spring 中org.mybatis.spring.mapper.MapperScannerConfigurer 创建 sqlsession 自动注入到 service 中的。 每次查询之后都要进行关闭 sqlSession ,关闭之后数据被清空。 所以 spring 整合之后,如果没有事务,一级缓存 是没有意义的。

cache 标签的属性

<cache
eviction="FIFO" // 回收策略为先进先出
flushInterval="60000" // 自动刷新时间60s
size="512" // 最多缓存512个引用对象
readOnly="true"/> // 只读

cache 标签可指定如下属性,每种属性的指定都是针对都是针对底层Cache的一种装饰,采用的是装饰器的模式。

  • blocking:默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思。

    使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据,详情可参考 BlockingCache的源码。 简单理解,也就是设置true时,在进行增删改之后的并发查询,只会有一条去数据库查询,而不会 并发。

  • eviction:eviction 就是驱逐的意思,也就是元素驱逐算法,默认是LRU。

    LRU 对应的就是LruCache,其默认只保存1024个Key,超出时按照最近最少使用算法进行驱逐, 详情请参考LruCache的源码。 如果想使用自己的算法,则可以将该值指定为自己的驱逐算法实现类,只需要自己的类实现 Mybatis的Cache接口即可。 除了LRU以外,系统还提供:

    • FIFO(先进先出,对应FifoCache)
    • SOFT(采用软引用存储Value,便于垃圾回收,对应SoftCache)
    • WEAK(采用弱引用存储Value,便于垃圾回收,对应WeakCache)这三种策略。 这里,根据个人需求选择了,没什么要求的话,默认的LRU即可。
  • flushInterval:清空缓存的时间间隔,单位是毫秒,默认是不会清空的。

    当指定了该值时会再用ScheduleCache包装一次,其会在每次对缓存进行操作时判断距离最近一次 清空缓存的时间是否超过了flushInterval指定的时间,如果超出了,则清空当前的缓存,详情可参考ScheduleCache的实现。

  • readOnly:是否只读 。默认为false。

    当指定为false时,底层会用SerializedCache包装一次,其会在写缓存的时候将缓存对象进行序列 化,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读取到的结果,也不会影响原来缓存的对象,即非只读,你每次拿到这个缓存结果都可以进行修改,而不会影响原来的缓存结果; 当指定为true时,那就是每次获取的都是同一个引用,对其修改会影响后续的缓存数据获取,这种 情况下是不建议对获取到的缓存结果进行更改,意为只读。 (不建议设置为true) 这是Mybatis二级缓存读写和只读的定义,可能与我们通常情况下的只读和读写意义有点不同。每 次都进行序列化和反序列化无疑会影响性能,但是这样的缓存结果更安全,不会被随意更改,具体 可根据实际情况进行选择。详情可参考SerializedCache的源码。

  • size:用来指定缓存中最多保存的Key的数量。

    其是针对LruCache而言的,LruCache默认只存储最多1024个Key,可通过该属性来改变默认值, 当然,如果你通过eviction指定了自己的驱逐算法,同时自己的实现里面也有setSize方法,那么也 可以通过cache的size属性给自定义的驱逐算法里面的size赋值。

  • type:指定当前底层缓存实现类,默认是PerpetualCache。

    如果我们想使用自定义的Cache,则可以通过该属性来指定,对应的值是我们自定义的Cache的全 路径名称。

cache-ref 标签

<cache-ref namespace="cn.chenhaoxiang.dao.UserMapper"/>

cache-ref 可以用来指定其它 Mapper.xml 中定义的Cache,有的时候可能我们多个不同的 Mapper 需 要共享同一个缓存的 是希望在MapperA中缓存的内容在MapperB中可以直接命中的,这个时候我们就可以考虑使用cache-ref,这种场景只需要保证它们的缓存的Key是一致的即可命中,二级缓存的Key是通过Executor接口的 createCacheKey()方法生成的,其实现基本都是BaseExecutor。

MyBatis的优缺点?

优点:

1.基于 SQL 语句编程,相当灵活,不会对应⽤程序或者数据库的现有设计造成任何影响,SQL 写在 XML ⾥,解 除 SQL 与程序代码的耦合,便于统⼀管理;提供XML标签,⽀持编写动态 SQL 语句,并可重⽤;

2.与 JDBC 相⽐,减少了代码量,消除了 JDBC ⼤量冗余的代码,不需要⼿动开关连接;

3.很好的与各种数据库兼容(因为 MyBatis 使⽤ JDBC 来连接数据库,所以只要 JDBC ⽀持的数据库 MyBatis 都⽀持);

4.提供映射标签,⽀持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,⽀持对象关系组件维护。

缺点:

1.SQL 语句的编写⼯作量较⼤,尤其当字段多、关联表多时,对开发⼈员编写 SQL 语句的功底有⼀定要求;

2.SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis 逻辑分⻚和物理分⻚的区别是什么?

1.物理分⻚速度上并不⼀定快于逻辑分⻚,逻辑分⻚速度上也并不⼀定快于物理分⻚。
2.物理分⻚总是优于逻辑分⻚:没有必要将属于数据库端的压⼒加到应⽤端来,就算速度上存在优势,然⽽其它 性能上的优点⾜以弥补这个缺点。

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

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

在Mybatis中,有两种占位符

  • #{}解析传递进来的参数数据
  • ${}对传递进来的参数原样拼接在SQL中
  • #{}是预编译处理,${}是字符串替换
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。

当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致

<select id="selectorder" parametertype="int" resultetype="me.gacl.domain.order"> 
     select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; 
  </select> 

第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系

<select id="getOrder" parameterType="int" resultMap="orderresultmap">
        select * from orders where order_id=#{id}
</select>
<resultMap type="me.gacl.domain.order" id="orderresultmap"> 
        <!–用id属性来映射主键字段–> 
        <id property="id" column="order_id"/> 
        <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> 
        <result property = "orderno" column ="order_no"/> 
        <result property="price" column="order_price"/> 
<resultMap>

我认为第二种方式会好一点。

如何获取自动生成的(主)键值?

如何获取自动生成的(主)键值?

如果我们一般插入数据的话,如果我们想要知道刚刚插入的数据的主键是多少,我们可以通过以下的方式来获取

需求:

  • user对象插入到数据库后,新记录的主键要通过user对象返回,通过user获取主键值。

解决思路:

  • 通过LAST_INSERT_ID()获取刚插入记录的自增主键值,在insert语句执行后,执行select LAST_INSERT_ID()就可以获取自增主键。

mysql:

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
    <selectKey keyProperty="id" order="AFTER" resultType="int">
        select LAST_INSERT_ID()
    </selectKey>
    INSERT INTO USER(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>

oracle:

实现思路:

  • 先查询序列得到主键,将主键设置到user对象中,将user对象插入数据库。
<!-- oracle在执行insert之前执行select 序列.nextval() from dual取出序列最大值,将值设置到user对象 的id属性 -->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
    <selectKey keyProperty="id" order="BEFORE" resultType="int">
        select 序列.nextval() from dual
    </selectKey>
    INSERT INTO USER(id,username,birthday,sex,address) VALUES(序列.nextval(),#{username},#{birthday},#{sex},#{address})
</insert> 

在mapper中如何传递多个参数?

在mapper中如何传递多个参数?

第一种:使用占位符的思想

  • 在映射文件中使用#{0},#{1}代表传递进来的第几个参数
    • 经大佬测试,如果使用的是JDK8的话,那么会有Bug
  • 使用@param注解:来命名参数
  • #{0},#{1}方式
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">  
    select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
</select>  
  • @param注解方式
public interface usermapper { 
 user selectuser(@param(“username”) string username, 
 @param(“hashedpassword”) string hashedpassword); 
}
<select id="selectuser" resulttype="user"> 
         select id, username, hashedpassword 
         from some_table 
         where username = #{username} 
         and hashedpassword = #{hashedpassword} 
</select>

第二种:使用Map集合作为参数来装载

try{
   //映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL     
 /**
   * 由于我们的参数超过了两个,而方法中只有一个Object参数收集
   * 因此我们使用Map集合来装载我们的参数
   */
       Map<String, Object> map = new HashMap();
       map.put("start", start);
       map.put("end", end);
       return sqlSession.selectList("StudentID.pagination", map);
   }catch(Exception e){
       e.printStackTrace();
       sqlSession.rollback();
       throw e;
   }finally{
       MybatisUtil.closeSqlSession();
   }
<!--分页查询-->
<select id="pagination" parameterType="map" resultMap="studentMap">
      /*根据key自动找到对应Map集合的value*/
      select * from students limit #{start},#{end};
</select>

Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

  • Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能
  • Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。
  • 其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能

详情Demo可参考大佬的文章:

Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

如果配置了namespace那么当然是可以重复的,因为我们的Statement实际上就是namespace+id

如果没有配置namespace的话,那么相同的id就会导致覆盖了。

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

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

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

通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

  • Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。
  • Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement

举例:

com.mybatis3.mappers.StudentDao.findStudentById,

可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在M

Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

详情可参考:

Mybatis比IBatis比较大的几个改进是什么

Mybatis比IBatis比较大的几个改进是什么

  • a.有接口绑定,包括注解绑定sql和xml绑定Sql ,
  • b.动态sql由原来的节点配置变成OGNL表达式,
  • c. 在一对一,一对多的时候引进了association,在一对多的时候引入了collection节点,不过都是在resultMap里面配置

接口绑定有几种实现方式,分别是怎么实现的?

接口绑定有几种实现方式,分别是怎么实现的?

接口绑定有两种实现方式:

  • 一种是通过注解绑定,就是在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定
  • 另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名.

Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis是如何进行分页的?分页插件的原理是什么?

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

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

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

分页插件使用参考资料:

简述Mybatis的插件运行原理,以及如何编写一个插件

简述Mybatis的插件运行原理,以及如何编写一个插件

Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

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

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

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

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

Mybatis都有哪些Executor执行器?它们之间的区别是什么?

Mybatis都有哪些Executor执行器?它们之间的区别是什么?

Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象
  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象
  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

MyBatis与Hibernate有哪些不同?

MyBatis与Hibernate有哪些不同?

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

还有很多其他的标签,<resultMap><parameterMap><sql><include><selectKey>,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。

Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。

有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

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

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

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

那么问题来了,join查询出来100条记录,如何确定主对象是5个,而不是100个?其去重复的原理是<resultMap>标签内的<id>子标签,指定了唯一确定一条记录的id列,Mybatis根据<id>列值来完成100条记录的去重复功能,<id>可以有多个,代表了联合主键的语意。

同样主对象的关联对象,也是根据这个原理去重复的,尽管一般情况下,只有主对象会有重复记录,关联对象一般不会重复。

Mybatis中如何指定使用哪一种Executor执行器?

在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

Mybatis是否可以映射Enum枚举类?

Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。

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

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

MyBatis 实现一对多有几种方式,怎么操作的?

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

模糊查询like语句该怎么写

(1)’%${question}%’ 可能引起SQL注入,不推荐

(2)"%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ‘,所以这里 % 需要使用双引号” “,不能使用单引号 ’ ‘,不然会查不到任何结果。

(3)CONCAT(’%’,#{question},’%’)使用CONCAT()函数,推荐

(4)使用bind标签

<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
  <bind name="pattern" value="'%' + username + '%'" />
  select id,sex,age,username,password from person where username LIKE #{pattern}
</select>

为什么需要预编译

定义:
SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。

为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。

请说说MyBatis的工作原理

image-20210911204900421

1)读取 MyBatis 配置文件:mybatis-config.xmlMyBatis的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

MyBatis的功能架构是怎样的

我们把Mybatis的功能架构分为三层:

image-20210911204922218

API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

参考

http://svip.iocoder.cn/MyBatis/Interview/#MyBatis%E7%BC%96%E7%A8%8B%E6%AD%A5%E9%AA%A4
https://www.cnnotes.com/lukelook/p/11099039.html