MyBatisPlus-入门

324 阅读6分钟

环境搭建

  1. 创建数据库

)_FKHA.png

  1. 创建对应实体类User
  2. 导入依赖
<dependencies>
    <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.3.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.3.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

  1. 配置属性
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
  1. 实现接口
//BaseMapper 默认提供了一系列的增删改查的基础理操作
@Repository
public interface UserMapper extends BaseMapper<User> {
}
  1. 修改启动类
@SpringBootApplication
@MapperScan("com.mapper") //指向mapper所在的包
public class plus_01Application {
    public static void main(String[] args) {
        SpringApplication.run(plus_01Application.class,args);
    }
}
  1. 配置日志 在原有的配置文件中添加以下代码即可
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

测试

class DemoApplicationTests {
    @Autowired
    private UserMapper userMapper;
    @Test
    void contextLoads() {
        //List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
        //queryWrapper 实体对象封装操作类(可以为 null)
        //selectList根据传入的条件进行查询,这里传入null即查询全部
        List<User> users = userMapper.selectList(null);
        for (User user:users)
            System.out.println(user);
    }
}

结果

MDA.png

CRUD与拓展

插入操作

    @Test
    public void testInsert(){
        User u=new User();
        u.setId(7);
        u.setEmail("baomidou.com");
        u.setName("jony");
        userMapper.insert(u);
    }

主键生成策略

分布式系统唯一id生成: www.cnblogs.com/haoxinyue/p…

雪花算法:

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看github.com/twitter/sno…

public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string),
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER_STR(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_UUID}
     */
    @Deprecated
    UUID(4);

    private final int key;

    IdType(int key) {
        this.key = key;
    }
}

/*
     * 主键类型
     * {@link IdType}
    IdType type() default IdType.NONE;*/
//我们通过@TableId来指定主键的生成方式,设置主键自增需要在数据库也配置为自增
    @TableId(type = IdType.AUTO)
    private int id;

更新操作

update 没有进行动态sql,解决在文末

MybatisPlus会自动帮我们进行sql的拼接

    @Test
    public void testUpdate(){
        User u=new User();
        u.setId(7);
        u.setEmail("@baomidou.com");
        //参数是对象类型的
        //int updateById(@Param(Constants.ENTITY) T entity);
        userMapper.updateById(u);
    }

XD.png

@Test
    public void testUpdate(){
        User u=new User();
        u.setId(7);
        u.setName("hony");
        u.setEmail("@baomidou.com");
        userMapper.updateById(u);
    }

NHN%GL.png

自动填充

数据库级别

在数据库增加两列

4MR016B.png 修改对应的实体类

public class User {
    @TableId(type = IdType.AUTO)
    private int id;
    private Integer age;
    private String name;
    private String email;
    //增加对应字段
    private Date createTime;
    private Date updateTime;
}

代码级别

  1. 添加注解@TableField
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private int id;
    private Integer age;
    private String name;
    private String email;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE )
    private Date updateTime;
    @Version
    private Integer version;
}
 /**
     * 字段自动填充策略
     * <p>
     * 在对应模式下将会忽略 insertStrategy 或 updateStrategy 的配置,等于断言该字段必有值
     */
    FieldFill fill() default FieldFill.DEFAULT;
    
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}

  1. 处理策略
@Component //需要注册到ioc容器中
public class MymetObejectHandler implements MetaObjectHandler {
//插入时的策略
    @Override
    public void insertFill(MetaObject metaObject) {
      log.info("inserte fill start ---------");
                        //数据库字段,填充的值 
      setFieldValByName("createTime",new Date(),metaObject) ;
      setFieldValByName("updateTime",new Date(),metaObject) ;
    }
//更新时的策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("update fill start ---------");
        setFieldValByName("updateTime",new Date(),metaObject) ;
    }
}

乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
  1. 在数据库添加字段version

CKHDI.png 2. 更新实体类,添加对应属性

@Version
private Integer version;
  1. 注册到ioc容器中
@Configuration
@MapperScan("com.mapper")
@EnableTransactionManagement //开启事务支持
public class MyConfig {
    @Bean
    public MybatisPlusInterceptor optimisticLockerInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
  1. 测试
    @Test
    public void testop2(){

        User u=userMapper.selectById(1);
        u.setEmail("t");
        User u2=userMapper.selectById(1);
        u2.setId(7);
        u2.setEmail("T2");
        userMapper.updateById(u2);
        userMapper.updateById(u);
    }
    // Email被改为T2,而不是t

查询操作

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);
    /*================================================*/    
     /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    /*================================================*/   
    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

Map查询会根据我们传入的参数进行动态sql

    @Test
    public void testSelect(){
        //通过id进行查询
        userMapper.selectById(1);
        //根据传入的数组进行批量查询
        userMapper.selectBatchIds(Arrays.asList(2,3,4));
        Map<String, Object> Map = new HashMap<>();
        Map.put("name","jony");
        Map.put("age","18");
        userMapper.selectByMap(Map);
    }

R)MX.png

YG9GSICD20$1C.png

K~4@RFA.png

分页查询

使用MybatisPlus自带的分页插件进行分页

  1. 注册插件
public class MyConfig {
    @Bean
    public MybatisPlusInterceptor optimisticLockerInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }}
  1. 使用
  /**
     * 分页构造函数
     *
     * @param current 当前页
     * @param size    每页显示条数
     */
    public Page(long current, long size) {
        this(current, size, 0);
    }
    /*=================================================*/
     /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 @Test
    public void testSelect(){
    // 5条记录一页,显示第二页
        Page<User> page = new Page<>(2,5);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println );
    }

OXQ45@38.png

删除操作

与上述的查询操作大同小异


    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);
/*===============================================*/
    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

/*===============================================*/

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    @Test
    public void testDelete(){
        userMapper.deleteById(8);
        userMapper.deleteBatchIds(Arrays.asList(2,3,4));
        Map<String, Object> Map = new HashMap<>();
        Map.put("name","jony");
        Map.put("age","18");
        userMapper.deleteByMap(Map);
    }

逻辑删除(只对自动注入的sql起效:)

  1. 在数据库中添加字段deleted(名称固定),设置默认值为0

5.png 2. 修改实体类并添加对应注解

注意: 低版本需要注册组件

 @Bean//逻辑删除组件
    public ISqlInjector sqlInjector(){
        return new LogicSqlInjector();
    }

VWI.png

  1. 修改配置文件 由于我们数据库设置deleted默认为0,所以这里我们设置logic-not-delete-value: 0 为不删除的条件。 logic-delete-value: 1为已删除的标志。 UGRED.png
  2. 测试

当我们删除id为8的用户时,执行的其实是更新操作

W89`.png

再次查询id为8的用户

BBM12NQ5G6IQQWXNSBUU8NG.png

性能分析插件

Mybatis-Plus在3.2版本以上移除了PerformanceInterceptor

  1. 导入依赖 该插件有性能损耗,不建议生产环境使用。
<dependency>
  <groupId>p6spy</groupId>
  <artifactId>p6spy</artifactId>
  <version>最新版本</version>
</dependency>
  1. 修改原本的数据库属性
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    username: root
    password: 123456
    url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
  1. 添加配置文件spy.properties
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,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

Q2ZLUD2.png

条件构造器wrapper

测试1 NotNull,lt

  @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //姓名邮箱不为空的18岁
    wrapper.isNotNull("name")
            .isNotNull("email")
            .lt("age",18);
    userMapper.selectList(wrapper);
}

@7.png

测试2 eq

  @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name","jony");
    userMapper.selectOne(wrapper);
}

6~36Z7AS.png

测试3 between

 @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age",18,20);
    userMapper.selectCount(wrapper);
}

)V%Q_TO6.png

测试4 like 模糊查询

 @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.notLike("name","j")//%j%
            .likeLeft("email","t");// %t
    userMapper.selectList(wrapper).forEach(System.out::println);
}

8EL.png

测试5 子查询

 @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id","select id from user where age>18");
    userMapper.selectList(wrapper).forEach(System.out::println);
}

5DZJ9.png

测试6 排序

 @Test
    public void TestWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //Desc降序
     wrapper.orderByDesc("id");
    userMapper.selectList(wrapper).forEach(System.out::println);
}

TVYYSU50.png

代码生成器

  1. 导入依赖 MyBatis-Plus 从 3.0.3 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.2</version>
</dependency>
// 模板引擎依赖,可更改,更改后需要在 AutoGenerator 中 设置模板引擎
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>
  1. 编写代码
    @Test
    public void auto(){
        AutoGenerator mpg=new AutoGenerator();
        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");//设置输出地址
        globalConfig.setAuthor("lin");//设置作者
        globalConfig.setOpen(false);//设置生成完毕后是否打开文件夹
        globalConfig.setFileOverride(false);//是否覆盖
        globalConfig.setServiceName("%sService");//去除service的I前缀
        globalConfig.setIdType(IdType.ASSIGN_ID);//  指定生成的主键的ID类型
        globalConfig.setDateType(DateType.ONLY_DATE);//只使用 java.util.date 代替
        globalConfig.setSwagger2(true);//使用swagger
        mpg.setGlobalConfig(globalConfig);

        //配置数据库
    DataSourceConfig dataSourceConfig = new DataSourceConfig();
    dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
    dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
    dataSourceConfig.setUsername("root");
    dataSourceConfig.setPassword("123456");
    dataSourceConfig.setDbType(DbType.MYSQL);
    mpg.setDataSource(dataSourceConfig);

    //包设置
    PackageConfig packageConfig = new PackageConfig();
    packageConfig.setModuleName("blog");
    packageConfig.setParent("com");
    packageConfig.setEntity("entity");
    packageConfig.setService("service");
    packageConfig.setMapper("mapper");
    packageConfig.setController("controller");
    mpg.setPackageInfo(packageConfig);
    // 策略配置
    StrategyConfig strategyConfig = new StrategyConfig();
    strategyConfig.setInclude("user");//设置映射的表名
    strategyConfig.setNaming(NamingStrategy.underline_to_camel);//下划线转驼峰命名
    strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);//下划线转驼峰命名
    strategyConfig.setSuperEntityClass("指定父类实体");//指定继承的父类
    strategyConfig.setEntityLombokModel(true);//自动Lombok
    strategyConfig.setRestControllerStyle(true);//restful风格
    strategyConfig.setLogicDeleteFieldName("deleted");//设置逻辑删除字段
    //自动填充
    TableFill gmtCreat=new TableFill("gmt_creat", FieldFill.INSERT);//字段,条件
    TableFill gmtModified=new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
    ArrayList<TableFill> arrayList= new ArrayList<>();
    arrayList.add(gmtCreat);
    arrayList.add(gmtModified);
    strategyConfig.setTableFillList(arrayList);
    //乐观锁
    strategyConfig.setVersionFieldName("version");//指定字段
    //驼峰转连字符/managerUserActionHistory -> /manager-user-action-history
    strategyConfig.setControllerMappingHyphenStyle(true);
    strategyConfig.setRestControllerStyle(true);//开启Restful

    mpg.setStrategy(strategyConfig);
    mpg.execute();
}

测试结果:

}8JK8JMPGI.png

debug

1. update 没有进行动态sql

原因:

  1. int是基本数据类型,Integer是引用数据类型;

  2. Ingeter是int的包装类,int的初值为0,Ingeter的初值为null; 对象中的数字被定义为int类型如果没赋值默认为0,然后mybatisplus就认为是你赋值为0而进行update,而不是对sql进行动态拼接。