一、MyBatis Plus 介绍

MyBatis Plus 是国内人员开发的 MyBatis 增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

MyBatis Plus 的核心功能有:支持通用的 CRUD、代码生成器与条件构造器。

  • 通用 CRUD:定义好 Mapper 接口后,只需要继承 BaseMapper<T> 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件
  • 条件构造器:通过 EntityWrapper<T> (实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的 SQL
  • 代码生成器支持一系列的策略配置与全局配置,比 MyBatis的代码生成更好用
/***
 * 通用删除操作 deleteBatchIds 通过多个ID进行删除
 */
@Test
public void testDeleteBatchIds() {
	List<Integer> idList = new ArrayList<Integer>();
	idList.add(5);
	idList.add(6);
	int result = employeeMapper.deleteBatchIds(idList);
	System.out.println("*******************" + result);
}

public void selectIn() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.setSqlSelect(WinAgent.COL_ID, WinAgent.COL_AG_NUM);
    wrapper.in(User.COL_AG_NUM, new String[]{"11","22","33","44"});
    List agreeList = Arrays.asList(agreeMan.split(",|,"));
    if (agreeList.size() > 1) {
      wrapper.in(User.COL_CREATE_USER, agreeList);
    } else {
      wrapper.eq(User.COL_CREATE_USER, agreeList.get(0));
    }
    userMapper.selectList(wrapper);
}
/**
 * 根据条件构造器删除
 */
public void testDeleteByWrapper(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    // 参数1-字段名,参数2-匹配的值
    wrapper.eq("user_name", "lisi");
    // count代表删除成功的记录数
    int count = userMapper.delete(wrapper);
}
/***
 * 通用更新操作
 */
@Test
public void testUpdateOrInsert() {
	Employee entity = new Employee();
	entity.setId(1);
	entity.setName("更新测试成功");
	int result = employeeMapper.updateById(entity);
	//int result = employeeMapper.insert(entity);
    System.out.println("插入后:employee id="+entity.getId());
}

public void testUpdate() {
	Employee entity = new Employee();
	entity.setName("更新测试成功");
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("id", "1");
	int result = employeeMapper.update(entity, queryWrapper);
}

/**
 * 查询所有数据
 * UserMapper 中的 selectList() 方法的参数
 * 为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件(条件构造器)
 */
@Test
public void selectList() {
    List<User> list = userMapper.selectList(null);
    list.forEach(System.out::println);
}

/**
 * 根据 entity 条件,查询一条记录
 * 其实 QueryWrapper 条件构造器(相当于给SQL的条件语句)
 * selectOne() 方法查询必须有且只能查询一条记录,多一条会报错。
 */
@Test
public void selectOne() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //查询名字为 Tom 的一条记录
    queryWrapper.eq("name", "Tom");
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
}

/**
 * 根据主键ID查询数据
 * 查询主键id=2 的一条数据,只能查询一个主键的数据不能多个
 */
@Test
public void selectById() {

    User user = userMapper.selectById(1);
    System.out.println(user);
}

/**
 * 根据 List 的 ID 集合 查询对应的用户list
 */
@Test
public void selectBatchIds() {
    List<User> list = userMapper.selectBatchIds(Arrays.asList(new String[] { "1", "2" }));
    list.forEach(System.out::println);
}

/**
 * 查询(根据 columnMap 条件),查询年龄为20岁的所有记录
 * <p>
 * 注意:建议尽量减少使用map这种方式。
 * 因为可能字段名可能存在修改的情况,
 * 如果,项目开发一段时间后,再修改,影响太大
 */
@Test
public void selectByMap() {
    Map<String, Object> map = new HashMap<>();
    map.put("age", "20");
    List<User> list = userMapper.selectByMap(map);
    list.forEach(System.out::println);
}

/**
 * 查询大于20岁的学生,名称中包含“J”的人,带条件判断的,可以采用这种方式
 * SELECT * FROM user WHERE (name LIKE ? AND age > ?)
 * gt()方法 相当于:大于 > 但没有等于=
 */
@Test
public void selectListWrapper() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name", "J");//格式:(字段,值)
    queryWrapper.gt("age", "19"); //查询不小于20岁
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

/**
 * 根据 Wrapper 条件,查询全部记录。
 * 查询名字含有 a 的,且年龄大于等于20。
 * ge()方法 相当于: 大于等于 >= 。
 * like()方法  相当于: NOT LIKE '%值%' 。
 */
@Test
public void selectMaps() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name", "a");
    queryWrapper.ge("age", "20");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

/**
 * 根据 Wrapper 条件,查询总记录数
 * 查询一共有多少条数据记录
 */
@Test
public void selectCount() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    Integer count = userMapper.selectCount(queryWrapper);
    System.out.printf("一共有" + String.valueOf(count) + "条记录");
}

不更新名称,前提没有配置FieldStrategy.IGNORED

Employee entity = new Employee();
entity.setId(1);
entity.setName(null);
employeeMapper.updateById(entity);

括号

//有很多中Wrapper实现类,我也不是很了解,这里我使用的是EntityWrapper
 EntityWrapper wrapper = new EntityWrapper();
 wrapper.eq("status", status);
 wrapper.andNew().gt("gmt_end", now).or().isNotNull("days");
 Integer count = couponMapper.selectCount(wrapper);

SELECT COUNT(1) FROM unimall_coupon WHERE status = 1 AND (gmt_end > '2019-07-14 16:14:23.51' OR days IS NOT NULL)

分页查询

/**
 * 根据 entity 条件,查询全部记录(并翻页)。
 * 查询年龄大于 18 且 小于等于30 岁的所有记录,分 3 条数记录为一页
 * gt()方法 相当于: 大于 > 。
 * le()方法 相当于: 小于等于 <= 。
 */
@Test
public void selectPage() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.gt("age", "18");
    queryWrapper.le("age", "30");
    //第一个参数表示当前页
    //第二个参数表示当前页显示多少条
    //第三个参数是否查询count
    Page<User> page = new Page<>(1, 3);
    Page<User> userIPage = userMapper.selectPage(page, queryWrapper);
    List<User> records = userIPage.getRecords();
    records.forEach(System.out::println);
}

public void selectPage1() {
	Wrapper<User> wrapper = new EntityWrapper<>();
	wrapper.last("limit 0,30");
	List<User> list = this.selectList(wrapper);
}
/**
 * 根据 Wrapper 条件,查询全部记录(并翻页)
 */
@Test
public void selectMapsPage() {
    QueryWrapper<User> query = new QueryWrapper<User>();
    //第一个参数表示当前页
    //第二个参数表示当前页显示多少条
    Page<Map<String, Object>> page = new Page<>(2, 3);
    Page<Map<String, Object>> maps = userMapper.selectMapsPage(page, query);
    maps.getRecords().forEach(System.out::println);
}

queryWrapper是mybatis plus中实现查询的对象封装操作类.。注意这是因为我的mybatisplus的版本是3.2版本,只有3.x的版本才有QueryWrapper而如果是2.x版本,是没有QueryWrapper的,如果找不到QueryWrapper,可以在配置里将mybatisplus的版本改为3.x

<mybatis-plus.version>2.2.0</mybatis-plus.version>

或者使用EntityWrapper,两者的功能其实差不多。 

条件参数说明


二、MyBatis Plus 集成 Spring

数据表结构

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

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

创建一个 SpringBoot 项目。直接使用 IDE 工具创建一个。 Spring Initializer。

注意选择Type,JDK等.

输入Group、Artifact、Package的对应的信息,再点击next 

注意:这里必须得选择web,否则demo不能使用controller层。 在我们的实际项目中,最好还是要引用spring-boot release版本的

添加 MyBatis-Plus 依赖(mybatis-plus-boot-starter)

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

使用 mysql,需要引入 mysql 相关依赖。为了简化代码,引入 lombok 依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

<!-- 工具类相关 -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
</dependency>

<!-- 测试相关  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

完整pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lyh.test</groupId>
    <artifactId>test-mybatis-plus</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>test-mybatis-plus</name>
    <description>测试 -- 测试 MyBatis-Plus 功能</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在 resources/application.yml 文件中配置 mysql 数据源信息。

server:
  port: 2525

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8

mybatis-plus:
  mapper-locations: classpath:mappers/*.xml

三、快速体验 MyBatis Plus

实体类

package entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private int age;
    private String email;
}

mapper 接口,这是 mybatis-plus 封装好的类。

@Select @Update

package mapper;

import entity.User;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;

import java.util.List;

public interface UserMapper extends BaseMapper<User> {
    //@Select("SELECT * FROM user where name = #{name}")
    //List<User> selectByName(@Param("name") String name);

    @Update({"UPDATE user SET supplier_fee_id=null,category_id=#{categoryId},supplier_id=#{supplierId} WHERE number_type=#{numberType} AND number=#{phone}"})
    boolean updateUserByPhone(@Param("phone") String phone, @Param("numberType") Integer numberType, @Param("supplierId") Integer supplierId, @Param("categoryId") Integer categoryId);
    
    //传入对象参数xml方式
    //List<User> selectAllUsers(@Param("user") User user, @Param("bm") String bm);
}

实体类、Mapper 类都写好了,就可以使用了。接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select @Update 等注解里面包含 Sql 语句来绑定,另外一种就是通过xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定;当 SQL 语句比较复杂时候,用 xml 绑定,一般用 xml 绑定的比较多

添加UserMapper.xml文件(可省略) 

对于$和#的用法区别

简单来说 #{} 会在将参数加上引号,而${}并不会在给参数加上引号。mybatis对参数没有进行任何的处理。通常${}用于GROUP BY,ORDER BY,IN等的后面
但是,实际应用中,并不提倡使用 ${},因为使用 #{} 写法,除了可以防止sql注入以外,还能在参数里含有单引号的时候自动转义

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="UserMapper">
    <select id="findUserCount" resultType="java.lang.Integer">
        select count(1) from user
    </select>
    <select id="selectAllUsers" resultMap="BaseResultMap">
        select * from user where number_type = ${@net.test.api.constants.ProductEnum@PHONE400.type}
        <if test="user.name != null and user.name != ''">
            and name like '%${user.name}%'
        </if>
        <if test="user.startTime != null ">
            AND start_time>='${user.startTime} 00:00:00' and <![CDATA[ start_time<='${user.startTime} 23:59:59' ]]>
        </if>
    </select>
</mapper>

ServiceImpl层 通用的IService实现crud功能

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    public Integer getUserNum() {
        return userMapper.findUserCount();
    }

    // 增
    @Override
    public int insertUser(User user) {
        return insert(user);
    }
    
    // 改
    @Override
    public int updateUser(User user) {
        return updateById(user);
    }
    
    // 删
    @Override
    public int deleteUser(User user) {
        Integer id = user.getUserId();
        return deleteById(id);
    }
    
    // 查
    @Override
    public User findUserByName(String userName) {
        return baseMapper.getUserByName(userName);
    }

    // 分页查
    @Override
    public IPage getUserPage(Page page, User user) {
        return baseMapper.getUsersPage( page, user );
    }
}

Service层

public interface UserService extends IService<User> {
  
   Integer getUserNum();

   int insertUser( User user );
   int updateUser( User user );
   int deleteUser( User user );
   User findUserByName( String userName );
   IPage getUserPage( Page page, User user );

Step1:先得在启动类里扫描 Mapper 类,即添加 @MapperScan 注解

package com.lyh.test.testmybatisplus;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("mapper")
//@MapperScan({"com.kfit.*.mapper","org.kfit.*.mapper"})
@SpringBootApplication
public class TestMybatisPlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestMybatisPlusApplication.class, args);
    }

}

多module项目扫描失败解决: Consider defining a bean of type 'xxx' in your configuration

@SpringBootApplication(scanBasePackages = {"com.xx.xx.xx.A", "com.xx.xx.common"})

Step2:写一个测试类测试一下。

package com.lyh.test.testmybatisplus;

import entity.User;
import mapper.UserMapper;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class TestMybatisPlusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        for(User user:userList) {
            System.out.println(user);
        }
    }

    @Test
    public void testSelectBySql() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
        wrapper.like("name" , "k").lt("age", 40).last("limit 4");

        List<User> userList = userMapper.selectBySql(wrapper);
        userList.forEach(System.out::println);
    }
}

上面几个例子中,并没有在 UserMapper 接口中定义任何方法,也没有在配置文件中编写 SQL 语句,而是通过继承 BaseMapper 接口获得通用的的增删改查方法,复杂的 SQL 也可以使用条件构造器拼接。通过这两种方式已经能够满足很多的开发需求了,不过复杂的业务需求还是要编写 SQL 语句的,流程和 MyBatis 一样。

若遇到 @Autowired 标记的变量出现 红色下划线,但是不影响 正常运行。可以进入 Settings,找到 Inspection,并选择其中的 Spring Core -> Code -> Autowiring for Bean Class,将 Error 改为 Warning,即可。

四、代码生成器

package com.example.demo;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
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.rules.NamingStrategy;

/**
 * MybatisPlus代码生成器
 */
public class MybatisPlusGeneratorRunner {
    public static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver";
    //模块名 如果有模块名,则需在模块名前加. 例:log
    public static final String MODULE_NAME = "";
    public static final String AUTHOR = "danny";
    public static final String MYSQL_URL = "jdbc:mysql://locallhost:3306/test?serverTimezone=UTC";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "0rb!t";
    public static final String[] DB_TABLE_NAMES = {"user", "role"};
    public static final String PACKAGE_NAME = "cn.kerninventory.mybatis";

    public static void main(String[] args) {
        AutoGenerator generator = new AutoGenerator();
        //配置数据库连接参数
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDriverName(MYSQL_DRIVER);
        dataSourceConfig.setUrl(MYSQL_URL);
        dataSourceConfig.setUsername(USERNAME);
        dataSourceConfig.setPassword(PASSWORD);
        generator.setDataSource(dataSourceConfig);

        //配置文件生成路径参数
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setModuleName(MODULE_NAME);
        packageConfig.setParent(PACKAGE_NAME);
        packageConfig.setEntity("entity");
        packageConfig.setXml("xml");
        packageConfig.setMapper("mapper");
        //packageConfig.setServiceImpl("serviceimpl");
        packageConfig.setService("service");
        packageConfig.setController("controller");
        generator.setPackageInfo(packageConfig);

        //策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        //表名转驼峰
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        //字段驼峰命名
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        //set方法builder模式
        strategyConfig.setChainModel(true);
        //使用lombok注解
        strategyConfig.setEntityLombokModel(true);
        //不生成serial version uuid
        //strategyConfig.setEntitySerialVersionUID(false);
        //要生成的表名
        strategyConfig.setInclude(DB_TABLE_NAMES);
        //rest风格
        strategyConfig.setRestControllerStyle(true);
        //是否生成字段常量
		strategyConfig.setEntityColumnConstant(true);
        //是否生成实体时,生成字段注解
        strategyConfig.setEntityTableFieldAnnotationEnable(true);
        generator.setStrategy(strategyConfig);

        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setServiceName("%sService");
		globalConfig.setXmlName("mapper");
        globalConfig.setBaseResultMap(true);  //XML中的ResultMap标签
        globalConfig.setBaseColumnList(true); //XML标签
        globalConfig.setOpen(false); //不弹出生成目录
        //输出目录
        globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
        globalConfig.setAuthor(AUTHOR);
        generator.setGlobalConfig(globalConfig);
        generator.execute();
    }
}

五、@TableField注解 

  • @TableField(exist = false) 注解加载bean属性上,表示当前属性不是数据库的字段,但在项目中必须使用,这样在新增等使用bean的时候,mybatis-plus就会忽略这个,不会报错
  • @TableField(.. , update="%s+1") 其中 %s 会填充为字段。输出 SQL 为:update 表 set 字段=字段+1 @TableField(.. , update="now()") 输出 SQL 为:update 表 set 字段=now() where 
  • @TableField(condition = SqlCondition.LIKE) 输出 SQL 为:select 表 where name LIKE CONCAT('%',值,'%')
  • @TableField(fill = FieldFill.INSERT)的字段进行自动填充
  • @TableField(select = false) 把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。如:密码

@TableName注解

  • @TableName(value = "my_table_name")  表名和类名不一致时指定

Mybatis-plus批量插入

saveBatch方法分析:底层也是通过for来完成,默认是一个事务一次提交N条数据。即伪批量性能比较差。引入依赖,3.4.0之上的版本都可以

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

insertBatchSomeColumn

自定义SQL注入器

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import java.util.List;
 
/**
 * 批量插入 SQL 注入器.
 **/
public class InsertBatchSqlInjector extends DefaultSqlInjector {
 
  @Override
  public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
    // super.getMethodList() 保留 Mybatis Plus 自带的方法
    List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
    // 添加自定义方法:批量插入,方法名为 insertBatchSomeColumn
    methodList.add(new InsertBatchSomeColumn());
    return methodList;
  }
}

把SQL注入器交给Spring

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
 
@Component
public class MybatisPlusConfig {

  /**
   * 自定义批量插入 SQL 注入器.
   */
  @Bean
  public InsertBatchSqlInjector insertBatchSqlInjector() {
    return new InsertBatchSqlInjector();
  }
}

到此定义完毕,继承下面定义的 MyBaseMapper,你就可以撒手不管了,直接调用就行。或者直接在ServiceImpl通过Mapper调用insertBatchSomeColumn,然后ALT+回车生成此方法

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
 
public interface MyBaseMapper<T> extends BaseMapper<T> {

  /**
   * 执行批量插入操作,针对指定列表中的对象插入数据库。 此方法专注于通过批量操作提高数据插入的效率,而不是逐个插入对象。这有助于减少数据库访问次数,从而提高性能。
   *
   * @param batchList 待插入数据库的对象列表。列表中的每个对象都将被插入到数据库中, 注意,对象的属性和数据库表的字段需要对应,以便正确插入数据。
   * @return 返回插入操作影响的行数。这可以用于判断插入操作的成功与否,或者了解操作的范围。
   */
  int insertBatchSomeColumn(@Param("list") List<T> batchList);
}

测试代码,调用insertBatchSomeColumn方法

@Test
void testInsertBatch() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        User user = new User();
        user.setName("犬小哈" + i);
        user.setAge(i);
        user.setGender(1);
        users.add(user);
    }
 
    userMapper.insertBatchSomeColumn(users);
}

mybatis-plus-join用法

mybatis plus 封装的 mapper 不支持 join,如果需要支持就必须自己去实现。但是对于大部分的业务场景来说,都需要多表 join。mybatis-plus-join是mybatis plus的一个多表插件,上手简单,十分钟不到就能学会全部使用方式,只要会用mp就会用这个插件,仅仅依赖了lombok,而且是扩展mp的构造器并非更改原本的构造器,不会对原有项目产生一点点影响,相信大多数项目都有这俩插件,四舍五入就是没依赖。需要 Mybatis-plus version >= 3.4.0

安装

  • maven
<dependency>
    <groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join</artifactId>
    <version>1.2.4</version>
</dependency>

使用

  • mapper继承MPJBaseMapper (必选)
  • service继承MPJBaseService (可选)
  • serviceImpl继承MPJBaseServiceImpl (可选)

分页查询

public class testMPJQueryWrapperPage{
    @Autowired
    private UserMapper userMapper;
    
    public void mpjQueryWrapperTestPage(){
        IPage<UserDTO> page = userMapper.selectJoinPage(new Page<>(1, 10), UserDTO.class,
                new MPJQueryWrapper<UserDO>()
                        .selectAll(UserDO.class)
                        .select("addr.tel", "addr.address")
                        .select("a.province")
                        .leftJoin("user_address addr on t.id = addr.user_id")
                        .rightJoin("area a on addr.area_id = a.id"));
   
    }
    
}

对应sql

SELECT 
    t.id,
    t.name,
    t.sex,
    t.head_img,
    addr.tel,
    addr.address,
    a.province
FROM 
    user t
    LEFT JOIN user_address addr on t.id = addr.user_id
    RIGHT JOIN area a on addr.area_id = a.id 
LIMIT ?,?

其他

Java计时StopWatch的使用方法详解

StopWatch 是位于 org.springframework.util 包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块。简单总结一句,Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。

想要使用它,首先你需要在你的 Maven 中引入 Spring 核心包,当然 Spring MVC 和 Spring Boot 都已经自动引入了该包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>

对一切事物的认知,都是从使用开始,那就先来看看它的用法,会如下所示:

public static void main(String[] args) throws InterruptedException {
    StopWatch stopWatch = new StopWatch();

    // 任务一模拟休眠3秒钟
    stopWatch.start("TaskOneName");
    Thread.sleep(1000 * 3);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskTwoName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 任务一模拟休眠10秒钟
    stopWatch.start("TaskThreeName");
    Thread.sleep(1000 * 10);
    System.out.println("当前任务名称:" + stopWatch.currentTaskName());
    stopWatch.stop();

    // 打印出耗时
    System.out.println(stopWatch.prettyPrint());
    System.out.println(stopWatch.shortSummary());
    // stop后它的值为null
    System.out.println(stopWatch.currentTaskName()); 
    
    // 最后一个任务的相关信息
    System.out.println(stopWatch.getLastTaskName());
    System.out.println(stopWatch.getLastTaskInfo());
    
    // 任务总的耗时  如果你想获取到每个任务详情(包括它的任务名、耗时等等)可使用
    System.out.println("所有任务总耗时:" + sw.getTotalTimeMillis());
    System.out.println("任务总数:" + sw.getTaskCount());
    System.out.println("所有任务详情:" + sw.getTaskInfo());
}

Hutool工具

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅。Hutool是对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类。

在项目的pom.xml的dependencies中加入以下内容:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.2</version>
</dependency>

禁用 netflix-eureka 客户端

  • Spring Cloud Eureka主要负责实现微服务架构中的服务治理功能
  • SpringCloudGateway作为SpringCloud生态系中的网关,为微服务架构提供统一的路由管理,并且根据http请求进行相应的匹配、断言、过滤
eureka.client.enabled=false

事务处理

public class TransactionStatusUtils {
    @Autowired
    PlatformTransactionManager dataSourceTransactionManager;

    public TransactionStatus getTransactionStatus(DataSourceType dbType){
        DbContextHolder.setDbType(dbType);
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        return transactionStatus;
    }

    public void commit(TransactionStatus transactionStatus) {
      dataSourceTransactionManager.commit(transactionStatus);
    }

    public void rollback(TransactionStatus transactionStatus) {
      dataSourceTransactionManager.rollback(transactionStatus);
    }
}

MyBatisPlus 获取实体表名或字段名

MyBatis Plus 通过实体的原理是通过TableId,TableField,TableName等注解或属性名称/类名称转下划线实现的

//表名
SqlHelper.table(User.class).getTableName();
//主键
SqlHelper.table(User.class).getKeyColumn();
//获取userName的数据库字段
WrapperKit.getTableFieldInfoByProperty(User.class, "userName").getColumn();

import com.baomidou.mybatisplus.entity.TableFieldInfo;
import com.baomidou.mybatisplus.entity.TableInfo;
import com.baomidou.mybatisplus.toolkit.TableInfoHelper;
import java.util.List;

public class WrapperKit {

  /**
   * 获取主键的实体属性名
   *
   * @param cls
   * @return
   */
  public static String getTableIdProperty(Class<?> cls) {
    return TableInfoHelper.getTableInfo(cls).getKeyProperty();
  }

  public static List<TableFieldInfo> getTableFieldInfo(Class<?> cls) {
    TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
    return tableInfo.getFieldList();
  }

  public static TableFieldInfo getTableFieldInfoByProperty(Class<?> cls, String property) {
    final List<TableFieldInfo> tableFieldInfoList = getTableFieldInfo(cls);
    for (TableFieldInfo tableFieldInfo : tableFieldInfoList) {
      if (tableFieldInfo.getProperty().equals(property)) {
        return tableFieldInfo;
      }
    }
	if (!getTableIdProperty(cls).equals(property)) {
		System.out.println(cls.toString() + " 属性" + property + "不存在,或属性没有配置@TableField");
	} else {
		System.out.println(property + "主键不支持,因为@TableId不会加入到fieldList");
	}
    return null;
  }

  public static <T> void appendDynamicEqual(Wrapper<T> wrapper, Object model, String... attrs) {
    TableInfo tableInfo = TableInfoHelper.getTableInfo(model.getClass());
    List<TableFieldInfo> fieldList = tableInfo.getFieldList();
    Map<String, TableFieldInfo> collect = fieldList.stream()
        .collect(Collectors.toMap(TableFieldInfo::getProperty, account -> account));
    for (String attr : attrs) {
      String[] attrArray = attr.split(":");
      // 最后一个就是真实的
      String reallyAttrName = attrArray[attrArray.length - 1];
      Object attrValue = ReflectionKit.getMethodValue(model, reallyAttrName);
      if (attrValue == null) {
        continue;
      }
      if (String.class.equals(attrValue.getClass()) && StrKit.isEmpty((String) attrValue)) {
        continue;
      }

      TableFieldInfo tableFieldInfo = collect.get(reallyAttrName);
      if (attrArray.length > 1) {
        switch (attrArray[0]) {
          case "in":
            wrapper.in(tableFieldInfo.getColumn(), attrValue.toString());
            break;
          case "like":
            wrapper.like(tableFieldInfo.getColumn(), attrValue.toString());
            break;
          case "notlike":
            wrapper.notLike(tableFieldInfo.getColumn(), attrValue.toString());
            break;
          default:
            wrapper.eq(tableFieldInfo.getColumn(), attrValue);
            break;
        }
      } else {
        wrapper.eq(tableFieldInfo.getColumn(), attrValue);
      }
    }
  }
}

更新不了空字符串或NULL问题

@TableField(strategy = FieldStrategy.IGNORED)

The valid characters are defined in RFC 7230 and RFC 3986

在yml中配置

server:
  tomcat:
    relaxedQueryChars: <,>, [,],^,`,{,|,} #参数内容
    relaxedPathChars: <,>, [,],^,`,{,|,} #参数名称

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐