笛里谁知壮士心,沙头空照征人骨。这篇文章主要讲述推荐学java——MyBatis高级相关的知识,希望能为你提供帮助。
补两张知识导图
最近的两篇文章《Maven初识》和《第一个MyBatis程序》 文中缺少了知识结构图,这里补充一下。
文章图片
文章图片
本节内容是关于
MyBatis
的高级部分,上节的内容重点是带大家从零开始搭建一个使用 MyBatis
框架的java项目,并且能使用 MyBatis
框架完成对数据库中表的增删改查操作;这听起来不难理解,但对于新手要实战一遍,还是需要多加练习,推荐大家通过新建 Module
的方式来操作。本节内容会在上一节的基础上进行,包括项目工程和数据库,内容包括但不限于:
- MyBatis核心配置文件中其他配置
- SQL语句如何动态化
- MyBatis 注解开发模式
- MyBatis 缓存机制
- 分页功能
配置文件标签
日志管理日志文件 是用于记录系统操作事件的记录文件或文件集合。
在实际开发中日志是非常重要的,能起到两个很大的作用,其一:帮助开发人员排错,其二:能通过记录管理谁操作了什么内容,这两点尤其和库关联的时候,就能起到非常关键的作用,删库跑路这样的说法相信各位都听说过吧!
日志框架掌握两个常用的即可:
STDOUT_LOGGING
LOG4J
mybatis-config.xml
中添加如下代码:<
settings>
<
!--配置日志-->
<
setting name="logImpl" value="https://www.songbingjia.com/android/STDOUT_LOGGING"/>
<
/settings>
对于
STDOUT_LOGGING
添加如上配置即可使用;对于 LOG4J
该库本身支持各种各样的定制,可单独增加属性配置文件,按照自己的需求来修改配置,做到定制化,关于如何配合以及有哪些属性,网上有很多教程,这里不做解释了(最近该库闹出了漏洞,大家都在填坑)。【推荐学java——MyBatis高级】需要注意的是:
MyBatis
核心配置文件中的 configuration
标签下配置的标签是有顺序的,依次为:- properties(属性配置,本文后面会做配置,需要掌握)
- settings(设置,需要掌握)
- typeAliases(类型别名,需要掌握,能认识会用)
- typeHandlers(类型处理器,了解即可)
- objectFactory(对象工厂,了解即可)
- plugins(插件,需要掌握,依赖一些插件的时候要求配置)
- environments(环境配置,熟练掌握)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器,熟练掌握)
settings
标签中配置了驼峰命名映射:<
!--开启驼峰命名映射-->
<
setting name="mapUnderscoreToCamelCase" value="https://www.songbingjia.com/android/true"/>
这是截至目前接触到的
settings
标签里的两个配置,下面看properties
标签,这是我们第一次接触。举个简单的例子你就知道它的作用了,还记得下面这段代码吗?<
!--开发环境-->
<
environment id="development">
<
transactionManager type="JDBC"/>
<
dataSource type="POOLED">
<
!-- mysql驱动-->
<
property name="driver" value="https://www.songbingjia.com/android/com.mysql.cj.jdbc.Driver"/>
<
!-- 指定数据库开放的端口、要连接的数据库名、编码方式-->
<
property name="url"
value="https://www.songbingjia.com/android/jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&
characterEncoding=utf8"/>
<
!-- mysql用户名-->
<
property name="username" value="https://www.songbingjia.com/android/root"/>
<
!-- mysql登录密码-->
<
property name="password" value="https://www.songbingjia.com/android/root"/>
<
/dataSource>
<
/environment>
这里的
value
对应的值通过这种直接写死的方式很不友好,也是不推荐的,实际开发中是有专门的属性文件来管理这些值的,这就要用到properties
标签了。在
resources
目录下新建文件db.properties
,编辑如下内容:driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&
characterEncoding=utf8
username=root
password=root
就是这么简单,有其他编程语言经验的道友应该不陌生,这就是简单的
k-v
形式存储值的方式,接下来就是我们的核心配置文件中的configuration
标签下配置如下代码:<
properties resource="db.properties"/>
还记得我们前面提到的顺序问题嘛!不要放错了位置(放错也没关系,IDEA太智能了,它会给你提示的)。
细心的你可能注意到我们有一处微小的变化:
&
amp;
->
&
那为什么 &
这个符号不能直接在xml中写呢?& lt;
:& lt;
& gt;
:& gt;
&
:& amp;
:
& apos;
"
:& quot;
<
!--查询所有记录-->
<
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-片段
- 尽量基于单表实现
- 不要嵌套 where 标签
<
!--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)
;
现在我们来看个很简单的例子:
实现这个功能,并不难,关键是我们要通过动态SQL技术来做,但我们熟悉的
三步骤
不会变。第一步,在TVSeriesDao
中定义接口List&
lt;
TVSeriesBean&
gt;
selectByDynamicSQL_if(Map&
lt;
String, Object&
gt;
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 测试
*/
@Test
public 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=2select * 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
标签后的or
和and
,正是因为这样,我们的SQL会自动拼装为可执行的语句。动态SQL-choose(when,otherwise)过程都一样,这里直接上代码了,定义接口
TVSeriesBean selectByDynamicSQL_choose(Map&
lt;
String, Object&
gt;
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
*/
@Test
public 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 条件语句的时候)。这里会分别讲解集合的泛型是
Integer
和Object
类型,他们只是稍微有点区别,先来看包装类型的集合遍历。定义接口
List&
lt;
TVSeriesBean&
gt;
selectByDynamicSQL_foreach(List&
lt;
Integer&
gt;
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 遍历泛型类型是包装类的集合
*/
@Test
public 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&
lt;
TVSeriesBean&
gt;
selectByDynamicSQL_foreachObj(List&
lt;
TVSeriesBean&
gt;
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 遍历泛型类型是对象类的集合
*/
@Test
public 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
会话。缓存失效的情况:
- 查询不同条件
update
、insert
、delete
语句会更新缓存
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中会优先使用注解别名
简单举例:查询表中所有数据
/**
* 注解方式
* 无须在 mapper 文件中写对应的 <
select>
标签
* @return
*/
@Select("select * from tv_series")
List<
TVSeriesBean>
selectAllTVSeries();
还提供了很多注解,感兴趣的可以自行尝试,这里不做过多解释了。
总结
- 这篇文章的内容涉及的知识点还是蛮多的,需要加强练习,尤其是
配置文件标签
和动态SQL
是重中之重 - 技术是不断演变的,注解开发方式还是要了解,知道是在干什么,因为一部分开发已经在用这种模式了
- 学技术是个慢过程,不要急于求成,慢下来,才能快
- 这将是2021年度最后一篇
推荐学java
系列原创文章,2022继续输出,各位加油!
推荐阅读
- tf.nn.conv2d 你不知道的那些事儿
- #yyds干货盘点#netty系列之:好马配好鞍,为channel选择配套的selector
- vSAN数据恢复异常断电导致上层虚拟机无法启动,vSAN底层数据损坏的数据恢复
- TKE 用户故事 - 作业帮 PB 级低成本日志检索服务
- seata入门介绍与seata-service部署与验证
- Spring认证中国教育管理中心-Apache Geode 的 Spring 数据教程十七
- 手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏01游戏窗口
- #yyds干货盘点#Spring源码三千问AdviceAdvisorAdvised都是什么接口()
- RandomAccessFile 解决多线程下载及断点续传