MyBatis学习笔记–下篇
文章目录
- MyBatis学习笔记--下篇
- 1、多对一的处理(关联)
- 1.1、表的创建
- 1.2、实体类
- 1.2、按照查询嵌套处理
- 1.3、按照结果嵌套处理(联表查询 )
- 2、一对多的处理(Collection)
- 2.1、实体类
- 2.2、按结果查询(联表查询)
- 2.3、按照结果嵌套查询
- 3、动态Sql
- 3.1、表的创建
- 3.2、动态sql中三大标签的使用
- 3.2.1、if
- 3.2.2、choose(when,otherwise)
- 3.2.3、 trim(where,set)
- 3.2.4、 for each
- 3.2.5、 Sql片段
- 4、缓存
- 4.1、简介
- 4.2、Mybatis缓存
- 4.3、一级缓存(作用小)
- 4.4、二级缓存
- 4.5、缓存原理
- 4.6、自定义缓存--Ehcache
- 5、问题
1、多对一的处理(关联)
- 多个学生,对应一个老师
- 对于学生而言,关联…多个关联一个老师
- 对于老师而言,**集合…**一个老师有多个学生
1.1、表的创建
Teacher表
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='老师表';
-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', 'radan');
INSERT INTO `teacher` VALUES ('2', 'js');
Student表
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`tid` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `tid` (`tid`),
CONSTRAINT `tid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '张三', '1');
INSERT INTO `student` VALUES ('2', '李四', '1');
INSERT INTO `student` VALUES ('3', '王五', '1');
INSERT INTO `student` VALUES ('4', '溜溜', '1');
1.2、实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
public class Student {
private int id;
private String name;
// 学生需要关联一个老师
private Teacher teacher;
}
1.2、按照查询嵌套处理
<!--
思路:
1、查询所有的学生信息
根据查询出来的学生的tid,寻找对应的老师! 子查询
-->
<resultMap id="studentAndTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--
复杂属性需要单独处理
对象:association
集合:collection
-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getAllStudents" resultMap="studentAndTeacher">
select * from student
</select>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
1.3、按照结果嵌套处理(联表查询 )
注意点:就是当要联立的两张表都有相同的字段名时,会起“冲突”,导致查询的结果都是前面一张表的字段属性值
解决思路:在编写sql语句时,给冲突的列起别名,然后在resultMap结果集映射中利用别名可以有效的避免冲突。
<!--
联表查询
注意点:就是当要联立的两张表都有相同的字段名时,会起“冲突”,导致查询的结果都是前面一张表的字段属性值。
解决思路:在编写sql语句时,给冲突的列起别名,然后在resultMap结果集映射中利用别名可以有效的避免冲突。
-->
<select id="getAllStudents2" resultMap="studentMap">
select s.id sid,s.name sname, t.name tname, t.id t_id from student s,teacher t where s.tid=t.id
</select>
<resultMap id="studentMap" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="id" column="t_id"/>
<result property="name" column="tname"/>
</association>
</resultMap>
2、一对多的处理(Collection)
例如:一个老师拥有多个学生
对于老师而言就是一个一对多的处理。
2.1、实体类
public class Teacher {
private int id;
private String name;
//一个老师拥有多个老师
List<Student> students;
}
public class Student {
private int id;
private String name;
//学生只有一个老师
private int tid;
}
2.2、按结果查询(联表查询)
<!-- 按照结果嵌套查询-->
<resultMap id="teacherMap" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--
复杂的属性:单独处理。 我们需要单独处理的是 对象:association 集合:collection
javaType=“” 填的都是指定属性的类型
在集合中一般都是泛型信息,使用ofType获取
-->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<select id="getAllTeacher" resultMap="teacherMap">
select s.id sid,s.name sname,t.id tid,t.name tname
from teacher t,student s where s.tid=t.id
</select>
2.3、按照结果嵌套查询
<!-- 按照结果嵌套查询-->
<select id="getTeacher2" resultMap="teacherMap2">
select * from teacher where id=#{id}
</select>
<resultMap id="teacherMap2" type="teacher">
<collection property="students" javaType="ArrayList" ofType="student" column="id" select="getStudentByTeacher_id">
</collection>
</resultMap>
<select id="getStudentByTeacher_id" parameterType="int" resultType="student">
select * from student where tid=#{tid}
</select>
总结:
- 关联—【association】–多对一
- 集合—【collection 】一对多
- javatype &ofType
- javatype :用来指定实体类中属性的类型
- ofType:用来映射到List或者集合的破击类型,泛型中的约束类型!
注意点:
- 保证sql的可读性,尽量保证sql的通俗易通
- 注意一对多和多对一中,属性名和字段的问题!
- 如果问题不好排查,可以使用日志,建议使用log4j
3、动态Sql
动态Sql:就是指根据不同的条件生成不同的SQL语句
3.1、表的创建
DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(255) DEFAULT NULL COMMENT '博客标题',
`author` varchar(255) DEFAULT NULL COMMENT '博客作者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`views` int DEFAULT NULL COMMENT '浏览量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='博客表';
-- ----------------------------
-- Records of blog
-- ----------------------------
INSERT INTO `blog` VALUES ('1', '游戏', 'randan', '2023-06-30 17:22:07', '1');
INSERT INTO `blog` VALUES ('2', '日志', 'radan', '2023-10-30 17:21:21', '22');
INSERT INTO `blog` VALUES ('3', '兰州牛肉面', 'js', '2023-06-30 17:21:20', '33');
3.2、动态sql中三大标签的使用
3.2.1、if
<select id="queryBlog" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title != null ">
and title=#{title}
</if>
<if test="author != null ">
and author=#{author}
</if>
</select>
3.2.2、choose(when,otherwise)
这个标签像Switch,Case语法的使用规则。(模糊查找,提供了什么就查询这些条件符合的记录)。
只能选择一个 按照顺序来判断
<select id="queryBlogChoose2" parameterType="map" resultType="blog">
<!-- where 标签可以有效的解决 1=1代码的重复编写-->
select * from blog
<where>
<choose>
<!--只能选择一个 按照顺序来判断-->
<when test="title !=null">
title=#{title}
</when>
<when test="author != null">
and author=#{author}
</when>
<otherwise>
and views =#{views}
</otherwise>
</choose>
</where>
</select>
3.2.3、 trim(where,set)
- where:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
- set:set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
Where
<select id="queryBlogChoose" parameterType="map" resultType="blog">
<!-- where 标签可以有效的解决 1=1代码的重复编写-->
select * from blog
<where>
<if test="title != null ">
and title=#{title}
</if>
<if test="author != null ">
and author=#{author}
</if>
</where>
Set(注意:必须有一个if成立,不然就会导致update语句后面没有set更新的值。)
<update id="updateBlog" parameterType="map" >
update blog
<set>
<if test="title != null">title=#{title},</if>
<if test="author != null">author=#{author}</if>
</set>
where id=#{id}
</update>
3.2.4、 for each
<select id="queryBlogForEach" parameterType="map" resultType="blog">
<!-- 查询前三个人的信息-->
select * from blog
<where>
<foreach collection="ids" index="index" item="id" open=" and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
测试:
@org.junit.Test
public void test006() throws IOException {
SqlSession sqlSession= MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map=new HashMap();
List<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
System.out.println(mapper.queryBlogForEach(map));
sqlSession.commit();
sqlSession.close();
}
3.2.5、 Sql片段
作用:将重复的SQL语句抽取出来,放到标签中,可以进行复用。
- 使用sql标签抽取公共部分
<sql id="title-author">
<if test="title != null ">
and title=#{title}
</if>
<if test="author != null ">
and author=#{author}
</if>
</sql>
- 使用include标签引用公共部分定义的sql片段
<select id="queryBlog" parameterType="map" resultType="blog">
select * from blog where 1=1
<!--refid:引用id-->
<include refid="title-author"></include>
</select>
注意事项:
- 最好基于单表来定义sql片段
- sql抽取的部分不要存在where标签
4、缓存
4.1、简介
查询数据–>连接数据库,好资源!
什么是缓存[Cache]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户查询数据就不用偶从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
为什么使用缓存? - 减少和数据库的交互次数,减少系统开销,提高系统效率。
什么样的数据能使用缓存
- 经常查询并且不经常改变的数据。
4.2、Mybatis缓存
-
MyBatis 包含了一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。
-
MyBatis 系统中默认定义了两级缓存:以及缓存和二级缓存
- 默认情况下,只有以及缓存开启。(SQLSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和设置,它是基于namespace级别的缓存。为了提高扩展性,MyBatis定义了缓存接口Cache 。我们可以通过缓存Cache接口来定义二级缓存。
4.3、一级缓存(作用小)
测试步骤:
- 开启日志
<settings>
<!--配置标准日志工厂-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- 测试(在一个session中查询两次相同操作)
@Test
public void test001() throws IOException {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allUsers = mapper.getAllUsers();
List<User> users = allUsers;
System.out.println(users==allUsers);
sqlSession.close();
}
缓存失效的情况:
- 查询不同的条件
- 增删改操作,可能会改变原来的啥时间,所以必定会刷新缓存!
- 查询不同的Mapper.xml
- 手动清理缓存!
sqlSession.clearCache();//清理缓存
小结:一级缓存默认是开启的,只在一次SQLSession中有效,也就是拿到连接到关闭连接的区间。
4.4、二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以产生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保持在二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
步骤:
- 在mybatis配置文件中,配置全局缓存设置;
<settings>
<!--显示的开启全局缓存 默认是开启的-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在mapper.xml加缓存标签
创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
<!--开启二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- eviction:缓存执行的策略
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
-->
- 测试
@Test
public void test002() throws IOException {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.findUserById(1);
sqlSession.close();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user2 = mapper1.findUserById(1);
System.out.println(user1==user2);
sqlSession1.close();
}
注意点:
二级缓存存在于 SqlSessionFactory 生命周期中。
我一直以为二级缓存针对的对象是一个Mapper对象,只要是针对同一个Mapper的操作,都可以实现二级缓存。但是前提是操作必须在同一个SqlSessionFactory 中进行。
在一开始的代码中,通过调用三次getFactory()并打开session,实例化了三个不同的SqlSessionFactory 对象,这样在后续的SQL操作中,是不可能命中二级缓存的。
4.5、缓存原理
4.6、自定义缓存–Ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存。
要在程序中使用,先导入依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
在mapper.xml中使用对应的缓存即可
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
编写ehcache.xml文件,如果在 加载时 未找到 /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"
updateCheck="false" dynamicConfig="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位
置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_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"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策
略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统宕机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当
eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建
时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存
活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store
persists between restarts of the Virtual Machine. The default value is
false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默
认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将
会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先
出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、
FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以
来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容
量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的
元素将被清出缓存。
-->
</ehcache>
测试结果还是和之前一样的。
5、问题
问题描述:当我们在mybatis的配置文件中,写好绑定Mapper.xml文件时,运行结果依旧提示找不到Mapper.xml或是没有绑定。
MyBatis 配置信息:
错误信息:
此时,我们可以查看target目录下的class文件夹,发现并没有编译出UserMapper.xml文件。
解决方法:主要原因就是Maven在导出资源失败,资源过滤出现问题。我们需要在pom.xml中添加防止一些指定的资源文件的过滤配置。
<!-- 在build中配置resources。来防止我们资源导出失败的问题 资源过滤问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>