注意事项

  • @TableField只对于自动生成时会生效,如果你手动写sql时手动写的这个字段不会被替换为@TableField的值。xml文件中mybatisPlus的别名规则仍旧生效
  • 不加@TableField或者加了@TableField但是参数为空时,mybatisplus会自动根据蛇盒命名找字段导致映射失败,如userName会去找数据库里user_Name字段。
  • 自增时传入指定的主键会被忽略
  • 注意auto自增取决于数据库是否支持,如果数据库不支持则加了tableId(type=auto)主键也为空,此时tableid规则失效,不会过滤传入的主键值

    简介

  • mybatis-plus是对mybatis的扩展工具,在mybatis基础上,只做增强,不改变mybatis的 用法

    优点

  • 主要就以下几点,更多的见官网

  1. 无侵入
  2. 损耗小,且对于基本CRUD操作提供了很多方法,直接面向对象的方式进行crud,且比mybatis操作更加方便
  3. 内置代码生成器
  4. 内置分页插件
  5. 内置性能分析插件,可以输出sql语句及其执行时间

    ID生成策略

Mybatis-Plus日志功能

  • 配置文件添加如下配置即可

    • 该日志比原生的logging输出sql信息更加详细一些,而且会自动对查询结果进行格式化,看着更舒服
      1. mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 原生如下:

    logging:
    level:
      com.xkcoding.multi.datasource.mybatis: debug      com...maybatis是mapper文件的路径所在包
    

    第一个Mybatis-Plus

    导入依赖

  • mybatis-plus的包中包含了mybatis,不要同时引入mybatis和myplus,以防冲突

  • 去仓库找的其他依赖有的加了其他配置,导致运行失败(报的错是找不到或者boot启动类错误等)。有的版本跟下面不同,其他一样,但是也失败

    <dependency>
              <groupId>com.baomidou</groupId>
              <artifactId>mybatis-plus-boot-starter</artifactId>
              <version>3.2.0</version>
          </dependency>
    

    Mapper接口

  • mapper接口继承一个**BaseMapper**泛型类,类型为实体类。 ```xml @Mapper public interface UserMapper extends BaseMapper {

}

<a name="r6fF8"></a>
## Service接口
```java
IService也支持链式调用,代码写起来非常简洁,查询示例如下

 @Test public void testChain() {  List<User> list = userService.lambdaQuery()    .gt(User::getAge, 39)    .likeRight(User::getName, "王")    .list(); 


 更新示例如下

 @Test public void testChain() {  userService.lambdaUpdate()    .gt(User::getAge, 39)    .likeRight(User::getName, "王")    .set(User::getEmail, "w39@baomidou.com")    .update(); }复制代

 删除示例如下

 @Test public void testChain() {  userService.lambdaUpdate()    .like(User::getName, "青蛙")    .remove(); }复制代码

测试

  • 接下来就可以使用BaseMapper提供的方法来进行crud
  • 很多提供的方法都会有一个wapper参数,这是条件解析器。不需要就传个null

  • 插入操作如果主键id未设置时,会自动生成id ```xml List userList = userMapper.selectList(null); userList.forEach(System.out::println);

User user=new User(); user.setName(“rzd”); int res=userMapper.insert(user);

<a name="AiaOe"></a>
# Mybatis-Plus注解

- mp一共提供了8个注解,这些注解是用在Java的实体类上面的。
- @TableName:
   - 注解在类上,指定类和数据库表的映射关系**(即表示实体类对应哪个数据表)**。**实体类的类名(转成小写后)和数据库表名相同时** ,可以不指定该注解。
- @TableId
   - 注解在实体类的某一字段上,**表示这个字段对应数据库表的主键** 。当主键名为id时(表中列名为id,实体类中字段名为id),无需使用该注解显式指定主键,mp会自动关联。若类的字段名和表的列名不一致,可用value属性指定表的列名。另,这个注解有个重要的属性type,用于指定主键策略,参见主键策略小节
- @TableField
   - 注解在某一字段上,指定Java实体类的字段和数据库表的列的映射关系。这个注解有如下几个应用场景。
   - **排除非表字段:**若Java实体类中某个字段,不对应表中的任何列,它只是用于保存一些额外的,或组装后的数据,则可以设置`**exist**`属性为**false**,这样在对实体对象进行插入时,会忽略这个字段。排除非表字段也可以通过其他方式完成,如使用static或transient关键字,但个人觉得不是很合理,不做赘述
   - **字段验证策略:**通过insertStrategy,updateStrategy,whereStrategy属性进行配置,可以控制在实体对象进行插入,更新,或作为WHERE条件时,对象中的字段要如何组装到SQL语句中。参见配置小节
   - **字段填充策略:**通过fill属性指定,字段为空时会进行自动填充
- @Version
   - 乐观锁注解,参见乐观锁小节
- @EnumValue
   - 注解在枚举字段上
- @TableLogic
   - 逻辑删除   参见逻辑删除小节
- KeySequence
   - 序列主键策略(oracle)
- InterceptorIgnore
   - 插件过滤规则
<a name="iFBxO"></a>
# CRUD接口

- mybatis-plus提供了2类crud接口。**T为实体类**
   - **Mapper层CRUD接口:Mapper接口继承**`**BaseMapper<T>**`**即可使用**
   - **Service层CRUD接口:接口继承**`**IService<T>**`**,并创建一个接口实现类,实现类需继承**`ServiceImpl<Mapper接口,T>`
      - **对比Mapper-CRUD,主要区别在于IService支持了更多的批量化操作** ,如saveBatch,saveOrUpdateBatch等方法。
<a name="LEdg4"></a>
## Mapper-CRUD接口

- 主要方法如下
- insert(T entity)  插入一条记录
- deleteById(Serializable id)  **根据主键id删除一条记录**
- `deleteByMap`  根据Map删除
- delete(Wrapper<T> wrapper) 根据条件构造器wrapper进行删除
- selectById(Serializable id) **根据主键id进行查找**
- selectBatchIds(Collection idList) 根据主键id进行批量查找
- selectByMap(Map<String,Object> map) 根据map中指定的列名和列值进行**等值匹配** 查找
- selectMaps(Wrapper<T> wrapper)  根据 wrapper 条件,查询记录,将查询结果封装为一个Map,Map的key为结果的列,value为值
- selectList(Wrapper<T> wrapper) 根据条件构造器wrapper进行查询
- update(T entity, Wrapper<T> wrapper) 根据条件构造器wrapper进行更新
- updateById(T entity)   **根据入参entity的id(主键)进行更新**,即自带`where 主键=...`的条件,因为主键具有唯一性
- 单表查询时,可以使用BaseMapper提供的selectPage或selectMapsPage进行分页查

---

- 以下着重讲几个特殊功能的方法:
- selectMaps(Wrapper<T> wrapper):这个方法会将查询结果封装为一个Map,Map的key为结果列,value为值。返回结果:List<Map>
   - 常用于部分列查询,减少实体类的封装
- `selectObjs(wrapper)`  只会返回第一个字段,舍弃其他字段
   - 即用于一个字段的查询
- `selectCount(wrapper)`  等同于`count(1)`
   - 使用这个方法,不能调用QueryWrapper的select方法设置要查询的列
<a name="RQvjo"></a>
## Service-CRUD接口
如`UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService`
<a name="tJ02o"></a>
# 条件构造器Wrapper

- 条件构造器类有很多,主要讲AbstractWrapper,QueryWrapper,UpdateWrapper
   - `QueryWrapper`,`UpdateWrapper`是`AbstractWrapper`的子类
   - 所有构造器最终的父类构造器是`Wrapper`
- `AbstractWrapper`中提供了非常多的方法用于构建WHERE条件,query和update用法都几乎一样的,不同在于:
   - `QueryWrapper`针对SELECT语句,提供了额外的select()方法,可自定义需要查询的列
   - `UpdateWrapper`针对UPDATE语句,提供了额外的set()方法,用于构造set语句
- **条件构造器都支持lambda,很方便**
- **条件构造器可以直接传入实体类对象,mp会根据对象的非空属性生成**`where条件`默认是等值where(可以通过实体类里各个字段上的@TableField注解中的condition属性进行改变)    如在实体类添加`@TableField(condition = SqlCondition.LIKE) `  即表示默认使用like
   - 有如下参数可以供使用,可以自定义参数,我觉得没必要,增加学习成本。
```java
public class SqlCondition {
    //下面的字符串中, %s 是占位符, 第一个 %s 是列名, 第二个 %s 是列的值
    public static final String EQUAL = "%s=#{%s}";
    public static final String NOT_EQUAL = "%s&lt;&gt;#{%s}";
    public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')";
    public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})";
    public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
}

条件方法

  • eq:equals,等于
  • allEq(Map<String,Object>):all equals,全等于
    • 如果传入有value为null,默认采用is null
      • 使用condition可以忽略map中value为null的元素allEq(param, false);
    • 还可以使用重载方法allEq(BiPredicate<R, V> filter, Map<R, V> params)自定义过滤规则,如allEq((k,v) -> !"name".equals(k), param); // 过滤掉map中key为name的元素
  • ne:not equals,不等于
  • gt:greater than ,大于 >
  • ge:greater than or equals,大于等于≥
  • lt:less than,小于<
  • le:less than or equals,小于等于≤
  • between:相当于SQL中的BETWEEN
  • notBetween
  • like:模糊匹配。like(“name”,”黄”),相当于SQL的name like ‘%黄%’
  • likeRight:模糊匹配右半边。likeRight(“name”,”黄”),相当于SQL的name like ‘黄%’
  • likeLeft:模糊匹配左半边。likeLeft(“name”,”黄”),相当于SQL的name like ‘%黄’
  • notLike:notLike(“name”,”黄”),相当于SQL的name not like ‘%黄%’
  • isNull
  • isNotNull
  • in
  • and:SQL连接符AND
  • or:SQL连接符OR
  • apply:用于拼接SQL,该方法可用于数据库函数,并可以动态传参

    • 条件表达式时当需要传入自定义的SQL语句,或者需要调用数据库函数时,可用apply()方法进行SQL拼接

      使用条件构造器

  • 默认都是and进行逻辑连接

  • 当AND或OR后面的条件需要被括号包裹时,将括号中的条件以lambda表达式形式,作为参数传入and()或or()
    • 特别的,当()需要放在WHERE语句的最开头时,可以使用nested()
  • boolean condition是第一个入参时表示是否生成到sql中,如如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
  • 条件方法参数一般都为key与value,如like("name", "佳") between("age", 20, 40)
  • 如需要使用()包含多个逻辑,则传入lambda即可,如.or( q -> q.lt("age",40) .gt("age",20) .isNotNull("email"));表示:或者(年龄小于40并且年龄大于20并且邮箱不为空)

    生成构造器对象,这里以QueryWrapper为例
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    然后调用条件方法即可
    

    条件构造器$注入

    ... where ${ew.sqlSegment}
    @Param("ew") QueryWrapper<T> wrapper
    

    Condition

  • 条件构造器的方法都可以指定一个boolean类型的参数,用于决定该条件是否生成到sql中,如 ```java String name = “黄”; // 假设name变量是一个外部传入的参数 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.like(StringUtils.hasText(name), “name”, name); // 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中 // 其实就是对下面代码的简化 if (StringUtils.hasText(name)) { wrapper.like(“name”, name); }

<a name="IMJIt"></a>
## lambda条件构造器

- 即`**LambdaQueryWrapper/LambdaUpdateWrapper**`,就是普通的`QueryWrapper`多了lambda功能
- 普通条件构造器指定列名,需要使用字符串形式。而lambda条件构造器可以直接以实体类的方法引用来指定列
   - 如普通:`wrapper.like("name","黄")`   等同于`wrapper.like(User::getName, "黄")`   
      - 1:忘记属性名时这样就更方便吧,自动提示然后查看方法选定列名 2:使用方法引用可以在编译时就避免列名错误,如果是字符串,则运行时才报sql错误
- **lambda和链式lambda指定列时都必须使用方法引用,使用字符串列名等都报错**
<a name="JZkWj"></a>
## 链式lambda条件构造器

- `LambdaQuery/UpdateChainWrapper<T> xx=new LambdaQuery/UpdateChainWrapper<>(BaseMapper);` ** 即需要传入实体类对应的mapper接口对象**
- **普通的条件构造器是创建好后,作为参数传给crud接口方法,而链式条件构造器自己就可以调用查询操作**
   - query构建条件后直接有四种操作:
      - `list()   List`
      - `one()    T`
      - `count()   Integer`
      - `page()    IPage`
   - update则是需要`.set().update()`来执行操作
      - set有2种,`set(列,value)`  `set(Boolean,列,value)`
         - set会返回一个Update对象
      - Update对象有3种方法可以操作
         - `update()`
         - `update(T entry)`
         - `remove()`
```java
LambdaQueryChainWrapper<User> chainWrapper = new LambdaQueryChainWrapper<>(userMapper);
  List<User> users = chainWrapper.like(User::getName, "黄").gt(User::getAge, 30).list();

  LambdaUpdateChainWrapper<User> wrapper = new LambdaUpdateChainWrapper<>(userMapper);
  wrapper.likeRight(User::getEmail, "share")  //条件
    .like(User::getName, "飞飞")   //条件
    .set(User::getEmail, "ff@baomidou.com")   //更新字段
    .update();
 }

多数据源

  • 有多种方式,这里使用mybatis-plus提供的支持
    • 使用配置文件环境切换虽然可以配置多个数据源,但是如果有多个环境的配置文件,这时又增加多个数据源,则相互组合就得配很多个配置文件。如3个数据源,三个环境,则要有9个配置文件
    • 通过数据库连接池好像也可以配置多个数据源
    • mybatis-plus也提供了多数据源支持

导入依赖

  • 不知道是否需要跟mybatis-plus版本一致,反正一致也没坏处就一致吧)

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
      <version>3.2.0</version>
    </dependency>
    

    数据库配置

    spring:
    datasource:
      dynamic:
        primary: master #设置默认的数据源或者数据源组,默认值即为master
        strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
        datasource:
          master:
            url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
          slave_1:
            url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver
          slave_2:
            url: ENC(xxxxx) # 内置加密,使用请查看详细文档
            username: ENC(xxxxx)
            password: ENC(xxxxx)
            driver-class-name: com.mysql.jdbc.Driver
    
         #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
    

    ```yaml

    多主多从 纯粹多库(记得设置primary) 混合配置

    spring: spring: spring: datasource: datasource: datasource: dynamic: dynamic: dynamic:

    datasource:                           datasource:                           datasource:
      master_1:                             mysql:                                master:
      master_2:                             oracle:                               slave_1:
      slave_1:                              sqlserver:                            slave_2:
      slave_2:                              postgresql:                           oracle_1:
      slave_3:                              h2:                                   oracle_2:
    
<a name="zAR3B"></a>
## @DS指定数据源

- 使用`@Ds`指定数据源
- `@DS` 可以注解在方法上或类上,同时存在就近原则 **方法上注解 优先于 类上注解。**
```java
@Service
@DS("slave")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }

  @Override
  @DS("slave_1")
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

使用原生方式写sql

  • 跟mybatis一毛一样,不过增加了个mp的xml配置,跟mybatis的xml位置配置差不多 ```properties mybatis-plus: mapper-locations: /mappers/*

若有多个地方存放mapper,则用数组形式进行配置

mybatis-plus: mapper-locations: - /mappers/ - /com/example/mp/


- 采用原生mybatis,也可以在sql中使用条件构造器,只需要固定条件构造器参数使用为`${ew.customSqlSegment}`
```java
public interface UserMapper extends BaseMapper<User> {    // SQL中不写 @Select("select * from user ${ew.customSqlSegment}") 
List<User> findAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}

分页查询

  • BaseMapper中提供了2个方法进行分页查询,分别是selectPageselectMapsPage

    创建配置类

  • 最新的mp不是PaginationInterceptor,见官网

  • 不写配置类虽然不报错,但是使用分页操作时输出的就是全部数据而不是分页操作指定的数据

    @Configuration
    @MapperScan("com.baomidou.cloud.service.*.mapper*")
    public class MybatisPlusConfig {
      @Bean
      public PaginationInterceptor paginationInterceptor() {
          PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
          // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
          // paginationInterceptor.setOverflow(false);
          // 设置最大单页限制数量,默认 500 条,-1 不受限制
          // paginationInterceptor.setLimit(500);
          // 开启 count 的 join 优化,只针对部分 left join
          paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
          return paginationInterceptor;
      }
    }
    

    使用分页操作

  • mp提供了一个分页接口IPagePage是其实现类。

  • new Page<>(long page,long limit) 查第page页,每页数据量为limit
  • Page有5个主要属性:
    • List records:实体类 使用get方法得到实体类集合
    • Total:数据总数 Long
    • Pages:总页数 Long
    • current:指定输出的页数
    • boolean isSearchCount 是否查询数据总数
      • 默认为true,为true时会执行2次sql,一次查总记录数,一次查具体数据 ```java // 设置分页信息, 查第3页, 每页2条数据 Page page = new Page<>(3, 2); // 执行分页查询 IPage userPage = userMapper.selectPage(page, null); System.out.println(“总记录数 = “ + userPage.getTotal()); System.out.println(“总页数 = “ + userPage.getPages()); System.out.println(“当前页码 = “ + userPage.getCurrent());

//如果是自己写sql IPage<实体/Map> xx (@Param(“page”)IPage iPage); // 获取分页查询结果 List records = userPage.getRecords(); records.forEach(System.out::println);

<a name="bqzwv"></a>
# 其他配置
<a name="DDyOX"></a>
#### 基本配置

- configLocation:若有单独的mybatis配置,用这个注解指定mybatis的配置文件(mybatis的全局配置文件)
- mapperLocations:mybatis mapper所对应的xml文件的位置
- typeAliasesPackage:mybatis的别名包扫描路径
<a name="e0FuO"></a>
#### 进阶配置

- mapUnderscoreToCamelCase:是否开启自动驼峰命名规则映射。(默认开启)
- dbTpe:数据库类型。一般不用配,会根据数据库连接url自动识别
- fieldStrategy:(已过时)字段验证策略。**该配置项在最新版的mp文档中已经找不到了** ,被细分成了insertStrategy,updateStrategy,selectStrategy。默认值是NOT_NULL,即对于实体对象中非空的字段,才会组装到最终的SQL语句中。有如下几种可选配置这个配置项,可在application.yml中进行**全局配置** ,也可以在某一实体类中,对某一字段用@TableField注解进行**局部配置**这个字段验证策略有什么用呢?在UPDATE操作中能够体现出来,若用一个User对象执行UPDATE操作,我们希望只对User对象中非空的属性,更新到数据库中,其他属性不做更新,则NOT_NULL可以满足需求。而若updateStrategy配置为IGNORED,则不会进行非空判断,会将实体对象中的全部属性如实组装到SQL中,这样,执行UPDATE时,可能就将一些不想更新的字段,设置为了NULL。
   - IGNORED:忽略校验。即,不做校验。实体对象中的全部字段,无论值是什么,都如实地被组装到SQL语句中(为NULL的字段在SQL语句中就组装为NULL)。
   - NOT_NULL:非NULL校验。只会将非NULL的字段组装到SQL语句中
   - NOT_EMPTY:非空校验。当有字段是字符串类型时,只组装非空字符串;对其他类型的字段,等同于NOT_NULL
   - NEVER:不加入SQL。所有字段不加入到SQL语句
- tablePrefix:添加表名前缀比如
```yaml
mybatis-plus:
  global-config:
    db-config:
      table-prefix: xx_

代码生成器

  • mp提供一个生成器,可快速生成Entity实体类,Mapper接口,Service,Controller等全套代码。
  • Mybatis-Plus - 图1

    public class GeneratorTest {
    @Test
    public void generate() {
    AutoGenerator generator = new AutoGenerator();
    
    // 全局配置
    GlobalConfig config = new GlobalConfig();
    String projectPath = System.getProperty("user.dir");
    // 设置输出到的目录
    config.setOutputDir(projectPath + "/src/main/java");
    config.setAuthor("yogurt");
    // 生成结束后是否打开文件夹
    config.setOpen(false);
    
    // 全局配置添加到 generator 上
    generator.setGlobalConfig(config);
    
    // 数据源配置
    DataSourceConfig dataSourceConfig = new DataSourceConfig();
    dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai");
    dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
    dataSourceConfig.setUsername("root");
    dataSourceConfig.setPassword("root");
    
    // 数据源配置添加到 generator
    generator.setDataSource(dataSourceConfig);
    
    // 包配置, 生成的代码放在哪个包下
    PackageConfig packageConfig = new PackageConfig();
    packageConfig.setParent("com.example.mp.generator");
    
    // 包配置添加到 generator
    generator.setPackageInfo(packageConfig);
    
    // 策略配置
    StrategyConfig strategyConfig = new StrategyConfig();
    // 下划线驼峰命名转换
    strategyConfig.setNaming(NamingStrategy.underline_to_camel);
    strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
    // 开启lombok
    strategyConfig.setEntityLombokModel(true);
    // 开启RestController
    strategyConfig.setRestControllerStyle(true);
    generator.setStrategy(strategyConfig);
    generator.setTemplateEngine(new FreemarkerTemplateEngine());
    
          // 开始生成
    generator.execute();
    }
    }
    

    逻辑删除

    链接

    自动填充

  • 通过@TableField设置自动填充

    • @TableField(fill = FieldFill.INSERT) // 插入时自动填充
    • @TableField(fill = FieldFill.UPDATE) // 更新时自动填充
  • 自动填充仅在该字段为空时会生效,若该字段不为空,则直接使用已有的值 ```java @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.UPDATE) // 更新时自动填充

    private LocalDateTime updateTime;

    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import
      org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import
      java.time.LocalDateTime;
      @Component //需要注册到Spring容器中
      public class MyMetaObjectHandler implements MetaObjectHandler {
          @Override
          public void insertFill(MetaObject metaObject) {       
          //     插入时自动填充        
            //   注意第二个参数要填写实体类中的字段名称,而不是表的列名称 
               strictFillStrategy(metaObject, "createTime", LocalDateTime::now);
               }
               @Override 
               public void updateFill(MetaObject metaObject) {        
              // 更新时自动填充 
         strictFillStrategy(metaObject, "updateTime", LocalDateTime::now); }
               }
    
<a name="Fj1iZ"></a>
# 乐观锁
略[链接](https://mmbiz.qpic.cn/mmbiz_png/JdLkEI9sZfdO6JPF4T4o5RTyotDN3eLknX1J1d9tCxs3yFk9glUc9uVFDkyI3FwmfqEy34NFxL8RyOauZre1Gg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)
<a name="euO4v"></a>
# 性能分析插件

- 该插件会输出SQL语句的执行时间,以便做SQL语句的性能分析和调优。
   - 注:3.2.0版本之后,mp自带的性能分析插件被官方移除了,而推荐食用第三方性能分析插件
```java
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver #换成p6spy的驱动
    url: jdbc:p6spy:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai #url修改
    username: root
    password: root
  • 在resource下添加该配置文件:spy.properties ```java

    spy.properties

    3.2.1以上使用

    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory

    真实JDBC driver , 多个以逗号分割,默认为空。由于上面设置了modulelist, 这里可以不用设置driverlist

    driverlist=com.mysql.cj.jdbc.Driver

    自定义日志打印

    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger

    日志输出到控制台

    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger

    若要日志输出到文件, 把上面的appnder注释掉, 或者采用下面的appender, 再添加logfile配置

    不配置appender时, 默认是往文件进行输出的

    appender=com.p6spy.engine.spy.appender.FileLogger

    logfile=log.log

    设置 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

    是否开启慢SQL记录

    outagedetection=true

    慢SQL记录标准 2 秒

    outagedetectioninterval=2

    执行时间设置, 只有超过这个执行时间的才进行记录, 默认值0, 单位毫秒

    executionThreshold=10
<a name="NzN88"></a>
# 多租户SQL解析器

- 共用一套系统,但是数据要隔离
- 略[链接](https://mmbiz.qpic.cn/mmbiz_png/JdLkEI9sZfdO6JPF4T4o5RTyotDN3eLknX1J1d9tCxs3yFk9glUc9uVFDkyI3FwmfqEy34NFxL8RyOauZre1Gg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)
<a name="Whbis"></a>
# 动态表名SQL解析器
<a name="nugzC"></a>
# 主键策略

- **除了**`**input**`**外,其他的策略自动生成的id都会回写回实体对象里**
- @TableId指定主键,而其type属性,可以指定主键策略。
   - mp支持多种主键策略,默认的策略是基于雪花算法的自增id。全部主键策略定义在了枚举类IdType中,使用`@TableId( type=IdType._?_)`
- 主键策略分为全局和局部策略,局部可以看成tableId设置的,全局则是在`application.yml`中配置
   - 局部优先级>全局
```yaml
mybatis-plus:
  global-config:
    db-config:
      id-type: auto

  • AUTO ID自增,依赖于数据库 。在插入操作生成SQL语句时,不会插入主键这一列
  • NONE未设置主键类型。若在代码中没有手动设置主键,则会根据主键的全局策略
  • INPUT 需要手动设置主键,若不设置。插入操作生成SQL语句时,主键这一列的值会是null。oracle的序列主键需要使用这种方式
  • ASSIGN_ID当没有手动设置主键,即实体类中的主键属性为空时,才会自动填充,使用雪花算法
    • 类型为int或者long或者string 使用uuid。新版本的默认策略
  • ASSIGN_UUID当实体类的主键属性为空时,才会自动填充,使用UUID
    • 新版本mp的默认策略,生成字符串

  • _AUTO_, _NONE_, _INPUT_, _ID_WORKER_, _UUID_, _ID_WORKER_STR_;老版本策略
  • id_worder 老版本默认策略 长整型类型
  • (默认的主键全局策略是基于雪花算法的自增ID)

    唯一ID生成方案

  • 自增:

    • 自增对于读写分离或者一主多从的情况,容易产生单点故障。但是自带排序功能
    • 不同数据库语法可能不同,迁移要考虑重复
    • 不利于合并或者迁移数据
    • 生成新的自增id可能影响性能
  • UUID:
    • uuid可以做到全球唯一,不会有性能问题,实现简单
    • 一般为字符串型,无法排序。长度太长,存储空间较大
  • redis生成id
    • 性能高
    • 生成策略是提前对所有可能用到服务器设定标识,根据标识再生成id,这样避免了分布式id冲突。但是要在开始就确定好哪些服务器,不过一般最多三五台服务器就够用了
    • 生成的是数字id,方便排序
  • 雪花算法(snowflake)
    • 推特推出的分布式id算法