Mybatis笔记

346 阅读4分钟

核心概念

mybatis的DAO接口是通过XXMapper.xml文件实现的,文件名与接口名相同,一个常见的mapper如下:

<mapper namespace="com.souche.repayment.dao.mapper.CalculationBaseDataDOMapper">
    <resultMap id="BaseResultMap" type="com.souche.repayment.bean.entity.CalculationBaseDataDO">
        <!--
          WARNING - @mbggenerated
        -->
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="order_no" property="orderNo" jdbcType="VARCHAR"/>
        <result column="term_no" property="termNo" jdbcType="INTEGER"/>
        <result column="version" property="version" jdbcType="INTEGER"/>
        <result column="version_date" property="versionDate" jdbcType="DATE"/>
    </resultMap>
    <sql id="Base_Column_List">
        <!--
          WARNING - @mbggenerated
        -->
        id, order_no, term_no, version, version_date, base_term_nom_prin, base_term_nom_int,
        base_term_ovd_prin, base_term_ovd_int, base_extension_amt, base_extension_var, gmt_create,
        gmt_update
    </sql>
    <select id="selectMaxVersionByOrderNo" resultMap="BaseResultMap">
        SELECT
        <include refid="Base_Column_List"/>
        FROM tb_calculation_base_data
        where order_no = #{orderNo,jdbcType=VARCHAR}
        and(term_no,`version`) in
        (select
        term_no,max(`version`)
        from tb_calculation_base_data
        where
        order_no=#{orderNo,jdbcType=VARCHAR}
        group by term_no)
    </select>

    <insert id="insertList" parameterType="java.util.List">
        INSERT INTO
        tb_calculation_base_data
        (order_no, term_no, version, version_date, base_term_nom_prin, base_term_nom_int,
        base_term_ovd_prin, base_term_ovd_int, base_extension_amt, base_extension_var, gmt_create,
        gmt_update)
        VALUES
        <foreach collection="calculationBaseDataDOList" index="index" item="item" separator=",">
            ( #{item.orderNo}, #{item.termNo}, #{item.version},
            #{item.versionDate}, #{item.baseTermNomPrin},#{item.baseTermNomInt}, #{item.baseTermOvdPrin},
            #{item.baseTermOvdInt}, #{item.baseExtensionAmt},#{item.baseExtensionVar},now(), now())
        </foreach>
    </insert>

    <select id="selectListByOrderNo" resultMap="BaseResultMap">
        SELECT
        <include refid="Base_Column_List"/>
        FROM tb_calculation_base_data
        WHERE order_no=#{orderNo,jdbcType=VARCHAR}
    </select>
</mapper>
  • resultMap
    结果集映射,将数据库字段映射到java bean,需要制定jdbcType
  • 常用标签
    select,update,delete,insert
    sql: 定义常用的sql片段
    include:引用sql片段 selectKey:为不支持自增主键的数据库提供主键生成策略,仅对update和insert有效
    cache:开启二级缓存
    还有很多标签用于动态sql
  • 参数传递
<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#
  {password},#{email},#{bio})
</insert>
  • 动态sql

1)if

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

2) choose, when, otherwise
类似于java的switch-case

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

3) where, set, trim
where标签是为了解决当where语句中第一个if没有匹配到的话会以and|or开始,sql语法错误。set是为了解决update..set最后可能多一个逗号的情况。trim更加强大可以覆盖上面两种情况。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

前面两种情况都可以用trim搞定

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

4) foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>
  • 缓存
    默认开启一级缓存,也就是基于sqlsession的缓存,mybatis每次执行sql会先用sqlsessionFactory(单例)创建一个sqlsession实例(每个线程一个),而每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件(Configuration.xml)构建出 SqlSessionFactory 的实例。
    sqlSession可以直接面向数据库执行sql
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

说了这个多就是想说以及缓存是sqlSession层面的,多个sqlSession不共用缓存。
mybatis也可以开启二级缓存,是基于mapper层面的,如果该mapper开启了二级缓存,那多个sqlSession用这个mapper时共享缓存,开启方式就是加入标签

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

单条语句比如select想开启的话,可以设置useCache=true

<select ... flushCache="false" useCache="true"/>  

核心实体

juejin.cn/post/684490…
statement
sqlsession
sqlsessionFactory mapper
plugin

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

注意,如果 localCacheScope 被设置为 SESSION,对于某个对象,MyBatis 将返回在本地缓存中唯一对象的引用。对返回的对象(例如 list)做出的任何修改将会影响本地缓存的内容,进而将会影响到在本次 session 中从缓存返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。

面试问题

1.mybatis日志怎么开启?

在配置文件configuration.xml中设置logImpl属性,开启全局log

<configuration>
  <settings>
    <setting name="logImpl" value="LOG4J"/>
  </settings>
</configuration>

logImpl 可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING
也可以对某个mapper的某个方法开启日志,假设用log4j,具体配置如下
引用jar包,然后创建一个log4j.properties文件

# 全局
log4j.rootLogger=ERROR, stdout
# 接口级别
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 方法级别
log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

2.mybatis执行流程

image.png

  1. 加载配置文件并初始化(SqlSession)

配置文件来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),一个是java代码中的注释,将sql的配置信息加载成为一个mappedstatement对象,存储在内存之中(包括传入参数的映射配置,结果映射配置,执行的sql语句)。

  1. 接收调用请求

调用mybatis提供的api,传入的参数为sql的id(有namespase和具体sql的id组成)和sql语句的参数对象,mybatis将调用请求交给请求处理层。

  1. 处理请求

根据sql的id找到对应的mappedstatament对象。

根据传入参数解析mappedstatement对象,得到最终要执行的sql。

获取数据库连接,执行sql,得到执行结果。

Mappedstatement对象中的结果映射对执行结果进行转换处理,并得到最终的处理结果。

释放连接资源。

  1. 返回处理结果
    具体可以查看 www.cnblogs.com/dongying/p/…
    juejin.cn/post/698763…

3. mybatis的缓存了解吗?

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,要加一个cache标签。

映射语句文件中的所有 select 语句的结果将会被缓存。
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

其他面试题看 github.com/whysoseriou…

4. sql注入

www.cnblogs.com/myseries/p/…