mybatis详解,mybatis一级二级缓存机制,以及Ehcache & Redis对比

101 篇文章 14 订阅
13 篇文章 0 订阅

最近项目需要研究mybatis,网上查了一下资料,这里记录一下

https://mybatis.org/mybatis-3/zh/configuration.html

 

Mybatis连接Mysql参数

先看看Mybatis连接MySQL时,可以使用的JDBC连接字符串参数

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?useAffectedRows=true&allowMultiQueries=true&characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&maxReconnects=10

spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=UTF8&useSSL=true&allowMultiQueries=true&serverTimezone=Asia/Seoul

JDBC中可以使用的参数:

  其他常见常用参数(参考自:https://blog.csdn.net/victoylin/article/details/79747156):
  UsePerformanceMonitor,userperfmon, perfmon:是否启用性能监视,默认 false
  IgnorePrepare:    是否忽略 Prepare() 调用,默认 true
  UseProcedureBodies,procedure bodies:是否检查存储过程体、参数的有效性,默认 true
  AutoEnlist:    是否自动使用活动的连接,默认 true
  TreatTinyAsBoolean:是否将 TINYINT(1) 列视为布尔型,默认 true
  AllowUserVariables:是否允许 SQL 中出现用户变量,默认 false
  FunctionsReturnString:所有服务器函数是否按返回字符串处理,默认 false
  UseAffectedRows:是否用受影响的行数替代查找到的行数来返回数据,默认 false
  Keepalive:    保持 TCP 连接的秒数,默认0,不保持。
  ConnectionLifeTime:连接被销毁前在连接池中保持的最少时间(秒)。默认 0
  Pooling:    是否使用线程池,默认 true
  MinimumPoolSize, min pool size:线程池中允许的最少线程数,默认 0
  MaximumPoolSize,max pool size:线程池中允许的最多线程数,默认 100
  ConnectionReset:连接过期后是否自动复位,默认 false
  CharacterSet, charset:向服务器请求连接所使用的字符集,默认:无

 

一、一二级缓存

mybaits提供一级缓存,和二级缓存。

 

二、二级缓存分布式实现

      mybatis中默认自带的二级缓存实现(PerpetualCache)是无法做到分布式的。 
      Java提供了一个开源的分布式缓存EhCache。它是一个纯Java的开源分布式缓存(进程内缓存),具有快速、精干等特点,也是hibernate中默认的CacheProvider。

1、分布式缓存 
为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式) 
 
mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

 

三、一级缓存陷阱

默认情况下,mybatis的local cache一级缓存是开启的。

     Mybatis一级缓存,也称本地缓存,默认是SqlSession级别的缓存。在一次程序与数据库的会话(Sqlsession)中,mybatis会维护一个以hashmap为存储结构的一级缓存,在这个会话中,只要在两次相同条件的查询中间,这个会话里没有出现增删改的操作,那么Mybatis会在第二次查询时候在缓存中将这个结果返回,导致两次查询的返回结果对象其实是一个,用“==”比较结果为true。

    Mybatis的一级缓存是默认开启的,且截至目前我们无法关闭。当一级缓存默认是SqlSession级别时,其生命周期和其所在的Sqlsession相同。而且,Mybatis的一级缓存使用了hashMap,且没有做容量上的限定。

    在分布式环境下,Mybatis的一级缓存无法集中统一管理,也就是说Mybatis无法搭建分布式缓存。在多个SqlSession环境下,虽然可以使用二级缓存,即全局缓存,以在多个SqlSession共享缓存,但是毕竟是本地存储,还是无法适应分布式的需求。所以为了避免在分布式环境下的脏数据读取。

    解决方法有两个:

  1.     一是可以设置Mybatis一级缓存为STATEMENT级别,因为在查询方法执行的最后,会判断一级缓存级别是否是STATEMENT级别,如果是的话,就清空缓存。如此一来,便相当于不使用一级缓存。
  2.     在select标签里面设置flushCache="true"即可,但是后面这种方式在一些版本中并不支持。在自己创建SqlSession的时候,每次查询完毕后调用SqlSession类的clearCache()方法也可以清空缓存。
     

那么local cache干了什么?在默认配置情况下,mybatis会将同一session内的查询结果都放在local cache中,这样可以提高性能,避免每次都hit到数据库。

如果是在分布式情况下,不同session修改读取数据就可能出现脏数据的情况。

有以下几种解决办法:

  1. 在mybatis配置文件中localCacheScope=STATEMENT
    mybatis.configuration.local-cache-scope=statement
    或者:
    <setting name="localCacheScope" value="STATEMENT"/>

  2. 在mapper配置文件中,给select设置flushCache=true。需要注意的是,这样会将local cache和cache都清空掉。
    <select>指定flushCache=”true”

  3. 不用事务

参考:

https://juejin.im/post/5cacaf2df265da03a97acd25

https://segmentfault.com/a/1190000008207977

 

四、Ehcache & Redis对比:

(1)Ehcache(分布式缓存)直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便

(2)Redis(集中式缓存)是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。

      因此考虑使用Redis支持分布式缓存,Mybatis官方提供mybatis-redis插件(http://www.mybatis.org/redis-cache/),官方插件需要单独配置/redis.properties并且维护一个JedisConfigPool,考虑到单独配置与项目已有Redis配置重复且无法复用本地配置,因此决定参照官方org.mybatis.caches.redis.RedisCache来重写自己的MybatisRedisCache 。

 

      如果是单个应用或者对缓存访问要求很高的应用,用ehcache。

      如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。


补充下:ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适

redis和memcached相比的独特之处:
      1、redis可以用来做存储(storage),而memcached是用来做缓存(cache)
这个特点主要因为其有持久化功能

      2、redis中存储的数据有多种结构,而memcached存储的数据只有一种类型“字符串”

 

五、Ehcache常见的问题

Windows上的Tomcat

有一个Tomcat或者是JDK的bug,在tomcat启动时如果tomcat的安装路径中有空格的话,在启动时RMI监听器会失败。参见http://archives.java.sun.com/cgi-bin/wa?A2=ind0205&L=rmi-users&P=797和http://www.ontotext.com/kim/doc/sys-doc/faq-howto-bugs/known-bugs.html

由于在Windows上安装Tomcat默认是装在“Program Files”文件夹里的,所以这个问题经常发生。

广播阻断

自动的peer discovery与广播息息相关。广播可能被路由阻拦,像Xen和VMWare这种虚拟化的技术也可以阻拦广播。如果这些都打开了,你可能还在要将你的网卡的相关配置打开。一个简单的办法可以告诉广播是否有效,

那就是使用ehcache remote debugger来看“心跳”是否可用。

广播传播的不够远或是传得太远

你可以通过设置badly misnamed time to live来控制广播传播的距离。用广播IP协议时,timeToLive的值指的是数据包可以传递的域或是范围。约定如下:

0是限制在同一个服务器

1是限制在同一个子网

32是限制在同一个网站

64是限制在同一个region

128是限制在同一个大洲

255是不限制

译者按:上面这些资料翻译的不够准确,请读者自行寻找原文理解吧。

在Java实现中默认值是1,也就是在同一个子网中传播。改变timeToLive属性可以限制或是扩展传播的范围。

 

六、二级缓存的问题

mybatis 二级缓存不推荐使用

    6.1 mybatis的缓存使用。

大体就是首先根据你的sqlid,参数的信息自己算出一个key值,然后你查询的时候,会先把这个key值去缓存中找看有没有value,如果有,直接返回出来,就不查询db了。如果没有,那么查询db,然后将key,value保存到缓存中,以便下次使用。

     1.1mybatis的一级缓存是基于sqlsession为生命周期的

当你这个session没有了,缓存就没有了,其次当你sql执行!isselect语句的时候,缓存也会被直接全部清理掉以保证数据一致性。

  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
     //清理缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

     1.2 mybatis的二级缓存是基于application为生命周期的

    范围是按照每个namepace一个缓存来存贮和维护,同一个namespace放到一个缓存对象中,当这个namaspace中执行了!isselect语句的时候,整个namespace中的缓存全部清除掉。

  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
     //清理缓存,并且!isselect语句的flushcache都是默认为true的。
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

     6.2 Cache使用时的注意事项


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

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

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

      这一点不需要多说,所有人都应该清楚。记住,这一点需要保证在1的前提下才可以!

 

   6.3 避免使用二级缓存


可能会有很多人不理解这里,二级缓存带来的好处远远比不上他所隐藏的危害。

缓存是以namespace为单位的,不同namespace下的操作互不影响。

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

通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
 

为什么避免使用二级缓存?

在符合【Cache使用时的注意事项】的要求时,并没有什么危害。
 

其他情况就会有很多危害了。

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


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

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

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

有关这样单表的操作可能不常见。但是你也许想到了一种常见的情况。
 

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

为什么不能?

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

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

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

像上面这个查询,你会写到那个xml中呢??

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

如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。


这点应该很容易理解。

在我看来,就以MyBatis目前的缓存方式来看是无解的。多表操作根本不能缓存。

如果你让他们都使用同一个namespace(通过<cache-ref>)来避免脏数据,那就失去了缓存的意义


看到这里,实际上就是说,二级缓存不能用。整篇文章介绍这么多也没什么用了。
 

    6.4 挽救二级缓存?


想更高效率的使用二级缓存是解决不了了。

但是解决多表操作避免脏数据还是有法解决的。解决思路就是通过拦截器判断执行的sql涉及到那些表(可以用jsqlparser解析),然后把相关表的缓存自动清空。但是这种方式对缓存的使用效率是很低的。

设计这样一个插件是相当复杂的,既然我没想着去实现,就不废话了。
 

最后还是建议,放弃二级缓存,在业务层使用可控制的缓存代替更好。

 

7. mybatis更新数据时只更新部分字段的问题

问题

在mybatis更新数据时,传入的是一个对象,但是有时候只是更新这个对象中的某些字段,并不是全部字段,如果使用如下方式更新,则有些字段则会被更新为null。

<update id="updateEmp" parameterType="com.mytest.mybatis.entity.Employee">
    update tbl_employee set last_name = #{lastName}, email = #{email}, gender = #{gender}, age = #{age} where id = #{id}
</update>

解决

使用mybatis提供的动态sql可以解决此问题,在拼接sql语句时加入判断。

<update id="updateEmp" parameterType="com.mytest.mybatis.entity.Employee">
    update tbl_employee
     <trim prefix="set" suffixOverrides="," suffix=" where id = #{id}">
         <if test="lastName != null and lastName.length()>0"> last_name=#{lastName} , </if>
         <if test="email != null and email.length()>0"> email=#{email} , </if>
         <if test="gender != null"> gender=#{gender} , </if>
         <if test="age != null and age > 0"> age=#{age} , </if>
     </trim>
</update>

经过测试,可以实现任意字段的更新操作。

trim标签

trim标签可以实现子句首尾的删除与添加。

# trim有四个属性
prefix # 子句的开头,比如以 set 开头
prefixOverrides # 跳过第一个 如 prefixoverride="AND |OR",就是去掉第一个 and 或 or
suffix # 子句的结尾,如上面的 where id = #{id} 
suffixOverrides # 跳过最后一个,如上面的 suffixOverrides=",",就是去掉最后一个','
 

 

8 mybatis——select、insert、update、delete

https://blog.csdn.net/bear_wr/article/details/52386257

 

参考:

https://tech.meituan.com/2018/01/19/mybatis-cache.html
https://blog.csdn.net/u012373815/article/details/47069223
https://blog.csdn.net/luanlouis/article/details/41280959
https://blog.csdn.net/soonfly/article/details/64482584
https://blog.csdn.net/nimoyaoww/article/details/79120698
https://blog.csdn.net/ss300400a/article/details/74566347
 

 

请我喝咖啡

如果觉得文章写得不错,能对你有帮助,可以扫描我的微信二维码请我喝咖啡哦~~哈哈~~

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值