Skip to content
0

Mybatis plus

概序

原先写 crud,需要在 xml 配置或注解中写 sql 语句,用了 MyBatisPlus 后,对单表进行 crud 无需再写 sql 语句

简单的增删改查还行,不支持多表操作,一般不在公司里使用,适合个人快速开发和偷懒使用

官网:https://baomidou.com/

简介:简化 mybatis,简化开发、提高写代码效率

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

引入依赖

在 Spring Boot 引入

xml
<!--mybatis-plus的spring boot支持-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<!--mysql的spring boot支持-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

配置日志

我们所有的 sql 现在是不可见的,我们希望知道他是怎么执行的,所以我们必须要看日志!

properties
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

实体类注解

@TableName

​ 指定实体类对应数据库表名,如果实体类和数据库表名不一致,就开启注解进行配置,两者一致可以不开启

放在实体类的上方

@TableId

​ 指定实体类的属性对应数据库的 ID,放在属性上方

属性值:

  1. value:字段值(驼峰命名方式,该值可无)
  2. type:主键 ID 类型(用法:@TableId(type = IdType.AUTO))
    1. AUTO:数据库 ID 自增(常用)
    2. NONE:该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
    3. INPUT:用户输入 ID,该类型可以通过自己注册自动填充插件进行填充
    4. 其他

@TableFiled

属性值进行除 ID 外的其他设置

解决问题:

  1. 对象中的属性名和字段名不一致的问题(非驼峰)

  2. 对象中的属性名和字段名不一致的问题(非驼峰)

属性值:

  1. exits:是否为数据库表字段,实体类存在但是数据库不存在的属性值,默认 true 存在,false 不存在
  2. select:是否进行 select 查询,可设置为 false 不加入 select 查询范围
  3. fill:填充,当属性指定为 null 时,配合 FieldFill 枚举类,可自动填充指定默认值,具体看大纲
  4. condition:字段 where 实体查询比较条件 默认 = 等值
  5. 其他

@Version

​ 当要更新一条记录的时候,希望这条记录没有被别人更新,通过进行 version 判断

​ 具体看大纲

@TableLogic

​ 删除数据,通过该注解标识的属性值对应的字段值被标记为删除,而并非真正的物理删除

​ 具体看大纲

注解例子:

java
@TableName("tb_user")
public class User extends Model<User> {  // ActiveRecord
    // 开启ID 自增,否则mybatis-plus自动创建随机ID插入数据库
    @TableId(type = IdType.AUTO)
    private Long id;
    //映射数据库字段,解决字段名不一致
    @TableField("user_name")
    private String userName;
    // 该注解:查询时候返回null,防止获得该字段信息
    @TableField(select = false,fill = FieldFill.INSERT)
    private String password;

    private String name;
    private Integer age;
    private String email;

    //该注解:如果该属性在数据库不存在,所以需要开启注解,否则插入报错
    @TableField(exist = false)
    private String phone;

    @Version
    private Integer Version;

    @TableLogic
    private Integer deleted;

}

通用 CRUD

所有方法用到的一个类,该类继承了 MybatisPlus 提供的一个基础类,并且绑定实体类,可以通过该类实现 sql 语句

java
@Repository
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
java
@Autowired
private UserMapper userMapper;

插入操作

  1. insert():参数为一个实体类

    需求

    插入 User 数据

    java
    public void insert(){
        User user = new User();
        user.setAge(20);
        user.setEmail("test@itcast.cn");
        user.setName("可乐");
        user.setUserName("kele");
        user.setPassword("123456");
        int result = userMapper.insert(user); //返回受影响的行数
        System.out.println("插入了:" + result + "条数据");
        System.out.println("插入后返回的自增ID:" + user.getId()); //自增的id会返回到User对象里
    }

删除操作

  1. deleteById():参数为 ID 值,删除失败不会报错

    需求

    删除 ID 为 8 的数据

    java
    public void deleteById(){
        int result = userMapper.deleteById(8L);
        System.out.println("处理行数:" + result);
    }
  2. deleteMap():参数为 Map 类型,key 为字段名,value 为字段对应的值,删除失败不会报错

    需求

    用 Map 方式删除用户名为可乐,年龄为 20 的数据

    java
    public void dleteByMap(){
        HashMap<String,Object> columnMap = new HashMap<>();
        // key为字段名,value为判断的值
        columnMap.put("name", "可乐");
        columnMap.put("age", 20);
    
        int result = userMapper.deleteByMap(columnMap); // 删除用户名为可乐,年龄为20的数据
        System.out.println("处理行数:" + result);
    }
  3. delete():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等

    需求

    用实体类方式删除用户名为可乐,年龄为 20 的数据

    java
    public void deleteByEntry(){
        User user = new User();
        user.setName("可乐");
        user.setAge(20);
        //将实体对象进行包装,包装为操作条件
        QueryWrapper<User> wrapper = new QueryWrapper<>(user);
    
        int result = userMapper.delete(wrapper); // 删除用户名为可乐,年龄为20的数据
        System.out.println("处理行数:" + result);
    }
  4. deleteBatchIds:参数为 ID 的集合,根据 ID 集合批量删除数据

    需求

    批量删除 ID 为 10,11,12 的数据

    java
    public void deleteByBatch(){
        int result = userMapper.deleteBatchIds(Arrays.asList(10L,11L,12L));//删除ID为10,11,12的数据
        System.out.println("处理行数:" + result);
    }

修改操作

  1. updateById():参数为实体类,实体类内部需要封装好 ID 值,以及修改的信息内容

    需求

    把 ID 为 6 的密码修改为 1111

    java
    public void updateById(){
        User user = new User();
        user.setId(6L);
        user.setPassword("1111");
    
        int result = userMapper.updateById(user);// 修改ID为6的密码为1111
        System.out.println("处理行数:" + result);
    }
  2. update():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等

    需求

    以 QueryWrapper 方式把 ID 为 6 的年龄修改为 18

    java
    public void updateByQueryWrapper(){
        User user = new User();
        user.setAge(18);
    
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("id", 6);
    
        int result = userMapper.update(user, wrapper); // 修改ID为6的年龄为18
        System.out.println("处理行数:" + result);
    }
  3. update():参数为 UpdateWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等

    1. 与 QueryWrapper<>区别:不需要指定实体类,直接在对象后面加入修改的字段名和值

    需求

    以 UpdateWrapper 方式把 ID 为 6 的年龄修改为 18

    java
    public void updateByUpdateWrapper(){
        UpdateWrapper<User> wrapper = new UpdateWrapper<>();
        wrapper.eq("id", 6).set("age", 19);
    
        int result = userMapper.update(null, wrapper);
        System.out.println("处理行数:" + result);
    }

查询操作

  1. selectById():参数为 ID 值,通过 ID 查询数据,返回一条数据

    需求

    查询 ID 为 6 的数据

    java
    public void selectById(){
        User user = userMapper.selectById(6L);
        System.out.println(user);
    }
  2. selectOne():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回一条数据

    需求

    查询名字为赵六的数据

    java
    public void selectOne(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name", "赵六");
        User user = userMapper.selectOne(wrapper);
        System.out.println(user);
    }
  3. selectList():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回多条数据

    需求

    查询年龄大于 20 的多条数据

    java
    public void selectList(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age",20);
        List<User> users = userMapper.selectList(wrapper);
        for (User user : users) {
            System.out.println(user);
        }
    }
  4. selectCount():参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等,返回数据总数

    需求

    查询年龄大于 20 的数据总数

    java
    public void selectCount(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age", 20);
        Integer count = userMapper.selectCount(wrapper);
        System.out.println("数据库数据总数为:" + count);
    
    }
  5. selectBatchIds():参数为 ID 的集合,根据 ID 批量查询数据,只返回存在的 ID 数据

    需求

    批量查询 ID 为 1,2,6,22 的数据

    java
    public void selectBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 6L,22L));
        for (User user : users) {
            System.out.println(user);
        }
    }
  6. selectPages():分页查询,参数为 QueryWrapper<>对象,该对象需要指定泛型,可以实现条件判断,封装实体类等

    1. 需要使用 MybatisPlus 的分页插件 Pages,并且需要开启该插件

      java
      @Configuration
      public class MybatisPlusPageConfig {
          /**
           * 分页插件配置
           */
          @Bean
          public PaginationInterceptor paginationInterceptor(){
              return new PaginationInterceptor();
          }
      }

    需求

    查询年龄大于 20 的第一页的一条数据

    java
    public void selectPages(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age", 20);
        Page<User> page = new Page<>();
        page.setCurrent(1);
        page.setSize(1);
        IPage<User> iPage = userMapper.selectPage(page, wrapper);
    
        System.out.println("数据总条数:" + iPage.getTotal());
        System.out.println("总页数:" + iPage.getPages());
    
        List<User> users = iPage.getRecords();
        for (User user : users) {
            System.out.println("分页查询的数据:" + user);
        }
    }

基本配置

configLocation

​ MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考 MyBatis 官方文档

  1. Spring Boot:

    xml
     mybatis-plus.config-location = classpath:mybatis-config.xml
  2. Spring MVC:

    xml
    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

mapperLocations

​ MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行 该配置,告诉 Mapper 所对应的 XML 文件位置。

  1. Spring Boot:

    xml
    mybatis-plus.mapper-locations = classpath*:mybatis/*.xml
  2. Spring MVC:

    xml
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
    </bean>

typeAliasesPackage

​ MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使 用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。

  1. Spring Boot:

    xml
    mybatis-plus.type-aliases-package = com.eight.entry
  2. Spring MVC:

    xml
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    	<property name="typeAliasesPackage"
    		value="com.baomidou.mybatisplus.samples.quickstart.entity"/>
    </bean>

进阶配置

mapUnderscoreToCamelCase

  1. 类型:boolean
  2. 默认值:true

​ 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属 性名 aColumn(驼峰命名) 的类似映射。

​ 注意:

  1. 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body 如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
  2. 该参数不能和 mybatis-plus.config-location 同时存在,如果两者都有,需要把该配置放入 mybatis-plus.config-location 里
xml
# 关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false

cacheEnabled

  1. 类型:boolean
  2. 默认值:true

​ 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。

xml
 mybatis-plus.configuration.cache-enabled=false

DB 策略配置

idType

  1. 类型:com.baomidou.mybatisplus.annotation.IdType
  2. 默认值:ID_WORKER

​ 全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。

Spring Boot:

xml
mybatis-plus.global-config.db-config.id-type=auto

Spring MVC:

xml
!--这里使用MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="globalConfig">
        <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
        	<property name="dbConfig">
        		<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
        			<property name="idType" value="AUTO"/>
       		 	</bean>
   			</property>
    	</bean>
    </property>
</bean>

tablePrefix

  1. 类型:String

  2. 默认值:null

    表名前缀,全局配置后可省略@TableName()配置

Spring Boot:

xml
ybatis-plus.global-config.db-config.table-prefix=tb_ #自定义实体类的前缀,尽量统一规则,匹配数据库表

Spring MVC:

xml
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="globalConfig">
    	<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
    	<property name="dbConfig">
   	 		<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
            <property name="idType" value="AUTO"/>
            <property name="tablePrefix" value="tb_"/>
    		</bean>
    	</property>
    	</bean>
    </property>
</bean>

QueryWrapper 使用

条件构造器

allEq

​ API:

java
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

​ 说明:

  1. params:Map 类型,key 为数据库字段名,value 为字段值
  2. null2IsNull:true 时,params 传入 null 的时候,调用 xxx is null,false 时,忽略 value 为 null 的数据

API:

java
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

​ 说明:

  1. filter:过滤函数,是否允许字段传入比对条件中 params

  2. 虽然传入的 params 有很多 key,但是只想以一两个 key 作为查询条件,用这个过滤

  3. 例(假设 params 里有 name、age、password,只需要查询 name 即可,即 where 后面只有 name = ?):

    wrapper.allEq((k, v) -> (k.equals("name")),params);

例子:

java
public void testWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //设置条件
    Map<String,Object> params = new HashMap<>();
    params.put("name", "曹操");
    params.put("age", "20");
    params.put("password", null);

    // SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ?
    wrapper.allEq(params);

    // SELECT * FROM tb_user WHERE name = ? AND age = ?(没有password is NULL)
    wrapper.allEq(params,false);

    // SELECT * FROM tb_user WHERE name = ? AND age = ?
    wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")),params);
    List<User> users = this.userMapper.selectList(wrapper);
    for (User user : users) {
    	System.out.println(user);
    }
}

基本比较操作

  1. eq:等于 =
  2. ne:不等于 <>
  3. gt:大于 >
  4. ge:大于等于 >=
  5. lt:小于 <
  6. le:小于或等于 <=
  7. between:between b AND c,在 b 和 c 之间的数据
  8. notBetween:notBetween b AND c,不在 b 和 c 之间的数据
  9. in: IN (key,value1,value2,...),查询 key 分别等于 value1 和 value2 的数据
  10. notIn:NOT IN (key,value1,value2,...),查询 key 分别不等于 value1 和 value2 的数据
java
public void testEq() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //SELECT id,user_name,password,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?)
    wrapper.eq("password", "123456")
    	.ge("age", 20)
    	.in("name", "李四", "王五", "赵六"); // 查询name 分别等于 李四,王玉,赵六的数据

    List<User> users = this.userMapper.selectList(wrapper);
    for (User user : users) {
    	System.out.println(user);
    }
}

模糊查询

  1. like

    1. LIKE '%值%'
    2. 例:like('name',"王"),执行的 SQL:name like '%王%'
  2. notLike

    1. NOT LIKE '%值%'
    2. 例:notLike('name',"王"),执行的 SQL:name not like '%王%'
  3. likeLeft

    1. LIKE '%值'
    2. 例: likeLeft('name',"王"),执行的 SQL:name not like '%王'
  4. likeRight

    1. LIKE '值%'
    2. 例: likeRight('name',"王"),执行的 SQL:name not like '王%'
    java
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ?
        // Parameters: %曹%(String)
        wrapper.like("name", "曹");
        List<User> users = this.userMapper.selectList(wrapper);
        for (User user : users) {
        	System.out.println(user);
        }
    }

排序

  1. orderBy(ASC 排序)

    1. 例:orderBy(true, true, "id", "name"),执行的 SQL:order by id ASC,name ASC
  2. orderByAsc

    1. 例:orderByAscc("id", "name"),执行的 SQL: order by id ASC,name ASC
  3. orderByDesc

    1. 例:orderByDesc("id", "name"),执行的 SQL:order by id DESC,name DESC
    java
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC
        wrapper.orderByDesc("age");
        List<User> users = this.userMapper.selectList(wrapper);
    
        for (User user : users) {
        	System.out.println(user);
        }
    }

逻辑查询

  1. or

    1. 拼接 OR
    2. 主动调用 or 表示紧接着下一个方法不是用 and 连接!(不调用 or 则默认为使用 and 连接)
  2. and

    1. AND 嵌套
    2. 默认使用 and
    java
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        //SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ?
        wrapper.eq("name","李四").or().eq("age", 24);
        List<User> users = this.userMapper.selectList(wrapper);
        for (User user : users) {
        	System.out.println(user);
        }
    }

select

​ 在 MP 查询中,默认查询所有的字段,如果有需要也可以通过 select 方法进行指定字段。

​ 需要在 QueryWrapper 对象使用

java
public void testWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    //SELECT id,name,age FROM tb_user WHERE name = ? OR age = ?
    wrapper.eq("name", "李四")
    	.or()
    	.eq("age", 24)
    	.select("id", "name", "age"); // 只查询id,name,age字段的数据
    List<User> users = this.userMapper.selectList(wrapper);
    for (User user : users) {
    	System.out.println(user);
    }
}

ActiveRecord

什么是 ActiveRecord

​ ActiveRecord 也属于 ORM(对象关系映射)层,由 Rails 最早提出,遵循标准的 ORM 模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。

主要思想

​ 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段 在类中都有相应的 Field; ActiveRecord 同时负责把自己持久化,在 ActiveRecord 中封装了对数据库的访问,即 CURD;; ActiveRecord 是一种领域模型(Domain Model),封装了部分业务逻辑;

AR 之旅

​ 让实体类继承 Model,并传入该实体类

java
public class User extends Model<User>{
	// ......
    // 指定 ID
    @TableId(type = IdType.AUTO)
    private Long id;
    // ......
}

​ 和通用 CRUD 方法一样,唯一区别就是不需要使用 mapper 类调用方法,直接使用实体类调用方法,但是实体类关联 mapper 类去掉,其实底层还是会去对应的 mapper 类调用 CRUD

  1. selectById():根据主键查询数据

    java
    public void testAR() {
        User user = new User();
        user.setId(2L);
        User user2 = user.selectById();
        System.out.println(user2);
    }
  2. insert():新增数据,成功返回 true,失败返回 false

    java
    public void testAR() {
        User user = new User();
        user.setName("刘备");
        user.setAge(30);
        user.setPassword("123456");
        user.setUserName("liubei");
        user.setEmail("liubei@itcast.cn");
        boolean insert = user.insert();  //成功返回true,失败返回false
        System.out.println(insert);
    }
  3. updateById():数据更新,成功返回 true,失败返回 false

    java
    public void testAR() {
        User user = new User();
        user.setId(8L);
        user.setAge(35);
        boolean update = user.updateById();
        System.out.println(update);
    }
  4. deleteById():数据删除,成功返回 true,失败返回 false

    java
    public void testAR() {
        User user = new User();
        user.setId(7L);
        boolean delete = user.deleteById();
        System.out.println(delete);
    }
  5. selectList():多条数据查询

    java
    public void testAR() {
        User user = new User();
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.le("age","20"); // 小于或者等于
        List<User> users = user.selectList(userQueryWrapper);
        for (User user1 : users) {
        System.out.println(user1);
    }
  6. 其他方法请看,mapper 类只需换成实体类即可

Oracle 主键 Sequence

Spring Boot 框架下

创建序列

sql
--创建序列
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1

驱动包

​ 由于版权原因,不能直接通过 maven 的中央仓库下载 oracle 数据库的 jdbc 驱动包,所以需要将驱动包安装到本地 maven 仓库,需要将驱动包放在 C 盘以外的其他盘的文件夹里,并且打开 cmd 进入当前文件夹操作

DgroupId:maven 的 groupId

DartifactId:maven 的 artifactId

Dversion:maven 的 version

Dfile:当前 cmd 目录下的 jar 包名

java
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=12.1.0.1 -
Dpackaging=jar -Dfile=ojdbc6.jar

引用坐标

xml
dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>12.1.0.1</version>
</dependency>

配置文件策略

  1. Oracle 的数据库连接配置

  2. 开启 ID 生成策略(和 Mysql 不同,需要把 auto 改为 input)

    1. 原理:通过配置寻找配置好的序列(下方),input 代表主动把配置好的序列匹配到表的 ID,实现 ID 自增
    properties
    #数据库连接配置
    spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
    spring.datasource.url=jdbc:oracle:thin:@192.168.31.81:1521:kele
    spring.datasource.username=system
    spring.datasource.password=oracle
    #id生成策略
    mybatis-plus.global-config.db-config.id-type=input

配置序列

  1. 在配置类里进行序列配置,添加 Bean 注解,自动放入容器中

    Oracle 序列配置的代码:

    java
    @Configuration
    public class MybatisPlusConfig {
        /**
         * 分页插件配置
         */
        @Bean
        public PaginationInterceptor paginationInterceptor(){
            return new PaginationInterceptor();
        }
    
        /**
         * Oracle序列配置
         */
        @Bean
        public OracleKeyGenerator oracleKeyGenerator(){
            return new OracleKeyGenerator();
        }
    
        // .... 其他插件配置的位置
    }
  2. 在实体对象中指定序列的名称:

    1. value:对应数据库的序列名字
    2. clazz:自增的 ID 类型
    java
    @KeySequence(value = "SEQ_USER", clazz = Long.class)
    public class User{
    	// ......
    }

插件

插件机制

作用

​ MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。

操作

默认情况下,MyBatis 允许使用插件来拦截的方法调用包括

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

    ​ 拦截执行器(SQL)的方法,如 update,query,commit,rollback 等方法,还有其他接口的 一些方法等。

  2. ParameterHandler (getParameterObject, setParameters)

    ​ 拦截参数的处理

  3. ResultSetHandler (handleResultSets, handleOutputParameters)

    ​ 拦截结果集的处理 4. 拦截 Sql 语法构建的处

  4. StatementHandler (prepare, parameterize, batch, update, query)

    ​ 拦截 Sql 语法构建的处理

插件配置

  1. 自定义拦截器(插件)继承 Mybatis 的 Interceptor 类

  2. 流程先经过 intercept 方法,然后经历四次 plugin 方法(该方法内部执行上方的四次方法),最后完成退出该类

  3. 记得使用注解@Configuration 或者方法上使用@Bean 把该类的对象注入到 spring 容器

    我们看到了可以拦截 Executor 接口的部分方法,比如 update,query,commit,rollback 等方法,还有其他接口的 一些方法等。如下只拦截了 update 方法,可以加入业务逻辑,如逻辑删除 version 的处理,如果仅使用插件,可不使用,源码已经有实现插件使用

java
@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //拦截方法,具体业务逻辑编写的位置
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (invocation.getArgs()[1] instanceof Pojo) {
            Pojo parameter = (Pojo) invocation.getArgs()[1];
            if (SqlCommandType.INSERT == sqlCommandType) {
                // 初始化主键ID
                Method initializeUUID = BeanUtils.findDeclaredMethod(parameter.getClass(),"initializeUUID");
                if (null != initializeUUID) {
                    initializeUUID.invoke(parameter);
                }
                if(null==parameter.getCreator()){
                    parameter.setCreator(SessionHelper.getId());
                }
                parameter.setUpdater(SessionHelper.getId());
                parameter.setCreatedDate(new Date());
                parameter.setUpdatedDate(parameter.getCreatedDate());
                parameter.setVersion(1);
            } else if (SqlCommandType.UPDATE == sqlCommandType) {
                parameter.setUpdater(SessionHelper.getId());
                parameter.setUpdatedDate(new Date());
                parameter.setVersion(parameter.getVersion() + 1);
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //创建target对象的代理对象,目的是将当前拦截器加入到该对象中
         if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        //属性设置
        if (null != properties && !properties.isEmpty()){
            props = properties;
        }
    }

}

在自定义配置类里注入自定义的拦截器(插件)到 spring 容器

java
public class MybatisPlusConfig {
	// 把自己注入到spring容器
    @Bean
    public MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }
}

或者通过 xml 配置,mybatis-config.xml

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
    	<plugin interceptor="cn.itcast.mp.plugins.MyInterceptor"></plugin>
    </plugins>
</configuration>

执行分析插件

作用

​ 在 MP 中提供了对 SQL 执行的分析的插件,可用作,注意:该插件仅适用于开发环境,不适用于生产环境。

操作

SpringBoot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)

  1. 创建 SqlExplainInterceptor 对象,该对象需要 ISqlParser 的集合
  2. 创建 sqlParserList 集合,该集合需要解析器或解析链
  3. 创建解析器或解析链(new BlockAttackSqlParser()),放入集合
  4. 将 sqlParserList 集合放入 SqlExplainInterceptor 对象,并返回 spring 容器
java
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
    SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
    List<ISqlParser> sqlParserList = new ArrayList<>();
    // 攻击 SQL 阻断解析器、加入解析链
    sqlParserList.add(new BlockAttackSqlParser());
    sqlExplainInterceptor.setSqlParserList(sqlParserList);
    return sqlExplainInterceptor;
}

性能分析插件

作用

​ 性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间(毫秒),超过时间会抛出异常。也可以指定 SQL 是否格式化 ,默认 false,不格式化。(该插件存在于 3.2.0 版本之前,3.2.0 版本之后被移出)

操作

SpringBoot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)

java
@Bean
public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    // maxTime 指的是 sql 最大执行时长
    performanceInterceptor.setMaxTime(100);
    // SQL是否格式化 默认false
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}

mybtais.config.xml 文件配置:

xml
?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
        <!-- SQL 执行性能分析,开发环境使用,线上不推荐 -->
        <plugin
            interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
            <!-- maxTime 指的是 sql 最大执行时长 -->
            <property name="maxTime" value="100" />
            <!-- SQL是否格式化 默认false -->
            <property name="format" value="true" />
        </plugin>
    </plugins>
</configuration>

乐观锁插件

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

乐观锁实现方式:

  1. 取出记录时,获取当前 version

  2. 更新时,带上这个 version

  3. 执行更新时, set version = newVersion where version = oldVersion

  4. 如果 version 不对,就更新失败

spring 配置:

xml
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>

spring boot 配置:(可以放到如上面的自定义配置类(MybatisPlusConfig)里,然后加入@Bean 注解放入 Spring 容器)

java
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
	return new OptimisticLockerInterceptor();
}

实体字段 Version

需要为实体字段添加@Version 注解。且数据库存有 version 字段

原理

​ 数据更新后,version 值都会自动加 1,

​ 每次更新数据时,先从数据库拿到字段 version 值,然后进行更新,如果拿到的字段 version 不是最新的(拿到 version 后别人修改数据了,version 值已经自动加 1),因为拿到的 version 不是最新的,所以修改失败,

操作

  1. 为表添加 version 字段,并且设置初始值为 1

  2. 为 User 实体对象添加 version 字段,并且添加@Version 注解

    java
    // ......
    @Version
    private Integer version;
    // ......
  3. 使用

    java
    public void testUpdate(){
        User user = new User();
        user.setAge(30);
        user.setId(2L);
    
    	User user1 = user.selectById();
    
        user.setVersion(user1.getVerson); //获取到version为1
        int result = this.userMapper.updateById(user);
        System.out.println("result = " + result);
    }

特别说明

  1. 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime

  2. 整数类型下 newVersion = oldVersion + 1

  3. newVersion(新的 version 值) 会回写到 entity 中

  4. 仅支持 updateById(id) 与 update(entity, wrapper) 方法

  5. 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

Sql 注入器

问题:如果 BaseMapper 的方法不符合需求,需要扩充自定义方法,如何实现?

需求:扩充自定义方法 queryAllUser()

自定义接口 MyBaseMapper

​ 编写自定义接口,继承 BaseMapper,写需要的扩充自定义方法 queryAllUser()或者其他方法

java
@Repository
@Mapper
public interface MyBaseMapper extends BaseMapper<User> {
    List<User> queryAllUser();
}

自定义类 MySqlInjector

  1. 编写自定义类,继承 DefaultSqlInjector,重写 getMethodList 方法进行扩展

  2. DefaultSqlInjector 是 AbstractSqlInjector 的子类,为什么自定义类不直接继承 AbstractSqlInjector 呢

  3. 如果自定义类直接继承 AbstractSqlInjector,原有的 BaseMapper 中的方法将失效,只能使用自定义的方法

  4. 创建 AbstractMethod 类的集合,先把父类(DefaultSqlInjector)的所有方法(框架自带的方法)加入,再把自定义类 queryAllUser 加入

java
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> abstractMethods =super.getMethodList(mapperClass);

        abstractMethods.add(new QueryAllUser());

        return abstractMethods;
    }
}

自定义类 queryAllUser

  1. 该类需要实现需求的自定义方法的逻辑和 SQL 语句
  2. 继承 AbstractMethod 类,重写 injectMappedStatement 方法,该方法就是实现逻辑和 SQL 语句,最后 return 出去
  3. MySqlInjector 类添加该类,获取该类的方法,实现需求的扩充自定义方法 queryAllUser()
  4. 注意:return 的addSelectMappedStatementForTable是 select 语句,其他方法为addXXXStatement,其中 XXX 就是增删改
java
public class QueryAllUser extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sqlMethod = "queryAllUser";
        String sql = "select * from " + tableInfo.getTableName();
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addSelectMappedStatementForTable(mapperClass, sqlMethod, sqlSource, tableInfo);
    }
}

注册到 Spring 容器

  1. 将自定义类 MySqlInjector 注册到 Spring 容器
  2. 方法一:通过@Component 注解直接注册
  3. 方法二:自定义一个配置类,该类创建一个方法,返回 MySqlInjector 的对象,记得方法上放添加@Bean 注解实现注册
java
@Configuration
public class MybatisPlusPageConfig {
    // ....其他插件
    @Bean
    public MySqlInjector sqlInjector(){
        return new MySqlInjector();
    }
    // ....其他插件
}

自动填充功能

​ 插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version 等。在 MP 中提供了这样的功能,可以实现自动填充。

​ 就是类似于数据库的 default:默认值

添加@TableField 注解

为 password 添加自动填充功能,在新增数据时有效。

java
@TableField(fill = FieldFill.INSERT) //插入数据时进行填充
private String password;

FieldFill 多种选择:

java
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}

自定义类 MyMetaObjectHandle

自定义类继承 MetaObjectHandler,重写 insertFill 和 updateFill 方法

insertFill():实体类添加注解的属性,在插入时执行该方法

updateFill():实体类添加注解的属性,在更新时执行该方法

java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //插入时填充
    @Override
    public void insertFill(MetaObject metaObject) {
        Object password = getFieldValByName("password", metaObject);
        if(null == password){
            //字段为空,可以进行填充
            setFieldValByName("password", "123456", metaObject);
        }
    }

    // 更新时填充
    @Override
    public void updateFill(MetaObject metaObject) {
    }
}

逻辑删除

​ 开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,而并非真正的物理删除(非 DELETE 操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免 数据被真正的删除。

  1. 为表添加 deleted 字段,用于表示数据是否被删除,1 代表删除,0 代表未删除。

  2. 实体类增加 deleted 属性并且添加@TableLogic 注解

    java
    @TableLogic
    private Integer deleted;

Spring Boot 配置:

properties
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0

​ 当删除某数据时,只是把该数据的 deleted 字段值改为 1,不是真的删除数据

​ 当查询某数据时,数据字段 deleted 为 1 的数据不会查询出来

通用枚举

​ 如性别在数据库值为 1 或者 2 代表男女,使用通用枚举,解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!

  1. 定义枚举类,继承 IEnum 类,重写 getValue 和 toString 方法

    java
    public enum SexEnum implements IEnum<Integer> {
        MAN(1,"男"),
        WOMAN(2,"女");
    
        private int value;
        private String desc;
    
        SexEnum(int value, String desc) {
            this.value = value;
            this.desc = desc;
        }
        @Override
        public Integer getValue() {
        	return this.value;
        }
        @Override
        public String toString() {
        	return this.desc;
        }
    }
  2. Spring Boot 配置

    properties
    # 枚举包扫描
    mybatis-plus.type-enums-package=com.eight.enums
  3. 操作(插入、查询、条件查询)

    java
    // 插入
    public void testInsert(){
        User user = new User();
        user.setName("貂蝉");
        user.setUserName("diaochan");
        user.setAge(20);
        user.setEmail("diaochan@itast.cn");
        user.setVersion(1);
    
        user.setSex(SexEnum.WOMAN); // 枚举类
    
        int result = this.userMapper.insert(user); // 把枚举类的key传入数据库
        System.out.println("result = " + result);
    }
    // 查询
    public void testSelectById(){
        User user = this.userMapper.selectById(2L);
        System.out.println(user);
    }
    // 条件查询
    public void testSelectBySex() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("sex", SexEnum.WOMAN);
        List<User> users = this.userMapper.selectList(wrapper);
        for (User user : users) {
        	System.out.println(user);
    	}
    }

    插入时,把枚举类的 key 作为值传给数据库

    查询时,先获取数据库的值,根据值(key)获取对应的 value,再返回

代码生成器

​ AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

  1. 引入依赖

    xml
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.1.1</version>
    </dependency>
  2. 例子

    java
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
    import com.baomidou.mybatisplus.core.toolkit.StringPool;
    import com.baomidou.mybatisplus.core.toolkit.StringUtils;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.InjectionConfig;
    import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
    import com.baomidou.mybatisplus.generator.config.FileOutConfig;
    import com.baomidou.mybatisplus.generator.config.GlobalConfig;
    import com.baomidou.mybatisplus.generator.config.PackageConfig;
    import com.baomidou.mybatisplus.generator.config.StrategyConfig;
    import com.baomidou.mybatisplus.generator.config.TemplateConfig;
    import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    import com.baomidou.mybatisplus.generator.config.rules.DateType;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    
    /**
     * @Author Kele-Bing
     * @Create 2021/8/30 14:34
     * @Version 1.0
     * @Describe mysql 代码生成器演示例子
     */
    public class MysqlGenerator {
        /**
         * 读取控制台内容
         */
        public static String scanner(String tip) {
            Scanner scanner = new Scanner(System.in);
            StringBuilder help = new StringBuilder();
            help.append("请输入" + tip + ":");
            System.out.println(help.toString());
            if (scanner.hasNext()) {
                String ipt = scanner.next();
                if (StringUtils.isNotBlank(ipt)) {
                    return ipt;
                }
            }
            throw new MybatisPlusException("请输入正确的" + tip + "!");
        }
    
        /**
         * main函数
         */
        public static void main(String[] args) {
            // 代码生成器
            AutoGenerator mpg = new AutoGenerator();
    
            // 全局配置
            GlobalConfig gc = new GlobalConfig();
            // 获取用户路径
            String projectPath = System.getProperty("user.dir");
            // 设置生成文件的位置
            gc.setOutputDir(projectPath + "/src/main/java");
            // 设置作者
            gc.setAuthor("kele-Bing");
            // 是否打开资源管理器
            gc.setOpen(false);
            // 是否覆盖原来的文件
            gc.setFileOverride(false);
            // 设置日期类型
            gc.setDateType(DateType.ONLY_DATE);
            // 将配置放到自动生成器中
            mpg.setGlobalConfig(gc);
    
            // 数据源配置
            DataSourceConfig dsc = new DataSourceConfig();
            dsc.setUrl("jdbc:mysql://localhost:3306/kele?useSSL-=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
            // dsc.setSchemaName("public");
            dsc.setDriverName("com.mysql.cj.jdbc.Driver");
            dsc.setUsername("root");
            dsc.setPassword("123456");
            // 设置数据库类型
            dsc.setDbType(DbType.MYSQL);
            mpg.setDataSource(dsc);
    
            // 包配置
            PackageConfig pc = new PackageConfig();
            pc.setModuleName(scanner("模块名"));
            pc.setParent("com.eight.generator");
            // 设置文件位置,默认值为下面,可以不写
            pc.setEntity("entity");
            pc.setMapper("mapper");
            pc.setService("service");
            pc.setController("controller");
            mpg.setPackageInfo(pc);
    
            // 自定义配置
            InjectionConfig cfg = new InjectionConfig() {
                @Override
                public void initMap() {
                    // to do nothing
                }
            };
            List<FileOutConfig> focList = new ArrayList<>();
            focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    /*
                     * 自定义输入文件名称
                     * /项目名/src/main/resources/xml文件的包名/
                     */
                    return projectPath + "/mybatis-plus/src/main/resources/mapper/" + pc.getModuleName()
                    +"/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
                }
            });
            cfg.setFileOutConfigList(focList);
            mpg.setCfg(cfg);
            mpg.setTemplate(new TemplateConfig().setXml(null));
    
            // 策略配置
            StrategyConfig strategy = new StrategyConfig();
            strategy.setNaming(NamingStrategy.underline_to_camel);
            strategy.setColumnNaming(NamingStrategy.underline_to_camel);
            // strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity");
            // 是否使用Lombok注解
            strategy.setEntityLombokModel(true);
            // strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.B aseController");
            // 需要映射的表名
            strategy.setInclude(scanner("表名"));
            strategy.setSuperEntityColumns("id");
            strategy.setControllerMappingHyphenStyle(true);
            strategy.setTablePrefix(pc.getModuleName() + "_");
            // 设置驼峰命名法
            strategy.setRestControllerStyle(true);
            mpg.setStrategy(strategy);
            // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
            mpg.setTemplateEngine(new FreemarkerTemplateEngine());
    
            //最终执行
            mpg.execute();
        }
    }

总结

​ 配置多个自定义类,放到 spring 容器的两者方法

  1. 直接在类上方加入@Component 注解(分批处理,快捷)
  2. 创建一个核心配置类(上方加入@Component 注解),然后在类里创建不同的方法,不同的方法 return 不同的自定义类对象,记得再方法上加入@Bean 注解。(统一处理,代码阅读性强)
最近更新