1、导入依赖
导入mybatis-plus 依赖
<!--导入 mybatis-plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
第二:配置,包括配置文件配置和代码中的配置
1、在本地配置文件或配置中心 配置mysql数据源和mybatis-plus;
注意:classpath* 与 classpath 的区别?
classpath* 记载本工程及其导入的依赖jar中的指定目录下的xml文件
classpath 只能导入本工程下的指定目录下的xml文件
spring:
#配置数据源datasource
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://IP:PORT/database?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: username
password: password
#配置mybatis-plus
mybatis-plus:
#mapper文件地址
mapper-locations: classpath*:/mapper/**/*.xml
#设置主键自增
global-config:
db-config:
id-type: auto
2、在工程的启动类上加上注解 @MapperScan(dao层接口的包路径) 来扫描注入dao层接口,
代码如下所示:
//@MapperScan("com.gulimall.product.dao")
@SpringBootApplication
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
3、向工程中注入mybatis-plus 的分页插件 PaginationInnerInterceptor,否则分页报错;
代码如下所示:
@Configuration
@EnableTransactionManagement //开启事务,只有这里开启了事务save/update/delete方法上的事务才会生效(可以在启动类上或mybatis配置上开启事务)
@MapperScan("com.gulimall.product.dao") //开启mapper接口扫描(可以在启动类上或mybatis配置上开启dao扫描)
public class MyBatisConfig {
/**
* 注入 mybatis plus 分页插件
* @return
*/
@Bean
PaginationInnerInterceptor paginationInnerInterceptor(){
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
//配置请求页数大于最大页时的操作,true=返回首页,false=继续请求;默认是false
paginationInnerInterceptor.setOverflow(true);
//设置最大单页数据量限制,默认500单页最大显示500条,-1 表示不限制单页数量(即-1表示不分页)
//paginationInnerInterceptor.setMaxLimit(500l);
return paginationInnerInterceptor;
}
}
第三:使用
在工程中使用mybatis-plus 时,有三点要注意:
1、dao层接口需要继承接口BaseMapper<T>,T表示sql要操作的对象类,代码如下所示:
//若BaseMapper 中的方法不满足业务场景,我们还可以根据业务需要定义相关方法
//并完成相应的sql
@Mapper
public interface CategoryDao extends BaseMapper<CategoryEntity> {
}
BaseMapper接口常用方法介绍介绍:
//插入操作
int insert(T entity);
//根据id删除
int deleteById(Serializable id);
//删除,根据参数删除,参数以map方式传入
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
//删除,根据queryWrapper 包含的条件删除
int delete(@Param("ew") Wrapper<T> queryWrapper);
//根据id集合批量删除
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
//根据id更改
int updateById(@Param("et") T entity);
//更新操作,只更新 entity包含的字段(updateWrapper中包含的条件字段不更新 )
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
//根据id查询
T selectById(Serializable id);
//根据多个id批量查询
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
//根据map参数查询
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
//查询一条数据
T selectOne(@Param("ew") Wrapper<T> queryWrapper);
//统计符合条件数据总条数
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
//查询,不分页
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
//查询结果是map
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
//对象查询
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
//分页查询
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
//分页查询
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
2、service接口需要集成IService<T>,T表示业务操作的实体类;
如果 IService 中的方法无法满足我们的业务需要,则可以在我们自己定义的service
接口中定义方法;代码如下图所示:
public interface CategoryService extends IService<CategoryEntity> {
/**
* 分页查询
* @param params
* @return
*/
PageUtils queryPage(Map<String,Object> params);
/**
* 将所有的商品分类查询出来,并构建成tree树结构
* @return
*/
List<CategoryEntity> listWithTree();
}
3、service 接口的实现类需要集成类 ServiceImpl<M,T>,其中M表示dao层的mapper接口,
T 表示要操作的实体类;代码如下所示:
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage page = this.page(new Query<CategoryEntity>().getPage(params),
new QueryWrapper<CategoryEntity>());
return new PageUtils(page);
}
/**
* 查询所有的商品分类,并按商品分类的层级关系构建出商品分类的Tree 树结构
* @return
*/
@Override
public List<CategoryEntity> listWithTree() {
//1、查询所有的商品分类
List<CategoryEntity> list = this.list();
//2、过滤1级分类
list.stream()
.filter(categoryEntity -> categoryEntity.getParentCid()==0) //过滤1级分类
.map((menu) -> { //map() 函数做一些业务处理
//设置当前分类的子分类
menu.setChildren(getChildrens(menu,list));
return menu;
})
.sorted((m1,m2) -> { //排序
return (m1.getSort()==null?0:m1.getSort()) - (m2.getSort()==null?0:m2.getSort());
})
.collect(Collectors.toList()); //将最终结果转换成集合
return list;
}
}
第四步:mybatis-plus 中 Wrapper 、IPage介绍
1、Wrapper
打开BaseMapper和 IService 接口,发现里面的方法很多参数类型都是Wrapper<T>;
Wrapper是由mybatis-plus提供的一个接口,用于动态封装sql查询或修改 条件,他最
常用的2个实现类是 QueryWrapper 和 UpdateWrapper
QueryWrapper 主要用于构建查询条件
UpdateWrapper 主要用于构建修改sql的条件
示例代码:
//1、QueryWrapper用法
//查询 attr_group_id = 100的数据,等价于sql 中的 where attr_group_id = 100
QueryWrapper<AttrAttrgroupRelationEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("attr_group_id",100);
List<AttrAttrgroupRelationEntity> relationList = relationDao.selectList(queryWrapper);
//2、UpdateWrapper 用法
//修改对象 groupRelation(groupRelation 是类AttrAttrgroupRelationEntity 的对象) 中 attr_id=10的数据,
//注意:只修改 groupRelation 对象中存在的字段(即有值的字段)
UpdateWrapper<AttrAttrgroupRelationEntity> wrapper = new UpdateWrapper()
wrapper.eq("attr_id",10)
//修改
relationDao.update(groupRelation,wrapper)
QueryWrapper 和 UpdateWrapper 常用条件方法介绍:
//column-表示 表字段名称,val-输入的字段值
//等于,等价于 column=val
eq(column,val)
//不等于,等价于 column <> val
ne(column,val)
//等价于 or,后边跟上判断函数
or()
//and嵌套,and(子查询)
and()
//大于
gt(column,val)
//大于等于
ge(column,val)
//小于,匹配 column 的值小于val的数据
lt(column,val)
//小于等于
le(column,val)
//匹配列column 的值在[val1,val2]之间的数据
between(column,val1,val2):
//模糊查询 like "%val%"
like(column,val)
//模糊查询 not like "%val%"
notLike(column,val)
//模糊查询 like "%val"
likeLeft(column,val)
//模糊查询 like "val%"
likeRight(column,val)
//匹配 column 的值为null的数据
isNull(column)
//匹配 column 的值不为null的数据
isNotNull(column)
等价于 in 关键字,匹配column 的值在集合 Collection中
in(column,Collection)
//not in
notIn(column,Collection)
//分组,单个或多个字段名称column
groupBy(column)
//根据列column 升序排序,排序列column可以有多个
orderByAsc(columns)
//根据列column 降序排序,排序列column可以有多个
orderByDesc(columns)
//having 关键字
//sqlHaving-sql语句,params-sql语句的参数值,如:having("sum(age)>${0}",12)-表示 having sum(age)>12, ${0} 中 0表示参数的下标
having(String sqlHaving, Object... params)
//匹配符合sql语句的数据
exists("sql语句")
//匹配步符合sql语句的数据
notExists("sql语句")
//val:这里的val是一个sql语句,等价于 column in "sql语句",即匹配 column 的值在 sql语句执行的结果中
inSql(column, val)
//匹配 column 的值不在 sql语句执行的结果中
notInSql(column, val)
//拼接sql,applySql-要拼接的sql,vale-sql参数值
apply(applySql, val)
2、IPage
IPage是mybatis plus 提供的用于分页查询的一个接口,打开分页方法的源码可以发现,在
接口IService中的分页方法page() 中第一个参数和返回值类型都是IPage,IPage接口只有
一个实现类,即Page
page() 分页方法结构如下:
//page()方法源码如下:
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
Page类核心属性如下:
//page 类核心属性
public class Page<T> implements IPage<T> {
private static final long serialVersionUID = 8545996863226528798L;
//查询结果数据列表
private List<T> records;
//总条数
private long total;
//每页展示数据条数
private long size;
//当前页码
private long current;
//排序字段
private List<OrderItem> orders;
private boolean optimizeCountSql;
private boolean isSearchCount;
private boolean hitCount;
public Page() {
this.records = Collections.emptyList();
this.total = 0L;
this.size = 10L;
this.current = 1L;
this.orders = new ArrayList();
this.optimizeCountSql = true;
this.isSearchCount = true;
this.hitCount = false;
}
public Page(long current, long size) {
this(current, size, 0L);
}
public Page(long current, long size, long total) {
this(current, size, total, true);
}
public Page(long current, long size, boolean isSearchCount) {
this(current, size, 0L, isSearchCount);
}
public Page(long current, long size, long total, boolean isSearchCount) {
this.records = Collections.emptyList();
this.total = 0L;
this.size = 10L;
this.current = 1L;
this.orders = new ArrayList();
this.optimizeCountSql = true;
this.isSearchCount = true;
this.hitCount = false;
if (current > 1L) {
this.current = current;
}
this.size = size;
this.total = total;
this.isSearchCount = isSearchCount;
}
}
由上边的代码可以发现2个问题,
1)每次分页查询我们都需要创建 Page 对象,并把前台传送过来的分页属性
一个个设置到Page对象中,这段代码是重复的,我们可以把这段代买抽成一个公用方法
去获取Page,如:getPage()
代码如下:
/**
*
* @param params 前端传递的查询参数集合
* @param defaultOrderField 排序字段
* @param isAsc 是否是默认排序规则ASC
* @return
*/
public class Query<T> {
public IPage<T> getPage(Map<String,Object> params){
return getPage(params,null,false);
}
/**
*
* @param params 查询参数集合
* @param defaultOrderField 排序字段
* @param isAsc 是否是默认排序规则ASC
* @return
*/
public IPage<T> getPage(Map<String,Object> params,String defaultOrderField,boolean isAsc){
//初始化分页参数
long curPage = 0;
long limit = 10;
//从参数params 中获取分页参数
if(params.get(Constant.PAGE) != null){
curPage = Long.parseLong(String.valueOf(params.get(Constant.PAGE)));
}
if(params.get(Constant.LIMIT) != null){
limit = Long.parseLong(String.valueOf(params.get(Constant.LIMIT)));
}
Page<T> page = new Page<T>(curPage,limit);
//
params.put(Constant.PAGE,page);
//过滤sql字符串
//获取排序字段和排序规则
String orderField = SQLFilter.sqlInject((String) params.get(Constant.ORDER_FIELD));
String order = (String) params.get(Constant.ORDER);
//前端字段排序
if(StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)){
if(Constant.ASC.equalsIgnoreCase(order)) {
return page.addOrder(OrderItem.asc(orderField));
}else {
return page.addOrder(OrderItem.desc(orderField));
}
}
//没有排序字段
if(StringUtils.isBlank(defaultOrderField)){
return page;
}
if(isAsc){
page.addOrder(OrderItem.asc(defaultOrderField));
}else {
page.addOrder(OrderItem.desc(defaultOrderField));
}
return page;
}
}
SQLFilter是一个sql防注入工具类,代码如下:
public class SQLFilter {
public static String sqlInject(String sql){
if(StringUtils.isBlank(sql)){
return null;
}
//过滤掉 '、"、;、\
sql = StringUtils.replace(sql,"'","");
sql = StringUtils.replace(sql,";","");
sql = StringUtils.replace(sql,"\"","");
sql = StringUtils.replace(sql,"\\","");
//sql字符串转小写
sql = StringUtils.lowerCase(sql);
//非法关键字
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"};
for(int i=0;i<keywords.length;i++){
if(sql.indexOf(keywords[i]) >= 0){
throw new RRException("包含非法字符");
}
}
return sql;
}
}
2)若我们直接把Page对象返回给前端,一些前端不需要的属性也会一起返回;针对这种问题
我们可以重新创建一个类只包含需要返回前端的属性,如 PageUtil
PageUtil 代码如下:
@Data
public class PageUtils implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总数据数
*/
private int totalCount;
/**
* 总页数
*/
private int totalPage;
/**
* 每页数据条数
*/
private int pageSize;
/**
* 当前页码
*/
private int currPage;
/**
* 数据列表
* 在泛型中 我们常常看到 <T>、<K>、<E>、<K,V> 等,但这些都是有固定类型的,类型在初始化时指定。
* 但 <?> 表示不确定的Java类型
*/
private List<?> list;
/**
* 分页
* @param list
* @param totalCount
* @param totalPage
* @param pageSize
* @param currPage
*/
public PageUtils(List<?> list,int totalCount,int totalPage,int pageSize,int currPage){
this.list = list;
this.totalCount = totalCount;
this.totalPage = totalPage;
this.pageSize = pageSize;
this.currPage = currPage;
}
/**
* 分页
* @param page
*/
public PageUtils(IPage<?> page){
this.list = page.getRecords();
this.totalCount = (int)page.getTotal();
this.totalPage = (int) page.getPages();
this.pageSize = (int)page.getSize();
this.currPage = (int)page.getCurrent();
}
}