Mybatis-Plus大家都熟悉了吧?是一个Mybatis的增强,提供了一些额外功能,比如条件构造器、分页插件、代码生成器等以便我们更专注于业务,而不是SQL语句的编写
官方教程:简介 | MyBatis-Plus
整合MyBatis-Plus
非常简单,第一步,在pom.xml中引入starter。
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency>
第二步,使用@MapperScan注解扫描mapper文件
@Configuration
@ComponentScan("com.github.paicoding.forum.service")
@MapperScan(basePackages = {"com.github.paicoding.forum.service.article.repository.mapper","com.github.paicoding.forum.service.user.repository.mapper","com.github.paicoding.forum.service.comment.repository.mapper","com.github.paicoding.forum.service.config.repository.mapper","com.github.paicoding.forum.service.statistics.repository.mapper","com.github.paicoding.forum.service.notify.repository.mapper",})
public class ServiceAutoConfig {
}
ServiceAutoConfig 是单独的配置类,mapper接口按照业务进行了分类,mapper.xml放在resources目录下。
第三步,在application.yml文件中增加MyBatis-Plus的统一配置
# mybatis 相关统一配置
mybatis-plus:configuration:#开启下划线转驼峰map-underscore-to-camel-case: true
map-underscore-to-camel-case: true 的作用是将数据库表中的下划线命名方式映射为Java对象中的驼峰命名方式。例如,数据库表中的列名为user_name,对应Java对象的属性名为userName。
上面三部就完成了MyBatis-Plus和Spring Boot项目的整合,接下来,我么来介绍用法,包括新增、注解、查询、条件构造器、自定义SQL、分页查询、更新删除、AR模式、主键策略,以及通过service。
MyBatis-Plus的基本使用
Service CRUD
技术派中的通用的增删改查是通过MyBatis-Plus的service CRUD接口实现的。
比如说我们要保存一个文章的标签,可以通过这种方式。
@Autowired
private TagDao tagDao;tagDao.save(tagDo);
1、tagDao是我们定义的数据访问对象(Data Access Object :DAO)它继承自MyBatis-Plus提供的ServiceImpl类。@Autowired注解将TagDao自动注入到当前类中。这是Spring提供的依赖注入(DI)功能,可以让我们在当前类中方便的使用TagDao
@Repository
public class TagDao extends ServiceImpl<TagMapper, TagDO> {
1.@Repository注解:这是Spring提供的注解,用于标识这个类是一个数据访问层(DAO)组件。Spring会自动扫描并将其实例化为一个Bean,方便在其他类中通过依赖注入(DI)使用
2. ServiceImpl<TagMapper, TagDO>: ServiceImpl是MyBatis-Plus提供的一个抽象类,提供了通用的CRUD方法。泛型参数<TagMapper, TagDO> 意味着TagDao类主要用来处理TagDO数据对象的数据库操作,并使用TagMapper接口定义的方法进行操作。
通过继承ServiceImpl类,TagDao就可以使用MyBatis-Plus提供的通用CRUD方法,如sava、getById、updateById等。这些方法已经实现了基本的数据库操作,通常无需自己编写SQL语句。
2、 参数tagDO是一个数据对象,表示数据库中的tag表。
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("tag")
public class TagDO extends BaseDO {private static final long serialVersionUID = 3796460143933607644L;/*** 标签名称*/private String tagName;/*** 标签类型:1-系统标签,2-自定义标签*/private Integer tagType;/*** 状态:0-未发布,1-已发布*/private Integer status;/*** 是否删除*/private Integer deleted;
}
1.@Data注解是Lombok提供的,用于自动生成类的getter、setter、equals、hashCode和toString方法,简化了代码编写。
2. @EqualsAndHashCode(callSuper = true)注解也是lombok提供的注解,callSuper = true 表示要调用父类(BaseDO)的equals和hashCode方法。
BaseDO是我们自定义的DO基类,实现了 Serializable 接口,并且自定义了主键(@TableId(type = IdType.AUTO))表示自动增长,是MyBatis-Plus提供的注解,创建时间为 createTime 更新时间为 updateTime。
@Data
public class BaseDO implements Serializable {@TableId(type = IdType.AUTO)private Long id;private Date createTime;private Date updateTime;
}
3. @TableName("tag") 注解是MyBatis-Plus提供的注解,用于指定数据库表名。
4.另外定义了四个属性:tagName ( 标签名称) 、tagType( 标签类型)、status( 状态)、deleted ( 是否删除)。这些数据对映数据库表中的列。
来看技术派的演示实战
启动Redis、服务端、admin端,通过admin端新增一个标签。
在控制台就可以看到新增的标签了。
Mapper CRUD
MyBatis-Plus除了提供Service的CRUD,还提供了基于Mapper的CRUD。
技术派中的一些特殊的增删改查是通过MyBatis-Plus的Mapper CRUD 接口实现的。
比如说我们保存文章,可以通过下面这种方式。
@Repository
public class ArticleDao extends ServiceImpl<ArticleMapper, ArticleDO> {@Resourceprivate ArticleDetailMapper articleDetailMapper;/*** 保存文章正文** @param articleId* @param content* @return*/public Long saveArticleContent(Long articleId, String content) {ArticleDetailDO detail = new ArticleDetailDO();detail.setArticleId(articleId);detail.setContent(content);detail.setVersion(1L);articleDetailMapper.insert(detail);return detail.getId();}
}
1. ArticleDetailMapper 是我们在当前类中注入的一个Mapper接口
public interface ArticleDetailMapper extends BaseMapper<ArticleDetailDO> {
}
他继承自MyBatis-Plus的BaseMapper接口
/**
Mapper 继承这个接口之后,无需编写 mapper.xml文件,即可获得·CRUD功能
<p>这个 Mapper支持id泛型*/
public interface BaseMapper<T> extends Mapper<T> {//插入一条记录 ,entity:实体对象int insert(T entity);//根据entity条件 ,删除记录 queryWrapper实体对象封装的操作类(可以为null,//里面的entity用于生成where语句)int delete(@Param("ew") Wrapper<T> queryWrapper);//根据wherEntity 条件,更新记录// entity 实体对象(set条件值 ,可以为null)//updateWrapper 实体对象操作类,可以为null,里面的entity用于生成where语句int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);//根据ID查询,id主键IDT selectById(Serializable id);}
这样ArticleDetailMapper也就具备了基本的增删改查功能。
在浏览器中访问 http://localhost:8080/ 并测试
可以在控制台看到文章的插入信息。
删改后面会讲到。
常用注解
- @TableName: 用于指定数据库表名,通常在实体类上使用,如@TableName("user")
- @TableId: 用于指定表中的主键字段。通常在实体类的主键属性上使用。如:@TableId(value = "id" , type = IdType.AUTO),其中value表示主键字段名,type表示主键生成策略。
- @TableFiled: 用于指定表中的非主键字段。可以用于实体类的属性上,以映射属性和数据库字段。如@TableFiled(value = "user_name" , exist = true),其中value表示数据库中的字段名,exist表示该字段是否存在,默认为true存在,false表示不存在。
- @TableLogic: 用于指定逻辑删除字段。逻辑删除字段是指数据库中标记某个记录已删除,而不是真正删除的记录。如: TableLogic(value = "0" ,delval = "1"),其中value表示未删除状态的默认值,delval删除状态的值。
- @Version: 用于指定乐观锁字段。乐观锁是一种并发控制机制策略,用于解决解决多个线程同时修改同一条记录的问题,如@Version private Integer version;
- @EnumValue: 用于指定枚举类型的字段的映射,如: @EnumValue private Integer status;
- @InterceptorIngnore: 用于忽略MyBatis-Plus拦截器的处理。如:@InterceptorIngnore(tenantLine = "true"),表示忽略多租户拦截器。
这几个是常见注解。
MyBatis -Plus 查询方法
普通查询
其中它的BaseMapper提供了多种查询方法,如技术派中根据ID查找文章是这样用的:
// 查询文章记录ArticleDO article = baseMapper.selectById(articleId);
除此之外还有根据ID批量查询的selectBatchlds::
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
用法也比较就简单
List<Long> ids = list.stream().map(ReadCountDO::getDocumentId).collect(Collectors.toList());List<ArticleDO> result = baseMapper.selectBatchIds(ids);
baseMapper.selectBatchIds(Arrays.asList(1,2));
根据键值对查询的selectMap:
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
用法如下(id为15)
Map<String , Object> map = new Hash<>();map.put("id" , 15L);
List<ArticleDo> dtoList = baseMapper.selectByMap(map);
条件构造器
MyBatis-Plus 的Wrapper是一个条件构造器,用于简化复杂的SQL查询条件的构建。它提供了一系列易于使用的API,让你能够以来链式编程的方式编写查询条件,而不需要手动编写SQL语句。
加入我们来查询这样的一个结果,包含“j”且状态是已经发布的标签。我们可以这样来构建条件构造器:
QueryWrapper: 用于构建查询条件。它继承自AbstractWrapper,提供了各种查询条件的构造方法,如eq、ne、gt、ge、lt、le、like、isNull、orderBy等等。
通过上面的方法,我们将返回全部列,如果只想返回一部分,可以通过select 来设置查询字段。
但存在一个问题,数据库表发生变化,代码和数据库就会不匹配了,所以更好的使用Lambda的方式,技术派的条件构造器就用的这种方式。
比如查询标签。
/*** 获取已上线 Tags 列表(分页)** @return*/public List<TagDTO> listOnlineTag(String key, PageParam pageParam) {LambdaQueryWrapper<TagDO> query = Wrappers.lambdaQuery();query.eq(TagDO::getStatus, PushStatusEnum.ONLINE.getCode()).eq(TagDO::getDeleted, YesOrNoEnum.NO.getCode()).and(!StringUtils.isEmpty(key), v -> v.like(TagDO::getTagName, key)).orderByDesc(TagDO::getId);if (pageParam != null) {query.last(PageParam.getLimitSql(pageParam));}List<TagDO> list = baseMapper.selectList(query);return ArticleConverter.toDtoList(list);}
- 1.可以通过 Wrappers.lambdaQuery();静态方法创建一个Lambda条件构造器。
- 2.eq(TagDO::getStatus, PushStatusEnum.ONLINE.getCode()):表示查询条件为status等于PushStatusEnum.ONLINE.getCode()的值(即查询线上的标签)
- 3.eq(TagDO::getDeleted, YesOrNoEnum.NO.getCode()):表示查询条件为deleted等于esOrNoEnum.NO.getCode()的值(即查询未删除的记录)
- 4.and(!StringUtils.isEmpty(key), v -> v.like(TagDO::getTagName, key)):表示如果key不为空,则添加一个查询条件,要求tag_name包含key。
- 5.orderByDesc(TagDO::getId);:表示按照id字段降序排序。
- 6.if (pageParam != null) { query.last(PageParam.getLimitSql(pageParam)); }:如果pageParam不为null,则添加分页参数。
这样的话,数据库字段和代码隔离,完全通过代码的方式去查询,再比如查询文章列表:
/*** 文章列表(用于后台)** @param pageParam* @return*/public List<ArticleDO> listArticles(PageParam pageParam) {return lambdaQuery().eq(ArticleDO::getDeleted, YesOrNoEnum.NO.getCode()).last(PageParam.getLimitSql(pageParam)).orderByDesc(ArticleDO::getId).list();}
- 1.lambdaQuery()是一个MyBatis-Plus的Iservice接口提供的一个默认方法,可以在Service中直接调用·返回一个Lambda条件构造器。
default LambdaQueryChainWrapper<T> lambdaQuery() {return ChainWrappers.lambdaQueryChain(this.getBaseMapper());}
- 2.eq(ArticleDO::getDeleted, YesOrNoEnum.NO.getCode()) :表示查询条件为delete等于YesOrNoEnum.No的值 ,即查询未删除的记录。
- 3. .last(PageParam.getLimitSql(pageParam)):在查询的最后添加一个分页语句,这里根据pageParam参数生成分页的SQL语句。
- 4. orderByDesc(ArticleDO::getId): 表示按照id字段降序排序。
- 5.list():执行查询,并返回查询结果的列表。
MyBatis-Plus自定义SQL
MyBatis-Plus支持自定义SQL语句,我们可以在接口中编写自定义SQL方法,并使用注解加自定义的SQL语句。
技术派在位信登录的时候会执行这条SQL语句:
public interface UserMapper extends BaseMapper<UserDO> {/*** 根据三方唯一id进行查询** @param accountId* @return*/@Select("select * from user where third_account_id = #{account_id} limit 1")UserDO getByThirdAccountId(@Param("account_id") String accountId);
}
接口中定义了一个名为getByThirdAccountId的方法,它接收一个名为accountId的参数。该方法使用了@Select注解,这个注解用于编写自定义的SQL查询。select * from user where third_account_id = #{account_id} limit 1,他会根据传入的account_id参数查询user表中的记录。
同时,方法参数accountId使用了@Param注解,指定了参数在SQL语句中的名称为ccount_id,这样,在执行SQL语句时,MyBatis会将参数值替换到对应的位置上。
我们来测试一下。
结果:
除此之外,技术派中还使用了xml的方式,用来定义一些复杂的SQL。比如说,我们要统计网站的PV、UV,那么我们在resources目录下新建一个名为QueryCountMapper.xml的文件,内容如下:
<?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.github.paicoding.forum.service.statistics.repository.mapper.RequestCountMapper"><select id="getPvTotalCount" resultType="java.lang.Long">select sum(cnt) from request_count</select><select id="getPvDayList" resultType="com.github.paicoding.forum.api.model.vo.statistics.dto.StatisticsDayDTO">SELECT sum(cnt) as count, dateFROM request_countgroup by date order by date asclimit #{day};</select><select id="getUvDayList" resultType="com.github.paicoding.forum.api.model.vo.statistics.dto.StatisticsDayDTO">SELECT count(*) as count, dateFROM request_countgroup by date order by date asclimit #{day};</select></mapper>
1.在resources目录下的好处是,MyBatis-Plus默认帮我们配置了xml的位置,这样我们就不需要在application.yml中配置了。
该XML文件定义了一个名为RequestCountMapper的映射器,它包含了三个自定义查询:getPvTotalCount、getPvDayList、getUvDayList。他与
com.github.paicoding.forum.service.statistics.repository.mapper.RequestCountMapper相匹配。
/*** 请求计数mapper接口** @author louzai* @date 2022-10-1*/
public interface RequestCountMapper extends BaseMapper<RequestCountDO> {/*** 获取 PV 总数** @return*/Long getPvTotalCount();/*** 获取 PV 数据列表* @param day* @return*/List<StatisticsDayDTO> getPvDayList(@Param("day") Integer day);/*** 获取 UV 数据列表** @param day* @return*/List<StatisticsDayDTO> getUvDayList(@Param("day") Integer day);
}
3. getPvTotalCount();查询:返回类型为java.lang.Long,查询语句为select sum(cnt) from request_count。此查询计算request_count表中所有记录的cnt列值之和。
4.getPvDayList查询: 返回类型为 StatisticsDayDTO 。此查询根据传入的day参数获取按日期分组的请求数量统计信息,并按日期升序排序。
5. getUvDayList查询: 返回类型同样为StatisticsDayDTO。此查询根据传入的day参数获取按日期分组的唯一访客数量统计信息,并按照日期升序排列。
打开admin端,查看这三项数据
MyBatis-Plus更新和删除
更新
我们来先看个最简单的,直接调用Service的updateById方法,也就时根据ID更新,比如更新标签的内容:
public void saveTag(TagReq tagReq) {TagDO tagDO = TagStructMapper.INSTANCE.toDO(tagReq);// 先写 MySQLif (NumUtil.nullOrZero(tagReq.getTagId())) {tagDao.save(tagDO);} else {tagDO.setId(tagReq.getTagId());tagDao.updateById(tagDO);}
Service的update其实是对Mapper得update做一个封装。
default boolean save(T entity) {return SqlHelper.retBool(this.getBaseMapper().insert(entity));}
后台把 “ 技术派” 的标签修改为 “ 技术派Π”。
后台可以看到SQL语句日志。
也可以通过xml的形式,当批量修改消息状态时,技术派是这样更新的。
<update id="updateNoticeRead">update notify_msg set `state` = 1 where `id` in<foreach collection="ids" open="(" close=")" separator="," item="id" index="index">#{id}</foreach></update>
对用的mapper是这样写得:
/*** 标记消息为已阅读** @param ids*/void updateNoticeRead(@Param("ids") List<Long> ids);
删除
技术派中的删除都是逻辑上的删除,不是物理删除,就时修改delete字段,而不是真的把记录从表里面删除,所以,最终调用的还是update方法,比如删除文章。
@Overridepublic void deleteArticle(Long articleId) {ArticleDO dto = articleDao.getById(articleId);if (dto != null && dto.getDeleted() != YesOrNoEnum.YES.getCode()) {// 查询该文章是否关联了教程,如果已经关联了教程,则不能删除long count = columnArticleDao.count(Wrappers.<ColumnArticleDO>lambdaQuery().eq(ColumnArticleDO::getArticleId, articleId));if (count > 0) {throw ExceptionUtil.of(StatusEnum.ARTICLE_RELATION_TUTORIAL, articleId, "请先解除文章与教程的关联关系");}dto.setDeleted(YesOrNoEnum.YES.getCode());articleDao.updateById(dto);// 发布文章删除事件SpringUtil.publishEvent(new ArticleMsgEvent<>(this, ArticleEventEnum.DELETE, dto));} else {throw ExceptionUtil.of(StatusEnum.ARTICLE_NOT_EXISTS, articleId);}}
MyBatis-Plus主键策略
技术派中的主键目前采用的是自增策略,也就是说,数据库表的ID会设置位Auto Increment(自动递增)。
然后,实体类DO会继承BaseDO,比如分类 CategoryDO:
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("category")
public class CategoryDO extends BaseDO {private static final long serialVersionUID = 1L;/*** 类目名称*/private String categoryName;/*** 状态:0-未发布,1-已发布*/private Integer status;/*** 排序*/@TableField("`rank`")private Integer rank;private Integer deleted;
}
其中BaseDO为MyBatis-Plus提供的基类,内部的id字段已经添加了@TableId(type = IdType.AUTO) 注解 ,。
@Data
public class BaseDO implements Serializable {@TableId(type = IdType.AUTO)private Long id;private Date createTime;private Date updateTime;
}
在插入数据时,无需设置主键值,数据库会自动分配主键值。
除了IdType.AUTO,MyBatis-Plus 还提供了其他几种策略。比如说Idtype.NONE:无主键策略。表示不使用任何主键生成策略,主键值需要手动设置。
IdType.ID_WORKER: 使用雪花算法生成分布式唯一的ID。插入数据时,MyBtis-Plus会自动生成一个雪花ID作为主键值。
小结:
MyBatis-Plus得基本使用:
- Spring Boot整合MyBatis-Plus:如何引入依赖,并配置数据源和配置类。
- MyBatis-Plus的基本使用: 如何创建实体类和Mapper接口,并在Service层和Mapper层中使用MyBatis-Plus提供的通用CRUD方法。
- MyBatis-Plus的查询方法: MyBatis-Plus各种查询方法,MyBatis-Plus条件构造器时中带你。(QueryWrapper和LambdaQueryWrapper)。
- MyBatis-Plus自定义SQL: 如何在MyBatis-Plus中使用自定义SQL语句,包括在Mapper接口中使用注解定义SQL和在XML文件中编写SQL。
- MyBatis-Plus更新和删除: MyBatis-Plus提供的更新和逻辑删除方法。
- MyBatis-Plus主键策略: MyBatis-Plus如何生成主键,支持的主键生成策略,如何使用@TableId注解配置主键策略。