写点什么

1 月月更|推荐学 java——MyBatis 高级

作者:逆锋起笔
  • 2022 年 1 月 14 日
  • 本文字数:8322 字

    阅读完需:约 27 分钟

1月月更|推荐学java——MyBatis高级

补两张知识导图

最近的两篇文章《Maven初识》《第一个MyBatis程序》 文中缺少了知识结构图,这里补充一下。




本节内容是关于 MyBatis 的高级部分,上节的内容重点是带大家从零开始搭建一个使用 MyBatis 框架的 java 项目,并且能使用 MyBatis 框架完成对数据库中表的增删改查操作;这听起来不难理解,但对于新手要实战一遍,还是需要多加练习,推荐大家通过新建 Module 的方式来操作。


本节内容会在上一节的基础上进行,包括项目工程和数据库,内容包括但不限于:


  • MyBatis 核心配置文件中其他配置

  • SQL 语句如何动态化

  • MyBatis 注解开发模式

  • MyBatis 缓存机制

  • 分页功能


tips:本文 demo 的源码和数据表,在公众号 推荐学java 回复 myBatisDemo 即可获得。


即将进入正题,请保持安静

配置文件标签

日志管理

日志文件 是用于记录系统操作事件的记录文件或文件集合。


在实际开发中日志是非常重要的,能起到两个很大的作用,其一:帮助开发人员排错其二:能通过记录管理谁操作了什么内容,这两点尤其和库关联的时候,就能起到非常关键的作用,删库跑路这样的说法相信各位都听说过吧!


日志框架掌握两个常用的即可:


  • STDOUT_LOGGING

  • LOG4J


使用方式比较简单,在主配置文件 mybatis-config.xml 中添加如下代码:


<settings>    <!--  配置日志  -->    <setting name="logImpl" value="STDOUT_LOGGING"/></settings>
复制代码


对于 STDOUT_LOGGING 添加如上配置即可使用;对于 LOG4J 该库本身支持各种各样的定制,可单独增加属性配置文件,按照自己的需求来修改配置,做到定制化,关于如何配合以及有哪些属性,网上有很多教程,这里不做解释了(最近该库闹出了漏洞,大家都在填坑)。


需要注意的是:MyBatis核心配置文件中的 configuration标签下配置的标签是有顺序的,依次为:


  • properties(属性配置,本文后面会做配置,需要掌握)

  • settings(设置,需要掌握)

  • typeAliases(类型别名,需要掌握,能认识会用)

  • typeHandlers(类型处理器,了解即可)

  • objectFactory(对象工厂,了解即可)

  • plugins(插件,需要掌握,依赖一些插件的时候要求配置)

  • environments(环境配置,熟练掌握)

  • environment(环境变量)

  • transactionManager(事务管理器)

  • dataSource(数据源)

  • databaseIdProvider(数据库厂商标识)

  • mappers(映射器,熟练掌握)

properties 标签

上节我们在settings标签中配置了驼峰命名映射:


<!--  开启驼峰命名映射  --><setting name="mapUnderscoreToCamelCase" value="true"/>
复制代码


这是截至目前接触到的settings标签里的两个配置,下面看properties标签,这是我们第一次接触。举个简单的例子你就知道它的作用了,还记得下面这段代码吗?


<!--    开发环境    --><environment id="development">    <transactionManager type="JDBC"/>    <dataSource type="POOLED">        <!-- mysql驱动-->        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>        <!-- 指定数据库开放的端口、要连接的数据库名、编码方式-->        <property name="url"                  value="jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8"/>        <!-- mysql用户名-->        <property name="username" value="root"/>        <!-- mysql登录密码-->        <property name="password" value="root"/>    </dataSource></environment>
复制代码


这里的 value 对应的值通过这种直接写死的方式很不友好,也是不推荐的,实际开发中是有专门的属性文件来管理这些值的,这就要用到properties标签了。


resources目录下新建文件db.properties,编辑如下内容:


driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8username=rootpassword=root
复制代码


就是这么简单,有其他编程语言经验的道友应该不陌生,这就是简单的 k-v 形式存储值的方式,接下来就是我们的核心配置文件中的configuration标签下配置如下代码:


<properties resource="db.properties"/>
复制代码


还记得我们前面提到的顺序问题嘛!不要放错了位置(放错也没关系,IDEA 太智能了,它会给你提示的)。


细心的你可能注意到我们有一处微小的变化:&amp; -> & 那为什么 & 这个符号不能直接在 xml 中写呢?


我们都知道 xml 里面都是标签,如果我们要写一个小于号<,那么 xml 在解析的时候会认为你这是某个标签的开始,且这个标签没有写完整,所以就有了一些对应的转义符号,常用的几个:


  • <&lt;

  • >&gt;

  • &&amp;

  • '&apos;

  • "&quot;

typeAliases 标签

该标签的作用是为了给繁长且多的 javaBean 起一个短名字,方便使用减少出错概率,上节内容我们有这样一个查询:


<!--  查询所有记录  --><select id="selectTVSeriesAll" resultType="com.javafirst.bean.TVSeriesBean">    select * from tv_series</select>
复制代码


这里的 com.javafirst.bean.TVSeriesBean 就是我们要用别名来代替的部分,想一下,如果项目稍微复杂一些,且经历了好几个开发者,那么使用别名就很 Nice.下面是别名标签的配置:


<!--  设置别名 --><typeAliases>    <!--    方式1 可以方便 mapper 中的 resultType 使用,直接使用别名即可    -->    <typeAlias type="com.javafirst.bean.TVSeriesBean" alias="bean_series"/>
<!-- 方式2 指定包名的全路径 别名默认是首字母小写类名(如果该类被添加了注解,那么别名会优先取注解名) 优点是可以一次指定多个,缺点是不能自定义 --><!-- <package name="com.javafirst.domain"/>--></typeAliases>
复制代码


这样设置后,我们在对应的mapper.xml里就可以这样使用:


<!--  查询所有记录  --><select id="selectTVSeriesAll" resultType="bean_series">    select * from tv_series</select>
复制代码


细心同学注意到了吧,有两种方式,但推荐大家用这里举例的方式,至于注解方式,本文后面会讲。

动态 SQL

动态 SQL 是指根据参数动态组织 SQL 的技术。简单来说,可以通过在 java 项目中动态的改变条件等来执行 SQL 得到需要的结果。其实并不难,也不高深,下面来一一了解:


  • 动态 SQL-if

  • 动态 SQL-where(对 if 的优化)

  • 动态 SQL-choose(when,otherwise)

  • 动态 SQL-foreach

  • 动态 SQL-片段

动态 SQL-片段

使用注意事项:


  • 尽量基于单表实现

  • 不要嵌套 where 标签


我们先来定义一个 SQL 片段:


<!--  SQL片段  --><sql id="select_front">    select * from tv_series</sql>
复制代码


语法很简单,使用sql标签,里面是基本的 SQL 语句,可以使增删改查中的其一,可以定义多个,只需 id 唯一即可,使用方式也很简单,看下面的示例:


<!--  查询所有记录  --><select id="selectTVSeriesAll" resultType="bean_series">    <include refid="select_front"/></select>
复制代码


当然,你也可以嵌套在语句中,比如这样:


<!--  查询所有记录  --><select id="selectTVSeriesAll" resultType="bean_series">    <include refid="select_front"/> where tv_id = #{tvId}</select>
复制代码


使用起来还是比较灵活的,尤其当我们的 SQL 比较长的时候,把一些独立的内容提取出来对我们理解程序有很大的意义。

动态 SQL-if

相信你应该已经猜到了,没错,就是要学习if标签,还要把它应用在 sql 语句书写中,作为条件判断使用,这就很有意思了。在开始之前先来插入几条数据,作为接下来学习使用,完整 SQL 如下,可以直接执行哈:


INSERT INTO tv_series (tv_title,tv_sub_title,tv_type)VALUES ("《天龙八部》","这部小说以宋哲宗时代为背景,通过宋、辽、大理、西夏、吐蕃等王国之间的武林恩怨和民族矛盾,从哲学的高度对人生和社会进行审视和描写,展示了一幅波澜壮阔的生活画卷。",2),("《侠客行》","孤悬海外的侠客岛,每十年即派出赏善罚恶二使来到中原,强行邀请武林各大门派掌门人赴岛喝腊八粥。凡不接受邀请的门派皆被二使斩尽杀绝,而去了侠客岛的掌门人又个个渺无音信。",1),("《笑傲江湖》","该剧讲述华山派大弟子令狐冲,生性豁达不羁。偶遇剑宗高人风清扬授以《独孤九剑》,又意外尽得五岳各派剑法精髓,导致师父岳不群猜疑,藉口逐出师门。",2),("《人民的名义》","夜幕下的汉东省京州市,看似平静的官场霎时间阴云密布。国家部委项目处处长赵德汉涉嫌受贿,遭到最高人民检察院反贪总局侦查处处长侯亮平(陆毅 饰)的突击调查。",6);
复制代码


现在我们来看个很简单的例子:


通过标题或者类型来查询数据,返回一个 List 集合.


实现这个功能,并不难,关键是我们要通过动态 SQL 技术来做,但我们熟悉的三步骤不会变。第一步,在TVSeriesDao中定义接口List<TVSeriesBean> selectByDynamicSQL_if(Map<String, Object> params) ;


第二步,在TVSeriesMapper.xml中完成 SQL 的编写:


<!--  动态SQL-if  --><select id="selectByDynamicSQL_if" parameterType="map" resultType="bean_series">    <include refid="select_front"/>    where    <if test="title!=null and ''!=title">        tv_title=#{title}    </if>
<if test="type > 0 and type < 7"> or tv_type = #{type} </if></select>
复制代码


第三步,就是测试方法,代码如下:


/*** 动态SQL-if 测试*/@Testpublic void testDynamicSQL_if() {  SqlSession sqlSession = MyBatisUtil.openSqlSession();  TVSeriesDao tvSeriesDao = sqlSession.getMapper(TVSeriesDao.class);
// 通过标题 或者 类型 来查找数据 Map<String, Object> params = new HashMap<>(); params.put("title", "《人民的名义》"); //params.put("type", 2);
List<TVSeriesBean> beanList = tvSeriesDao.selectByDynamicSQL_if(params);
for (TVSeriesBean seriesBean : beanList) { System.out.println(seriesBean); }
sqlSession.close();}
复制代码


完成以上三步,最后就是看结果了,这里给各位几组测试参数:


// 第一组params.put("title", "《人民的名义》");//params.put("type", 2);
// 第二组params.put("title", "《人民的名义》");params.put("type", 2);
// 第三组//params.put("title", "《人民的名义》");params.put("type", 2);
// 第四组params.put("title", "");params.put("type", 12);
复制代码


大家测试后一定会发现第三组和第四组参数是会出错的,这就是只使用IF标签带来的问题,此时这两组参数对应的 SQL 变为下面这样了:


select * from tv_series where or tv_type=2
select * from tv_series where
复制代码


很明显,这两条 SQL 语法都错误的,无法执行,导致我们的程序出现异常。那么解决方案是什么呢?这就是接下来要学的动态 SQL-where 标签和 if 结合使用。

动态 SQL-where(if)

在前面的基础上,我们只需要修改一下 mapper.xml中的代码即可测试结果:


<!--  动态SQL-where-if  --><select id="selectByDynamicSQL_where_if" parameterType="map" resultType="bean_series">    <include refid="select_front"/>    <where>        <if test="title!=null and ''!=title">            tv_title=#{title}        </if>
<if test="type > 0 and type < 7"> or tv_type = #{type} </if> </where></select>
复制代码


验证结果各位自行测试,这里不做演示,需要说明的是MyBatis会自动取消紧跟在where标签后的orand,正是因为这样,我们的 SQL 会自动拼装为可执行的语句。

动态 SQL-choose(when,otherwise)

过程都一样,这里直接上代码了,定义接口TVSeriesBean selectByDynamicSQL_choose(Map<String, Object> params);接着是mapper.xml中的代码如下:


<!--  动态SQL-choose-when-otherwise  --><select id="selectByDynamicSQL_choose" parameterType="map" resultType="bean_series">    <include refid="select_front"/>    <where>        <choose>            <when test="type == 6">                tv_type = #{type}            </when>
<when test="title == null or title == ''"> tv_title = '《人民的名义》' </when>
<otherwise> tv_id = #{id} </otherwise> </choose> </where></select>
复制代码


这里说明一下代码功能,choose-when-otherwise 结构和我们在 java 基础部分学习过的switch-case结构作用是等同的,只是书写语法不同而已,理解起来一点难度没有。


简言之,只要其中一个when标签的判断条件满足情况,那么就不会走后面的逻辑;如果when条件都不满足,那么会走otherwise后的逻辑,就这么简单。


最后是我们的测试代码,如下:


/*** 动态SQL-choose-when-otherwise*/@Testpublic void testSelectByDynamicSQL_choose() {  SqlSession sqlSession = MyBatisUtil.openSqlSession();  TVSeriesDao tvSeriesDao = sqlSession.getMapper(TVSeriesDao.class);
Map<String, Object> params = new HashMap<>(); params.put("title", "xxx"); params.put("type", 12); // 条件都不满足 则取默认值 params.put("id", 2);
TVSeriesBean bean = tvSeriesDao.selectByDynamicSQL_choose(params);
System.out.println(bean);
sqlSession.close();}
复制代码


结果大家自行尝试,通过变换参数来实践结果,理解更透彻。

动态 SQL-foreach

常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。这里会分别讲解集合的泛型是IntegerObject类型,他们只是稍微有点区别,先来看包装类型的集合遍历。


定义接口List<TVSeriesBean> selectByDynamicSQL_foreach(List<Integer> ids);然后是mapper.xml中的代码:


<!-- 动态SQL-foreach    collection:表示遍历的是数组(array)还是集合(list);    open:循环开始时的字符;    close:循环结束时的字符;    item:集合成员,自定义变量;    separator:元素之间的分隔符;--><select id="selectByDynamicSQL_foreach" resultType="bean_series">    <include refid="select_front"/>    <if test="list != null and list.size > 0">        where tv_id in        <foreach collection="list" open="(" close=")" item="cus_id" separator=",">            #{cus_id}        </foreach>    </if></select>
复制代码


这里对每个属性的意义都做了解释,其实是完全对应我们之前学习过集合的结构来的,所以理解起来并不难,因为 xml 中始终都是按照节点来做解析的。


测试代码如下:


/*** 动态SQL-foreach 遍历泛型类型是包装类的集合*/@Testpublic void testSelectByDynamicSQL_foreach() {  SqlSession sqlSession = MyBatisUtil.openSqlSession();  TVSeriesDao tvSeriesDao = sqlSession.getMapper(TVSeriesDao.class);
List<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(2); ids.add(3); //ids.add(5);
List<TVSeriesBean> beanList = tvSeriesDao.selectByDynamicSQL_foreach(ids);
for (TVSeriesBean seriesBean : beanList) { System.out.println(seriesBean); }
sqlSession.close();}
复制代码


大家自行测试,结果满足我们的需求即可:从表中查找出我们指定的 id 集合中的记录。


我们都知道集合的泛型可以使基本数据类型对应的包装类,也可以是自定义对象类型,那么现在我们就来学习变量对象类型的集合。


首先定义接口List<TVSeriesBean> selectByDynamicSQL_foreachObj(List<TVSeriesBean> seriesBeans);接着是我们的mapper.xml文件中代码:


<!-- foreach 参数泛型类型是 对象类型--><select id="selectByDynamicSQL_foreachObj" resultType="bean_series">    <include refid="select_front"/>
<if test="list != null and list.size > 0"> where tv_id in <foreach collection="list" open="(" close=")" separator="," item="series"> #{series.tvId} </foreach> </if></select>
复制代码


唯一的变化就是这里的item,如果是对象类型的集合,那么这里定义的就是对象,对应的值就是对象名.属性值;如果是基本类型的包装类集合,那么这里定义的就是数据类型本身,对应的值就是其元素本身。


最后是测试代码:


/*** 动态SQL-foreach 遍历泛型类型是对象类的集合*/@Testpublic void testSelectByDynamicSQL_foreachObj() {  SqlSession sqlSession = MyBatisUtil.openSqlSession();  TVSeriesDao tvSeriesDao = sqlSession.getMapper(TVSeriesDao.class);
List<TVSeriesBean> seriesBeans = new ArrayList<>();
TVSeriesBean seriesBean1 = new TVSeriesBean(); seriesBean1.setTvId(1); seriesBean1.setTvType(1); seriesBeans.add(seriesBean1);
TVSeriesBean seriesBean3 = new TVSeriesBean(); seriesBean3.setTvId(6); seriesBean3.setTvType(1); seriesBeans.add(seriesBean3);
List<TVSeriesBean> beanList = tvSeriesDao.selectByDynamicSQL_foreachObj(seriesBeans);
for (TVSeriesBean bean : beanList) { System.out.println(bean); }
sqlSession.close();}
复制代码


同样,结果大家自行验证,只要我们理解了我们在做什么?那么结果就是意料之中的事情,哈哈~ 到这,动态 SQL 就学完了,当然还有动态更新set标签,其实一点不难,大家官网查看例子就可以搞定。

缓存机制

一级缓存

一级缓存默认开启,无法关闭,缓存范围是SqlSession会话。


缓存失效的情况:


  • 查询不同条件

  • updateinsertdelete 语句会更新缓存


关于缓存其实我们了解到这里就可以,后面做缓存用的都是redis.

二级缓存

二级缓存需要手动开启,范围是Mapper namespace .


  • useCache=false 代表不使用缓存

  • flushCache=true 代表强制清空缓存


开启缓存:


<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
复制代码


eviction 可取值:


  • LRU 表示会将最久未使用的对象清除掉。

  • FIFO 先进先出,按照对象进入缓存的顺序来清楚。

  • SOFT 软引用:移除基于垃圾收集器状态和软引用规则的对象。

  • WEAK 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。


flushInterval:代表间隔多长时间自动清除缓存,单位毫秒。


size 缓存存储的对象数量。一个集合算一个对象。


二级缓存触发时机


只有当回话提交或者关闭的时候,才会提交到二级缓存中,默认会加入到一级缓存。

PageHelper 插件

官网地址:https://pagehelper.github.io/


当然也可以取 mav 搜索库去找:https://search.maven.org/search


使用流程:

1、添加 maven 依赖

<dependency>    <groupId>com.github.pagehelper</groupId>    <artifactId>pagehelper</artifactId>    <version>5.3.0</version></dependency>
复制代码

2、配置 plugins

<plugins>    <plugin interceptor="com.github.pagehelper.PageInterceptor"/></plugins>
复制代码


这个标签的声明位置在本文开始部分配置文件标签里提到过顺序,注意位置即可。

2、使用示例

// 参数:页号;每页数量PageHelper.startPage(2, 3);
复制代码


分页功能具体要看需求,有时候遇到的可能被要求自己写,那就是我们之前学习过的limit

注解开发

这种方式开发对于简单的业务可以使用,如果是比较复杂一点,注解开发方式实现起来就比较困难了,而且会显得很乱,结构不清晰,易读性差。


需要借助插件:lombok ,提供了 N 多个注解,最常用的代替 getXXX()/setXXX()以及toString()/hashCode() 等方法的注解:@Data.


其他常用注解:


  • @AllArgsConstructor:所有参数的构造方法

  • @NoArgsConstructor:没有参数的构造方法

  • @Alias(""):别名注解,这就是本文前面提到的如果添加了注解别名,那么 mapper 中会优先使用注解别名


关于插件的下载,这里不做演示,但有个温馨提示:


如果IntelliJ IDEA连接网线始终打不开plugins -> Marketplace,那么你就断开网络,链接手机热点,嗖的一下就成功了!


简单举例:查询表中所有数据


/** * 注解方式 * 无须在 mapper 文件中写对应的 <select> 标签 * @return */@Select("select * from tv_series")List<TVSeriesBean> selectAllTVSeries();
复制代码


还提供了很多注解,感兴趣的可以自行尝试,这里不做过多解释了。

总结

  • 这篇文章的内容涉及的知识点还是蛮多的,需要加强练习,尤其是配置文件标签动态SQL是重中之重

  • 技术是不断演变的,注解开发方式还是要了解,知道是在干什么,因为一部分开发已经在用这种模式了

  • 学技术是个慢过程,不要急于求成,慢下来,才能快

  • 这是 2021 年度最后一篇推荐学java系列原创文章,2022 继续输出,各位加油!


本文所涉及的源码和数据表,在公众号 推荐学java 回复 myBatisDemo 即可获得。

小编特意创建了一个公众号:推荐学java,分享原创 java 内容,欢迎大家微信搜索关注(关注即送视频教程),一起学 Java~

发布于: 刚刚阅读数: 2
用户头像

逆锋起笔

关注

公众号「逆锋起笔」主理人 2018.07.31 加入

程序视角,转射人生!

评论

发布
暂无评论
1月月更|推荐学java——MyBatis高级