Mybatis
分页
Limit 分页
- 基于 sql 实现
接口
//分页
List<User> getUserListByLimit(Map<String,Integer> map);
实现
<select id="getUserListByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startindex},#{pagesize}
</select>
测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startindex",0);
map.put("pagesize",2);
List<User> userListByLimit = mapper.getUserListByLimit(map);
for (User user : userListByLimit) {
System.out.println(user);
}
sqlSession.close();
}
RowBounds 分页
- 基于 Java 代码实现
接口
//rowbounds分页
List<User> getUserByRowBounds();
实现
<select id="getUserByRowBounds" resultMap="UserMap">
select *
from mybatis.user;
</select>
测试
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(0, 2);
List<User> userList = sqlSession.selectList("com.yqly.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
分页插件(PageHelper)
使用注解开发
面向接口编程
-
解耦,可拓展,提高复用,规范性更好
-
在分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易
-
接口是定义(规范,约束)与实现(名实分离的原则)的分离
-
接口本身反映了系统设计人员对系统的抽象理解
-
接口有两类
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)
注解开发
//获取全部用户列表
@Select("select * from mybatis.user")
List<User> getUserList();
省略了 Mapper.xml 配置文件,但需要在 Mybatis 核心配置文件中绑定接口
<mappers>
<mapper class="com.yqly.mapper.UserMapper"/>
</mappers>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
查询
//获取指定id的用户列表
//方法存在多个参数时,所有的参数前面都必须加上@Param("id")注解
@Select("select * from mybatis.user where id=#{id}")
User getUserById(@Param("id") int id);
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
增加
//添加用户
@Insert("insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(6,"溜溜","123456"));
System.out.println(i);
sqlSession.close();
}
修改
//修改用户
@Update("update mybatis.user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUser(new User(1, "张三", "123456"));
System.out.println(i);
sqlSession.close();
}
删除
//删除用户
@Delete("delete from mybatis.user where id=#{id}")
int deleteUser(@Param("id") int id);
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(6);
System.out.println(i);
sqlSession.close();
}
增删改都可以在工具类中开启事务自动提交
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true); //开启事务自动提交
}
#{} 和 ${} 的区别
- 前者可以防止 sql 注入而后者不行
@Param() 注解
- 基本类型或 String 类型的参数需要加上
- 引用类型不需要加
- 如果只有一个基本类型,可以忽略,但建议加上
- 在 sql 中引用的参数就是在 @Param() 中设置的属性名
Lombok
- Java 开发插件,可以通过定义一些注解来简化代码
@Data
- 自动生成无参构造,get,set,tostring,hashcode,equals 方法
@AllArgsConstructor
- 自动生成有参构造
@NoArgsConstructor
- 自动生成为无参构造
多对一处理
数据库编写
create table `teacher`(
`id` int not null ,
`name` varchar(20) default null,
primary key (`id`)
);
insert into `teacher`(id, name) VALUES (1,'李老师');
create table `student`(
`id` int not null ,
`name` varchar(20) default null,
`tid` int not null,
primary key (`id`),
key `fktid` (`tid`),
constraint `fktid` foreign key (`tid`) references `teacher` (`id`)
);
insert into student (id, name, tid)
values
(1,'小赵',1),
(2,'小钱',1),
(3,'小孙',1),
(4,'小李',1),
(5,'小周',1),
(6,'小吴',1),
(7,'小郑',1);
环境搭建
-
导入 lombok
-
新建实体类 Teacher,Student
package com.yqly.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class Student { private int id; private String name; private Teacher teacher;}
package com.yqly.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private int id; private String name; }
-
建立 Mapper 接口
-
建立 Mapper.xml 配置文件
-
在 Mybatis 核心配置文件中绑定接口
-
测试环境是否搭建成功
多表查询
对于复杂的属性,需要单独处理,指定属性类型为对象用 association,集合用 collection
javaType 用来定义指定类型
集合中的泛型信息,使用 ofType 定义指定类型
查询嵌套处理
<resultMap id="studentte" type="student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getStudent" resultMap="studentte">
select * from student;
</select>
<select id="getTeacher" resultType="teacher">
select * from teacher where id=#{tid};
</select>
结果嵌套处理
<resultMap id="studentte" type="student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="getStudent" resultMap="studentte">
select s.id,s.name,s.tid tid,t.name tname from student s,teacher t where s.tid=t.id;
</select>
一对多处理
数据库与环境和上面的多对一基本一样,实体类需要修改如下
package com.yqly.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
package com.yqly.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
动态 SQL
- 根据不同的条件生成不同的 SQL 语句
表结构
create table `blog`(
`id` varchar(50) not null comment '博客id',
`title` varchar(100) not null comment '博客标题',
`author` varchar(20) not null comment '博客作者',
`create_time` datetime not null comment '创建时间',
`views` int comment '阅读量'
)
实体类
package com.yqly.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
if
- 一次判断条件,达成则添加
<select id="getBlogListIf" 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>
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("title","我的2021年终总结");
map.put("author","小肖");
List<Blog> blogList = mapper.getBlogListIf(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}
choose
- 由上到下一次判断,当上面条件满足时,不再执行下面的语句,必须传一个条件都不满足时的执行语句的参数
<select id="getBlogListChoose" 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>
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("title","我的2020年终总结");
map.put("author","小肖");
map.put("views",11111);
List<Blog> blogList = mapper.getBlogListChoose(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}
trim(where,set)
- 定制元素功能
where
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
set
- set 元素会动态地在行首插入 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>
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("title","2022年年度计划表");
map.put("author","区委②");
map.put("id","1");
int i = mapper.updateBlog(map);
System.out.println(i);
sqlSession.close();
}
foreach
- 允许指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符
- 这个元素也不会错误地添加多余的分隔符
<select id="getBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" separator="or">
id=#{id}
</foreach>
</where>
</select>
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
ArrayList ids = new ArrayList();
ids.add(1);
ids.add(2);
map.put("ids",ids);
List<Blog> blogList = mapper.getBlogForeach(map);
for (Blog blog : blogList) {
System.out.println(blog);
}
sqlSession.close();
}
SQL 片段
- 对于经常使用的 sql 语句,可以使用 SQL 标签包装起来,便于复用
<sql id="if-title-author">
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
<select id="getBlogListIf" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if-title-author"/>
</where>
</select>
缓存
- 存在内存的临时数据
- 将用户经常查询的数据放在缓存中,用户去查询时不用从磁盘上查询而是在缓存中查询
- 减少和数据库的交互次数,减少系统开销,提高系统效率,解决了高并发系统的性能问题
Mybatis 缓存
- 默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下只有一级缓存开启
- 二级缓存需要手动开启和配置
- 为了提高扩展性,Mybatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存
- 所有 select 语句的结果将会被缓存
- 所有 insert、update 和 delete 语句会刷新缓存(增删改可能会改变原来的数据)
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改
Mybatis 缓存的清除策略
- LRU – 最近最少使用:移除最长时间不被使用的对象
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
默认的清除策略是 LRU
一级缓存
- SqlSession 级别的缓存,也称为本地缓存
@Test
public void test5(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
User user1 = mapper.getUserById(1);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
输出了两次查询结果,却只进行了一次 SQL 语句查询,第二次是从缓存中查询
二级缓存
-
namespace 级别的缓存,也称全局缓存
-
开启二级缓存时要先在 Mybatis 核心配置文件中设置 cacheEnabled 为 true
-
二级缓存是事务性的
当 SqlSession 完成并提交或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新
-
工作机制
一个会话(SqlSession)查询一条数据,这个数据也就被放在当前会话的一级缓存中
当会话(SqlSession)关闭后,一级缓存消失,缓存数据被存放到二级缓存中
新的会话(SqlSession)查询信息,就可以从二级缓存中获取数据
cache 标签
在要使用二级缓存的 Mapper.xml 加中开启二级缓存,可以自定义一些参数
-
enviction(清除策略),默认的清除策略是 LRU
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象FIFO
– 先进先出:按对象进入缓存的顺序来移除它们SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
-
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
-
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
-
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
数据查看顺序:二级缓存,一级缓存,数据库
@Test
public void test5(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
//测试二级缓存
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
System.out.println("====================");
User user1 = mapper1.getUserById(1);
System.out.println(user1);
//测试一级缓存
User user2 = mapper1.getUserById(2);
System.out.println(user2);
System.out.println("====================");
User user3 = mapper1.getUserById(2);
System.out.println(user3);
sqlSession1.close();
}
四次查询只进入了两次数据库,第二次查询在二级缓存中查询,第四次查询在一级缓存中查询
自定义缓存(ehcache)
- 一种广泛使用的开源 Java 分布式缓存,主要面向通用缓存
<cache type="org.mybatis.caches.ehcache.EhBlockingCache"/>
配置文件
<?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">
<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"/>
</ehcache>