最全的MybatisPlus框架中常用应用功能

MybatisPlus中常用应用功能摘录

提示:常见的数据库持久层框架MP的常用功能的操作,特此摘录!!!

自定义方法(MP自带方法)使用条件构造器来实现

在SQL语句中获取Warpper拼接的SQL片段进行拼接添加Warpper类型的参数,并且要注意给其指定参数名

UserMapper.Java
@Mapper
//@Mapper 注解,使当前的Mapper 接口,被Spring进行管理,不然需要在,启动类上声明 @MapperScan("com.xuguoguo.mapper") 类扫描指定包下,mapper接口文件;
public interface UserMapper  extends BaseMapper<User> {
    //Mapper 接口 extends集成 BaseMapper<T> 泛型对应的实体类;
    //  Ctrl+左击, 进入BaseMapper 中可以看到, MP 默认给对应实体类提供好的实现方法();
    //  增删改查... 即各种的, 重载 CRUD 的操作;

    //如果,BaseMapper<T> 中,没有提供的,后面还可以在,该 xxxMapper 文件中, 自定义自己需要的方法();

    /** 自定义方法 **/
    // 根据id查询对象;
    User findMyUser(Long id);

    // 自定义方法,使用MP 的Wrapper
    List<User> findUsers(@Param(Constants.WRAPPER) Wrapper<User> wrapper);  //wrapper 是MP包下的依赖~
}

方法定义中添加Warpper类型的参数

UserMapper.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 -->
<mapper namespace="com.xuguoguo.mapper.UserMapper">
    <!-- 指定sql 对应的方法名,返回的结果集类型; -->
    <select id="findMyUser" resultType="com.xuguoguo.entity.User">
       select * from user where id = #{id}
    </select>

    <!-- 指定sql 对应的方法名,返回的结果集类型; -->
    <select id="findUsers" resultType="com.xuguoguo.entity.User">
       select * from user  ${ew.customSqlSegment}
    </select>
</mapper>
程序测试:
test模块下:com.xuguoguo.MPTest.Java
/** 自定义方法查询 +MP的 wrapper 条件构造 **/
@Test
public void testfindusers(){
    // 定义QueryWrapper 构造器,查询 age 大于 18 的数据;
    QueryWrapper<User> query = new QueryWrapper<>();
    query.gt("age", 18);
    // 调用 自定义的方法(wrapper 参数);
    List<User> users = userMapper.findUsers(query);
    users.forEach(System.out::println);
}
注意点:
 1、mapper接口中的参数名 @Param(Constants.WRAPPER) Wrapper<User> wrapper

 2、mapper.xml映射文件中中的拼接特殊字符 ${ew.customSqlSegment}

说明:

${ew.customSqlSegment}:拼接 where 后的语句(在动态sql中请勿处于 标签内)
${ew.sqlSelect}:拼接 select SQL 主体
${ew.sqlSet} :拼接 update 主体
${ew.sqlSegment} : 拼接where 后的语句

多表分页查询:

提示:这里需要分页设置

准备工作:
定义需要的数据库sql
CREATE TABLE `orders` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `price` int(11) DEFAULT NULL COMMENT '价格',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注',
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `version` int(11) DEFAULT '1' COMMENT '版本',
  `del_flag` int(1) DEFAULT '0' COMMENT '逻辑删除标识,0-未删除,1-已删除',
  `create_by` varchar(100) DEFAULT NULL COMMENT '创建人',
  `update_by` varchar(100) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `orders` */

insert  into `orders`
(`id`,`price`,`remark`,`user_id`,`update_time`,`create_time`,`version`,`del_flag`,`create_by`,`update_by`) values 
(1,2000,'无',2,'2022-08-24 21:02:43','2022-08-24 21:02:46',1,0,NULL,NULL),
(2,3000,'无',3,'2022-08-24 21:03:32','2022-08-24 21:03:35',1,0,NULL,NULL),
(3,4000,'无',2,'2022-08-24 21:03:39','2022-08-24 21:03:41',1,0,NULL,NULL);
com.xuguoguo.entity 包下的实体类:Orders.Java
@Data
@NoArgsConstructor
@AllArgsConstructor
// 因为设置了全局的表前缀 tb_ 为了方便操作,@TableName 指定表;
@TableName("orders")
public class Orders {
    // Orders 表属性:
    private Long id;
    private Integer price;
    private String remark;
    private Integer userId;
    private LocalDateTime updateTime;
    private LocalDateTime createTime;
    private Integer version;
    private Integer delFlag;

    // 多表查询,User表扩展属性;
    // MP 默认的CRUD 不对该属性进行sql映射,自定义Mapper 可以通过同名/取别名 自动映射;
    @TableField(exist = false)
    public String userName;
}
多表 sql 准备:

在这里插入图片描述

OrdersMapper.Java
  • 因为是多表分页,所以需要自定义sql 和 Mapper接口,自定义分页需要传入参数 Page并且返回类型也是 Page
@Mapper
public interface OrdersMapper extends BaseMapper<Orders> {
    // 自定义多表分页方法
    public Page<Orders> selPageOrdUs(Page<Orders> page);
}
② 定义OrderMapper.xml 映射文件
OrdersMapper.xml

```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 -->
<mapper namespace="com.xuguoguo.mapper.OrdersMapper">
<!-- MP多表分页 -->
<select id="selPageOrdUs" resultType="com.xuguoguo.entity.Orders">
    SELECT
        o.*,u.`user_name` as userName
    FROM  orders o
    INNER JOIN tb_user u
    ON u.id = o.user_id
</select>

</mapper>
③ 编写测试程序:
MPTest.Java
/** selectPage(); Mapper 多表分页 */
//定义 OrdersMapper 对象;
@Autowired        
OrdersMapper ordersMapper;

@Test
public void testselPageOrdUs(){ 
    // 定义MP 分页对象,并设置分页属性
    Page<Orders> ordersPage = new Page<>();
    // 每页行,当前页
    ordersPage.setSize(2);
    ordersPage.setCurrent(1);

    // 进行查询,返回分页对象, 因为: 引用类型的实参改变形参会受影响~所以, ordersPage == ordersIPage
    Page<Orders> ordersIPage = ordersMapper.selPageOrdUs(ordersPage);
    System.out.println("ordersPage == ordersIPage是否相等:"+(ordersPage == ordersIPage));
    // 返回的结果
    System.out.println("获取总记录数");
    System.out.println(ordersIPage.getTotal());
    System.out.println("获取当前页的数据");
    System.out.println(ordersIPage.getRecords());
}
④ 运行测试:
ordersPage == ordersIPage是否相等:true
获取总记录数
3
获取当前页的数据
[Orders(id=1, price=2000, remark=, userId=2, updateTime=2022-08-24T21:02:43, createTime=2022-08-24T21:02:46, version=1, delFlag=0, userName=admin), Orders(id=3, price=4000, remark=, userId=2, updateTime=2022-08-24T21:03:39, createTime=2022-08-24T21:03:41, version=1, delFlag=0, userName=admin)]

Service CRUD接口:

MP 也为我们提供了Service 层的接口来完成 CRUD的操作:为什么MP 有了 Mapper接口, 还要 Service接口:
因为:为了方便开发程序现在程序大部分都是 三层架构``Dao持久 Service业务 Controller
控制【MVC架构】而定义的Mapper接口,很多时候又要在Service 里面进行重新调用
而,很多时候Servcie又很简单只能调用了Mapper的方法(); MP
为了简化开发者工作,对Service接口也进行了继承,这样对于一些简单的功能,只需要编写
Controller控制层就可以完成开发,开发者不需要在写简单的Service代码 还有,Service 中有很多是对Mapper
的整合方法();

  • SaveOrUpdate(T entity) 更新记录T 如果不存在,插入一条记录;
  • saveOrUpdate(T entity, Wrapper updateWrapper); 根据条件修改一条数据, 如果没有匹配则删除
  • saveOrUpdateBatch(Collection entityList); 批量修改插入
  • Service 对 Mapper 多了更多的组合批量操作 算是, 节省了开发者的工作量; 官方🐱‍

编写User Service 的CRUD操作:

在使用MP Service 的 CRUD 之前还是需要确保,Mapper 继承 BaseMapper

① 编写 Service

UserService.Java

创建 Service 接口,extends继承 IService<操作的实体类T>
IService类中,定义了Service的很多批量CRUD方法~

// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {   

}
② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类> 
// 因为是 Service的实现, 实现对应的接口 implement Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {   


}
③ 测试:

MPTest.Java

/** Service 的CRUD **/
// 从Spring容器中获取Service对象;
@Autowired
UserService userService;
// 只获取匹配的第一条数据;
@Test
public void testGetOne(){
    LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
    wrapper.gt(User::getAge, 28);
    User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异常,而返回第一条记录
    System.out.println(one);
}

自定义 Service

就是正常的引用 Mapper

① 编写 Service

UserService.Java

// Service接口 继承MP的 IService<T>  T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {
    //自定义Service 实现:
    List<User> selectAll();
}
② 编写 Service 实现 ServiceImpl

UserServiceImpl.Java

// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类>
// 因为是 Service的实现, 实现对应的接口 implement Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    // 创建UserMapper 对象实例;
    @Autowired
    UserMapper userMapper;

    @Override
    public List<User> selectAll() {
        // 直接调用Mapper 的查询全部~ 当然Service业务逻辑层,可以写很多更加复杂的操作...
        return userMapper.selectList(null);
    }
}

MP 的代码生成器:

MP提供了一个代码生成器,可以让我们一键生成实体类,Mapper接口,Service,Controller等全套代码

  • 只需要点击生成:实体entity、mapper、service、controller
① 添加依赖
<!-- Mybatsi-Plus 代码生成器依赖: -->
<!--mybatisplus代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<!--模板引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>
② 添加生成类,运行生成文件

一般情况下,可以将这个文件放在 项目Util 包下,作为一个工具类使用:甚至,可以不声明在项目中,因为它可以指定 代码生成的地址…创建完成之后,将需要的东西拖到项目中也可以

MyGeneratorUtil.Java

public class MyGeneratorUtil {
    @Test
    public void generate() {
        AutoGenerator generator = new AutoGenerator();
        
        // 全局配置
        GlobalConfig config = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        
        // 设置输出到的目录: 可以更改为任何路径 D盘 C盘...
        // config.setOutputDir("D:/MP");
        config.setOutputDir(projectPath + "/src/main/java");
        // 生成的作者名
        config.setAuthor("xuguoguo");
        // 生成结束后是否打开文件夹
        config.setOpen(false);
        // 全局配置添加到 generator 上
        generator.setGlobalConfig(config);
        // 数据源配置: 配置自己的数据库要生成的数据库 用户/密码 
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/plus?characterEncoding=utf-8&serverTimezone=UTC");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("123");
        // 数据源配置添加到 generator
        generator.setDataSource(dataSourceConfig);

        // 包配置, 生成的代码放在哪个包下
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.xuguoguo");
        // 包配置添加到 generator
        generator.setPackageInfo(packageConfig);
        
        // 策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        // 下划线驼峰命名转换
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        // 开启lombok,生成的实体类上面就会又 lombok注解;
        strategyConfig.setEntityLombokModel(true);
        // 开启RestController
        strategyConfig.setRestControllerStyle(true);
        generator.setStrategy(strategyConfig);
        generator.setTemplateEngine(new FreemarkerTemplateEngine());

        // 开始生成
        generator.execute();
     }
}

MP高级 自动填充

在实际项目中表不仅仅会有开发中需要的功能字段有时候还会需要很多的附属字段:

更新时间 创建时间 创建人 更新人 逻辑删除列 乐观锁Version 备用1 备用2…

而这些字段需要我们手动进行维护会很麻烦,每个数据新增 修改都要进行手工维护;MP 提供了 自动填充 来完成对这些数据的操作

① 在需要操作的实体上, 添加 @TableFieId注解

在对应字段上增加注解,@TableFieId 的 fill属性来设置字段的自动填充; Orders为例子fill 属性:是一个枚举FieldFill

  • DEFAULT默认值无任何处理
  • INSERT新增触发
  • UPDATE修改时触发
  • INSERT_UPDATE 新增或修改时触发
② 编写适配器:

MetaObjectHandler.Java

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 新增时候触发,并设置新增时候对应数据要赋的值
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }

    // 修改时候触发,并设置修改时候对应数据要赋的值
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}
最新的版本的编写方式可以参考官网中的示例即可

https://baomidou.com/pages/4c6bcf/

在这里插入图片描述

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 或者
        this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
        // 或者
        this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }
}
③ 测试:
/** MP 自动填充: **/
@Test
public void testinsertOrd(){
    // 创建新增对象
    Orders orders = new Orders();
    orders.setPrice(1000);
    orders.setRemark("无");
    orders.setUserId(2);
    // 执行新增
    int insert = ordersMapper.insert(orders);
    if(insert>0)
        System.out.println("新增成功");
    else
        System.out.println("新增失败");
}

MP高级 逻辑删除

一般企业的数据都是不允许真实删除的,这样后面找都不好找所以
很多公司的数据库都会添加一个字段逻辑删除,用来判断数据是否删除,一般只有两个值:0|1

  • 0: 就是正常的数据
  • 1: 就算被删除的数据,并不会真的删除,而是查询时候默认就不查询 1 的数据;
实现:
  • 通常逻辑删除 只需要在 yml 配置一下即可

注意:3.3.0版本之前还需要在对应的字段上加上@TableLogic注解

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted      # 全局逻辑删除的实体字段名, 3.3.0配置后可以不添加注解,之前的还需要添加注解 @Tablelogic)
      logic-delete-value: 1         # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0     # 逻辑未删除值(默认为 0)
测试:
/** MP 逻辑删除 **/
@Test
public void delOrd(){
    // 配置了逻辑删除之后,直接调用MP 的删除方法就是进行逻辑删除了! 注意: 自定义sql的操作还需要自己完成注意!
    int i = ordersMapper.deleteById(1);
    if(i>0)
        System.out.println("逻辑删除成功");
    else
        System.out.println("逻辑删除失败");
}

MP高级 乐观锁

为了避免多线程情况下数据紊乱需要对数据进行加锁,而当多个线程同时操作一个数据时候可能出现数据被重复修改的情况 典型的冲突

  • 丢失更新
    一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失用户A把值从8改为6,用户B把值从6改为8,则用户A丢失了他的更新
  • 脏读
    当一个事务读取其它完成一半事务的记录时,就会发生脏读取,用户A,B看到的值都是8,用户B把值改为6,用户A读到的值仍为8
乐观锁 和 悲观锁
乐观锁:

比较乐观 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题

通常使用数据版本(Version)记录机制实现

通过为数据库表增加一个数字类型的 “version” 字段来实现,当读取数据时,将version字段的值一同读出,`数据每更新一次,对此version值加一

Update set version=version+1 where version = version 每次更新前都要判断传入的版本是否是现在最新版本!

  • A B 同时要更新数据 1 都获取了version版本 1
  • A 先更新:Update set version=version+1 where version = 1 version就是1 所以更新成功!
  • B 在更新:Update set version=version+1 where version = 1 version已经被A +1 所以version 是2 where 2=1 不成立 B更新失败
悲观锁:

比较悲观,认为一定会发送数据改变在对数据库的一条记录进行修改时,先直接加锁(数据库的锁机制),锁定这条数据,然后再进行操作. 同一时间只能,允许一个人来修改这条数据!

总结:

在读多写少的场景下,乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量
在写多读少的场景下,悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降

MP 实现:
① 配置乐观锁插件

参考官网编写即可:https://baomidou.com/pages/0d93c0/#optimisticlockerinnerinterceptor

在这里插入图片描述

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
② 在实体类中表示版本的字段上添加注解@Version

Orders.Java 省略其它未更改代码;

// 添加乐观锁的注解,使用MP 的方式实现乐观锁 保证数据安全;
// version字段,类型只支持int,long,Date,Timestamp,LocalDateTime
@Version
private Integer version;
③ 操作:

注意:
在更新前我们一定要先查询到version设置到实体类上再进行更新才能生效 传入的对象一定要携带 Version列有值 乐观锁插件仅支持updateById(id)与update(entity, wrapper)方法wrapper不能复用!会出现重复参数使用;

MPTest.Java

/** MP 乐观锁 */
// 操作前要去抱必须获取到数据最新的 version: 先查询在修改:
@Test
public void testupdVer(){
    // 先查询:
    Orders orders = ordersMapper.selectById(1);
    // 设置更新列
    orders.setPrice(123);
    System.out.println(orders);

    // 修改: 乐观锁插件仅支持`updateById(id)`与`update(entity, wrapper)`方法
    int i = ordersMapper.updateById(orders);
    if(i>0)
        System.out.println("修改成功");
    else
        System.out.println("修改失败");
}

小结

提示:本章节内容主体是讲解在使用数据库持久层的mp(MybatisPlus)框架的常用的应用功能,分页、自动填充、代码生成、逻辑删除、乐观锁等

多学一招,偷偷的努力,然后惊艳所有人!!!卷就完了……

本小节完毕,敬请期待后续更新(可留言需要学习哪方面的内容哈)!如果需要源码或者工具的朋友们可关注微信公众号"锅锅编程生活"或者扫描二维码关注回复关键字/后台留言获取即可!

在这里插入图片描述

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值