mybatis面试

mybatis的实际使用

@Test
public void test() {
    try(SqlSession sqlSession = sqlSessionFactory.openSession()){
        // mybatis在 getMapper就会给我们创建jdk动态代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp emp = mapper.QueryEmp(4);
    }
}

为什么使用Mybatis

JDBC

优点:运行期:快捷、高效

缺点:编辑期:代码量大、繁琐异常处理、不支持数据库跨平台

DBUtils

封装了对 JDBC 的操作

Hibernate

优点:

  • 使用 xml 映射 java类到 数据库表格,不用编写任何代码

  • 为在数据库中直接存储和检索 Java 对象提供简单 API

  • 数据库出现变化,仅需要修改 XML 文件属性

  • 抽象不熟悉的 SQL,提供熟悉的 Java 对象

缺点:

  • 完全封装导致无法使用数据的一些功能

  • 缓存问题

  • 对于代码的耦合度太高

  • 寻找bug 困难

  • 批量数据操作需要大量的内存空间且执行过程中需要的对象太多

JDBCTemplate

优点:运行期:高效、内嵌框架中、支持基于 AOP 的声明式事务

缺点:必须与 Spring 框架结合在一起使用、不支持数据库跨平台、默认没有缓存

mybatis

半 ORM(对象关系映射) 框架(需要自己写SQL),开发者只需要关注 SQL 本身,不需要关心 加载驱动,创建连接,创建 statement 等过程。

可以使用 XML 或注解配置和映射原生信息,将 POJO 映射为数据库中的记录。将执行的各种 statement 配置起来,通过 java 对象和 statement中的 SQL动态参数映射生成最终执行的 SQL 语句。

优点:

  1. 基于 SQL 语句编程,不会对应用程序或者数据库的现有设计造成影响,SQL 写在 XML 里,可重用,易维护。
  2. 减少代码量(手动开关连接)
  3. 与各种数据库兼容(mybatis 使用 JDBC 连接数据库,JDBC 支持的数据库都支持)
  4. 与 Spring 很好集成
  5. 提供映射标签

缺点:

  1. SQL语句的编写工作量大,尤其字段多、关联表多时
  2. SQL 语句依赖于数据库,导致数据库移植性差,不能随便更换数据库(更换数据库SQL语句很大差别)

#{} 与 ${}

${} 相当于直接替换为括号中内容,有SQL注入风险,一般用在动态表名、列名。如

select * from ${teble_name};
使用 select * from #{table_name};会造成生成的表名有 "" 而查询失败

#{} 会预处理,相当于 ? 占位符

其中的参数不用@param注解时,1个参数随便写什么值,多个参数则使用arg1 arg2 param1 param2

传参为 javaBean(一个java对象)时

  1. 1个对象,可以直接使用对象的属性名(如#{id} #{name}等)
  2. 多个对象,例子:#{param1} #{param2.id} (参数一是基本数据类型,参数二是对象类型)

集合或数组

  1. 集合(list):
    • 用了@Param注解,使用#{usernames[0]}(注解定义名字)。
    • 没用注解,使用#{list[0]} #{arg0[0]}
  2. 数组:会自动封装为 map {key: "array": value: username}
    • 使用#{arg0[0]} #{param0[0]} #{array[0]} #{myArray[0]}

映射map:与 javaBean 相同

传递多个参数

  1. 使用#{0} #{1} #{2}
  2. 使用 @param 注解,#{参数名}
  3. 使用 map 传参,#{map的key},注意 parameterType 为 map

Dao接口和XML文件的SQL 如何创建关联

  1. 创建SqlSource,mybatis会把SQL标签封装成SqlSource 对象。

img

  1. 创建 MappedStatement对象,每一个SQL标签对应一个 MappedStatement对象。创建完 MappedStatement对象后将它缓存到 Configuration中。

Configuration是mybatis中的大管家,基本所有的配置信息都维护在这里。

img

  1. 通过全限定类名+方法名找到MappedStatement对象,然后解析里面的SQL内容,执行即可。

Dao代理接口, 通过 jdk 动态代理,返回一个Dao 接口代理对象,这个代理对象的处理器是 MapperProxy对象,所以,我们通过 @Autowired 注入Dao 接口时,注入的就是这个代理对象,调用 Dao 接口方法时,则会调用到 MapperProxy 对象的 invoke() 方法

invoke() 方法实际上调用的就是 SqlSession中的东西

img

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

不同的Xml 映射文件,如果配置了namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复。因为 namespace+id 是作为 Map<String, MapperStatement>的 key 使用的。(新版本中namespace是必须的)

如何分页,分页原理

使用 RowBounds 对象进行分页,是将结果查出后在内存中进行分页。

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

ParameterHandler:拦截执行Sql的参数的组装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7TClZPPo-1624950967312)(E:\学习\img\36-339645944.png)]

插件运行原理

Configuration:所有配置的相关信息。

ResultSetHandler:用于拦截执行结果的组装。

ParameterHandler:拦截执行Sql的参数的组装。

Executor:执行Sql的全过程,包括组装参数、组装结果和执行Sql的过程。

BoundSql:执行的Sql的相关信息。

mybatis可以编写针对 ParameterHandlerResultSetHandlerStatementHandlerExecutor四种接口的插件。使用jdk动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。具体是InvocationHandler 中的 invoke()方法。

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

是否支持延迟加载

仅支持 association 关联对象和 collection关联集合对象,association 指一对一,collection 指的就是一对多查询。需要开启延迟加载

<settings>
    <!-- 开启延迟加载,所有分步查询都是懒加载 (默认是立即加载)-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--当开启时, 使用 pojo 中任意属性都会加载延迟查询 ,默认是 false -->
    <setting name="aggressiveLazyLoading" value="false"></setting> 默认FALSE,不设置也可
    <!--设置对象的哪些方法调用会加载延迟查询   默认:equals,clone,hashCode,toString-->
    <setting name="lazyLoadTriggerMethods" value=""/>
</settings>
    <!-- 单独关闭一个延迟加载 -->
    <association property="dept" fetchType="eager"  column="dept_id"  			select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
     </association>

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZoIREMyn-1624950967314)(E:\学习\img\1-10.png)]

在映射文件中,元素和元素中都已默认配置了延迟加载属性,即默认属性fetchType=”lazy”(属性fetchType=”eager”表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置

一对一:返回一个结果,有一个 association 标签

一对多:返回一个结果对象,有每个结果映射有 collection 标签

多对一:返回一个 LIst 对象,映射结果有 一个 association 标签

多对多:返回一个 List 对象,映射结果有一个 collection 标签

一对一方式association

  1. 使用级联
 <resultMap type="PersonResult" id="PersonMap">
     <id property="id" column="id" />
     <result property="name" column="name" />
     <!-- 一对一关联:单向。方式零:使用级联属性 -->
     <result property="idCard.id" column="cid"/>
     <result property="idCard.number" column="number"/>
     <result property="idCard.expiredTime" column="expired_time"/>
 </resultMap>
  1. 使用拓展类,在子类将需要组装的属性拆分为单个属性,重写 setter 方法以达成对关联对象的组装
     <resultMap type="PersonVO" id="PersonMap">
         <id property="id" column="id" />
         <result property="name" column="name" />
  <!-- 一对一关联:单向。方式一:使用扩展类,必须重写setter方法,并且父类必须将字段修改成protected,同时修改type。不推荐 -->
         <result property="cardId" column="cid" />
         <result property="cardNumber" column="number" />
         <result property="cardExpiredTime" column="expired_time" />
     </resultMap>
  1. 使用association内联
 <resultMap type="PersonResult" id="PersonMap">
     <id property="id" column="id" />
     <result property="name" column="name" />
     <!-- 一对一关联:单向。方式二:使用内联方式直接列出。 -->
     <association property="idCard" column="idcard_id" javaType="IdCard">
         <id column="cid" property="id" />
         <result column="number" property="number" />
         <result column="expired_time" property="expiredTime" />
     </association>
 </resultMap>
  1. 使用 association 引用 resultMap
 <resultMap type="PersonResult" id="PersonMap">
         <id property="id" column="id" />
         <result property="name" column="name" />
  <!-- 一对一关联:单向。方式三:使用resultMap引用。 注意的是column名称必须与关联表select时的一致 -->
         <association property="idCard" column="cid" resultMap="IdCardMap" />
     </resultMap>
  1. 使用单表查询,不用 inner 查询(以避免再次查询,可以利用延迟加载)
<resultMap type="PersonResult" id="PersonMap">
    <id property="id" column="id" />
    <result property="name" column="name" />

    <!-- 一对一关联:单向。方式四:使用select引用,可以设置延迟加载方式 -->
    <association property="idCard" column="idcard_id"
                 javaType="IdCard"
                 //这个select意思是,当要拥戴idCard对象时,再到IdcardMapper接口中去查找填充
                 select="com.dyq.mybatis.mapper.IdCardMapper.selectById" 	
                 fetchType="lazy"/>
</resultMap>
<select id="selectById" parameterType="Integer" resultMap="PersonMap">
    select id, name, idcard_id from t_person p where p.id=#{id}
</select>

多对多collection

//select 引用方式,可以延迟加载
<collection property="courses" column="sid"
            ofType="Course"
            select="com.sunwii.mybatis.mapper.CourseMapper.selectByStudent"
            fetchType="lazy" />

<collection  property="categories"  ofType="com.mybatis.entity.Category">
    <id property="cid" column="cid" />
    <result property="cname" column="cname" />    
</collection>

一级缓存,二级缓存

一级缓存:本地缓存,会话层面(SqlSession),默认开启,基于 PerpetualCache 的 HashMap 本地缓存,当Session flush 或 close 之后,该 session 中的所有 Cache 就将清空

localCacheScope=STATEMENT关闭一级缓存

二级缓存:默认也采用 PerpetualCache,HashMap 存储,不同在于作用域为 Mapper(Namespace),并且可以自定义存储源,如 Ehcache。mapper.xml文件中使用<cache/>进行开启,使用二级缓存需要实现 Serializable 序列化接口。

缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Namespace)进行了 C/UD 操作后,默认将作用域下的所有 select 中的缓存将被 clear。要执行sqlsession.commit() 之后才清理

全局二级缓存加 <setting name="cacheEnabled" value="true"/>(默认开启)。需要使用的 mapper.xml 文件中 加上 <cache/>,需要实现 Serializable 序列化接口。

二级缓存可以通过select标签 useCache=false 关闭缓存,其他SQL标签flushCache=false 关闭更新后的缓存刷新。sqlSession.clearCache()清除一级缓存

先从二级缓存中获取,再从一级缓存中获取

二级缓存属性

eviction:表示缓存回收策略,默认是LRU

  • LRU:最近最少使用的,移除最长时间不被使用的对象
  • FIFO:先进先出,按照对象进入缓存的顺序来移除
  • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
  • WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushInternal:刷新间隔,单位毫秒

  • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

size:引用数目,正整数

  • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出

readonly:只读,true/false

  • true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
  • false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值

如何封装对象并返回

使用 <resultMap> 标签,逐一定义数据库列名和对象属性名之间的映射关系

第二种使用 sql 列的别名功能,将列名书写为属性名,或者写为下划线命名法,开启驼峰规则,自动映射

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

动态Sql

9中动态SQL标签:trim,where,set,foreach,if,choose,when,otherwise,bind

if

<select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp where 
    <if test="empno!=null">
        empno > #{empno} and
    </if>
    <if test="ename!=null">
        ename like #{ename} and
    </if>
    <if test="sal!=null">
        sal > #{sal}
    </if>
</select>

where

子元素只有值才添加 where 语句,会自动去除子句开头的 AND 或者 OR

<select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp
    <where>
        <if test="empno!=null">
            empno > #{empno}
        </if>
        <if test="ename!=null">
            and ename like #{ename}
        </if>
        <if test="sal!=null">
            and sal > #{sal}
        </if>
    </where>
</select>

trim

trim截取字符串:

prefix:前缀,为sql整体添加一个前缀

prefixOverrides:去除整体字符串前面多余的字符,如写 “and” ,当SQL 开头为 and 时 会清除该 and

suffixOverrides:去除后面多余的字符串

<select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp
    <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
        <if test="empno!=null">
            empno > #{empno} and
        </if>
        <if test="ename!=null">
            ename like #{ename} and
        </if>
        <if test="sal!=null">
            sal > #{sal} and
        </if>
    </trim>
</select>

foreach

foreach 是对集合进行遍历

collection = “deptnos” 指定要遍历的集合

close="" 表示以什么结束

index="" 给定一个索引值

item="" 遍历的每一个元素的值

open="" 表示以什么开始

separator="" 表示多个元素的分隔符

<select id="getEmpByDeptnos" resultType="Emp">
    select * from emp where deptno in
    <foreach collection="deptnos" close=")" index="idx" item="deptno" open="(" separator=",">
        #{deptno}
    </foreach>
</select>

choose、when、otherwise

有点像 Switch 语句

<select id="getEmpByConditionChoose" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp
    <where>
        <choose>
            <when test="empno!=null">
                empno > #{empno}
            </when>
            <when test="ename!=null">
                ename like #{ename}
            </when>
            <when test="sal!=null">
                sal > #{sal}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>
</select>

set

<update id="updateEmpByEmpno">
    update emp
    <set>
        <if test="empno!=null">
            empno=#{empno},
        </if>
        <if test="ename!=null">
            ename = #{ename},
        </if>
        <if test="sal!=null">
            sal = #{sal}
        </if>
    </set>
    <where>
        empno = #{empno}
    </where>
</update>

bind

在 OGNL 表达式以外创建一个变量,将其绑定到当前的上下文。

_parameter代表的就是一个参数对象,getTitle()是getter 方法,也可直接使用 title

<select id="selectBlogsLike" resultType="Blog">
    <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
    SELECT * FROM BLOG
    WHERE title LIKE #{pattern}
</select>

sql

定义可重用的 SQL 代码片段

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

<select id="selectUsers" resultType="map">
    select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
    from some_table t1
    cross join some_table t2
</select>

常用 OGNL 表达式

	e1 or e2
    e1 and e2
    e1 == e2,e1 eq e2
    e1 != e2,e1 neq e2
    e1 lt e2:小于 
    e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
    e1 in e2
    e1 not in e2
    e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2		
    !e,not e:非,求反
    e.method(args)调用对象方法
    e.property对象属性值
    e1[ e2 ]按索引取值,List,数组和Map
    @class@method(args)调用类的静态方法
    @class@field调用类的 静态字段值 

like 语句怎么写

使用 bind,使用 "%"#{questtion}"%" 必须使用 “” ,使用CONCAT('%', #{questtion}, '%')

  1. 代码种拼写,传入xml
string wildcardname =%smi%;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
    select * from foo where bar like #{value}
</select>
  1. 在xml中拼接,有SQL注入风险
//会引起SQL注入
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);

<select id=”selectlike”>
    select * from foo where bar like "%"${value}"%"
</select>

主键回填

主键自增长(其他主键策略在 java 代码中生成主键)

  1. 使用 useGeneratedKeys="true"

    <insert id="insertBook" useGeneratedKeys="true" keyProperty="id">
        insert into t_book (b_name,author) values (#{name},#{author});
    </insert>
    
  2. 利用MySQL 自带的 last_insert_id() 查询刚插入的 id

    <insert id="insertBook">
        <selectKey keyProperty="id" resultType="java.lang.Integer" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into t_book (b_name,author) values (#{name},#{author});
    </insert>
    

selectKey:keyProperty,语句结果设置的对象

order,可以为BEFORE 或 AFTER,Before 是先进行设置后执行insert语句,After相反

主键策略

数据库支持自增(mysql)与不支持自增(oracle)

<!--如果数据库支持自增可以使用这样的方式-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user(user_name) values(#{userName})
</insert>
<!--如果数据库不支持自增的话,那么可以使用如下的方式进行赋值查询-->
<insert id="insertUser2" >
    <selectKey order="BEFORE" keyProperty="id" resultType="integer">
        select max(id)+1 from user
    </selectKey>
    insert into user(id,user_name) values(#{id},#{userName})
</insert>

Mapper 编写有几种方式

  1. 接口实现类继承 SqlSessinDaoSupport:需要编写 mapper 接口,mapper接口实现类,mapper.xml 文件
  1. 在 sqlMapConfig.xml 中配置 mapper.xml 的位置
  <mappers>
      <mapper resource="mapper.xml文件的地址" />
      <mapper resource="mapper.xml文件的地址" />
  </mappers>
  1. 定义mapper接口
  2. 实现类继承 SqlSessionDaoSupport
  • mapper方法中可以this.getSqlSession()进行数据增删改查。
  1. spring 配置
<bean id=" " class="mapper接口的实现">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean> 
  1. 使用org.mybatis.spring.mapper.MapperFactoryBean
  1. (1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和 mappre 接口的名称相同且在同一个目录,这里可以不用配置
<mappers>
    <mapper resource="mapper.xml文件的地址" />
    <mapper resource="mapper.xml文件的地址" />
</mappers>
  1. 定义mapper接口:
    2. mapper.xml 中的 namespace 为 mapper 接口的地址
    2. mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致
    3. Spring 中定义

    <bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface"   value="mapper接口地址" /> 
        <property name="sqlSessionFactory" ref="sqlSessionFactory" /> 
    </bean>
    
  1. 使用 mapper 扫描器
  1. mapper.xml 文件编写:
    • mapper.xml 中的 namespace 为 mapper 接口的地址;
    • mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致;
    • 如果将 mapper.xml 和 mapper 接口的名称保持一致则不用在 sqlMapConfig.xml 中进行配置。
  2. 定义 mapper 接口:
    • 注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录
  3. 配置mapper扫描器:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="mapper接口包地址"></property>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> 
</bean>
  1. 使用扫描器后从 spring 容器中获取 mapper 的实现对象。

Dao接口(Mapper接口)与 Xml 映射文件的对应关系

调用接口方法时,接口全限名+方法名拼接为 key,可唯一定位一个 MappedStatement,可以唯一找到该 namespace下与 key 的方法名相同的 MappedStatement(每一个<select> <insert> <update>都会解析为一个 MappedStatement对象)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值