本篇博客为MyBatis的后篇学习,关于前篇部分的学习通过下面链接进行学习吧。
SSM框架---MyBatis学习前篇学习连接:SSM框架---MyBatis学习前篇
@TOC
七.注解开发
7.1.在Mybatis中使用注解开发
在前面说到在映射语句时可以不使用Mapper.xml方式来映射语句,可以使用注解方式实现,这一点在官方文档中也提到,如下点击跳转官方文档查看:
对于要使用注解还是xml形式官方文档说得非常明确:使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
7.2 SQL注解
SQL中CRUD对应注解:
@Select(这里面写Sql语句) 查询注解
@Update(这里面写Sql语句) 修改注解
@Delete(这里面写Sql语句) 删除注解
@Insert(这里面写Sql语句) 增加注解
7.2.1.查询@Select
业务1.使用注解查询数据库中所有用户信息
步骤1:在接口中编写查询数据的方法,并在方法上使用注解
在步骤1这里,使用注解后就可以省略以前步骤2的操作,在对应xml中写映射语句
步骤2:在DemoTest中编写测试方法
这里的操作和以前没有任何变化
运行结果:
注意:在mybatis-config.xml的配置文件中Mappers的配置只需要找到该接口即可,如果以前为找到对应的.xml的话,需要改过来.
改为以下二种任意一种:
7.2.2 增加@Insert
步骤1:在接口中编写增加数据的方法,并在方法上使用注解
步骤2:写测试方法
运行结果:
7.2.3 删除@Delete
步骤1:在接口中编写删除数据的方法,并在方法上使用注解
步骤2:写测试方法
运行结果:
7.2.4 修改@Update
步骤1:在接口中编写修改数据的方法,并在方法上使用注解
步骤2:写测试方法
运行结果:
7.2.5 实体类和数据库字段名不一致问题
执行selectUserAll_annotation()我们会发现查询出来的数据password=0:
对于这个问题在前篇中造成的原因: 实体类和数据库字段名不一致问题
对于这个问题ResultMap就无法使用,这里就只能通过取别名的方式进行解决,所以在注解Sql语句时候要考虑这一个因素。
这里给pwd取别名:
再在测试类中执行selectUserAll_annotation()【问题解决】:
7.3 @Param()注解
7.3.1 关于@Param()
@Param就是给方法的形式参数取名
注意:
- 基本类型的参数或者String类型,需要加上@Param()
- 引用数据类型不需要加
- 如果只有一个基本数据类型的话,可以忽略,但是建议加上
- 我们在SQL中引用的就是我们这里的@Param()中设定的属性名
- 方法存在多个参数,每一个参数必须加上
就像这样进行使用:
1. 关于最后一条(我们在SQL中引用的就是我们这里的@Param()中设定的属性名)进行解释:
步骤1.现在测试类中执行addUserTest_annotation()
此时数据如下,多了id=6的数据
接口中的删除方法:
步骤2.在测试类中执行delUserTest_annotation()进行测试:
执行后会报错,这是因为@Param("id2") 这里参数为id2而Sql语句中id=#{id} 这里是id,,所以这里出现id没有找到。
将@Param("id2") 这里的id2改为id即可:
再在测试类中执行delUserTest_annotation()进行测试:
7.4 LomBok
7.4.1 为什么使用LomBok
LomBok是一个插件,可以大大的简化Java代码,特别是在实体类中,可以通过相应的注解来代替Get,Set,有参,无参构造器,toString()等实现代码,但是LomBok改变了写源码的方式所以在使用的时候注意取舍。
7.4.2 LomBok在IDEA中使用
步骤1:安装LomBok插件
步骤2:导入LomBok的Maven
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
步骤3:在实体类中使用LomBok
关于实体类中的User类
使用注解:
从上面的注解方式的使用可以看出,使用注解可以少写很多代码。
7.4.3 常用注解说明
- @Data
注解在类上。在JavaBean或类JavaBean中使用,这个注解包含范围最广,它包含getter、setter、NoArgsConstructor、equals、canEqual、hashCode、toString注解,即当使用注解时,会自动生成包含的所有方法;
- @Setter
- 注解在类上。使用此注解会生成对应的setter方法。
- Getter
注解在类上。使用此注解会生成对应的getter方法。
- @NoArgsConstructor
注解在类上。使用此注解会生成对应的无参构造方法。
- @AllArgsConstructor
注解在类上。使用此注解会生成对应的有参构造方法。
- @ToString
注解在类上。使用此注解会自动重写对应的toStirng方法。
- @EqualsAndHashCode
注解在类上。使用此注解会自动重写对应的equals方法和hashCode方法。
- @Slf4j
注解在类上。当项目中使用了slf4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可;
- @Log4j
注解在类上。当项目中使用了log4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可。
- val
用在局部变量前面,相当于将变量声明为final。
- @NonNull
给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException)
- @Cleanup
自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流
- @Value
用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法
- @Builder
用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
- @SneakyThrows
自动抛受检异常,而无需显式在方法上使用throws语句
- @Synchronized
用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock或$LOCK,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误
关于LomBok更多的讲解,可以观看此篇博文,非常详细博客园-日拱一兵-随笔 -Lombok 使用详解,简化Java编程
八. XML映射文件
8.1第四章补充内容ResultMap
详细信息查看官方文档:点击跳转到官方文档
8.1.1 多对一
多对一的关系:在一个班中有多名学生,但是他们只有一位语文老师,这种多位学生对应一位老师的关系叫做多对一。
业务1:查询所有学生信息,包括对应的老师信息。
步骤1:设计数据库,并在项目中pojo中编写实体类
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO teacher(`id`, `name`) VALUES (1, 秦老师);
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, 小明, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, 小红, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, 小张, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, 小李, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, 小王, 1);
步骤2:添加Mapper接口和对应xml文件
步骤3:编写对应的映射语句
这里有二种方式可以实现:
- 实现方式:按结果集进行查询
就是单纯的编写完整的SQL语句,别名问题就用ResultMap解决,ResultMap中对象用association,集合用collection。
完整SQ语句L:
select s.id as sid,s.name as sname,s.tid as stid,t.name as tname
from student s,teacher t
where s.tid=t.id;
这里:Javatype:全限定名就是实体类Teacher
javaType="Teacher" 这里其实是省略了的完整应该是 javaType=" pojo.Teacher"
- 通过子查询方式进行获取
步骤3:编写对应的测试方法
按结果集进行查询:
按子查询方式进行查询:
运行结果:
8.1.2 一对多
一对多关系:一个班里多名学生,但是只有一个语文老师,一个语文老师对应多个学生,这种关系就是一对多关系。
业务2:查询指定老师信息,并查询出对应的学生
步骤1:编写Mapper接口和相应xml
老师的实体信息:
步骤2:编写相应的映射语句
- 按结果集进行查询
- 按子查询方式进行查询
步骤3:编写对应的测试方法
按结果集进行查询:
按子查询方式进行查询:
运行结果:
九 动态SQL
9.1 什么是动态SQL
详细信息参照官方文档:点击跳转官方文档查看详细信息
根据相应条件生成对应的SQL语句,相对于以前的纯Java代码中,就是通过相应的If判断进行SQL语句的拼接,这就是动态SQL。
在Mybatis中用于动态SQL的标签:
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
9.2 在项目中使用动态SQL
9.2.1 if
在前面的项目中,有一个selectUserAll方法查找所有用户信息,这里我们通过用户提供的参数情况进行动态查找
在UserMapper接口中添加一个selectUserAll_2(Map<String,Object> map)的方法
这里增加了二个条件判断,通过二个条件判断可以动态的实现数据查找
在测试类中进行测试:
这样进行传参就实现了前面的selectUserCondition()方法
二者的运行结果是一致的,都是查找名字中包含张的用户:
如果不添加任何条件就是查询所有用户,实现了selectUserAll()方法的业务:
运行结果:
如果只传递id的话就是实现了selectUserById()方法:
运行结果:
9.2.2 where
在前面的if使用中有一个1=1其实是没有必要存在的,因为1=1是必定存在。
如果把where 1=1去掉,大概会修改为这样
但是这样的话,如果id=null
的时候这时候的SQL语句就变成这样:select * from user_test And name like %传递的参数%
,这样显然是错误的,这也是为什么加where 1=1
的原因,为了解决这个问题,这里可以使用<where>
这样就可以当Where 后面第一个出现And
或Or
的时候自动的去掉,并且如果SQL语句后面没有条件的时候<where>
就不会在对应的SQL后面添加where
。
当没有参数时候运行结果:
和前面有where 1=1
的时候一样,但是和where 1=1
相比这样更加智能,并且更符合逻辑:
9.2.3 set
在数据修改中有的时候,某列数据没有做修改但是我们在以前写的SQL语句,其实进行了修改。
比如上面这个SQL映射语句,当用户不修改密码,只修改姓名时,这里的密码是和数据库一致的,但是在执行SQL语句的时候还是对密码这一字段进行了修改,对于这种情况我们可以使用<set>
。
在接口中添加一个updateUser_2(Map<String,Object> map)方法
相应的SQL映射语句如下:
测试:
运行结果:
并且<set>
对于后面遇见的第一个,
会自动去掉:
9.2.4 choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
在接口中添加一个selectUserAll_3(Map<String,Object> map)的方法
在接口类对应的映射语句
通过这个语句,可以看出,有哪一个参数不为空就查哪一个,如果都为空就查用户id=1
的用户信息:
测试:
运行结果:
9.2.5 trim
在前面的<where>
和<set>
如果和你的期望不一样,你可以通过<trim>
来自定义。
1.<trim>
中的参数解释:
- prefix:前缀
- prefixOverrides:前缀无效
- suffix:后缀
- suffixOverrides:后缀无效
这里的前后的意思就是<trim>
中相应的映射语句的最前面和最后面
2.前面的<where>
通过<trim>
来自定义:prefix="where"
如果<trim>
中有要执行的SQL语句,就在该SQL语句最前面添加Where,相反没有就不添加,prefixOverrides="AND |OR"
如果Where
后面有AND
或者OR
就移除掉,相反没有就不移除。
测试:
运行结果和用<where>
是一样的:
3.前面的<set>
通过<trim>
来自定义:prefix="set"
如果<trim>
中有要执行的SQL语句,就在该SQL语句最前面添加set,相反没有就不添加,suffixOverrides=","
如果最后面中有,
最后面的,
就移除掉,相反没有就不移除。
测试:
运行结果和updateUserTest_2前面一致:
9.2.6 foreach
业务:查询用户Id包含1,3,5的用户信息
步骤1:在接口类中添加方法
步骤2:在对应接口类的xml中编写映射语句open="("
当传递的集合有值时,先加"("
close=")"
集合遍历完之后在最后面加")"
separator=","
每遍历集合中的一个数后加一个","
collection="list"
传递的集合
执行过后就是
select * from user_test where id in (1,3,5)
测试:
执行结果:
十. 缓存
### 10.1 什么是缓存
缓存就是一个临时存放数据的地方,没有使用缓存之前对于数据的操作(这里的操作主要是查询)基本上是直接和服务器进行交互,如果多次查询同一个数据,这样就十分消耗资源,为了针对这种情况,将查询过后的数据放在一个临时地方,当多次查询同一个数据的时候,第二次就无需再从服务器中进行数据查询,而是从这个临时存放数据的地方进行查询,为了保障数据的一致性,如果用户执行增删改等操作,缓存会进行刷新。
没有使用缓存情况:
使用缓存情况:
10.2 一级缓存
MyBatis缓存分为一级缓存和二级缓存,一级缓存和二级缓存最大的区别在于生命周期不同,一级缓存的生命周期是SqlSession对象的使用期间,随着SqlSession对象的死亡而消失,并且一级缓存可由SqlSession来手动清除。
10.2.1 一级缓存测试
在测试类中查询二次selectUserAll_2,传递参数相同:
可以看出,第二次查询的时候是没有从服务器中返回数据的,而是直接从缓存中返回数据
如果参数不同呢:
这时就没有从缓存中取到数据,而是在服务器中查询之后再返回查询结果:
如果执行二个不同的方法:
执行结果:
在二个查询相同的方法中间执行增|删|改操作,验证了前面说的当执行了增删改操作之后对应的缓存会刷新:
10.3 二级缓存
二级缓存又叫全局缓存,在一个nameSpace下有效,二级缓存需要手动配置。
1.<settings>中
开启缓存,其实默认是开启的。
2.在映射语句xml中添加 <cache>
标签
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval:
(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。size:
(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。readOnly:
(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
10.3.1 二级缓存测试
使用一级缓存执行以下测试:
测试结果:
使用二级缓存进行测试:
运行结果:
Mybatis缓存查询顺序,如果二级缓存开启,先从二级缓存进行查找,如果二级缓存没有数据,就向一级缓存去查找,如果一级缓存没有数据就从数据库进行查找。
10.3.2 自定义缓存Ehcache
Java缓存框架 EhCache EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
使用Ehcache:
1.导入Ehcache的依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
2.创建ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="F:/ehcache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
<!--
属性说明:
l diskStore:当内存中不够存储时,存储到指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
3.在对应的mapper类中使用指定的缓存,而要使用第三方缓存则要实现Cache接口(此接口是Mybatis提供的)
使用自定义缓存实现一些测试:
实现结果:
其实运行效果和二级缓存,一级缓存没有任何区别,就是一种新的缓存框架,因为在实际开发中使用不多,这里只需要了解即可。
使用第三方缓存则要实现Cache接口:这里只需要实现Cache接口即可并且重写该接口的相应方法来实现对应的缓存操作,这里了解一下即可:
----------- 完结 -----------撒花 -----------撒花 -----------撒花 -----------撒花 -----------撒花 -----------撒花 -----------撒花 ----------- 撒花----------- ---撒花
该博文存在什么问题?,或者你有什么想说的?欢迎留言区进行留言!