MyBatis

Posted on By Guanzhou Song

Mybatis概述

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁 移到了google code,并且改名为MyBatis。是一个基于Java的持久层框架

无论是Mybatis、Hibernate都是ORM的一种实现框架,都是对JDBC的一种封装!

Mybatis工作流程

  • 通过Reader对象读取Mybatis映射文件

  • 通过SqlSessionFactoryBuilder对象创建SqlSessionFactory对象

  • 获取当前线程的SQLSession

  • 事务默认开启

  • 通过SQLSession读取映射文件中的操作编号,从而读取SQL语句

  • 提交事务

  • 关闭资源

动态查询

使用if/else statement判断是否添加对应字段

<!--多条件查询【动态SQL】--> 
<!--会自动组合成一个正常的WHERE字句--> 
<!--name值会从map中寻找-->
<select id="findByCondition" resultMap="studentMap" parameterType="map"> 
	select * from students
		<where>
		<if test="name!=null">
			and name= #{name}
		</if>
		<if test="sal!=null">
		    and sal < #{sal}
		</if>
	</where>
</select>

<!--动态更新-->
<!--不要忘了逗号-->
<update id="updateByConditions" parameterType="map">
    update students
    <set>
        <if test="name!=null">
            name = #{name},
        </if>
        <if test="sal!=null">
            sal = #{sal},
        </if> </set>
    where id = #{id}
</update>

动态插入

 
<!--SQL片段默认是不帮我们自动生成合适的SQL,因此需要我们自己手动除去逗号--> 
<sql id="key">
    <trim suffixOverrides=","> 
        <if test="id!=null">
            id,
        </if>
        <if test="id!=null"> 
            name,
        </if>
        <if test="id!=null"> 
            sal,
        </if>
    </trim>
</sql>

<sql id="value">
    <trim suffixOverrides=",">
        <if test="id!=null"> 
            #{id},
        </if>
        <if test="id!=null"> 
            #{name},
        </if>
        <if test="id!=null"> 
            #{sal},
        </if>
    </trim>
</sql>

<!--动态插入-->
<insert id="insertByConditions" parameterType="zhongfucheng.Student">
    insert into students (<include refid="key"/>) values (<include refid="value"/>)
</insert>

MyBatis缓存

  1. mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据

  2. 二级缓存是跨sqlSession,是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是 可以共享的。

一级缓存

Mybatis的一级缓存原理:

第一次发出一个查询sql,sql查询结果写入sqlsession的一级缓存中,缓存使用的数据结构是一个 map<key,value>

  • key:hashcode+sql+sql输入参数+输出参数(sql的唯一标识)

  • value:用户信息

同一个sqlsession再次发出相同的sql,就从缓存中取不走数据库。如果两次中间出现commit操作(修 改、添加、删除),本sqlsession中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据 库查询,从数据库查询到再写入缓存。

二级缓存

二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构,结构是map<key,value>。

每次查询先看是否开启了二级缓存,如果开启则首先在二级缓存中查找,如果missed,再进入一级缓存查找,再次miss,开始数据库查询。

Mybatis解决JDBC编程的问题

  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
    • 解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
  2. Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
    • 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
  3. 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
    • 解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
  4. 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
    • 解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

面试题

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

在Mybatis中,有两种占位符

#{} 解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中

#{} 是预编译处理,${}是字符串替换。 使用#{}可以有效的防止SQL注入,提高系统安全性。

${} 可以用于传入一些固定的字符串,例如表名或字段名:

from ${TABLE_NAME} ORDER BY ${COL_NAME} 

#{}则会进行预编译,防止通过sql注入的方式进行攻击

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

  1. 第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. 第2种: 通过 来映射字段名和实体类属性名的一一对应的关系 ```sql

<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” /> </reslutMap>

常用第二种


#### 如何获取自动生成的(主)键值?
如果我们一般插入数据的话,如果我们想要知道刚刚插入的数据的主键是多少,我们可以通过以下的方式来获取
> 需求: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>

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

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

  • 在映射文件中使用#{0},#{1}代表传递进来的第几个参数
  • 使用@param注解:来命名参数

  • #{0},#{1} 方式 ```sql //对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。

<select id=”selectUser”resultMap=”BaseResultMap”> select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1} </select>


* @param注解方式

```java
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

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的功能。

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

如果配置了namespace那么当然是可以重复的,因为我们的Statement实际上就是namespace+id 如果没有配置namespace的话,那么相同的id就会导致覆盖了。

通常一个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。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。

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

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

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

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

  • 一种是通过注解绑定,就是在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定

  • 另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名.