Mybatis-Plus novice introduction, one article is enough

Table of contents

1. Introduction to MyBatis-Plus

1 Introduction

2. Characteristics

3. Support database

4. Frame structure

5. Official address

2. Introductory case

1. Development environment

2. Build database and table

3. Create a project

4. Configure encoding

1.BaseMapper

5. Test query

Three, add, delete, modify and check

1.BaseMapper

2. Call the Mapper layer to implement CRUD

2.1 insert

2.2 delete

2.3 Modifications

2.4 Query

3. General Service

4. Call Service layer to operate data

Four, common annotations

1. @TableName

1.1 Leading to the problem

1.2 Problem solving

2.@TableId

2.1 Leading to the problem

2.2 Problem solving

2.3 The value attribute of @TableId

2.4 The type attribute of @TableId

3.@TbaleField

3.1 Case 1

3.2 Case 2

4.@TableLogic

4.1 Tombstone

4.2 Realize logical deletion

5. Condition constructor

1. Wrapper introduction

2.QueryWrapper

3.UpdateWrapper

4.condition parameters

5.LambdaQueryWrapper

6.LambdaUpdateWrapper

Six, commonly used plug-ins

1. Pagination plugin

2. Custom pagination

3. Optimistic lock

3.1 Scenarios

3.2 Optimistic lock and pessimistic lock

3.3 Simulate modification conflicts

3.4 Optimistic locking solves the problem

Seven, general enumeration

8. Code generator

1. Introduce dependencies

2. Rapid generation

Nine, multiple data sources

1. Create database and table

2. New projects introduce dependencies

3. Write configuration files

4. Create entity classes

5. Create Mapper and Service

6. Write the test method

10. MyBatisX plugin

1. Install the MyBatisX plugin

2. Generate code quickly

3. Quickly generate CRUD



1. Introduction to MyBatis-Plus

1 Introduction

MyBatis-Plus (opens new window) (MP for short) is an enhanced tool for MyBatis (opens new window) . On the basis of MyBatis, only enhancements are made without changes, and it was born to simplify development and improve efficiency.

Our vision is to become the best partner of MyBatis, just like the 1P and 2P in Contra, the efficiency is doubled when paired with friends.

2. Characteristics

  • No intrusion : only enhancement and no change, the introduction of it will not affect the existing project, as smooth as silk

  • Low loss : the basic CURD will be automatically injected when it is started, the performance is basically lossless, and the object-oriented operation is directly performed

  • Powerful CRUD operations : Built-in general Mapper and general Service, most of the CRUD operations on a single table can be realized with only a small amount of configuration, and there is a powerful condition constructor to meet various usage needs

  • Support Lambda form call : through Lambda expressions, it is convenient to write various query conditions, no need to worry about field typos

  • Supports automatic primary key generation : supports up to 4 primary key strategies (including a distributed unique ID generator - Sequence), which can be freely configured to perfectly solve the primary key problem

  • Support ActiveRecord mode : support ActiveRecord form call, the entity class only needs to inherit the Model class to perform powerful CRUD operations

  • Support custom global general operations : support global general method injection ( Write once, use anywhere )

  • Built-in code generator : use code or Maven plug-in to quickly generate Mapper, Model, Service, Controller layer code, support template engine, and more custom configurations are waiting for you to use

  • Built-in paging plug-in : Based on MyBatis physical paging, developers don't need to care about specific operations. After configuring the plug-in, writing paging is equivalent to ordinary List query

  • The paging plug-in supports multiple databases : supports MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, Postgre, SQLServer and other databases

  • Built-in performance analysis plug-in : It can output SQL statements and their execution time. It is recommended to enable this function during development and testing, which can quickly find out slow queries

  • Built-in global interception plug-in : Provides intelligent analysis and blocking of delete and update operations on the entire table, and can also customize interception rules to prevent misoperations

3. Support database

Any database that can MyBatisuse CRUD and supports standard SQL, the specific support is as follows, if not in the list below, check the pagination part of the tutorial PR for your support.

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb

  • Dameng Database, Xugu Database, Renmin University Jincang Database, Nanda General (Huaku) Database, Nanda General Database, Shentong Database, Hangao Database

4. Frame structure

First scan the entity class (scan entity), extract the attributes in the entity class through reflection extraction (reflection extraction), and then analyze who is the table to be operated on, who is the attribute in the entity class to be operated, and who is the field. Then generate the sql statement and inject it into the mybati container. The table we want to operate must be determined by the entity class and the attributes of the entity class.

5. Official address

Official Website: MyBatis-Plus

Official document: Introduction | MyBatis-Plus

2. Introductory case

1. Development environment

  • IDEA: IDEA 2019.3.5

  • JDK:JDK8+

  • Build tools: Maven 3.5.4

  • MySQL:MySQL 8.0.24

  • Navicat:Navicat Premium 15

  • Spring Boot:2.6.7

  • MyBatis-Plus:3.5.1

2. Build database and table

  • Open Navicat and run the following SQL script to build database and table

    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 some test data

    INSERT INTO user (id, name, age, email) VALUES 
    (1, 'Jone', 18, '[email protected]'), 
    (2, 'Jack', 20, '[email protected]'), 
    (3, 'Tom', 28, '[email protected]'), 
    (4, 'Sandy', 21, '[email protected]'), 
    (5, 'Billie', 24, '[email protected]');

3. Create a project

  • Use Spring Initializerquick initialization of a Spring Boot project

  • Imported MyBatis-Plusdependencies

    <!--mybatis-plus启动器-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    ​
        <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.27</version>
     </dependency>

  • The most complete jar dependency package If the last step of the above screenshot operation is not checked, you can directly copy all the following dependencies to the prom file

    <dependencies>
    ​
        <!--web场景开发启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    ​
    ​
        <!--lombok:简化实体类开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    ​
        <!--开启测试启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    ​
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
    ​
        <!-- mybatis-plus启动器-->
        <!-- mybatis-plus 是自己开发,并非官方的! -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
    ​
        <!--多数据源依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
    ​
    </dependencies>

  • install Lombokplugin

4. Configure encoding

  • configuration application.yamlfile

    spring:
      #配置数据库
      datasource:
        # 配置连接数据库信息
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: 123456
    ​
    mybatis-plus:
      # 配置MyBatis日志
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    ​
        #全局配置
      global-config:
        db-config:
          #配置统一的主键策略为自增,如果不设置默认为雪花算法
          id-type: auto
          # 设置实体类所对应的表的统一前缀,为实体类所对应的表名设置默认的前缀
          table-prefix: t_
    ​
      #配置mapper映射文件路径,名字要和mapper接口名字一致 这是默认路径不写也行
      mapper-locations: classpath*:/mapper/**/*.xml
    ​
      #配置类型别名所对应的包
      type-aliases-package: com.yka.boot02mybatis_plus.pojo
    ​
      # 扫描通用枚举的包
      type-enums-package: com.yka.boot02mybatis_plus.enums

  • Create a config package to create a configuration class, add the @MapperScan` annotation, scan the Mapper folder and add the @Configuration annotation**

    @Configuration //告诉SpringBoot这是一个配置类 == spring配置文件
    @MapperScan("com.yka.boot02mybatis_plus.mapper")//扫描接口包 在当前配置类(spring.xml)中填写注解最合适
    //配置mapper接口的扫描配置
    //由mybatis-spring提供,可以将指定包下所有的mapper接口创建代理实现类
    //并将这些动态代理作为IOC容器的bean管理,接口就可以自动装配了,直接可以调用接口中的方法
    public class MyConfig {
    }

  • Write the entity class User.java(Lombok is used here to simplify the code)

    @Data//自动提供get set方法、tosString方法,equals方法
    @AllArgsConstructor//有参构造器
    @NoArgsConstructor//无参构造器
    @TableName("user")//绑定表 yaml文件中设置了全局配置这里可以不用注解了 yaml文件配置文件中:db-config:table-prefix: t_
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }

  • Write UserMapperthe interface

  • 1.BaseMapper<T>

    illustrate:

    • General CRUD encapsulates the BaseMapper interface, automatically resolves the entity table relationship mapping at Mybatis-Plusstartup and converts it into Mybatisan internal object injection container

    • Generics Tare any entity object

    • The parameter Serializableis any type of primary key Mybatis-Plus. It is not recommended to use a composite primary key. Every table has its own unique idprimary key.

    • object Wrapperis a conditional initializer

    The basic CRUD in MyBatis-Plus has been implemented in the built-in BaseMapper, so we can use it directly after inheriting this interface.

    The CRUD operation demonstrated this time does not include methods with conditional constructors as parameters, and the conditional constructors will be demonstrated in a separate chapter. CRUD methods provided in BaseMapper:

  • // 在对应的Mapper上面继承基本的类 BaseMapper,就能直接用BaseMapper接口里面的sql语句了
    //MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,因此我们继承该接口以后可以直接使用。
    @Repository // 代表持久层
    public interface UserMapper extends BaseMapper<User> {
    // 所有的CRUD操作都已经编写完成了
    // 你不需要像以前的配置一大堆文件了!
    ​
        @Select("select * from user where id = #{id}")
        public User selById(Integer id);
    ​
    }

5. Test query

  • write a test classMyBatisPlusTest.java

    @SpringBootTest
    public class MyBatisPlusTest {
        @Resource
        private UserMapper userMapper;
    ​
        /**
         * 测试查询所有数据
         */
        @Test
        void testSelectList(){
            //通过条件构造器查询一个list集合,若没有条件,则可以设置null为参数
            List<User> users = userMapper.selectList(null);
            users.forEach(System.out::println);
        }
    }
  • The console prints the query results

Three, add, delete, modify and check

1.BaseMapper<T>

illustrate:

  • General CRUD encapsulates the BaseMapper interface, automatically resolves the entity table relationship mapping at Mybatis-Plusstartup and converts it into Mybatisan internal object injection container

  • Generics Tare any entity object

  • The parameter Serializableis any type of primary key Mybatis-Plus. It is not recommended to use a composite primary key. Every table has its own unique idprimary key.

  • object Wrapperis a conditional initializer

The basic CRUD in MyBatis-Plus has been implemented in the built-in BaseMapper, so we can use it directly after inheriting this interface.

The CRUD operation demonstrated this time does not include methods with conditional constructors as parameters, and the conditional constructors will be demonstrated in a separate chapter.


CRUD methods provided in BaseMapper:

  • Added: Insert

    // 插入一条记录
    int insert(T entity);
  • Delete: Delete

    // 根据 entity 条件,删除记录
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
    // 删除(根据ID 批量删除)
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根据 ID 删除
    int deleteById(Serializable id);
    // 根据 columnMap 条件,删除记录
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
  • Modification: Update

    // 根据 whereWrapper 条件,更新记录
    int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
    // 根据 ID 修改
    int updateById(@Param(Constants.ENTITY) T entity);
  • Query: Select

    // 根据 ID 查询
    T selectById(Serializable id);
    // 根据 entity 条件,查询一条记录
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    ​
    // 查询(根据ID 批量查询)
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根据 entity 条件,查询全部记录
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 查询(根据 columnMap 条件)
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    // 根据 Wrapper 条件,查询全部记录
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    ​
    // 根据 entity 条件,查询全部记录(并翻页)
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录(并翻页)
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询总记录数
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

2. Call the Mapper layer to implement CRUD

2.1 insert


The result of the final execution, the obtained id is 1527206783590903810

This is because MyBatis-Plus will generate id based on the snowflake algorithm strategy by default when inserting data

/**
  * 测试插入一条数据 INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
  * MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id,后面常用注解可以修改
  */
@Test
public void testInsert(){
    User user = new User();
    user.setName("Vz");
    user.setAge(21);
    user.setEmail("[email protected]");
    int result = userMapper.insert(user);
    System.out.println(result > 0 ? "添加成功!" : "添加失败!");
    System.out.println("受影响的行数为:" + result);
    //1527206783590903810(当前 id 为雪花算法自动生成的id)
    System.out.println("id自动获取" + user.getId());
}

2.2 delete


a. Delete data according to ID

Call method: int deleteById(Serializable id);

/**
  * 测试根据id删除一条数据  DELETE FROM user WHERE id=?
  */
@Test
public void testDeleteById(){
    
    int result = userMapper.deleteById(1527206783590903810L);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

b. Delete data in batches according to ID

Call method: int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

/**
  * 测试通过id批量删除数据  DELETE FROM user WHERE id IN ( ? , ? )
  */
@Test
public void testDeleteBatchIds(){
    List<Long> ids = Arrays.asList(6L,7L,8L);
    int result = userMapper.deleteBatchIds(ids);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}
//删除所有数据 DELETE FROM user 
userMapper.delete(null);

c. Delete data according to Map conditions

调用方法:int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

/**
   * 测试根据Map集合中所设置的条件删除数据  
   */
@Test
public void testDeleteByMap(){
    //当前演示为根据name和age删除数据
    //执行SQL为:DELETE FROM user WHERE name = ? AND age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("name","Vz");
    map.put("age",21);
    int result = userMapper.deleteByMap(map);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

2.3 Modifications

Call method: int updateById(@Param(Constants.ENTITY) T entity);

/**
  * 测试根据id修改用户信息
  */
@Test
public void testUpdateById(){
    //执行SQL为: UPDATE user SET name=?, age=?, email=? WHERE id=?
    User user = new User();
    user.setId(6L);
    user.setName("VzUpdate");
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.updateById(user);
    System.out.println(result > 0 ? "修改成功!" : "修改失败!");
    System.out.println("受影响的行数为:" + result);
}

2.4 Query


a. Query user information based on ID

Call method: T selectById(Serializable id);

/**
  * 测试根据id查询用户数据 SELECT id,name,age,email FROM user WHERE id=?
  */
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

b. Query multiple user information based on multiple IDs

调用方法:List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

/**
  * 根据多个id查询用户数据
  */
@Test
public void testSelectBatchIds(){
    //执行SQL为:SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
    List<Long> ids = Arrays.asList(1L,2L,3L);
    List<User> users = userMapper.selectBatchIds(ids);
    users.forEach(System.out::println);
}

c. Query user information according to Map conditions

调用方法:List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

/**
  * 根据Map所设置的条件查询用户
  */
@Test
public void testSelectByMap(){
    //执行SQL为:SELECT id,name,age,email FROM user WHERE age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("age",18);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

d. Query all user information

Call method: List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

/**
  * 测试查询所有数据  SELECT id,name,age,email FROM user
  */
@Test
void testSelectList(){
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

3. General Service

illustrate:

  • General Service CRUD encapsulation IServiceinterface, further encapsulation CRUD use get 查询单行 remove 删除 list 查询集合 page 分页prefix naming method to distinguish Mapperlayers to avoid confusion,

  • Generics Tare any entity object

  • It is recommended that if there is a possibility to customize the general Service method, please create your IBaseServiceown Mybatis-Plusbase class inherited from the provided

  • object Wrapperis a conditional initializer

There is an interface IServiceand ServiceImpl, which encapsulates common business layer logic, see the source code IService and ServiceImpl for details

Therefore, when we use it, we only need to Serviceinherit IServicethe interface in the interface we define, implement our own Service in our own implementation class and inherit it ServiceImpl.


CRUD methods in IService

  • Added: Save, SaveOrUpdate

    // 插入一条记录(选择字段,策略插入)
    boolean save(T entity);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList, int batchSize);
    ​
    // TableId 注解存在更新记录,否插入一条记录
    //saveOrUpdate:会去表里查该id,如果表里有该id修改,无id是增加,新增不需要id,更改需要id
    boolean saveOrUpdate(T entity);
    // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
    boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
    // 批量修改插入 会去表里查该id,如果表里有该id修改,无id是增加,新增不需要id,更改需要id
    boolean saveOrUpdateBatch(Collection<T> entityList);
    // 批量修改插入 有id修改无id是增加
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
  • Delete: Remove

    // 根据 entity 条件,删除记录
    boolean remove(Wrapper<T> queryWrapper);
    // 根据 ID 删除
    boolean removeById(Serializable id);
    // 根据 columnMap 条件,删除记录
    boolean removeByMap(Map<String, Object> columnMap);
    // 删除(根据ID 批量删除)
    boolean removeByIds(Collection<? extends Serializable> idList);
  • Modification: Update

    // 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
    boolean update(Wrapper<T> updateWrapper);
    // 根据 whereWrapper 条件,更新记录
    boolean update(T updateEntity, Wrapper<T> whereWrapper);
    // 根据 ID 选择修改
    boolean updateById(T entity);
    // 根据ID 批量更新 有id修改无id是增加
    boolean updateBatchById(Collection<T> entityList);
    // 根据ID 批量更新 有id修改无id是增加
    boolean updateBatchById(Collection<T> entityList, int batchSize);
  • Query: Get, List, Count

    // 根据 ID 查询
    T getById(Serializable id);
    // 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
    T getOne(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);
    // 根据 Wrapper,查询一条记录
    Map<String, Object> getMap(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    ​
    ​
    // 查询所有
    List<T> list();
    // 查询列表
    List<T> list(Wrapper<T> queryWrapper);
    // 查询(根据ID 批量查询)
    Collection<T> listByIds(Collection<? extends Serializable> idList);
    // 查询(根据 columnMap 条件)
    Collection<T> listByMap(Map<String, Object> columnMap);
    // 查询所有列表
    List<Map<String, Object>> listMaps();
    // 查询列表
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
    // 查询全部记录
    List<Object> listObjs();
    // 查询全部记录
    <V> List<V> listObjs(Function<? super Object, V> mapper);
    // 根据 Wrapper 条件,查询全部记录
    List<Object> listObjs(Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录
    <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    ​
    // 查询总记录数
    int count();
    // 根据 Wrapper 条件,查询总记录数
    int count(Wrapper<T> queryWrapper);
  • Pagination: Page

    default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {}

4. Call Service layer to operate data

In our Service interface, by inheriting the IService interface provided by MyBatis-Plus, we can not only obtain the CRUD methods provided by it, but also use the methods defined by ourselves.

  • create UserServiceand inheritIService

    /**
      * UserService继承IService模板提供的基础功能 
      */
    public interface UserService extends IService<User> {}
  • Created UserServiceimplementation class and inheritedServiceImpl

    /**
      * ServiceImpl实现了IService,提供了IService中基础功能的实现 
      * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
      */
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
  • Number of test query records

    Call method: int count();

    @Test
    public void testGetCount(){
        //查询总记录数
        //执行的SQL为:SELECT COUNT( * ) FROM user
        long count = userService.count();
        System.out.println("总记录数:" + count);
    }

Four, common annotations

The annotations provided by MyBatis-Plus can help us solve some problems of mapping between databases and entities.

1. @TableName

After the above tests, when using MyBatis-Plus to implement basic CRUD, we did not specify the table to be operated, but set the generic User when the Mapper interface inherited BaseMapper, and the operated table is the user table, thus It is concluded that when MyBatis-Plus determines the table to operate, it is determined by the generic type of BaseMapper, that is, the entity type, and the table name of the default operation is consistent with the class name of the entity type.

1.1 Leading to the problem


If the class name of the entity class type is inconsistent with the table name of the table to be operated, what problems will occur?

  • We userrename the table t_userand test the query function

  • The program throws an exception, Table 'mybatis_plus.user' doesn't exist , because the current table name t_user, and the table name of the default operation is consistent with the class name of the entity type, that is, userthe table

1.2 Problem solving

a. Use annotations to solve problems

Add it to the entity class type @TableName("t_user")to identify the table corresponding to the entity class, and the SQL statement can be successfully executed

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

b. Use global configuration to solve the problem

In the process of development, we often encounter the above problems, that is, the tables corresponding to the entity classes have fixed prefixes, for example t_or tbl_global configuration provided by MyBatis-Plus to set the table name corresponding to the entity class Set the default prefix, then there is no need to identify the table corresponding to the entity class through @TableName on each entity class

mybatis-plus:
  global-config:
    db-config:
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_

2.@TableId

After the above tests, when MyBatis-Plus implements CRUD, it will use id as the primary key column by default, and when inserting data, it will generate id based on the snowflake algorithm strategy by default

2.1 Leading to the problem


If the entity class and table represent the primary key instead of id, but other fields, such as uid, will MyBatis-Plus automatically recognize uid as the primary key column?

  • The attributes in our entity class idare changed uid, and the fields in the table idare also changed uidto test the addition function

  • The program throws an exception, Field 'uid' doesn't have a default value , indicating that MyBatis-Plus has not assigned uida value as the primary key

2.2 Problem solving


By identifying the uid attribute as the primary key in the entity class @TableId, the SQL statement can be successfully executed

@Date
public class User {
    //将属性所对应的字段指定为主键
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

2.3 The value attribute of @TableId


If the attribute corresponding to the primary key in the entity class is id, and the field representing the primary key in the table is uid, at this time, if only the annotation @TableId is added to the attribute id, an exception Unknown column 'id' in 'field list' will be thrown, that is MyBatis-Plus will still use id as the primary key of the table, and the primary key in the table is the field uid. At this time, you need to specify the primary key field in the table through the value attribute of the @TableId annotation, @TableId("uid")or@TableId(value="uid")

2.4 The type attribute of @TableId


The type attribute is used to define the primary key strategy: the default snowflake algorithm

Commonly used primary key strategies:

value describe
IdType.ASSIGN_ID (default) The data id is generated based on the snowflake algorithm strategy, regardless of whether the database id is set to auto-increment
IdType.AUTO Use the auto-increment strategy of the database. Note that for this type, please ensure that the database is set with id auto-increment.
    
//如果在.yaml文件中设置了全局配置这里就不用再写注解了
    //IdType.ASSIGN_ID(默认)基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
    //@TableId(type = IdType.AUTO,value = "id")//使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,
    // value:如果属性名和字段主键名不一致,可以指定主键字段
    private Long id;

Configure the global primary key strategy: after configuration, there is no need to write annotations in the entity class

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

3.@TbaleField

After the above tests, we can find that when MyBatis-Plus executes SQL statements, it must ensure that the attribute names in the entity class are consistent with the field names in the table

If the attribute name and field name in the entity class are inconsistent, what will happen?

3.1 Case 1


If the attributes in the entity class use the camel case naming style, and the fields in the table use the underscore naming style

For example, entity class attributes userName, fields in tablesuser_name

At this point MyBatis-Plus will automatically convert the underscore naming style to the hump naming style

Equivalent to configuring in MyBatis

3.2 Case 2


If the attributes in the entity class and the fields in the table do not satisfy condition 1

For example, entity class attributes name, fields in tablesusername

@TableField("username")At this time, you need to use the field name corresponding to the setting attribute on the entity class attribute

public class User {
    @TableId("uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}

4.@TableLogic

4.1 Tombstone


Physical deletion: real deletion, the corresponding data is deleted from the database, and the deleted data cannot be queried afterwards

Logical deletion: False deletion, modify the status of the field representing whether it is deleted in the corresponding data to "deleted status", and then this data record can still be seen in the database

Usage scenario: data recovery is possible

4.2 Realize logical deletion


  • Create a tombstone status column in the database, set the default value to 0, 0 not deleted, 1 deleted

  • Add tombstone attribute in entity class

  • Test the delete function, what is actually performed is the modification

    public void testDeleteById(){
        int result = userMapper.deleteById(1527472864163348482L);
        System.out.println(result > 0 ? "删除成功!" : "删除失败!");
        System.out.println("受影响的行数为:" + result);
    }

  • Execute the query method at this time, and the result of the query is to automatically add conditionsis_deleted=0,查询的是未删除的数据

5. Condition constructor

1. Wrapper introduction

  • Wrapper: conditionally constructed abstract class, the topmost parent class

  • AbstractWrapper: Used for encapsulating query conditions and generating where conditions for sql 

    • QueryWrapper: Encapsulation of query conditions

    • UpdateWrapper: Update conditional package

    • AbstractLambdaWrapper: Use Lambda syntax

      • LambdaQueryWrapper: Query Wrapper for Lambda syntax usage

      • LambdaUpdateWrapper: Lambda update package Wrapper

2.QueryWrapper

  • Assembling query conditions

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

  • Assembly Sort Conditions

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

  • Assembly delete condition

    执行SQL:UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)

    public void test03(){
        //删除邮箱地址为null的用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
        int result = userMapper.delete(queryWrapper);
        System.out.println(result > 0 ? "删除成功!" : "删除失败!");
        System.out.println("受影响的行数为:" + result);
    }

  • modify function

    @Test
        public void test04(){
            //将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
            //UPDATE t_user SET name=?, email=? WHERE is_deleted=0 AND (age > ? AND name LIKE ? OR email IS NULL)
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.gt("age",20).like("name","a")
                    .or()
                    .isNull("email");
            User user = new User();
            user.setName("小明");
            user.setEmail("[email protected]");
    ​
            int result = userMapper.update(user,queryWrapper);
            System.out.println(result > 0 ? "修改成功!" : "修改失败!");
            System.out.println("受影响的行数为:" + result);
        }

    Priority of conditions

     @Test
        public void test05(){
            //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
            //UPDATE t_user SET name=?, email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
            //lambda中优先执行,i就是条件构造器queryWrapper .and()和.or()都有lambda表达式
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like("name","a")
                    .and(i-> i.gt("age",20).or().isNull("email"));
    ​
            User user = new User();
            user.setName("小红");
            user.setEmail("[email protected]");
    ​
            int result = userMapper.update(user, queryWrapper);
            System.out.println(result > 0 ? "修改成功!" : "修改失败!");
            System.out.println("受影响的行数为:" + result);
        }
     
  • Assembling the select clause

    执行SQL:SELECT username,age,email FROM t_user WHERE is_deleted=0

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

  • implement the subquery

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

3.UpdateWrapper

UpdateWrapper not only has the assembly condition function of QueryWrapper, but also provides the set method to modify the database information of the corresponding condition

 @Test
    public void test08(){
        //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
        //UPDATE t_user SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("name","a")
                .and( i -> i.gt("age",20).or().isNull("email"))
                .set("name","小黑").set("email","[email protected]");//设置修改的字段
​
        int result = userMapper.update(null, updateWrapper);
        System.out.println(result > 0 ? "修改成功!" : "修改失败!");
        System.out.println("受影响的行数为:" + result);
    }

4.condition parameters

If the condition of the condition parameter is met, the condition in the condition constructor is executed

Simulate the parameters passed from the client to the server during development and test

In the actual development process, assembling conditions is a common function, and these condition data come from user input and are optional. Therefore, when we assemble these conditions, we must first determine whether the user has selected these conditions. If so, we need to Assemble the condition, if there is no selection (null), it must not be assembled, so as not to affect the result of SQL execution

  • Idea 1 Complicated writing

    执行SQL: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 <= ?)

    //复杂写法,模拟开发中客户端传到服务器的参数就行测试
        @Test
        public void test09(){
            //SELECT id,name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (name LIKE ? AND age <= ?)
            String username = "a";
            Integer ageBegin = null;
            Integer ageEnd = 30;
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            if(StringUtils.isNotBlank(username)){
                //isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
                queryWrapper.like("name", username);
            }
            if(ageBegin != null){
                queryWrapper.ge("age", ageBegin);
            }
            if(ageEnd != null){
                queryWrapper.le("age", ageEnd);
            }
            List<User> list = userMapper.selectList(queryWrapper);
            list.forEach(System.out::println);
        }

  • Idea 2 Simple way of writing: use the condition parameter

  • If the condition of the condition parameter is met, the condition in the condition constructor is executed

  • There is no problem with the above implementation scheme, but the code is more complicated. We can use the overloaded method with the condition parameter to construct the query condition and simplify the code writing

     //简单写法:使用condition参数
        @Test
        public void test10(){
            //SELECT id,name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (name LIKE ? AND age <= ?)
            String username = "a";
            Integer ageBegin = null;
            Integer ageEnd = 30;
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like(StringUtils.isNotBlank(username),"name",username)
                    .ge(ageBegin!=null,"age",20)
                    .le(ageEnd!=null,"age",20);
            List<User> list = userMapper.selectList(queryWrapper);
          list.forEach(System.out::println);
        }

5.LambdaQueryWrapper

The function is equivalent to QueryWrapper, and the syntax of Lambda expression is provided to avoid filling in wrong column names. Automatically match field names by attribute names

 //LambdaQueryWrapper:提供了Lambda表达式的语法可以避免填错列名。通过属性名自动匹配字段名
    //SELECT id,name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (name LIKE ? AND age <= ?)
    @Test
    public void test11(){
        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,20) //User::getAge:通过属性名自动匹配字段名
                .le(ageEnd!=null,User::getAge,20);
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

6.LambdaUpdateWrapper

The function is equivalent to UpdateWrapper, and the syntax of Lambda expression is provided to avoid filling in wrong column names.

//LambdaUpdateWrapper:功能等同于UpdateWrapper,提供了Lambda表达式的语法可以避免填错列名。
//UPDATE t_user SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
 @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,"[email protected]");
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result:"+result);
}

Six, commonly used plug-ins

1. Pagination plugin

MyBatis Plus has its own paging plug-in, and the paging function can be realized with simple configuration

  • Import mybatis-plus dependencies

    <!--mybatis-plus启动器-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    ​
     <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.27</version>
    </dependency>

  • Add configuration classMyBatisPlusConfig

    @Configuration
    @MapperScan("com.atguigu.mybatisplus.mapper")
    public class MyBatisPlusConfig {
        //配置MybatisPlus的插件的  Interceptor:拦截器
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            //添加分页插件  DbType:数据库类型
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    }
  • write test method

    @Test
    public void testPage(){
        //new Page()中的两个参数分别是当前页码,每页显示数量
        //SELECT id,name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?,?
            Page<User> page = new Page<>(2,2);
            Page<User> userPage = userMapper.selectPage(page, null);
    ​
            System.out.println(userPage);
          List<User> users = page.getRecords();//分页后的数据
            users.forEach(System.out::println);
    ​
            System.out.println("总页数:"+userPage.getPages());
            System.out.println("总条数:"+userPage.getTotal());
            System.out.println("当前页:"+userPage.getCurrent());
            System.out.println("当前页显示条数:"+userPage.getSize());
            System.out.println("是否有下一页:"+userPage.hasNext());
            System.out.println("是否有上一页:"+userPage.hasPrevious());
    }

2. Custom pagination

The above call is the method with paging provided by MyBatis-Plus, so how do we implement paging in the method defined by ourselves?

  • UserMapperdefine a method in the interface

    /**
      * 根据年龄大于20的用户查询用户列表,分页显示 
      * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位 
      * @param age 年龄 
      * @return 
      */
    Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
  • UserMapper.xmlWrite SQL to implement this method in

    <select id="selectPageVo" resultType="User">
        select id,username as name,age,email from t_user where age > #{age}
    </select>
  • write test method

    @Test
    public void testPageVo(){
        Page<User> page = userMapper.selectPageVo(new Page<User>(1,2), 20);
        List<User> users = page.getRecords();
        users.forEach(System.out::println);
    }

    second way of writing

    //自定义分页 根据年龄大于20的用户查询用户列表,分页显示 
    @Test
    public void testPageVo(){
        Page<User> page = new Page<>(1,2);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age",20);
        userMapper.selectPage(page,queryWrapper);
        List<User> users = page.getRecords();
        users.forEach(System.out::println);
    }

3. Optimistic lock

Function: When a record is to be updated, it is hoped that this record has not been updated by others

Implementation of optimistic locking:

  • When fetching records, get the current version

  • When updating, bring this version

  • When performing an update, UPDATE t_product SET name=?, price=100+50, version(new value after change+1)=1 WHERE id=? AND version(old value)=0.

  • Satisfy the condition, newVersion: oldVersion The old value will be +1 every time

  • If the version is incorrect, the update will fail

3.1 Scenarios


  • A commodity has a cost price of 80 yuan and a selling price of 100 yuan. The boss first notified Xiao Li that you should increase the price of the product by 50 yuan. Xiao Li was playing a game and was delayed for an hour. Exactly one hour later, the boss felt that the price of the product had increased to 150 yuan, which was too high and might affect sales. Also inform Xiao Wang that you will reduce the price of the product by 30 yuan.

  • At this time, Xiao Li and Xiao Wang operate the commodity background system at the same time. When Xiao Li operated, the system first took out the product price of 100 yuan; Xiao Wang was also operating, and the price of the product taken out was also 100 yuan. Xiao Li added 50 yuan to the price, and stored 100+50=150 yuan in the database; Xiao Wang reduced the product by 30 yuan, and stored 100-30=70 yuan in the database. Yes, if there is no lock, Xiao Li's operation will be completely covered by Xiao Wang's.

  • Now the commodity price is 70 yuan, which is 10 yuan lower than the cost price. A few minutes later, this product quickly sold more than 1,000 items, and the boss lost more than 10,000.

3.2 Optimistic lock and pessimistic lock


  • In the above story, if it is an optimistic lock, Xiao Wang will check whether the price has been modified before saving the price. If it has been modified, the revised price will be retrieved again, 150 yuan, so that he will store 120 yuan in the database.

  • If it is a pessimistic lock, after Xiao Li takes out the data, Xiao Wang can only operate on the price after Xiao Li finishes the operation, and the final price will be guaranteed to be 120 yuan.

3.3 Simulate modification conflicts


  • Add commodity table to database

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

  • add a piece of data

    INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

  • add an entity classProduct

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

  • Add a Mapper interfaceProductMapper

    public interface ProductMapper extends BaseMapper<Product> {}

  • Test Methods

      
      //模拟修改冲突
        @Test
        public void testProduct01(){
            //1.小李获取商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productLi = productMapper.selectById(1);
            System.out.println("小李获取的商品价格为:" + productLi.getPrice());//100
    ​
            //2.小王获取商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productWang = productMapper.selectById(1);
            System.out.println("小王获取的商品价格为:" + productWang.getPrice());//100
    ​
            //3.小李修改商品价格+50
            productLi.setPrice(productLi.getPrice()+50);
            //UPDATE t_product SET name=?, price=100+50, version=? WHERE id=?
            productMapper.updateById(productLi);//100+50=150
    ​
            //4.小王修改商品价格-30
            productWang.setPrice(productWang.getPrice()-30);
            //UPDATE t_product SET name=?, price=100-30, version=? WHERE id=?
            productMapper.updateById(productWang);//100-30=70
    ​
            //5.老板查询商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productBoss = productMapper.selectById(1);
            System.out.println("老板获取的商品价格为:" + productBoss.getPrice());//70
        }

  • Results of the

3.4 Optimistic locking solves the problem


  • versionAdd annotations to entity class fields@Version

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

  • Add optimistic lock plugin configuration

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

  • Execute the test method again, optimistic lock test

    Xiao Li inquires about product information:

    SELECT id,name,price,version FROM t_product WHERE id=?

    Xiao Wang inquires about product information:

    SELECT id,name,price,version FROM t_product WHERE id=?

    Xiao Li modifies the product price and automatically adds version+1

    UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

    Parameters: Alien Notebook (String), 150(Integer), 1(Integer), 1(Long), 0(Integer)

    Xiao Wang modifies the price of the product. At this time, the version has been updated. If the condition is not met, the modification fails.

    UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

    Parameters: Alien Notebook (String), 70(Integer), 1(Integer), 1(Long), 0(Integer)

    In the end, Xiao Wang failed to modify, and the query price: 150

    SELECT id,name,price,version FROM t_product WHERE id=?

  • //乐观锁测试
    @Test
    public void testProduct02(){
     //1.小李获取商品价格
     //SELECT id,name,price,version FROM t_product WHERE id=?
     Product productLi = productMapper.selectById(1);
     System.out.println("小李获取的商品价格为:" + productLi.getPrice());//100
    ​
     //2.小王获取商品价格
     //SELECT id,name,price,version FROM t_product WHERE id=?
     Product productWang = productMapper.selectById(1);
     System.out.println("小王获取的商品价格为:" + productWang.getPrice());//100
    ​
     //3.小李修改商品价格+50
     productLi.setPrice(productLi.getPrice()+50);//100+50=150
     //UPDATE t_product SET name=?, price=100+50, version=1 WHERE id=? AND version=0
     productMapper.updateById(productLi);//现在版本号已经变成1了
    ​
     //4.小王修改商品价格-30
     productWang.setPrice(productWang.getPrice()-30); //100-30=70
     //UPDATE t_product SET name=?, price=100-30, version=1 WHERE id=? AND version=0
     productMapper.updateById(productWang);//现在版本号已经变成1了,所以这里没有修改成功
    ​
     //5.老板查询商品价格
     //SELECT id,name,price,version FROM t_product WHERE id=?
     Product productBoss = productMapper.selectById(1);
     System.out.println("老板获取的商品价格为:" + productBoss.getPrice());//150
    }

  • Optimize the execution process

      //优化乐观锁测试
        @Test
        public void testProduct03(){
            //1.小李获取商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productLi = productMapper.selectById(1);
            System.out.println("小李获取的商品价格为:" + productLi.getPrice());//100
    ​
            //2.小王获取商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productWang = productMapper.selectById(1);
            System.out.println("小王获取的商品价格为:" + productWang.getPrice());//100
    ​
            //3.小李修改商品价格+50
            productLi.setPrice(productLi.getPrice()+50);//100+50=150
            //UPDATE t_product SET name=?, price=100+50, version=1 WHERE id=? AND version=0
            productMapper.updateById(productLi);//现在版本号已经变成1了
    ​
            //4.小王修改商品价格-30
            productWang.setPrice(productWang.getPrice()-30); //100-30=70
            //UPDATE t_product SET name=?, price=100-30, version=1 WHERE id=? AND version=0
            int result = productMapper.updateById(productWang);//现在版本号已经变成1了,所以这里没有修改成功
            if(result == 0){
                //操作失败,重试
                //重新查询一次,这是小李修改玩的数据
                Product productNew = productMapper.selectById(1);
                //再次更新,现在价格是150-30
                productNew.setPrice(productNew.getPrice()-30);
                //再次修改
              productMapper.updateById(productNew);
            }
    ​
            //5.老板查询商品价格
            //SELECT id,name,price,version FROM t_product WHERE id=?
            Product productBoss = productMapper.selectById(1);
            System.out.println("老板获取的商品价格为:" + productBoss.getPrice());//120
        }

Seven, general enumeration

Some field values ​​in the table are fixed, such as gender (male or female), at this time we can use the general enumeration of MyBatis-Plus to achieve

  • Add field to database tablesex

  • Create a generic enum type

    @Getter
    public enum SexEnum {
        MALE(1, "男"),
        FEMALE(2, "女");
    ​
        @EnumValue //将注解所标识的属性的值存储到数据库中
        private int sex;
        private String sexName;
    ​
        SexEnum(Integer sex, String sexName) {
            this.sex = sex;
            this.sexName = sexName;
        }
    }

  • Add attribute sex to User entity class

    public class User {
        private Long id;
        @TableField("username")
        private String name;
        private Integer age;
        private String email;
    ​
        @TableLogic
        private int isDeleted;  //逻辑删除
    ​
        private SexEnum sex;
    }

  • Configure Scanning Generic Enumeration

    #MyBatis-Plus相关配置
    mybatis-plus:
      #指定mapper文件所在的地址
      mapper-locations: classpath:mapper/*.xml
      configuration:
        #配置日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        banner: off
        db-config:
          #配置mp的主键策略为自增
          id-type: auto
          # 设置实体类所对应的表的统一前缀
          table-prefix: t_
      #配置类型别名所对应的包
      type-aliases-package: com.atguigu.mybatisplus.pojo
      # 扫描通用枚举的包
      type-enums-package: com.atguigu.mybatisplus.enums

  • Execute the test method

    @Test
    public void test(){
        User user = new User();
        user.setName("admin");
        user.setAge(33);
        user.setSex(SexEnum.MALE);
        int result = userMapper.insert(user);
        System.out.println("result:"+result);
    }

8. Code generator

1. Introduce dependencies

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>//代码生成器的核心依赖
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>//freemarker引擎模板依赖
    <version>2.3.31</version>
</dependency>

2. Rapid generation

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

Nine, multiple data sources

Applicable to a variety of scenarios: pure multi-library, read-write separation, one master and multiple slaves, mixed mode, etc.

Scenario description:

We create two libraries, namely: mybatis_plus(the previous library does not move) and (new), and move the tables mybatis_plus_1of the mybatis_plus library to the mybatis_plus_1 library, so that each library has a table, and the user data and commodity data are obtained through a test case product, if it is obtained, it means that the multi-library simulation is successful

1. Create database and table

  • Create database mybatis_plus_1and table `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) 
    );
  • Add test data

    INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
  • drop table mybatis_plusin libraryproduct

    use mybatis_plus; 
    DROP TABLE IF EXISTS product;

2. New projects introduce dependencies

Create a new Spring Boot project by yourself and select the MySQL driver and Lombok dependencies

Introduce dependencies on multiple data sources

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

3. Write configuration files

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

4. Create entity classes

  • Create a new Userentity class (if the database table name has a t_ prefix, remember to configure it)

    @Data
    @TableName("t_user")//指定表
    public class User {
    ​
        private Long id;
    ​
        private String name;
    ​
        private Integer age;
    ​
        private Integer sex;
    ​
        private String email;
    ​
        private Integer isDeleted;
    }

  • Create a new entity classProduct

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

5. Create Mapper and Service

  • new interfaceUserMapper

    @Repository
    public interface UserMapper extends BaseMapper<User> {}
  • new interfaceProductMapper

    @Repository
    public interface ProductMapper extends BaseMapper<Product> {}
  • Create a new Service interface UserServiceto specify the data source for the operation

    public interface UserService extends IService<User> {}
  • Create a new Service interface ProductServiceto specify the data source for the operation

    public interface ProductService extends IService<Product> {}
  • Created UserServiceimplementation class

    @Service
    @DS("master")//多数据源操作,指定要操作的数据源,master指定的是mybatis_plus数据库里面有t_user表,不写注解使用默认数据源
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    }

    Created ProductServiceimplementation class

    @Service
    @DS("slave_1")//多数据源操作,指定要操作的数据源,slave_1指定的是mybatis_plus1数据库里面有product表,不写注解使用默认数据源
    public class ProductServiceImpl extends ServiceImpl<ProductMapper,Product> implements ProductService{
    ​
    }

6. Write the test method

Remember to add annotations to the startup class@MapperScan()

class TestDatasourceApplicationTests {
    @Resource
    UserService userService;
​
    @Resource
    ProductService productService;
​
    @Test
    void contextLoads() {
        User user = userService.getById(1L);
        Product product = productService.getById(1L);
        System.out.println("User = " + user);
        System.out.println("Product = " + product);
    }
​
}

10. MyBatisX plugin

MyBatis-Plus provides us with powerful mapper and service templates, which can greatly improve development efficiency.

But in the actual development process, MyBatis-Plus cannot solve all problems for us, such as some complex SQL, multi-table joint query, we need to write code and SQL statements by ourselves, how can we quickly solve this problem, At this time, you can use the MyBatisX plug-in.

MyBatisX is an IDEA-based rapid development plug-in, born for efficiency.

1. Install the MyBatisX plugin

Open IDEA, File-> Setteings->Plugins->MyBatisX, search for MyBatisX in the search bar and install it.

2. Generate code quickly

  • Create a new Spring Boot project to introduce dependencies (remember to check the lombok and mysql drivers when creating the project)

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

  • Configure data source information

    spring:
      #配置数据库
      datasource:
        # 配置连接数据库信息
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: 123456

  • Establish a link with the database in IDEA

  • Fill in the database information and save

  • ?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false 

  • Find the table we need to generate and right click

  • Next step after filling in the information

  • continue to fill in the information

  • You're done (really easy to use yyds)

3. Quickly generate CRUD

MyBaitsX can quickly help us generate the corresponding sql statement according to the method name we input in the Mapper interface

public interface UserMapper extends BaseMapper<User> {
​
    //mybatisX快速生成CRUD 方法名都是见名实意
    //增加
    int insertSelective(User user);
​
    //删除 通过id和年龄和姓名删除
    int deleteByIdAndAgeAndName(@Param("id") Long id, @Param("age") Integer age, @Param("name") String name);
​
    //修改 通过id修改年龄跟性别
    int updateAgeAndSexById(@Param("age") Integer age, @Param("sex") Integer sex, @Param("id") Long id);
​
    //查询 通过开始年龄和结束年龄查询年龄和性别
    List<User> selectAgeAndSexByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
​
    //查询全部
    List<User> selectAll();
​
    //通过年龄降序查询全部
    List<User> selectAllOrderByAgeDesc();
}
​

test

@SpringBootTest
class Boot04MybatisxDemoApplicationTests {
​
    @Autowired
    UserMapper userMapper;
​
    @Test
    void contextLoads() {
        
        List<User> users = userMapper.selectAll();
        System.out.println(users);
        
        List<User> users1 = userMapper.selectAgeAndSexByAgeBetween(10, 20);
        System.out.println(users1);
    }
​
}

Guess you like

Origin blog.csdn.net/m0_65992672/article/details/130565444