Java-MyBatis-Plus

MyBatis-Plus

一、框架结构

image-20220521100835784

官网地址:https://baomidou.com/

二、入门案例

2.1 开发环境

IDE:idea 2019.2

JDK:JDK8+

构建工具:maven 3.5.4

MySQL版本:MySQL 5.7

Spring Boot:2.6.3

MyBatis-Plus:3.5.1

2.2 创建数据库及表

创建表

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
    `id` bigint(20) NOT NULL COMMENT '主键ID',
    `name` varchar(30) DEFAULT NULL COMMENT '姓名',
    `age` int(11) DEFAULT NULL COMMENT '年龄',
    `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加数据

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

2.3 创建Spring Boot工程

  1. 初始化工程

使用 Spring Initializr 快速初始化一个 Spring Boot 工程

image-20220505134631223

image-20220518105258286

image-20220505134614938

image-20220505134803959

  1. 引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!--mybatis-plus启动器-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
    <!--lombok用于简化实体类开发-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>
  1. 安装Lombok插件

image-20220518110120169

lombok常用注解:

注解 功能
@NoArgsConstructor 自动生成无参构造
@AllArgsConstructor 自动生成全参构造
@Getter@Setter 自动生成属性的get和set方法
@EqualsAndHashCode 自动生成hashCode() 和 equals()方法
@ToString 自动覆写toString方法,可以加其他参数
@Data 相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@NoArgsConstructor
@Value 是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法
@Log 生成常用日志实例
@Cleanup 用在变量前面,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法

2.4 编写代码

  1. 配置application.yml
spring:
  datasource:
    # 配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    # 配置连接数据库的各个信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
    username: root
    password: abc123

驱动类driver-class-name

spring boot 2.0(内置jdbc5驱动),驱动类使用:

driver-class-name: com.mysql.jdbc.Driver

spring boot 2.1及以上(内置jdbc8驱动),驱动类使用:

driver-class-name: com.mysql.cj.jdbc.Driver

  1. 启动类

在Spring Boot启动类MybatisplusApplication中添加@MapperScan注解,扫描mapper包

@SpringBootApplication
@MapperScan("fun.it.mybatisplus.mapper")
public class MybatisplusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisplusApplication.class, args);
    }
}
  1. 添加实体
@Data // lombok注解
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
  1. 添加mapper

BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型。

// 标记为持久层组件,使其不报错
@Repository
public interface UserMapper extends BaseMapper<User> {
}
  1. 测试
@SpringBootTest
public class MyBatisPlusTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList() {
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);
    }
}

结果

image-20220521111528551

  1. 添加日志

在application.yml中配置日志输出

mybatis-plus:
  configuration:
    # 配置日志信息,使其输出SQL语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

image-20220521111636040

三、基本CRUD

3.1 BaseMapper

MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:

public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     */
    int deleteById(Serializable id);

    /**
     * 根据实体(ID)删除
     */
    int deleteById(T entity);

    /**
     * 根据 columnMap 条件,删除记录
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID或实体 批量删除)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);

    /**
     * 根据 ID 修改
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查询(根据 columnMap 条件)
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        List<T> ts = this.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(ts)) {
            if (ts.size() != 1) {
                throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
            }
            return ts.get(0);
        }
        return null;
    }

    /**
     * 根据 Wrapper 条件,判断是否存在记录
     */
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0;
    }

    /**
     * 根据 Wrapper 条件,查询总记录数
     */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     */
    <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     */
    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

3.2 插入

@Test
public void testInsert() {
    // 新增用户信息
    User user = new User();
    user.setName("张三");
    user.setAge(23);
    user.setEmail("zhangsan@qq.com");
    int result = userMapper.insert(user);
    System.out.println("受影响行数:" + result);
    //1475754982694199298
    System.out.println("id = " + user.getId());
}

MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id。

3.3 删除

  1. 通过id删除记录
@Test
public void testDelete() {
    int result = userMapper.deleteById(1527538007976022017L);
    System.out.println("result = " + result);
}
  1. 通过id批量删除记录
@Test
public void testDelete() {
    //通过多个id批量删除
    //DELETE FROM user WHERE id IN ( ? , ? , ? )
    List<Long> list = Arrays.asList(1L, 2L);
    int result = userMapper.deleteBatchIds(list);
    System.out.println("result = " + result);
}
  1. 通过map条件删除记录
@Test
public void testDelete() {
    //根据map集合中所设置的条件删除记录
    //DELETE FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("name", "张三");
    map.put("age", 23);
    int result = userMapper.deleteByMap(map);
    System.out.println("result = " + result);
}

3.4 修改

@Test
public void testUpdate() {
    // 将需要修改的信息放入实体对象
    User user = new User();
    user.setId(1L);
    user.setName("李四");
    user.setEmail("lisi@qq.com");
    //UPDATE user SET name=?, age=? WHERE id=?
    int result = userMapper.updateById(user);
    System.out.println("result = " + result);
}

3.5 查询

  1. 根据id查询用户信息
@Test
public void testSelect() {
    //根据id查询用户信息
    //SELECT id,name,age,email FROM user WHERE id=?
    User user = userMapper.selectById(1L);
    System.out.println(user);
}
  1. 根据多个id查询多个用户信息
@Test
public void testSelect() {
    //根据多个id查询多个用户信息
    //SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
    List<Long> list = Arrays.asList(1L, 2L, 3L);
    List<User> users = userMapper.selectBatchIds(list);
    users.forEach(System.out::println);
}
  1. 通过map条件查询用户信息
@Test
public void testSelect() {
    //通过map条件查询用户信息
    //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
    Map<String, Object> map = new HashMap<>();
    map.put("name", "Jack");
    map.put("age", 20);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}
  1. 查询所有数据
@Test
public void testSelect() {
    //查询所有用户信息
    //SELECT id,name,age,email FROM user
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

BaseMapper中的大多方法中都有Wrapper类型的形参,此为条件构造器,可针对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所有数据。

3.6 通用Service

通用 Service CRUD 封装IService接口,进一步封装 CRUD 。采用get 查询单行remove 删除list 查询集合page 分页前缀命名方式区分Mapper层避免混淆。

MyBatis-Plus中有一个接口IService和其实现类ServiceImpl,封装了常见的业务层逻辑。

  1. 创建Service接口和实现类
/**
 * UserService继承IService模板提供的基础功能
 */
public interface UserService extends IService<User> {
}
/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
  1. 测试查询记录数
@Autowired
private UserService userService;

@Test
public void testGetCount(){
    //查询总记录数
    //SELECT COUNT( * ) FROM user
    long count = userService.count();
    System.out.println("总记录数:"+count);
}
  1. 测试批量插入
@Test
public void testInsertMore(){
    // SQL长度有限制,海量数据插入单条SQL无法实行,
    // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
    
    //批量添加
    //INSERT INTO user ( name, age ) VALUES ( ?, ? )
    List<User> list = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        User user = new User();
        user.setName("AA"+i);
        user.setAge(20+i);
        list.add(user);
    }
    boolean b = userService.saveBatch(list);
    System.out.println(b);
}

四、常用注解

4.1 @TableName

解决实体类名和数据库表名不一致的问题。

@TableName("t_user")
public class User {
    private Long id;
    private String userName;
    private Integer age;
    private String email;
}

也可通过全局配置解决问题,为所有的表都设置默认的前缀。

mybatis-plus:
  configuration:
    # 配置MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 设置MyBatis-Plus的全局配置
  global-config:
    db-config:
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_

4.2 @TableId

MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id。若实体类和表中表示主键的不是id,而是其他字段时,需要手动标识主键 。

//@TableName("t_user")
public class User {

    //将属性所对应的字段指定为主键
    //@TableId注解的value属性用于指定主键的字段
    //@TableId注解的type属性设置主键生成策略
    //@TableId(value = "uid", type = IdType.AUTO)
    @TableId("uid")
    private Long id;
    private String userName;
    private Integer age;
    private String email;

}

@TableId的value属性:标识表中主键所对应的字段名。

@TableId的type属性:用来定义主键策略。

常用的主键策略

描述
IdType.ASSIGN_ID(默认) 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
IdType.AUTO 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效

配置全局主键策略

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 设置MyBatis-Plus的全局配置
  global-config:
    db-config:
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_
      # 设置统一的主键生成策略
      id-type: auto

4.3 @TableField

MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格,在自动转换规则不满足时,可以手动标识属性所对应的字段名。

@TableName("t_user")
public class User {

    @TableId("uid")
    private Long id;

    //指定属性所对应的字段名
    @TableField("user_name")
    private String name;

    private Integer age;

    private String email;


}

4.4 @TableLogic

逻辑删除:将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库
中仍旧能看到此条数据记录。

实现逻辑删除

  1. 数据库中创建逻辑删除状态列,设置默认值为0

image-20220523110235023

  1. 实体类中添加逻辑删除属性

image-20220523110316882

在设置逻辑删除功能后,删除时真正执行的是修改,查询时被逻辑删除的数据也不会被查询。

五、条件构造器和常用接口

5.1 wrapper介绍

image-20220523110555529

  • Wrapper : 条件构造抽象类,最顶端父类
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : 查询条件封装
      • UpdateWrapper : Update 条件封装
      • AbstractLambdaWrapper : 使用Lambda 语法
        • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper

5.2 QueryWrapper

5.2.1 组装查询条件

@Test
public void test01(){
    //查询用户名包含a,年龄在20到30之间,邮箱信息不为null的用户信息
    //SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("user_name", "a")
            .between("age", 20, 30)
            .isNotNull("email");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.2.2 组装排序条件

@Test
public void test02(){
    //查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
    //SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,uid ASC
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("age")
            .orderByAsc("uid");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.2.3 组装删除条件

@Test
public void test03(){
    //删除邮箱地址为null的用户信息
    //UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    int result = userMapper.delete(queryWrapper);
    System.out.println("result:"+result);
}

5.2.4 条件的优先级

@Test
public void test04(){
    //将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
    //UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.gt("age", 20)
            .like("user_name", "a")
            .or()
            .isNull("email");
    User user = new User();
    user.setName("小明");
    user.setEmail("test@qq.com");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("result:"+result);
}
@Test
public void test05(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    //lambda中的条件优先执行
    //UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("user_name", "a")
            .and(i->i.gt("age",20).or().isNull("email"));
    User user = new User();
    user.setName("小红");
    user.setEmail("test@qq.com");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("result:"+result);
}

5.2.5 组装select子句

@Test
public void test06(){
    //查询用户的用户名、年龄、邮箱信息
    //SELECT user_name,age,email FROM t_user WHERE is_deleted=0
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("user_name", "age", "email");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

5.2.6 实现子查询

@Test
public void test07(){
    //查询id小于等于100的用户信息
    //SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 100))
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.inSql("uid", "select uid from t_user where uid <= 100");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.3 UpdateWrapper

@Test
public void test08(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.like("user_name", "a")
            .and(i -> i.gt("age", 20).or().isNull("email"));
    //组装set子句以及修改条件
    updateWrapper.set("user_name", "小黑").set("email","abc@qq.com");
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result:"+result);
}

5.4 condition(条件)

根据判断结果来选择是否组装该查询条件。

@Test
public void test10(){
    String username = "a";
    Integer ageBegin = null;
    Integer ageEnd = 30;
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // 第一个参数为条件,条件成立时,组装该查询条件
    queryWrapper.like(StringUtils.isNotBlank(username), "user_name", username)
            .ge(ageBegin != null, "age", ageBegin)
            .le(ageEnd != null, "age", ageEnd);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.5 LambdaQueryWrapper

@Test
public void test11(){
    //SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)
    String username = "a";
    Integer ageBegin = null;
    Integer ageEnd = 30;
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    //避免使用字符串表示字段,防止运行时错误
    queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
            .ge(ageBegin != null, User::getAge, ageBegin)
            .le(ageEnd != null, User::getAge, ageEnd);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.6 LambdaUpdateWrapper

@Test
public void test12(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.like(User::getName, "a")
            .and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
    updateWrapper.set(User::getName, "小黑").set(User::getEmail,"abc@qq.com");
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result:"+result);
}

六、插件

6.1 分页插件

  1. 添加配置类
@Configuration
//扫描mapper接口所在的包
@MapperScan("fun.it.mybatisplus.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页插件
        interceptor.addInnerInterceptor(
            new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
  1. 测试
@Test
public void testPage(){
    //设置分页参数,查询第2页,每页3条
    Page<User> page = new Page<>(2, 3);
    userMapper.selectPage(page, null);
    //获取分页数据
    System.out.println(page.getRecords());		// 当前页的结果
    System.out.println(page.getPages());		// 总页数
    System.out.println(page.getTotal());		// 总记录数
    System.out.println(page.hasNext());			// 是否有下一页
    System.out.println(page.hasPrevious());		// 是否有上一页
}

6.2 xml自定义分页

  1. UserMapper中定义接口方法
/**
 * 通过年龄查询用户信息并分页
 * @param page MyBatis-Plus所提供的分页对象,必须位于第一个参数的位置
 * @param age
 * @return
 */
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
  1. UserMapper.xml中编写SQL
<!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->
<select id="selectPageVo" resultType="User">
    select uid,user_name,age,email from t_user where age > #{age}
</select>
  1. 测试
@Test
public void testPageVo(){
    Page<User> page = new Page<>(1, 3);
    userMapper.selectPageVo(page, 20);
    System.out.println(page.getRecords());
    System.out.println(page.getPages());
    System.out.println(page.getTotal());
    System.out.println(page.hasNext());
    System.out.println(page.hasPrevious());
}

6.3 乐观锁

6.3.1 模拟修改冲突

  1. 数据库中增加商品表
CREATE TABLE t_product
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
    price INT(11) DEFAULT 0 COMMENT '价格',
    VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (id)
);
  1. 添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
  1. 添加实体
package fun.it.mybatisplus.pojo;

import lombok.Data;

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

  1. 添加mapper
public interface ProductMapper extends BaseMapper<Product> {
}
  1. 测试
@Test
public void testProduct01(){
    //小李查询商品价格
    Product productLi = productMapper.selectById(1);
    System.out.println("小李查询的商品价格:"+productLi.getPrice());
    //小王查询商品价格
    Product productWang = productMapper.selectById(1);
    System.out.println("小王查询的商品价格:"+productWang.getPrice());
    //小李将商品价格+50
    productLi.setPrice(productLi.getPrice()+50);
    productMapper.updateById(productLi);
    //小王将商品价格-30
    productWang.setPrice(productWang.getPrice()-30);
    int result = productMapper.updateById(productWang);

    //老板查询商品价格
    Product productLaoban = productMapper.selectById(1);
    System.out.println("老板查询的商品价格:"+productLaoban.getPrice());
}

6.3.2 乐观锁实现

  1. 数据库中添加version字段
  2. 取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
  1. 更新时,version + 1,如果where语句中的version不对,则更新失败
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1

6.3.3 MyBatis-Plus实现乐观锁

  1. 修改实体类
package fun.it.mybatisplus.pojo;

import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version //标识乐观锁版本号字段
    private Integer version;
}

  1. 添加乐观锁插件配置
@Configuration
//扫描mapper接口所在的包
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}
  1. 测试

再次执行测试语句,第二次的修改会失败。

  1. 优化流程
@Test
public void testProduct01(){
    //小李查询商品价格
    Product productLi = productMapper.selectById(1);
    System.out.println("小李查询的商品价格:"+productLi.getPrice());
    //小王查询商品价格
    Product productWang = productMapper.selectById(1);
    System.out.println("小王查询的商品价格:"+productWang.getPrice());
    //小李将商品价格+50
    productLi.setPrice(productLi.getPrice()+50);
    productMapper.updateById(productLi);
    //小王将商品价格-30
    productWang.setPrice(productWang.getPrice()-30);
    int result = productMapper.updateById(productWang);
    if(result == 0){
        //操作失败,重试
        Product productNew = productMapper.selectById(1);
        productNew.setPrice(productNew.getPrice()-30);
        productMapper.updateById(productNew);
    }
    //老板查询商品价格
    Product productLaoban = productMapper.selectById(1);
    System.out.println("老板查询的商品价格:"+productLaoban.getPrice());
}

七、通用枚举

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现。

  1. 数据库表添加字段sex

image-20220523152838015

  1. 创建通用枚举类型
package fun.it.mybatisplus.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum SexEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    @EnumValue //将注解所标识的属性的值存储到数据库中
    private Integer sex;
    private String sexName;

    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}
  1. 配置扫描通用枚举
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 设置MyBatis-Plus的全局配置
  global-config:
    db-config:
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_
      # 设置统一的主键生成策略
      id-type: auto
  # 配置类型别名所对应的包
  type-aliases-package: com.atguigu.mybatisplus.pojo
  # 扫描通用枚举的包
  type-enums-package: com.atguigu.mybatisplus.enums
  1. 测试
@Test
public void testSexEnum(){
    User user = new User();
    user.setName("Enum");
    user.setAge(20);
    //设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库
    user.setSex(SexEnum.MALE);
    //INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? )
    //Parameters: Enum(String), 20(Integer), 1(Integer)
    userMapper.insert(user);
}

八、代码生成器

8.1 引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

8.2 快速生成

public class FastAutoGeneratorTest {

    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=false", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("li") // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://mybatis_plus"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("fun.it") // 设置父包名
                            .moduleName("mybatisplus") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

}

九、多数据源

适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等。

场景说明:我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功。

9.1 创建数据库及表

  1. 创建数据库mybatis_plus_1和表product
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `mybatis_plus_1`;
CREATE TABLE product
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
    price INT(11) DEFAULT 0 COMMENT '价格',
    VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (id)
);
  1. 添加测试数据
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
  1. 删除mybatis_plus库中的product表
use mybatis_plus;
DROP TABLE IF EXISTS product;

9.2 引入依赖

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

9.3 配置多数据源

spring:
  # 配置数据源信息
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456

9.4 创建用户service

public interface UserService extends IService<User> {
}
@Service
@DS("master") //指定所操作的数据源
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

9.5 创建商品service

public interface ProductService extends IService<Product> {
}
@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}

9.6 测试

@Autowired
private UserService userService;
@Autowired
private ProductService productService;

@Test
public void test(){
	System.out.println(userService.getById(1));
	System.out.println(productService.getById(1));
}

都能顺利获取对象,则测试成功。

十、MyBatisX快速开发插件

功能

  1. xml跳转

跳转

  1. 生成代码

  2. 重置模板

生成代码

  1. JPA提示

生成新增

生成新增

生成查询

生成查询

生成修改

生成修改

生成删除

生成删除

详见官方文档

posted @   ChingFun  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示