mybatis学习笔记(关于映射文件和mybatis缓存)

2018-10-02

<mapper namespace="com.dao.AdministratorDao">
<insert id="insertInfo" useGeneratedKeys="true" keyProperty="id">
insert into administrator(id,name,age,job) VALUES
(#{id},#{administrator.name},${administrator.age},#{administrator.job})
</insert>
</mapper>

只有insert和update标签中有useGenaratedKeys和keyProperty两个属性,这两个属性是为了获取
数据库中自增字段用的,将返回的值复制给keyProperty中设置的变量,然后可以在下面的sql语句这样
使用,你发现,我的administrator只有插入了三个属性,id属性是没有插入的,这个属性是为了接收
用的,也就是说在执行完这条语句后,我调用administrator.getId()会获取到数据库自增字段id的值。

参考链接:https://blog.csdn.net/hellostory/article/details/6790248

对于不支持自动生成主键的JDBC驱动或者数据库,需要在insert标签中加上标签<selectKey />
例如:
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
其中属性值order需要注意,这个属性值只有两个值,一个是BEFORE,另一个是AFTER
如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。
如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素

<sql>标签可以被用来定义可重用的SQL代码段,可以包含在其他语句中

<include></include>标签里有个标签<property>
使用这个标签的时候要注意
我们知道在include标签中有个属性refid用来指向一个sql标签的
那么这个property标签则是对sql标签中的值进行复制用的,我们来看下例子:
<select id="selectAll" resultType="Administrator">
select * from administrator
where job = <include refid="job"><property name="tt" value="程序员" /></include>

</select>

<sql id="job">
    ${tt}
</sql>

这条语句返回结果是错的,它显示找不到程序员这行,
我们在引用语句左右两边都加上引号,现在我们来看下输出的sql语句:
select * from administrator where job = " 程序员 "
发现了吧,我们传入值的左右两边都有空格,也就是说,在使用字段是字符串的情况下,不能这样写,否则会出现上面的情况。

#{变量名,属性:xxxx,属性:xxx}

关于使用和不使用@Param()注释的区别
1、不使用@Param注释,参数只能有一个,并且参数是对象,这样使用#{}和使用{}都可以取值 2、不使用@Param注释,参数只有一个,如果参数是基本类型和String类型,则使用#{}正常取值,{}取不到值
3、使用@Param注释,#{}和${}都可以取到值,并且单个参数和多个参数的情况都适用


2018-10-09

以作用域+返回值+绝对路径的方法名
e.g:
public abstract java.until.List com.dao.PersonDao.select_PersonAndJob() 这个作为key
将MapperMethod作为value

MapperMethod中有两个比较重要的内部类
1、sqlCommand //封装了SQL标签的类型:insert update delete select
2、MethodSignature //封装了方法的参数信息 返回类型信息等

mybatis缓存

1、一级缓存是通过SqlSession来实现的

image.png

图片来自链接:https://blog.csdn.net/qq_25689397/article/details/52066179
这是只有mybatis框架的情况下,缓存的大体过程

如果只有mybatis的时候,当进入到MapperMethod类中调用selectList方法的时候,进入的是DefaultSqlSession类
然后在这个类中调用执行器executor的query方法,在执行器中有个成员变量用于存储缓存,就是PerpetualCache类
这个类中的成员变量cache存储缓存,这个成员变量类型是hashmap类型,也就是mybatis的缓存数据是存储在hashmap中的

注意:获取mapperMethod
将路径作为key,通过方法cachedMapperMethod()方法来操作,如果key存在则直接取出MapperMethod对象
如果key不存在则新建一个mapperMethod对象,并将对象存入ConcurrentHashMap集合中,然后返回该对象。
接着是调用mapperMethod的execute()方法,根据mapperMethod的command的属性类型,进行相对应的操作,
基本就是sqlSession的四个操作:insert update delete select
下面以select为例
调试进入这个方法发现是进入SqlSessionTemplate类,因为我是整合好的SSM框架,所以在这个时候是spring在
对这些sqlSession进行代理操作,在调用这个方法的时候,会进入到这个类的内部类SqlSessionInterceptor
在这个类invoke方法中,我们发现,他在finally{}代码块中对sqlSession进行了关闭操作,这也是为什么我们
每一次调用sql语句,不是从缓存中取出来,而是直接跟数据库打交道,也就是每个session运行完一个方法后,就
会被销毁,即创建快销毁的也快

2、二级缓存是通过mapper的namespace实现共享

也就是不同的session对于namespace的操作是共享的,每个namespace都有一块缓存,在执行查询的时候默认不刷新缓存,
而在执行增删改的时候是默认要刷新缓存的,mybatis提供给我们两个属性进行设置,一个是useCache一个是flushCache
并且默认值如下:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
增删改是没有useCache属性的,被定死了
mybatis本身的二级缓存很容易出现问题,也就是到多表操作的时候,容易出现脏数据的问题,而且mybatis本身的二级缓存
无法实现分布式具体的问题,可以查看:https://www.cnblogs.com/liouwei4083/p/6025929.html
基于此,mybatis给我们提供了可以自定义的二级缓存,也就是添加了Cache接口,实现这个接口,可以自己来实现缓存,
通常可以借助实现这个接口来使用redis做mybatis的二级缓存,然后再在配置文件里的<cache />标签里添加这个类的路径,
这样便可以实现分布式。

SSM框架整合后,我们看下调试过程
我们看下代码的实现,首先进入MapperProxy类,也就是spring代理类

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }   

这个方法中,最重要的是最后两行的代码,cachedMapperMethod()方法是判断当前方法路径下是否有缓存,有缓存则将缓存返回,没有缓存,则重新创建一个MapperMethod对象,并且将对象存入ConcurrentHashMap中,这也是跟单独使用mybatis的区别之一,存储容器不同。然后通过MapperMethod的方法execute()执行sql语句。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据sql的类型,选择不同的执行方式
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
      //当前方法调试的入口
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }   

我们可以看到不管是增删改查哪一个都会调用sqlSession对应的方法,程序进一步进行,会到类SqlSessionTemplate类,调用selectList()方法,在调用这个方法的时候,会先进入到这个类的内部类SqlSessionInterceptor中,也就是Session的拦截器,调用了拦截器的Invoke方法,我们看下这个方法的实现:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          //注意每次SqlSession操作数据库后,都会自动断开,这也是为什么mybatis一级缓存会失效的原因
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }   

看到这块基本就能了解,为什么mybatis的一级缓存在SSM框架中会失效了,也就是spring管理使用sqlSession后,每次使用都会关闭,也就是sqlSession的创建和关闭变得频繁了

参考链接:
https://www.cnblogs.com/volatileAndCrazy/p/7826331.html
https://blog.csdn.net/z69183787/article/details/78402892
https://www.cnblogs.com/winclpt/articles/7511672.html
https://blog.csdn.net/qq_25689397/article/details/52066179
https://www.cnblogs.com/linkstar/p/7182695.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容