Mybatis学习笔记03
1.一对多和多对一处理
1.1 多对一的处理
多对一的理解:
- 多个学生对应一个老师
- 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师
- 数据库设计:
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),(3,"小李",1),(4,"小刘",1),(5,"小陈",1);


搭建测试环境
IDEA安装Lombok插件
引入Maven依赖
<dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency></dependencies>
- 在代码中增加注解
@Datapublic class Student {private int id;private String name;//学生需要关联一个老师private Teacher teacher;}
@Datapublic class Teacher {private int id;private String name;}
- 编写实体类对应的Mapper接口
public interface TeacherMapper {@Select("select * from teacher where id=#{tid}")Teacher getTeacher(@Param("tid") int id);}
public interface StudentMapper {}
- 编写Mapper接口对应的mapper.xml对应文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.TeacherMapper"></mapper>
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.StudentMapper"></mapper>
- 注册mapper
<mappers><mapper resource="StudentMapper.xml"/><mapper resource="TeacherMapper.xml"/></mappers>
- 测试
public class MyTest {public static void main(String[] args) {SqlSession sqlSession = MybatisUtils.getSqlSession();TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);Teacher teacher = mapper.getTeacher(1);System.out.println(teacher);sqlSession.close();}}

按查询嵌套处理和按结果嵌套处理
给StudentMapper接口增加方法
//查询所有的学生信息以及老师信息public List<Student> getStudents();public List<Student> getStudents2();
- 编写对应的mapper文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.StudentMapper"><select id="getStudents" resultMap="StudentTeacher">select * from student</select><resultMap id="StudentTeacher" type="student"><association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/></resultMap><select id="getTeacher" resultType="teacher">select * from teacher where id = #{id}</select><!-- ================================================--><select id="getStudents2" resultMap="StudentTeacher2">select s.id sid, s.name sname, t.name tname from student s,teacher t where s.tid=t.id</select><resultMap id="StudentTeacher2" type="Student"><id property="id" column="sid"/><result property="name" column="sname"/><association property="teacher" javaType="Teacher"><result property="name" column="tname"/></association></resultMap></mapper>
- 编写完毕后去Mybatis配置文件中注册mapper
<!-- 注册mapper--><mappers><mapper resource="StudentMapper.xml"/><mapper resource="TeacherMapper.xml"/></mappers>
- 分别测试
@Testpublic void test1(){SqlSession sqlSession = MybatisUtils.getSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students=mapper.getStudents();for (Student student : students) {System.out.println("学生名:"+student.getName()+"\t老师:"+student.getTeacher().getName());}sqlSession.close();}@Testpublic void test2(){SqlSession sqlSession = MybatisUtils.getSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students=mapper.getStudents2();for (Student student : students) {System.out.println("学生名:"+student.getName()+"\t老师:"+student.getTeacher().getName());}sqlSession.close();}
- 结果

总结:
- 按照查询进行嵌套处理就像sql中的子查询
- 按照结果表进行嵌套处理就像sql中的联表查询
1.2 一对多处理
一对多的理解:
- 一个老师拥有多个学生
- 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生
- 实体类编写
@Datapublic class Student {private int id;private String name;private int tid;}
@Datapublic class Teacher {private int id;private String name;//一个老师对应多个学生private List<Student> students;}
按结果嵌套处理和按查询嵌套处理表
首先TeacherMapper接口编写方法
public interface TeacherMapper {//获取指定老师及老师下的所有学生public Teacher getTeacher(int id);public Teacher getTeacher2(int id);}
然后编写接口对应的Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.TeacherMapper"><select id="getTeacher" resultMap="TeacherStudent">select s.id sid, s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{id}</select><resultMap id="TeacherStudent" type="Teacher"><result property="name" column="tname"/><collection property="students" ofType="Student"><result property="id" column="sid"/><result property="name" column="sname"/><result property="tid" column="tid"/></collection></resultMap><!-- =====================================================--><select id="getTeacher2" resultMap="TeacherStudent2">select * from teacher where id=#{id}</select><resultMap id="TeacherStudent2" type="Teacher"><!-- column是一对多的外键,写的是一的主键的列名--><collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/></resultMap><select id="getStudentByTeacherId" resultType="Student">select * from student where tid=#{id}</select></mapper>
最后将Mapper文件注册到mybatis-config文件中
<!-- 注册mapper--><mappers><mapper resource="StudentMapper.xml"/><mapper resource="TeacherMapper.xml"/></mappers>
- 分别进行测试
@Testpublic void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();TeacherMapper mapper= sqlSession.getMapper(TeacherMapper.class);Teacher teacher=mapper.getTeacher(1);System.out.println(teacher.getName());System.out.println(teacher.getStudents());}@Testpublic void test2(){SqlSession sqlSession = MybatisUtils.getSqlSession();TeacherMapper mapper= sqlSession.getMapper(TeacherMapper.class);Teacher teacher=mapper.getTeacher2(1);System.out.println(teacher.getName());System.out.println(teacher.getStudents());}
- 结果

1.3 小结
- 关联-association
- 集合-collection
- association用于一对一和多对一,而collection是用于一对多的关系
JavaType和ofType都是用来指定对象类型的
- JavaType是用来指定pojo中属性的类型
- ofType指定的是映射到list集合属性中pojo的类型
注意说明:
- 保证sql的可读性,尽量通俗易懂
- 根据实际要求,尽量编写性能更高的sql语句
- 注意属性名与字段不一致的问题
- 注意一对多和多对一中:字段和属性对应的问题
- 尽量使用Log4j,通过日志来查看自己的错误
2.动态SQL
2.1 动态SQL环境搭建
什么是动态SQL?
动态SQL指的是根据不同的查询条件,生成不同的sql语句
我们之前写的sql语句都比较简单,如果有比较复杂的业务,我们需要写复杂的sql语句,往往需要拼接,而拼接sql,稍微不注意,由于引号,空格等缺失可能都会导致错误,那么怎么解决这个问题呢?这时就需要使用Mybatis动态sql,通过if,choose,when,otherwise,trim,where,set,foreach等标签可以组成非常灵活的sql语句,从而在提高sql语句的准确性的同时,也大大提高了开发人员的效率
搭建环境:
新建一个数据库表:blog
字段:id ,title,author,create_time,views
CREATE TABLE blog (id VARCHAR(50) NOT NULL COMMENT '博客id',title VARCHAR(100) NOT NULL COMMENT '博客标题',author VARCHAR(30) NOT NULL COMMENT '博客作者',create_time datetime NOT NULL COMMENT '创建时间',views INT(30) NOT NULL COMMENT '浏览量') ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 实体类编写(注意set方法作用)
@Datapublic class Blog {private String id;private String title;private String author;private Date createTime;private int views;}
- IDutil工具类(用来随机获得id)
public class Idutils {public static String getId(){return UUID.randomUUID().toString().replaceAll("-","");}@Testpublic void test(){System.out.println(Idutils.getId());}}
- 编写mapper接口及xml文件
public interface BlogMapper {//插入数据int addBlog(Blog blog);}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.BlogMapper"><insert id="addBlog" parameterType="blog">insert into mybatis.blog(id, title, author, create_time, views)VALUES (#{id},#{title},#{author},#{createTime},#{views})</insert></mapper>
- mybatis核心配置文件,配置下峰线驼峰自动转换
<settings><setting name="mapUnderscoreToCamelCase" value="true"/><setting name="logImpl" value="STDOUT_LOGGING"/></settings>
- 初始化博客方法
@Testpublic void addInitBlog(){SqlSession sqlSession = MybatisUtils.getSqlSession();BlogMapper mapper=sqlSession.getMapper(BlogMapper.class);Blog blog=new Blog();blog.setId(Idutils.getId());blog.setTitle("mybatis如此简单");blog.setAuthor("jcsune");blog.setCreateTime(new Date());blog.setViews(9999);mapper.addBlog(blog);blog.setId(Idutils.getId());blog.setTitle("Java如此简单");mapper.addBlog(blog);blog.setId(Idutils.getId());blog.setTitle("Spring如此简单");mapper.addBlog(blog);blog.setId(Idutils.getId());blog.setTitle("微服务如此简单");mapper.addBlog(blog);sqlSession.close();}
- 查看结果

2.2 动态SQL之IF语句
需求:根据作者名字和博客名字来查询博客,如果作者名字为空,那么只根据博客名字查询,反之则根据作者名字查询
- 编写接口类
//IF查询List<Blog> queryBlogIf(Map map);
- 编写sql语句
<select id="queryBlogIf" parameterType="map" resultType="blog">select * from blog where<if test="title != null">title=#{title}</if><if test="author != null">and author= #{author}</if></select>
- 测试
@Testpublic void testQueryBlogIf(){SqlSession session = MybatisUtils.getSqlSession();BlogMapper mapper = session.getMapper(BlogMapper.class);HashMap<String, String> map = new HashMap<String, String>();map.put("title","mybatis如此简单");map.put("author","jcsune");List<Blog> blogs = mapper.queryBlogIf(map);System.out.println(blogs);session.close();}
2.3 动态SQL常用标签
Where
修改上节的SQL语句:
<select id="queryBlogIf" parameterType="map" resultType="blog">select * from blog<where><if test="title != null">title=#{title}</if><if test="author != null">and author= #{author}</if></where></select>
这个where标签会知道如果它包含的标签中有返回值的话,它就插入一个“where”,此外如果标签返回的内容是以“and”或者“or”开头的,则它会剔除掉
Set
同理,上面的对于查询SQL语句包含where关键字,如果在进行更新操作的时候,含有set关键词,我们怎么处理呢
- 编写接口方法
int updateBlog(Map map);
- SQL配置文件
<update id="updateBlog" parameterType="map">update mybatis.blog<set><if test="title!=null">title=#{title},</if><if test="author!=null">author=#{author}</if></set>where id=#{id}</update>
- 测试
@Testpublic void testUpdateBlog(){SqlSession session = MybatisUtils.getSqlSession();BlogMapper mapper = session.getMapper(BlogMapper.class);HashMap<String, String> map = new HashMap<String, String>();map.put("title","微服务如此简单2");map.put("author","jcsune");map.put("id","228916648b4f454885962b2d9165fec8");mapper.updateBlog(map);session.close();}
- 结果

Choose
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用Choose标签可以解决此类问题,类似于java的switch语句
- 编写接口方法
List<Blog> queryBlogChoose(Map map);
- sql 配置文件
<select id="queryBlogChoose" parameterType="map" resultType="blog">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></mapper>
- 测试
@Testpublic void testQueryBlogChoose(){SqlSession session = MybatisUtils.getSqlSession();BlogMapper mapper = session.getMapper(BlogMapper.class);HashMap<String, String> map = new HashMap<String, String>();map.put("title","mybatis如此简单");map.put("author","jcsune");map.put("views","9999");List<Blog> blogs = mapper.queryBlogChoose(map);System.out.println(blogs);session.close();}
2.4 动态SQL之Foreach
SQL片段
有时候可能某个sql语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用
提取SQL片段:
<sql id="if-title-author"><if test="title!=null">title=#{title}</if><if test="author!=null">and author=#{author}</if></sql>
引用SQL片段:
<select id="queryBlogIf2" parameterType="map" resultType="blog">select * from blog<where>/* 引用sql片段,如果refid指定的不在本文件中,那么需要在前面加上namespace*/<include refid="if-title-author"> </include></where></select>
注意:
- 最好基于单表来定义sql片段,提高片段的可重用性
- 在sql片段中不要包含where
Foreach:
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询blog表中id分别为1,2,3,的博客信息
- 编写接口
List<Blog> queryBlogForeach(Map map);
- 编写SQL语句
<select id="queryBlogForeach" parameterType="map" resultType="blog">select * from blog<where><!--collection:指定输入对象中的集合属性item:每次遍历生成的对象open:开始遍历时的拼接字符串close:结束时拼接的字符串separator:遍历对象之间需要拼接的字符串select * from blog where 1=1 and (id=1 or id=2 or id=3)--><foreach collection="ids" item="id" open="(" close=")" separator="or">id=#{id}</foreach></where></select>
- 测试
@Testpublic void testQueryBlogForeach(){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);List<Blog> blogs=mapper.queryBlogForeach(map);System.out.println(blogs);sqlSession.close();}
- 结果:

总结:
其实动态sql语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的sql语句出来,然后再通过mybatis动态sql对照着改,防止出错,多在实践中使用才是熟练掌握它的技巧
3. 缓存
3.1 缓存简介
- 简介
什么是缓存?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
为什么使用缓存?
- 减少与数据库的交互次数,减少系统开销,提高系统效率
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
- mybatis缓存
- Mybatis包含一个非常强大的查询缓存特性,他可以非常方便的定制和配置缓存,缓存可以极大的提升查询效率
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存
- 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存
3.2 一级缓存
一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试:
- 在mybatis中加入日志,方便测试结果
- 编写接口方法
public interface UserMapper {//根据id查询用户User queryUserById(@Param("id") int id);}
- 接口对应的mapper文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jcsune.dao.UserMapper"><select id="queryUserById" resultType="user">select * from user where id=#{id}</select></mapper>
- 测试
@Testpublic void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user =mapper.queryUserById(1);System.out.println(user);System.out.println("============================");User user2=mapper.queryUserById(1);System.out.println(user2);System.out.println(user==user2);sqlSession.close();}
- 结果分析

一级缓存失效的四种情况
一级缓存是Sqlsession级别的缓存,是一直开启的,我们关闭不了它
一级缓存失效的情况:没有使用到当前的一级缓存,效果就是还需要再向数据库中发起一次查询要求
- sqlsession不同
@Testpublic void testQueryUserById(){SqlSession session = MybatisUtils.getSession();SqlSession session2 = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);UserMapper mapper2 = session2.getMapper(UserMapper.class);User user = mapper.queryUserById(1);System.out.println(user);User user2 = mapper2.queryUserById(1);System.out.println(user2);System.out.println(user==user2);session.close();session2.close();}
观察结果:发现发送了两条sql语句

结论:每个SqlSession中的缓存相互独立
- sqlsession相同,查询条件不同
@Testpublic void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user =mapper.queryUserById(1);System.out.println(user);System.out.println("============================");User user2=mapper.queryUserById(2);System.out.println(user2);System.out.println(user==user2);sqlSession.close();}
观察结果发现发送了两条SQL语句:

- sqlsession相同,两次查询之间执行了增删改操作
增加方法:
//修改用户int updateUser(Map map);
编写mapper:
<update id="updateUser" parameterType="map">update user set name=#{name} where id=#{id}</update>
测试:
@Testpublic void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user =mapper.queryUserById(1);System.out.println(user);System.out.println("============================");HashMap map = new HashMap();map.put("name","jcsune");map.put("id",4);mapper.updateUser(map);User user2 =mapper.queryUserById(1);System.out.println(user2);System.out.println(user==user2);sqlSession.close();}
结果:

观察结果:查询在中间执行了增删改操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响
- sqlsession相同,手动清理了一级缓存
@Testpublic void testQueryUserById(){SqlSession session = MybatisUtils.getSession();UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.queryUserById(1);System.out.println(user);//手动清除缓存session.clearCache();User user2 = mapper2.queryUserById(1);System.out.println(user2);System.out.println(user==user2);session.close();session2.close();}
结果:

一级缓存就是一个map
3.3 二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据诶保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
使用步骤:
- 开启全局缓存【Mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
- 去mapper.xml中配置使用二级缓存【UserMapper.xml】
<cache/>官方示例=====>查看官方文档<cacheeviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
- 代码测试
- 所有的实体类应先实现序列化接口
public class User implements Serializable {......}
- 测试代码
@Testpublic void test(){SqlSession sqlSession = MybatisUtils.getSqlSession();SqlSession sqlSession2 = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);User user =mapper.queryUserById(1);System.out.println(user);sqlSession.close()System.out.println("============================");User user2=mapper2.queryUserById(1);System.out.println(user2);System.out.println(user==user2);sqlSession2.close();}
- 结果

结论:
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
- 查出的数据都会默认先放在一级缓存中
- 只有会话提交或关闭以后,一级缓存中的数据才会转到二级缓存中
3.4 缓存原理

缓存说白了就是提高查询的效率
