0
点赞
收藏
分享

微信扫一扫

MyBatisPlus学习笔记 学习使用看这一篇就够了

jjt二向箔 2022-04-30 阅读 48

文章目录

前言

本篇博客是MyBatisPlus实际应用的学习笔记,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

导航

MyBatis-Plus



工具接口及类

BaseMapper

BaseMappermp提供给我们用于增强mapper接口方法:覆盖了大量的针对于单表的查询操作

public interface BaseMapper<T> extends Mapper<T> {
    //新增
    int insert(T entity);
	
    int deleteById(Serializable id);

    //根据字段删除
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);

    int delete(@Param("ew") Wrapper<T> wrapper);

    //批量删除
    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    //单条更新
    int updateById(@Param("et") T entity);

    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);

    //查询多条记录根据属性字段
    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);

    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);
}


IService与ServiceImpl(接口定义与实现)

下面是ServiceImpl给我们实现的抽象类方法

image-20210823093857486

你能想到的单表查询几乎都有。

public interface IService<T> {
    int DEFAULT_BATCH_SIZE = 1000;

    default boolean save(T entity) {
        return SqlHelper.retBool(this.getBaseMapper().insert(entity));
    }

    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean saveBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 1000);
    }

    boolean saveBatch(Collection<T> entityList, int batchSize);

    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return this.saveOrUpdateBatch(entityList, 1000);
    }

    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

    default boolean removeById(Serializable id) {
        return SqlHelper.retBool(this.getBaseMapper().deleteById(id));
    }

    default boolean removeByMap(Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty", new Object[0]);
        return SqlHelper.retBool(this.getBaseMapper().deleteByMap(columnMap));
    }

    default boolean remove(Wrapper<T> queryWrapper) {
        return SqlHelper.retBool(this.getBaseMapper().delete(queryWrapper));
    }

    default boolean removeByIds(Collection<? extends Serializable> idList) {
        return CollectionUtils.isEmpty(idList) ? false : SqlHelper.retBool(this.getBaseMapper().deleteBatchIds(idList));
    }

    default boolean updateById(T entity) {
        return SqlHelper.retBool(this.getBaseMapper().updateById(entity));
    }

    default boolean update(Wrapper<T> updateWrapper) {
        return this.update((Object)null, updateWrapper);
    }

    default boolean update(T entity, Wrapper<T> updateWrapper) {
        return SqlHelper.retBool(this.getBaseMapper().update(entity, updateWrapper));
    }

    @Transactional(
        rollbackFor = {Exception.class}
    )
    default boolean updateBatchById(Collection<T> entityList) {
        return this.updateBatchById(entityList, 1000);
    }

    boolean updateBatchById(Collection<T> entityList, int batchSize);

    boolean saveOrUpdate(T entity);

    default T getById(Serializable id) {
        return this.getBaseMapper().selectById(id);
    }

    default List<T> listByIds(Collection<? extends Serializable> idList) {
        return this.getBaseMapper().selectBatchIds(idList);
    }

    default List<T> listByMap(Map<String, Object> columnMap) {
        return this.getBaseMapper().selectByMap(columnMap);
    }

    default T getOne(Wrapper<T> queryWrapper) {
        return this.getOne(queryWrapper, true);
    }

    T getOne(Wrapper<T> queryWrapper, boolean throwEx);

    Map<String, Object> getMap(Wrapper<T> queryWrapper);

    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

    default int count() {
        return this.count(Wrappers.emptyWrapper());
    }

    default int count(Wrapper<T> queryWrapper) {
        return SqlHelper.retCount(this.getBaseMapper().selectCount(queryWrapper));
    }

    default List<T> list(Wrapper<T> queryWrapper) {
        return this.getBaseMapper().selectList(queryWrapper);
    }

    default List<T> list() {
        return this.list(Wrappers.emptyWrapper());
    }

    default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
        return this.getBaseMapper().selectPage(page, queryWrapper);
    }

    default <E extends IPage<T>> E page(E page) {
        return this.page(page, Wrappers.emptyWrapper());
    }

    default List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper) {
        return this.getBaseMapper().selectMaps(queryWrapper);
    }

    default List<Map<String, Object>> listMaps() {
        return this.listMaps(Wrappers.emptyWrapper());
    }

    default List<Object> listObjs() {
        return this.listObjs(Function.identity());
    }

    default <V> List<V> listObjs(Function<? super Object, V> mapper) {
        return this.listObjs(Wrappers.emptyWrapper(), mapper);
    }

    default List<Object> listObjs(Wrapper<T> queryWrapper) {
        return this.listObjs(queryWrapper, Function.identity());
    }

    default <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
        return (List)this.getBaseMapper().selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
    }

    default <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper) {
        return this.getBaseMapper().selectMapsPage(page, queryWrapper);
    }

    default <E extends IPage<Map<String, Object>>> E pageMaps(E page) {
        return this.pageMaps(page, Wrappers.emptyWrapper());
    }

    BaseMapper<T> getBaseMapper();

    default QueryChainWrapper<T> query() {
        return ChainWrappers.queryChain(this.getBaseMapper());
    }

    default LambdaQueryChainWrapper<T> lambdaQuery() {
        return ChainWrappers.lambdaQueryChain(this.getBaseMapper());
    }

    default UpdateChainWrapper<T> update() {
        return ChainWrappers.updateChain(this.getBaseMapper());
    }

    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return ChainWrappers.lambdaUpdateChain(this.getBaseMapper());
    }

    default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
        return this.update(entity, updateWrapper) || this.saveOrUpdate(entity);
    }
}


IPage与Page

image-20210817011208095

在Page对象中,包含了关于分页的相关信息,其中包含对应的查询结果集records以及对于分页的相关属性:

image-20210817011240481

可以说满足了基本的需求了。



一、springboot集成Mybatis plus实现CRUD

Mapper CRUD 接口

image-20210816231056986

引入依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

yml配置项:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mybatisplusExer?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    # 启动时需要初始化的建表sql语句
    schema: classpath:sql/schema.sql
    # 执行好就修改为never
    initialization-mode: always  # always为始终执行初始化,embedded只初始化内存数据库(默认值),如h2等,never为不执行初始化

#控制台打印sql(默认不会有打印sql语句)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 默认开启时会进行pojo字段映射如:lastName => last_name
    mapUnderscoreToCamelCase: false   # 取消自动驼峰命名规则映射(默认是开启的),这里仅仅是用于测试

sql

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

pojo

/**
 * @ClassName User
 * @Author ChangLu
 * @Date 2021/8/16 22:11
 * @Description TODO
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {  //mp会默认将pojo类名当做表名(首字母小写),若是类名与表名不一致可使用注解
    //默认会识别id作为主键,这里是对id自动生成进行设置: IdType.AUTO为自动,还有其他很多策略
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    // 设置表字段别名(当表属性不一致时即可进行指定)  默认是自动开启别名转换的(这里只是为了熟悉使用)
    @TableField("last_name")
    private String lastName;  //建议pojo字段名与表名像lastName=>last_name要对应,否则使用(如name=>last_name)包装器select()查询返回赋值会无效。
    private Integer age;
    private String email;

}
  • 默认mybatis-plus中会将你的pojo类自动进行转换,如字段lastName,之后来查询数据库会自动转为last_name来进行查询。
  • 若是你插入一条数据的id(mp默认会认为你的id为主键id)没有设置,mp会自动帮你生成一个全局唯一的id(字符串)!并且你插入后该对象就会得到对应自动生成的id值。

UserMapper

/**
 * @ClassName UserMapper
 * @Author ChangLu
 * @Date 2021/8/16 22:11
 * @Description TODO
 */
public interface UserMapper extends BaseMapper<User> {
}

springboot启动类上标注自动扫描mapper包:

@MapperScan("com.changlu.mybatisplusexer.mapper")

这里暂时没有Wrapper相关的操作,只是进行了对selectByMapdeleteByMap以及其他普通的测试用例,其是根据传入map的属性来进行字段查询的,可设置多个字段,这里仅有一个字段。

特别说明的是:mp中对于查询的键值对,传入与数据库中不同类型的也可以兼容,真的是强大呀,例如数据库中的age字段为int类型,你设置map属性字段中传入put(“age”,“20”)也完全是ok的。

import com.changlu.mybatisplusexer.mapper.UserMapper;
import com.changlu.mybatisplusexer.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootTest
class MybatisplusexerApplicationTests {

    @Resource
    private UserMapper userMapper;

    @Test
    void queryAll() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
    }

    @Test
    void query(){
        System.out.println(userMapper.selectById(1));
    }

    @Test
    void query2(){  //selectByMap测试:根据条件筛选
        Map<String, Object> columnMap = new HashMap<>(1);
        columnMap.put("age",20);//条件为age=20
        List<User> users = userMapper.selectByMap(columnMap);
        System.out.println(users);
    }

    @Test
    void insert(){
        User user = new User(null, "changlu", 18, "939974883@qq.com");
        //执行保存sql前会先生成id到user中
        int result = userMapper.insert(user);
        System.out.println("user=>"+user+",result=>"+result);
    }

    @Test
    public void delete(){
        int result = userMapper.deleteById(1);
        System.out.println(result);
    }

    @Test
    public void delete2(){ //deleteByMap:根据指定字段删除
        Map<String, Object> columnMap = new HashMap<>(1);
        columnMap.put("age","20");//条件为age=20,
        int result = userMapper.deleteByMap(columnMap);
        System.out.println(result);
    }

    @Test
    public void update(){
        User user = new User((long)2, "changlu", 20, "939974883@qq.com");
        int result = userMapper.updateById(user);
        System.out.println(result);
    }

}


二、通用IService使用

在一中介绍的是对mapper接口进行的增强,在mp中还可以对service进行增强,也就是说一些常见的普通的service方法已经帮你进行封账好了,能够更快的进行一系列的操作。

Service CRUD 接口

说明:

//更新操作
//①若是更新某个model实体类,使用updateWrapper一定要进行指定id,否则就会更新整张表
//②若是批量对多个model的某个字段进行更新,调用方法
updateBatchById()
    
save():执行完后返回的是一个布尔值,表示是否插入成功,并且若是你没有填写id,会自动先生成主键到指定实体类中,再进行执行sql保存操作

在示例一的基础上,我们来添加service接口以及service实现类,对应的接口需要实现IService,实现类需要继承一个抽象Service,同样也是mp给我们封装号的对应增强方法接口的实现体。

image-20210817003910217

UserService:service接口定义

public interface UserService extends IService<User> {
}

UserServiceImpl:实现类定义

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

在mp给我们提供的service方法中,对于增删改操作也都包含了对应的事务处理,总体来说还是特别方便的!

@SpringBootTest
class MybatisplusexerApplicationTests {

    @Autowired
    private UserService userService;

    //saveOrUpdate:先根据id查,若是有则进行更新,没有进行插入
    @Test
    public void saveOrUpdate(){
        //测试插入
        User user = new User((long)1,"changlu",18,"9999");
        boolean b = userService.saveOrUpdate(user);//true:成功;false:失败
        System.out.println(b);
    }

    //saveOrUpdate:先根据id查,若是有则进行更新,没有进行插入
    @Test
    public void saveOrUpdate2(){
        //测试更新
        User user = new User((long)1,"liner",22,"368");
        boolean b = userService.saveOrUpdate(user);
        System.out.println(b);
    }

    //listByIds:根据多个id来进行批量查询。(底层采用in方式来进行查询)
    @Test
    public void query(){
        List<User> users = userService.listByIds(Arrays.asList(1, 3, 5));
        System.out.println(users);
    }


}


三、分页查询

3.1中的分页方法只对调用IService里的分页方法才有效!

我们编写请求实现类时,若是进行分页可以直接继承下面的实体类就是表示分页和:

@Data
public class BasePage implements Serializable {
    private static final long serialVersionUID = 8491572200460447988L;
    protected long size = 10;
    protected long current = 1;
}


3.1、借助mp的分页插件

在mp的Iservice中提高了四个分页方法:

image-20210817010654080

在BaseMapper中提供了两个分页方法:

<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);

注意:要想使用mp中的分页功能,就必须需要安装分页插件,否则不会生效!

step1:编写一个mp的配置类,在该配置类中添加分页插件

@Configuration
@MapperScan("com.changlu.mybatisplusexer.mapper")  //使用了分配类,即可将启动器的自动打包放置在这里
public class MybatisPlusConfig {
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));  //设置方言为MySQL
        return interceptor;
    }
}

数据库中有四条记录,我们来对其进行测试:

image-20210817010955168

@SpringBootTest
class MybatisplusexerApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    public void saveOrUpdate() throws JsonProcessingException {
        //当前页与每页数量
        IPage<User> page = new Page<>(1,2);
        //分页查询       
        IPage<User> pageResult = userService.page(page);
        //对象转JSON字符串
        ObjectMapper om = new ObjectMapper();
        String json = om.writeValueAsString(pageResult);
        System.out.println(json);
    }
    
}

image-20210817011403523

可以看到查询了两条sql,一个是查询数量,另一个是进行limit查询操作!

为了方便查看Page的相关属性,我们将其转为JSON字符串并且借助工具网站来美化显示其中的内容:

image-20210817011530643

  • records:分页查询的记录结果。
  • total:总记录数。
  • size:每页显示条数,根据初始定义的Page决定。
  • current:当前页。
  • pages:总共2页。

这样的结果内容,对于前台简直不要太舒服了!!!



3.2、XML 自定义分页

对于我们自定义sql语句,也就是书写xml也可以实现自定义分页!

在mp中我们无需指定mapper位置,因为对应springboot启动器已经为我们设置默认mapper读取位置:

image-20210817224140184

若是我们想要修改mapper映射配置的位置,直接在yml配置中进行配置即可:

mybatis-plus:
  mapper-locations: classpath*:/mapperxxx/**/*.xml

<br/0

image-20210817224742083

直接在resources下的mapper文件夹中编写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.changlu.mybatisplusexer.mapper.UserMapper">
    <select id="queryByAge" resultType="com.changlu.mybatisplusexer.pojo.User">
        select * from Blog where age = #{age}
    </select>
</mapper>

若是想要对xml的查询结果进行分页,那么只需要在mapper中接口返回值以及属性中设置即可!

public interface UserMapper extends BaseMapper<User> {
	//此时就会根据我们传入的page来进行对结果进行分页返回
    IPage<User> queryByAge(Page<?> page,Integer age);
}

测试一下

@Resource
private UserMapper userMapper;

@Test
public void saveOrUpdate() throws JsonProcessingException {
    //当前页与每页数量
    Page<User> page = new Page<>(1,1);
    IPage<User> userIPage = userMapper.queryByAge(page, 22);
    String json = new ObjectMapper().writeValueAsString(userIPage);
    System.out.println(json);
}

image-20210817230540782

根据设置的Page信息,查询出来的分页内容与实际情况符合,分页完成!

image-20210817230518925



3.3、第三方插件:PageHelper

底层使用了一个PageInterceptor分页拦截器

PageHelper文档

引入依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.3</version>
</dependency>

注意一下:若是你同时引入mybatis-plus以及pagehelper启动器依赖,运行时就会报异常如下。

image-20210820113302095

如何解决呢?将对应pagehelper启动器中的指定mybatis依赖移除即可。

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>
</dependencies>

主要就是test方法中的第1以及第3行,即可实现分页效果!

@GetMapping("/")
public ResultBody test(@RequestParam("pagenum")Integer pageNum,@RequestParam("pagesize")Integer pageSize){
    PageHelper.startPage(pageNum,pageSize);//设置起始页以及每页数量  
    List<User> users = userMapper.selectList(null);//此时底层对于select就是进行分页来得到的结果集
    PageInfo<User> userPageInfo = new PageInfo<>(users);//封装PageInfo对象
    return ResultBody.success(userPageInfo);
}

image-20210820133749697

最终拿到的返回值对象:其中包含了很多的关于分页的属性,前端判断就更加容易了

image-20210820133826937



四、条件构造器wrapper

相关条件构造器

条件构造器:包含所有条件信息超详细。

eq:=

in:in ()

insql:in (sql)

like:like ‘%xx%’

强烈推荐使用lambda形式来筛选出指定字段!



4.1、QueryWrapper(独有select)

20210409160809914

介绍一下普通绑定属性以及lambda形式(推荐)绑定:

@SpringBootTest
class MybatisplusexerApplicationTests {

    @Resource
    private UserMapper userMapper;

    @Test
    public void select() throws JsonProcessingException {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //静态绑定形式
//        queryWrapper
                .select("last_name","email")
                .eq("age",18);
        //lambda表达式(可动态绑定指定字段)  => SELECT last_name AS lastName,email FROM user WHERE (age = ?)
        queryWrapper.lambda()
                .select(User::getLastName,User::getEmail)
                .eq(User::getAge,18);
        List<User> users = userMapper.selectList(queryWrapper);
        System.out.println(users);
    }
}

image-20210818000354774


@Resource
private UserMapper userMapper;

@Test
public void select() throws JsonProcessingException {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //子查询 and 模糊查询  => SELECT id,last_name AS lastName,age,email FROM user WHERE (age IN (18,22) AND email LIKE ?)  默认多个条件时就是使用and连接!
    queryWrapper.lambda()
        .inSql(User::getAge,"18,22")   //子查询
        .like(User::getEmail,"baomidou.com");  //模糊查询
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

image-20210818001805192



4.2、UpdateWrapper(独有set方法)

强烈推荐使用lambda表达式:因为只要你对应实体类字段没有写错,就不会有问题其是动态的

@Resource
private UserService userService;

@Test
public void select() throws JsonProcessingException {
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //采用lambda表达式   => UPDATE user SET last_name=? WHERE (id = ?)
    updateWrapper.lambda()
        .set(User::getLastName,"changlu")  //更新某个字段
        .eq(User::getId,1);
    userService.update(updateWrapper);//配合service层
}


五、全局id生成策略

我们可以通过全局yml配置的形式来为主键id设置生成策略:

# mp配置
mybatis-plus:
  global-config:
    db-config:
      id-type: auto  # 全局配置

单表指定字段设置:

@TableId(value = "id",type = IdType.AUTO)

注意点:若是设置id策略为自增,那么一定要在数据库的指定表中的主键id字段勾选自动递增,不然的话mp插入报异常,一定要注意下这个点。

image-20210818072630985



六、逻辑删除

逻辑删除:在指定的表中添加一个字段默认为1,当我们想要删除该表的某条记录时,将该字段标注为0表示删除,并不是真正的将该条记录给删除掉。

mp中配置逻辑删除十分简单:

  1. 表中添加任意一个字段,设置为int类型,默认1表示未删除。
  2. 实体类中对该字段添加@TableLogic注解,可以单独对该注解进行配置来描述未删除、已删除分别的状态表示。也可进行全局配置。
  3. 配置好注解后,我们使用mp给我们在mapper或service的增强方法的select与remove时都会执行逻辑删除操作!

step1:表中添加字段,并且设置默认值为1

image-20210818225741703

step2:可以进行局部配置(针对单表)或者全局配置(针对所有的表)

局部配置

@TableLogic(value = "1",delval = "0")  //局部配置逻辑删除,未删除为1,删除为0
private Integer deleted;

全局配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted   # 配置逻辑删除的字段
      logic-delete-value: 0   # 删除标注1
      logic-not-delete-value: 1  # 未删除标注0

测试:我们来进行删除与查询操作

①插入

@Resource
private UserService userService;

@Test
public void save() throws JsonProcessingException {
    User user = new User(null, "xiaotian", 18, "86547528@qq.com");
    userService.save(user);
}

由于我们对deleted设置了默认值,所以我们插入对应实体类时不设置该字段也会给我们自动添加上默认值1,并且其中id为自动生成。

image-20210818230914843

②删除:也就是逻辑删除,一旦我们配置好@TableLogic执行删除操作就都是逻辑删!

@Test
public void update() throws JsonProcessingException {
    //逻辑删除:删除刚刚新增的记录
    userService.remove(new QueryWrapper<User>().lambda().eq(User::getLastName,"xiaotian"));
}

image-20210818231236626

image-20210818231249639

mp的删除操作底层实际上就是对指定表示字段置为删除状态 ,也就是执行一个更新字段的操作!

③查询:设置逻辑删除的表在进行查询时,就会自动过滤掉已经是删除状态的记录

@Test
public void select() throws JsonProcessingException {
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

image-20210818231652592

额外说明:一般对于某个表设置逻辑删除时,正常层面业务中是不会执行物理删除操作的,若是想要进行物理删除操作呢,就只能我们来编写xml的sql文件来进行删除了!



七、自动填充

对于数据库表,我们需要自动填充的字段包含:id, gmt_create, gmt_modified,后两个是创建与更新时间,创建是在插入时自动填充,而更新是在插入与修改时进行更新!

有两种方式来实现自动填充:

①数据库层面

对于日期字段可以设置默认值,在mysql5.7版本可以设置根据当前时间戳更新,而在5.5不能设置时间戳更新。

image-20210819004405058

CURRENT_TIMESTAMP

设置好以后,进行插入或者更新时我们不传参则会进行自动更新时间戳!


②mp提供的自动填充

首先是指定自动填充字段设置注解

@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

编写handler执行器,在进行插入与更新时来对指定标注了注解执行填充操作,插入时对两个字段都进行填充,更新时只对修改字段进行填充:

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        //第一个是更新的字段名;第二个为更新的值;第三个为指定的原对象
        this.setFieldValByName("gmtCreate",new Date(),metaObject);
        this.setFieldValByName("gmtModified",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.setFieldValByName("gmtModified",new Date(),metaObject);
    }
}

测试:

@Test
public void save() throws JsonProcessingException {
    User user = new User(null, "xiaomei", 20, "86547528@qq.com");
    userService.save(user);
}

@Test
public void update() throws JsonProcessingException {
    User user = new User(null, "mining", 28, "86547528@qq.com");
    userService.saveOrUpdate(user,new QueryWrapper<User>().lambda().eq(User::getLastName,"xiaomei"));
}

下面是更新时的sql打印:

image-20210819005854433

此时我们就通过在代码层面实现了自动填充的效果!而不是直接在数据库上进行操作。



八、执行 SQL 分析打印

效果:

image-20210819122426272


①引入依赖

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>

②yml配置数据源:

driver-class-name: com.p6spy.engine.spy.P6SpyDriver  # 设置为p6spy的驱动
# jdbc:p6spy:前缀包含p6spy
url: jdbc:p6spy:mysql://127.0.0.1:3306/mybatisplusExer?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

image-20210819122628182

③在resource目录下新建spy.properties文件:

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

此时就已经配置完成,我们执行sql语句即可出现对应效果!

说明:在生产环境不建议进行分析配置,有可能会影响性能!

再介绍一个IDEA插件:Mybatis-log,对于执行的sql语句会在一个小窗口显示:

image-20210819235523303

image-20210819235510636



九、代码生成器(学习)

引入依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.0</version>
</dependency>
<--  辅助 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

效果:根据指定的数据库来进行代码自动生成

image-20210820004625832


重点关注这几行:

  1. 25行设置指定数据库。
  2. 35行指定生成的目录文件名称。
  3. 36行指定的包路径,根据对应的项目。
  4. 54行,过滤数据库表的前缀,如一些t_blog、t_user我们在生成对应的实体类时是不需要这些前缀的此时就可以进行过滤。
   public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");//默认为当前工程路径,如D:\workspace\workspace_idea\mybatisplusexer
        gc.setOutputDir(projectPath + "/src/main/java");//设置输出文件路径
        gc.setAuthor("changlu");
        gc.setOpen(false);//代码生成是不是要打开文件夹
        gc.setSwagger2(true); //生成Swagger2 注解
        gc.setBaseResultMap(true);//每个mapper文件中都生成通用结果映射集
        gc.setFileOverride(true);//下次生成文件时进行覆盖(不设置的话当进行第二次生成就会在同一个目录产生相同的文件)
//        gc.setEnableCache(true);//开启二级缓存
        //设置对应文件名称
        gc.setEntityName("%sEntity");//生成实体类文件名,如:%sEntity 生成 UserEntity
        gc.setMapperName("%sMapper");//生成dao,这里我们配置生成如 UserMapper
        gc.setMapperName("%sMapper");//生成mapper.xml文件,这里生成如 UserMapper.xml
        gc.setServiceName("%sService");//生成service接口
        gc.setServiceImplName("%sServiceImpl");//生成service实现类
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        //生成的模块名,也就是生成目标指定文件名的路径下
//        pc.setModuleName(scanner("模块名"));
        pc.setModuleName("blog");
        pc.setParent("com.changlu.mybatisplusexer");
        mpg.setPackageInfo(pc);


        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);//表名生成策略:下划线转驼峰,如pms_pro=>PmsPro
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//字段名生成策略,同上,如last_name=>lastName
//        strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);//支持lombok
//        strategy.setRestControllerStyle(true);//controller中设置@RestController
        // 公共父类
//        strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        //要生成的表名 多个用逗号分隔   若是想要按照前缀生成多张表:strategy.setLikeTable(new LikeTable("pms_"))
//        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        //默认控制器映射配置为:pms_pro=> controller @ReuqestMapping("/pms/pmsProduct")  也就是/模块名/表名
//        strategy.setControllerMappingHyphenStyle(true);//驼峰转连字符,如pms_pro=> controller @ReuqestMapping("/pms/pms_product")
        //设置表的替换前缀:这里就是起到过滤作用
        strategy.setTablePrefix("t_");
        strategy.setEntityTableFieldAnnotationEnable(true);//生成字段注解
        mpg.setStrategy(strategy);
//        mpg.setTemplateEngine(new FreemarkerTemplateEngine());  //根据默认模板生成
        mpg.execute();
    }


插件

1、乐观锁使用

乐观锁:一直保持乐观,总认为其他人没有抢占锁,通常使用一个version版本控制字段来保证更新的原子性,保证其是没有被干扰的。对于乐观锁是很容易产生失败的情况,其是非阻塞的。

悲观锁:一直保持着悲观的心态,当操作一个公共资源的前会去拿锁,若是锁已被别人先拿走就会进入阻塞状态,会等待锁释放,一旦释放抢占锁,来进行操作。


step1:给表添加版本控制字段

image-20210819160358834

@Version
private Integer version;

step2:配置插件,在第9行进行添加插件

@Configuration
@MapperScan("com.changlu.mapper")  //使用了分配类,即可将启动器的自动打包放置在这里
public class MybatisPlusConfig {
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));  //分页插件,设置方言为MySQL
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//添加乐观锁插件
        return interceptor;
    }
}

此时我们就配置完成了,使用也十分简单,当我们想要对某条记录进行修改时,先查询,接着进行更新,mp就会自动为我们实现底层的判断version字段操作。

乐观锁实现过程

  1. 取出记录时,获取当前version。
  2. 新时,带上这个version。
  3. 执行更新时, set version = newVersion where version = oldVersion。
  4. 如果version不对,就更新失败。

若是我们自己手写实现乐观锁应该是这样的:①查询指定字段,将对应version字段也查询出来。②执行更新业务操作。③业务操作完成后进行更新,我们需要额外带上更新判断条件version=xxx,这个xxx就是我们初始查询出来version。

mp中就很简单了,你只要在更新前先查一下拿到对应的实体类(该实体类中有version字段嘛),接着调用mp提供给我们的update方法,我们无需自己去考虑version的判断,mp会自动来为我们进行添加version的更改逻辑。

测试

@Test
public void test(){
    User user = userMapper.selectById("2");//模拟线程1
    User user2 = userMapper.selectById("2");//模拟线程2
    user.setUserName("xiaoxiao");//线程1执行业务操作
    user2.setUserName("dada");//线程2执行业务操作

    System.out.println(userService.updateById(user));//进行更新字段,此时数据库中该条记录version+1
    System.out.println(userService.updateById(user2));//这里再进行更新时由于version版本不匹配就会失败!
}

image-20210819161918282

总结

  1. 当你某条记录使用update()时,该实体类的内部属性必须要有version字段值,若是为nullnamemp就不会走乐观锁实现了。
  2. 我们可以通过自己填充或者查询数据库的方式来得到实体类(推荐使用selectById先拿到),接着调用mp的update()即可实现乐观锁。

注意点:当你在mp中执行update()更新操作时,它会对该实体类的version进行加1,若是重试多次就会依次累加去与数据库比对

@Test
public void test(){
    User user = new User((long)2, "hello", 22, "test",1);
    User user2 = new User((long)2, "hello2", 22, "test",1);
    //进行更新操作(2次)
    boolean b;
    b  = userService.updateById(user);
    user.setVersion(2);
    b  = userService.updateById(user);
    boolean b2;
    int bb2 = 1;
    //进行重试操作:重试时,mp会先将该实体类中的version字段进行自增之后再进行比对数据库(直至插入成功)
    do{
        b2  = userService.updateById(user2);
        System.out.println("bb2:"+bb2+++",user2:"+user2);
    }while(!b2);
}

image-20210819170827525

搞了好久,这里仅仅是用调用接口的角度去推测内部的实现操作,未来之后进行深入源码底层来去探究实现!



实际业务

1、查询指定记录中的某个字段

image-20210817101018180

@Resource
private UserMapper userMapper;

@Test
public void testSelect() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT name FROM user WHERE (name = ?)
    queryWrapper.select("name").eq("name","Jone");
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

image-20210817101507388


@Resource
private UserMapper userMapper;

@Test
public void testSelect() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT id,name FROM user  ,省略掉了age与email
    queryWrapper.select(User.class, info -> !info.getColumn().equals("age")
                        && !info.getColumn().equals("email"));
    List<User> users = userMapper.selectList(queryWrapper);
    System.out.println(users);
}

image-20210817101352695

注意:我测试了去除id,最终还是给我查出来id了!可能是主键原因。



参考文章

[1]. 关于MybatisPlus使用@TableId(value = “id“, type = IdType.AUTO)注解使主键id自增长无效的问题

[2]. SpringBoot整合MyBatis-Plus各种使用点超级详细:非常详细,包含分页插件、自动填充数据、逻辑删除、乐观锁实现

[3]. MyBatis Plus——分页插件【PaginationInnerInterceptor】:基本分页使用

[4]. SpringBoot Mybatis 乐观锁重试机制代码实现:重试机制实现

MyBatisPlus最新2021完整教程通俗易懂 √,已学习

举报

相关推荐

0 条评论