Mybatis分享

一.Mybatis的前世今生

Mybatis的前身是Apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为Mybatis。2013年11月迁移到Github,目前Mybatis由Github维护。

Mybatis是一个基于Java的持久层框架,它几乎能做到JDBC所能做到的所有事情,并(在注意一些规则的基础上)可以完成自动映射,而无需再写任何的映射规则,大大提高了开发效率和灵活性。

二.Mybatis的基本构成

  • SqlSessionFactoryBuilder:它会根据配置信息或者代码生成SqlSessionFactory
  • SqlSessionFactory: 依靠工厂来生成SqlSession
  • SqlSession:

         非线程安全;

         获取Mapper,让Mapper通过命名空间(namespace)和方法名称找到对应SQL,发送给数据库执行后返回结果。

         直接通过命名信息去执行SQL返回结果(iBatis版本留下的方式)

  • SQL Mapper:它是由一个Java接口和XML文件(或注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL语句,并返回结果

          

三.SqlSession构成与运行原理

SqlSession下有四大对象,Mapper的执行就是它们来完成数据库操作和结果返回的。

  • Executor代表执行器,由它来调度StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL.
  • StatementHandler的作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用。
  • ParameterHandler用于SQL对参数的处理。
  • ResultHandler是进行最后数据集(ResultSet)的封装返回处理的。

Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterHandler设置参数,完成预编译。然后就执行select或update,如果是select,还会用ResultHandler封装结果返回给调用者。

        

四.插件

Mybatis可以拦截SqlSession下四大对象的任意一个,根据功能来确定需要拦截什么对象。

Executor是执行SQL的全过程,包括组装参数,组装结果集返回和执行SQL过程,都可以拦截,较为广泛,一般用得不算多。

StatementHandler是执行SQL的过程,我们可以重写执行SQL的过程。这是我们最常用的拦截对象。

ParameterHandler主要是拦截执行SQL的参数组装,可以重写组装参数规则。

ResultHandler用于拦截执行结果的组装,可以重写组装结果的规则。

#稍后DEMO

五.常规操作

Generator

Mapper,POJO,XML

配置

Configuration(#DEMO TypeHandler)

六.批处理

 用foreach实现(#DEMO)

foreach主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item,index,collection,open,separator,close。 

item表示集合中每一个元素进行迭代时的别名,

index指定一个名字,用于表示在迭代过程中,每次迭代到的位置,

open表示该语句以什么开始,

separator表示在每次进行迭代之间以什么符号作为分隔符,

close表示以什么结束,

collection在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况: 

  1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
  2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
  3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key

下面分别来看看上述三种情况的示例代码:

1) 单参数List的类型:

<select id="dynamicForeachTest" resultType="Blog">  
  select * from t_blog where id in  
  <foreach collection="list" index="index" item="item" open="(" separator="," close=")">  

#{item}  
  </foreach>  
</select>

上述collection的值为list,对应的Mapper代码如下:

public List<Blog> dynamicForeachTest(List<Integer> ids);  
测试代码:  
    @Test  
    public void dynamicForeachTest() {  
        SqlSession session = Util.getSqlSessionFactory().openSession();  
        BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
        List<Integer> ids = new ArrayList<Integer>();  
        ids.add(1);  
        ids.add(3);  
        ids.add(6);  
        List<Blog> blogs = blogMapper.dynamicForeachTest(ids);  
        for (Blog blog : blogs)  
            System.out.println(blog);  
        session.close();  
    }

2)单参数array数组的类型:  

<select id="dynamicForeach2Test" resultType="Blog">  
        select * from t_blog where id in  
        <foreach collection="array" index="index" item="item" open="(" separator="," close=")">  
            #{item}  
        </foreach>  
</select>

上述collection为array,对应的Mapper代码如下:

public List<Blog> dynamicForeach2Test(int[] ids);  
对应的测试代码:  
    @Test  
    public void dynamicForeach2Test() {  
        SqlSession session = Util.getSqlSessionFactory().openSession();  
        BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
        int[] ids = new int[] {1,3,6,9};  
        List<Blog> blogs = blogMapper.dynamicForeach2Test(ids);  
        for (Blog blog : blogs)  
            System.out.println(blog);  
        session.close();  
    }

3)自己把参数封装成Map的类型 

<select id="dynamicForeach3Test" resultType="Blog">  
        select * from t_blog where title like "%"#{title}"%" and id in  
        <foreach collection="ids" index="index" item="item" open="(" separator="," close=")">  
            #{item}  
        </foreach>  
</select>

上述collection的值为ids,是传入的参数Map的key,对应的Mapper代码如下:

public List<Blog> dynamicForeach3Test(Map<String, Object> params);  
对应测试代码:  
    @Test  
    public void dynamicForeach3Test() {  
        SqlSession session = Util.getSqlSessionFactory().openSession();  
        BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
        final List<Integer> ids = new ArrayList<Integer>();  
        ids.add(1);  
        ids.add(2);  
        ids.add(3);  
        ids.add(6);  
        ids.add(7);  
        ids.add(9);  
        Map<String, Object> params = new HashMap<String, Object>();  
        params.put("ids", ids);  
        params.put("title", "中国");  
        List<Blog> blogs = blogMapper.dynamicForeach3Test(params);  
        for (Blog blog : blogs)  
            System.out.println(blog);  
        session.close();  
    }

七.缓存

全局开关:默认是true,如果它配成false,其余各个Mapper XML文件配成支持cache也没用。
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>

7.1一级缓存

如果没将上述cacheEnabled的值显式设置为false, Mybatis默认会开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

当执行SQL时两次查询中间发生了增删改操作,则SqlSession的缓存清空。

7.2 二级缓存

二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

各个Mapper XML文件,默认是不采用cache。在配置文件加一行就可以支持cache:
 <cache />

在同一个namespace下的mapper文件中,执行相同的查询SQL,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中取。当执行SQL时两次查询中间发生了增删改操作,则二级缓存清空。

7.3 开启二级缓存

Mybatis默认开启一级缓存。

二级缓存的开启如下:

1、  在配置文件中开启缓存总开关(cacheEnabled设置为true,默认为true,不设置也行):

2、在映射(mapper)文件中,加入cache配置,开启二级缓存:

注意1,由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。如果该类存在父类,那么父类也要实现序列化。

注意2,Mapper XML文件配置支持cache后,文件中所有的Mapper statement就支持了。此时若要个别对待某条statement禁用二级缓存,该statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询。默认情况下是useCache=true,即该statement使用二级缓存。

7.4 注意事项

1) 只能在【只有单表操作】的表上使用缓存

不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。

2) 在可以保证查询远远大于insert,update,delete操作的情况下使用缓存

因为insert,update,delete操作会清空所在namespace下的全部缓存

7.5 此时请避免用二级缓存

在符合上述【2.4 注意事项】的要求时,使用二级缓存并没有什么危害。但是,请记住以下三点特性:

  • 缓存是以namespace为单位的,不同namespace下的操作互不影响
  • insert,update,delete操作会清空所在namespace下的全部缓存。
  • 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace

以下两种情况请不要使用二级缓存:

Case1:针对一个表的某些操作不在其独立的namespace下进行。

例如在UserMapper.xml中有大多数针对user表的操作。但是在一个XxxMapper.xml中,还有针对user单表的操作。

这会导致user在两个命名空间下的数据不一致。如果在UserMapper.xml中做了刷新缓存的操作,在XxxMapper.xml中缓存仍然有效,如果有针对user的单表查询,使用缓存的结果可能会不正确。

更危险的情况是在XxxMapper.xml做了insert,update,delete操作时,会导致UserMapper.xml中的各种操作充满未知和风险。

Case2:多表操作一定不能使用缓存

首先不管多表操作写到哪个namespace下,都会存在某个表不在这个namespace下的情况。

例如两个表:roleuser_role,如果我想查询出某个用户的全部角色role,就会涉及到多表的操作。

<select id="selectUserRoles" resultType="UserRoleVO">
    select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>

不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。

如果user_role和role的Mapper都使用同一个namespace(通过<cache-ref>)来避免脏数据,那就失去了缓存的意义。

注意:

1、在SqlSession未关闭之前,如果对于同样条件进行重复查询,此时采用的是local session cache,而不是上面说的二级cache。

2、MyBatis缓存查询到的结果集对象,而非结果集数据,是将映射的PO对象集合缓存起来。

7.6 Advices

如果不符合上述【7.4 注意事项】的要求时,在业务层使用可控制的缓存代替。

根据项目/业务实际情况,在set入缓存的时候配置合理的缓存失效时间,提高缓存的利用率。

八.未完待续

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值