一般物理分页,即通过sql语句分页,都是在sql语句后面添加limit分页语句,在xml文件里传入分页的参数,再多配置一条sql,用于查询总数:
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
select * from student limit #{currIndex} , #{pageSize}
</select>
<select id="queryStudentsCount" resultType="java.lang.Integer" parameterType="java.util.Map">
select count(*) from student
</select>
这样可以实现分页,但是每条sql都这样写,很冗余,而且不好维护,所以高级一点的方式就是自定义的一个拦截器,拦截所有需要分页的查询语句,并且利用获取到的分页相关参数统一在sql语句后面加上limit分页的相关语句,一劳永逸,不需要再每条语句都配置一下,拦截器具体怎么实现不说了,因为Mybatis Plus 已经有类似这样一个拦截器的分页插件,利用这个分页插件,不需要自己写拦截器就可以轻松实现分页查询:
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1、 添加Mybatis-plus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.1.8</version>
</dependency>
2、配置拦截器
@Configuration //标识我是一个配置类
public class MybatisPlusConfig {
//配置分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
3、service实现
传统的查询方式:
SELECT *
FROM sys_user
WHERE (name='张三' AND sex=0 AND age BETWEEN '18' AND '50')
LIMIT 0,10
利用 Mybtis-plus,可以轻松实现分页查询
// 分页查询 10 条姓名为‘张三’、性别为男,且年龄在18至50之间的用户记录
List<User> userList = userMapper.selectPage(
new Page<User>(1, 10),
new EntityWrapper<User>().eq("name", "张三")
.eq("sex", 0)
.between("age", "18", "50")
)
以上是单表查询的分页实现,多表查询,或者比较复杂的查询语句也可以轻松实现:
public Page<User> selectUserPage(Page<User> page, Integer state) {
// 不进行 count sql 优化,解决 MP 无法自动优化 SQL 问题
// page.setOptimizeCountSql(false);
// 不查询总记录数
// page.setSearchCount(false);
// 注意!! 分页 total 是经过插件自动 回写 到传入 page 对象
return page.setRecords(userMapper.selectUserList(page, state));
}
注意:需要根据前端传来的分页参数,当前第几页,每页多少条,构造一个page 对象,初始化这些参数,将page对象传递给mapper,拦截器会自动在sql语句加上limit查询,自动查询总记录数并写回page对象。
4 mapper 接口以及 xml
public interface UserMapper{//可以继承或者不继承BaseMapper
/**
* <p>
* 查询 : 根据state状态查询用户列表,分页显示
* </p>
*
* @param page
* 翻页对象,可以作为 xml 参数直接使用,传递参数 Page 即自动分页
* @param state
* 状态
* @return
*/
List<User> selectUserList(Pagination page, Integer state);
}
UserMapper.xml 等同于编写一个普通 list 查询,mybatis-plus 自动替你分页
<select id="selectUserList" resultType="User">
SELECT * FROM user WHERE state=#{state}
</select>
注意:一定要传递page参数,否则不能实现分页,查询sql 可以是多表联合查询的复杂语句。
官方文档说明:
com.baomidou.mybatisplus.annotations.TableField
TableField 注解新增属性 update 预处理 set 字段自定义注入
(讲解:比如我们使用mybatisplus自带的insert()方法向数据库插入数据时,假设我们给age字段赋值为1,但是我们在age字段上的@TableField注解里面加了update=“%s+1”,那么真真插入到数据库的值就是age=2,而不是age+1了)
例如:@TableField(.. , update="%s+1") 其中 %s 会填充为字段
输出 SQL 为:update 表 set 字段=字段+1 where ...
如果给某个字段上@TableField注解里面写update=“now()”,那么最后我们使用mybatisplus自带的insert()方法向数据库插入数据时,这个字段插入到数据库中的值就为当前时间,看下面代码的sql语句即可明白
例如:@TableField(.. , update="now()") 使用数据库时间
输出 SQL 为:update 表 set 字段=now() where ...
TableField 注解新增属性 condition 预处理 WHERE 实体条件自定义运算规则,下面会有代码讲解
@TableField(condition = SqlCondition.LIKE)
private String name;
输出 SQL 为:select 表 where name LIKE CONCAT('%',值,'%')
讲解:举个例子来说明@TableField(condition = SqlCondition.LIKE)的作用
@Data
@TableName("admin_role")
public class RoleDO extends Model<RoleDO> {
/**
* 角色ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 角色名称
*/
@TableField(condition = SqlCondition.LIKE)
private String name;
/**
* 角色描述
*/
private String description;
/**
* 是否启用:0-不可用,1-可用
*/
private Boolean enabled;
/**
* 删除标示:0-未删除,1-已删除
*/
@TableLogic
private Boolean deleted;
/**
* 创建人ID
*/
protected Long creatorId;
/**
* 创建人
*/
protected String creator;
/**
* 创建时间
*/
@SuppressFBWarnings("EI_EXPOSE_REP")
protected Date dateCreated;
/**
* 修改人ID
*/
protected Long modifierId;
/**
* 修改人
*/
protected String modifier;
/**
* 更新时间
*/
@SuppressFBWarnings("EI_EXPOSE_REP")
protected Date lastModified;
/** 指定主键 */
@Override
protected Serializable pkVal() {
return this.id;
}
}
我们通过直接给EntityWrapper对象传入RoleDO实体类来构造EntityWrapper,, EntityWrapper eWrapper = new EntityWrapper(roleDO); (代码如下)
/**
* 查询角色列表(分页)
*
* @param roleParam 角色参数
* @return 查询角色分页列表
*/
public Page<RoleDO> selectListPage(ListRoleParam roleParam) {
RoleDO roleDO = new RoleDO();
BeanUtils.copyProperties(roleParam, roleDO);
Page<RoleDO> page = new Page<RoleDO>((int)roleParam.getPi(), (int)roleParam.getPs()); //得到分页的信息
EntityWrapper<RoleDO> eWrapper = new EntityWrapper<RoleDO>(roleDO);//构建条件查询对象
Page<RoleDO> roleDOList = roleDO.selectPage(page, eWrapper);//这里使用的就是Model提供的AR
return roleDOList;
}
而对于name这样的字段在日常查询中往往是通过like方式来进行匹配的而非精确匹配,所以此处通过@TableField(condition = SqlCondition.LIKE)来实现动态组合查询条件,会根据前台传入的参数自动组合查询语句并进行分页。
讲解如下:
实体类中有如下属性,通过上面的自动填充属性,我们可以实现在进行插入(insert)操作时对添加了注解@TableField(fill = FieldFill.INSERT)的字段进行自动填充(解释:后面会写配置自动填充的配置类,该配置类的作用用于配置自动填充的值)。
对添加了注解@TableField(fill = FieldFill.INSERT_UPDATE)的字段在进行插入(insert)和更新(update)时进行自动填充。(解释:后面会写配置自动填充的配置类,该配置类的作用用于配置自动填充的值)。
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private Long creatorId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date gmtCreat;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long modifierId;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
/**
* 是否可用
*/
@TableField(fill = FieldFill.INSERT)
private Boolean availableFlag;
这样我们在具体业务中对实体类进行赋值就可以不用对这些公共字段进行赋值,在执行插入或者更新时就能自动赋值并插入数据库。
那么要自动赋的值在哪里配置?
在项目的config包下新建自动填充处理类使其实现接口MetaObjectHandler,接下来我们来写自动赋值的配置类,并重写其方法:
package com.ws.api.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 自动填充处理类
* @author badao
* @version 1.0
* @see
**/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override //在执行mybatisPlus的insert()时,为我们自动给某些字段填充值,这样的话,我们就不需要手动给insert()里的实体类赋值了
public void insertFill(MetaObject metaObject) {
//其中方法参数中第一个是前面自动填充所对应的字段,第二个是要自动填充的值。第三个是指定实体类的对象
this.setFieldValByName("modifierId", new Long(111), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
this.setFieldValByName("creatorId", new Long(111), metaObject);
this.setFieldValByName("gmtCreat",new Date(), metaObject);
this.setFieldValByName("availableFlag",true, metaObject);
}
@Override//在执行mybatisPlus的update()时,为我们自动给某些字段填充值,这样的话,我们就不需要手动给update()里的实体类赋值了
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("modifierId", new Long(111), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
到此,@TableField完成字段自动填充的内容就讲完了
接下来我们来看@TableField(exist=false)的作用
比如在实体类中有一个属性为remark,但是在数据库中没有这个字段,但是在执行插入操作时给实体类的remark属性赋值了,那么可以通过在实体类的remark属性上添加
@TableField(exist=false)
private String remark;
就不会报错了。
乐观锁还有CAS其他的一些问题,我们可以在这篇文章里深入了解【进阶之路】包罗万象——JAVA中的锁
当要更新一条记录的时候,希望这条记录没有被别人更新
3.1)实体类的字段上加上@Version注解
@Version
private Integer version;
3.2) 创建bean
@Bean
public MybatisPlusInterceptor myOptimisticLockerInnerInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
3.3)测试