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>
- 在代码中增加注解
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
@Data
public 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 mapper
PUBLIC "-//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 mapper
PUBLIC "-//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 mapper
PUBLIC "-//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>
- 分别测试
@Test
public 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();
}
@Test
public 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 一对多处理
一对多的理解:
- 一个老师拥有多个学生
- 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生
- 实体类编写
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public 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 mapper
PUBLIC "-//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>
- 分别进行测试
@Test
public 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());
}
@Test
public 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方法作用)
@Data
public 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("-","");
}
@Test
public void test(){
System.out.println(Idutils.getId());
}
}
- 编写mapper接口及xml文件
public interface BlogMapper {
//插入数据
int addBlog(Blog blog);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//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>
- 初始化博客方法
@Test
public 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>
- 测试
@Test
public 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>
- 测试
@Test
public 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>
- 测试
@Test
public 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>
- 测试
@Test
public 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 mapper
PUBLIC "-//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>
- 测试
@Test
public 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不同
@Test
public 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相同,查询条件不同
@Test
public 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>
测试:
@Test
public 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相同,手动清理了一级缓存
@Test
public 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/>
官方示例=====>查看官方文档
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
- 代码测试
- 所有的实体类应先实现序列化接口
public class User implements Serializable {
......
}
- 测试代码
@Test
public 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 缓存原理
缓存说白了就是提高查询的效率