**技术派项目源码地址 : **
- **Gitee : 技术派 - https://gitee.com/itwanger/paicoding
- **Github : 技术派 - https://github.com/itwanger/paicoding
**Mybatis-Plus 官网 : **MyBatis-Plus 🚀 为简化开发而生 (baomidou.com)
整合Mybatis-Plus
引入依赖
<mybaits-plus.version>3.5.2</mybaits-plus.version>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
使用 @MapperScan 注解扫描 mapper 文件
- **如果Mapper包统一管理 可以直接在启动类加 @MapperScan **
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 如果不是统一管理就单独写一个配置Config来扫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 {
}
修改配置文件application.yml
mybatis-plus:
configuration:
#开启下划线转驼峰
map-underscore-to-camel-case: true
MyBatis-Plus 的基本使用
Service CRUD
@Repository
public class TagDao extends ServiceImpl<TagMapper, TagDO> {
-
@Repository 注解:这是 Spring 提供的注解,用于标识这个类是一个数据访问层(DAO)组件。Spring 会自动扫描并将其实例化为一个 Bean,方便在其他类中通过依赖注入(DI)使用。
-
ServiceImpl<TagMapper, TagDO>:ServiceImpl 是 MyBatis-Plus 提供的一个抽象类,提供了通用的 CRUD 方法。泛型参数 <TagMapper, TagDO> 意味着 TagDao 类主要用于处理 TagDO 数据对象的数据库操作,并使用 TagMapper 接口定义的方法进行操作。
-
通过继承 ServiceImpl 类,TagDao 就可以使用 MyBatis-Plus 提供的通用 CRUD 方法,如 save、getById、updateById 等。这些方法已经实现了基本的数据库操作,通常无需自己编写 SQL 语句。
@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;
}
-
@Data 注解是 Lombok 提供的,用于自动生成类的 getter、setter、equals、hashCode 和 toString 方法,简化了代码编写。
-
@EqualsAndHashCode(callSuper = true) 注解也是 Lombok 提供的注解,callSuper = true 表示要调用父类(BaseDO)的 equals 和 hashCode 方法。
-
@TableName(“tag”) 注解是 MyBatis-Plus 提供的注解,用于指定数据库表名。
-
BaseDO 是我们自定义的 DO 基类,实现了 Serializable 接口,并且定义了主键 id(@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;
}
Mapper CRUD
- 技术派中一些特殊的增删改查是通过 MyBatis-Plus 的 Mapper CRUD 接口实现的。
public interface ArticleDetailMapper extends BaseMapper<ArticleDetailDO> {
}
@Repository
public class ArticleDao extends ServiceImpl<ArticleMapper, ArticleDO> {
@Resource
private ArticleDetailMapper articleDetailMapper;
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();
}
}
- 它继承自 MyBatis-Plus 的 BaseMapper 接口。
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 entity 条件,删除记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
}
常用注解
@TableName:用于指定数据库表名,通常在实体类(DO 或 Entity)上使用。
@TableName("user")
@TableId:用于指定表中的主键字段。通常在实体类的主键属性上使用。
@TableId(value = "id", type = IdType.AUTO)
其中 value 表示主键字段名,type 表示主键生成策略。
@TableField:用于指定表中的非主键字段。可以用于实体类的属性上,以映射属性和数据库字段。
@TableField(value = "user_name", exist = true)
其中 value 表示数据库中的字段名,exist 表示该字段是否存在(默认为 true)。
@TableLogic:用于指定逻辑删除字段。指在数据库中标记某个记录已删除,而不是真正地删除记录。
@TableLogic(value = "0", delval = "1")
其中 value 表示未删除状态的默认值,delval 表示删除状态的值。
@Version:指定乐观锁字段。乐观锁是一种并发控制策略,解决多个线程同时修改同一条记录的问题。
@Version
private Integer version
@EnumValue:用于指定枚举类型字段的映射。
@EnumValue
private Integer status
@InterceptorIgnore:用于忽略 Mybatis-Plus 拦截器的处理。
@InterceptorIgnore(tenantLine = "true") // 表示忽略多租户拦截器。
MyBatis-Plus 查询方法
普通查询
- 先注入对应Mapper
@Autowired
private ArticleTagDao articleTagDao;
ArticleDO article = articleTagDao.selectById(articleId);
articleTagDao.selectBatchIds(Arrays.asList(1,2));
Map<String, Object> map = new HashMap<>();
map.put("id", 15L);
List<ArticleDO> dtoList = baseMapper.selectByMap(map);
条件构造器 wrapper 查询
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
假如我们来查询这样一个结果,包含“j”且状态是已发布的标签。我们可以这样来构建条件构造器:
@Test
public void testWrapper() {
QueryWrapper<TagDO> wrapper = new QueryWrapper<>();
// 包含“j”且状态是已发布
wrapper.like("tag_name", "j").eq("status", 1);
BaseMapper<TagDO> baseMapper = tagDao.getBaseMapper();
List<TagDO> tagList = baseMapper.selectList(wrapper);
tagList.forEach(System.out::println);
}
- 通过表的字段总感觉很不舒服,万一哪天数据库表发生变化怎么办呢?代码和数据库就不匹配了呀。
- 更优雅的做法是采用 Lambda 的方式,技术派中的条件构造器就用的这种方式。
public List<ArticleDO> listArticles(PageParam pageParam) {
return lambdaQuery()
.eq(ArticleDO::getDeleted, YesOrNoEnum.NO.getCode())
.last(PageParam.getLimitSql(pageParam))
.orderByDesc(ArticleDO::getId)
.list();
}
MyBatis-Plus 自定义 SQL
- 在 Mapper 接口中编写自定义 SQL 方法,并使用注解添加自定义的 SQL 语句。
- 方法参数 accountId 使用了 @Param 注解,指定了参数在 SQL 语句中的名称为 account_id。
- 这样,在执行 SQL 语句时,MyBatis 会将参数值替换到对应的位置上。
public interface UserMapper extends BaseMapper<UserDO> {
/**
* 根据三方唯一id进行查询
*/
@Select("select * from user where third_account_id = #{account_id} limit 1")
UserDO getByThirdAccountId(@Param("account_id") String accountId);
}
- 除此之外,技术派中还使用了 xml 的方式,用来定义一些复杂的 SQL。
- 在 resources 目录下的好处是,MyBatis-Plus 默认帮我们配置了 xml 的位置。
- 这样我们就不需要在 application.yml 中再配置了。
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);
}
<?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, date
FROM request_count
group by date order by date asc
limit #{day};
</select>
<select id="getUvDayList" resultType="com.github.paicoding.forum.api.model.vo.statistics.dto.StatisticsDayDTO">
SELECT count(*) as count, date
FROM request_count
group by date order by date asc
limit #{day};
</select>
</mapper>
- 该 XML 文件定义了一个 RequestCountMapper 的映射器, 与 RequestCountMapper 相匹配。
- 它包含了三个自定义查询:getPvTotalCount、getPvDayList 和 getUvDayList。
MyBatis-Plus 更新和删除
更新操作
- 普通更新 : 直接调用 Service 的 updateById 方法,也就是根据 ID 更新
public void saveTag(TagReq tagReq) {
TagDO tagDO = ArticleConverter.toDO(tagReq);
if (NumUtil.nullOrZero(tagReq.getTagId())) {
tagDao.save(tagDO);
} else {
tagDO.setId(tagReq.getTagId());
tagDao.updateById(tagDO);
}
}
- 也可以通过 xml 的形式,当批量修改消息的状态时,技术派是通过这种方式更新的
void updateNoticeRead(@Param("ids") List<Long> ids);
<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>
删除操作
技术派中的删除都是逻辑删除,不是物理删除,就是修改 delete 字段,而不是真的把记录从表里删除,所以,最终调用的还是 update 方法,比如说删除文章。
public void deleteArticle(Long articleId, Long loginUserId) {
ArticleDO dto = articleDao.getById(articleId);
if (dto != null && !Objects.equals(dto.getUserId(), loginUserId)) {
// 没有权限
throw ExceptionUtil.of(StatusEnum.FORBID_ERROR_MIXED, "请确认文章是否属于您!");
}
if (dto != null && dto.getDeleted() != YesOrNoEnum.YES.getCode()) {
dto.setDeleted(YesOrNoEnum.YES.getCode());
articleDao.updateById(dto);
// 发布文章删除事件
SpringUtil.publishEvent(new ArticleMsgEvent<>(this, ArticleEventEnum.DELETE, articleId));
}
}
MyBatis-Plus 主键策略
主键自增策略
@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;
}
无主键策略
- 不使用任何主键生成策略,主键值需要手动设置。
public class User {
@TableId(type = IdType.NONE)
private Long id;
// ...
}
UUID主键策略
- 插入数据时,MyBatis-Plus 会自动生成一个 UUID 值作为主键值。
public class User {
@TableId(type = IdType.UUID)
private String id;
// ...
}
雪花算法主键策略
- 雪花算法生成分布式唯一 ID
public class User {
@TableId(type = IdType.ID_WORKER)
private Long id;
// ...
}