MyBatis-Plus 三、(进阶使用)

一、typeHandler 的使用

   1、存储json格式字段

        如果字段需要存储为json格式,可以使用JacksonTypeHandler处理器。使用方式非常简单,如下所示:
        

        只需要加上两个注解即可:
         @TableName(autoResultMap = true) 表示自动映射resultMap
         @TableField(typeHandler = JacksonTypeHandler.class)表示将UserInfo对象转为json对象入库

        2、自定义 typeHandler 实现类

        例如当我们 某个字段存储的类型为List或者Map时,我们可以自定义一个TypeHandler,以 list 为例,我们想存储一个字段类型为 list ,在数据库中的存储的格式是 多条数据以逗号分割,当查询时会自动根据逗号分割成列表格式。

        需要实现BaseTypeHandler抽象类:

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ListTypeHandler extends BaseTypeHandler<List<String>> {

    private static final String DELIM = ",";

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
        String value = StringUtils.collectionToDelimitedString(strings, DELIM);
        preparedStatement.setString(i, value);
    }

    @Override
    public List<String> getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String value = resultSet.getString(s);
        return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
    }

    @Override
    public List<String> getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String value = resultSet.getString(i);
        return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
    }

    @Override
    public List<String> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String value = callableStatement.getString(i);
        return Arrays.asList(StringUtils.tokenizeToStringArray(value, DELIM));
    }
}
  • @MappedJdbcTypes:表示SQL语句中查出来的类型;
  • @MappedTypes:表示要转成 Java 对象的类型;
  • DELIM:表示字符串的分隔符,如果你是用空格分开的就赋值为空格。
  • setNonNullParameter(插入时设置参数类型)

  • getNullableResult(获取时转换回的自定义类型)

        根据列名获取

resultSet:结果集
columnName:列名
public UserInfo getNullableResult(ResultSet resultSet, String columnName)

        根据索引位置获取

resultSet:结果集
columnIndex:列索引
public UserInfo getNullableResult(ResultSet resultSet, int columnIndex)

        根据存储过程获取 

callableStatement:结果集
columnIndex:列索引
public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex)

        然后再字段上指定该实现了即可:

    @TableField(typeHandler = ListTypeHandler.class)
    private List<String> job;

        最后在数据库中存储格式为:  

        3、存储自定义对象字段

        例如我们刚才使用json格式存储 Unit 字段,如果不加  @TableField(typeHandler = JacksonTypeHandler.class) 就会报错,因为 typeHandler 可以指定我们在Java实体类所包含的自定义类型存入数据库后的类型是什么,也可以从数据库中取出该数据后自动转换为我们自定义的Java类型。

        我们虽然在 Java中定义了 Unit 类型,但是数据库不认识,我们现在就需要将 Unit 转换为数据库认识的类型。Java自带的类型在存取的时候不会出错,我们自定义的类型就会出错 是因为 mybatis已经将这些类型的TypeHandler提前写好了:
                   

         所以如果我们要存储 Unit 类型的字段,又不想用 默认的json 格式,我们也需要自定义一个 关于Unit 的 TypeHandler,如下:

public class JsonUtils {
    private static ObjectMapper objectMapper = new ObjectMapper();

    //初始化相关的配置
    static {
        //只引用不为空的值
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        //取消默认转换timestemp
        objectMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);

        //忽略空bean转换错误
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        //忽略在json中存在,在java对象不存在的错误
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 解决jackson2无法反序列化LocalDateTime的问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
    }

    /**
     * 将java对象转换成json字符串
     *
     * @param obj
     * java 对象
     * @param <T>
     * @return
     */
    public static <T> String objectToString(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将json字符串转换成java对象
     *
     * @param json
     * 字符串
     * @param tClass
     * 要转换的对象
     * @param <T>
     * @return
     */
    public static <T> T getObjetFormString(String json, Class<T> tClass) {
        if (StringUtils.isBlank(json) || tClass == null) {
            return null;
        }

        try {
            return tClass.equals(String.class) ? (T) json : objectMapper.readValue(json, tClass);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将字符串转换成java对象
     * @param json
     * 字符串
     * @param tTypeReference
     * 对象
     *
     * @param <T>
     * @return
     */
    public static <T> T fromString(String json, TypeReference<T> tTypeReference){
        if (StringUtils.isBlank(json) || tTypeReference == null) {
            return null;
        }

        try {
            return tTypeReference.getType().equals(String.class) ? (T) json : objectMapper.readValue(json, tTypeReference);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将json字符串转换成java集合对象
     * @param json
     * 字符串
     * @param collectionClass
     * 集合类型
     * @param elementClazzes
     * 成员类型
     * @param <T>
     * @return
     */
    public static <T> T fromString(String json, Class<?> collectionClass, Class<?> ... elementClazzes){
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClazzes);
        try {
            return objectMapper.readValue(json, javaType);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
@MappedTypes(UserInfo.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> {

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, UserInfo userInfo, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i,  JsonUtils.objectToString(userInfo));
    }

    @Override
    public UserInfo getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        return JsonUtils.fromString(resultSet.getString(columnName),UserInfo.class);
    }

    @Override
    public UserInfo getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
        return JsonUtils.fromString(resultSet.getString(columnIndex),UserInfo.class);
    }

    @Override
    public UserInfo getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
        return 
 JsonUtils.fromString(callableStatement.getString(columnIndex),UserInfo.class);
    }
}

        其实还是将该对象转成了 json ,但是只不过转成 json 的工具类可以 由我们自己指定。

二、mybatisplus 存储枚举类型

        存储枚举类型有两种形式,第一种就是自定义  typeHandler 跟上面一样,还有一种是使用mybatis提供的默认的处理类,如下所所示:

        数据库表结构:


         mybatisPlus对枚举处理器进行了补充:


        创建枚举类:

@Getter
public enum OrderState {
    NORMAL(0, "正常"),
    CANCEL(1, "取消"),
    DELETE(2, "删除");
 
    // 状态码
    @EnumValue // 用于数据库存储
    private Integer code;
    // 描述
    @JsonValue // 用于序列化返回的json数据
    private String desc;
 
    OrderState(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
 
}


        TOrder实体类:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_order")
public class TOrder implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @TableId(value = "order_id", type = IdType.AUTO)
    private Integer orderId;
 
    @TableField("user_id")
    private Integer userId;
 
    @TableField("price")
    private BigDecimal price;
 
    @TableField("status")
    private OrderState status;
 
}


在配置文件中配置统一的枚举处理器,实现类型转换:

        调用:

三、使用CommandLineRunner 修改 JacksonTypeHandler 实现类

@Component
public class JsonTypeHandlerConfig implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        JacksonTypeHandler.setObjectMapper(JsonUtils.getMapper());
    }
}

四、静态工具类Db

        因为实际开发中,各种Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db
        使用前提是 mybatisplus的版本在 3.5.3 以上,不然没有引入。

        

        使用示例:

//select id,name,age from user where name like '%zhang%' and age>10
QueryWrapper<User> userQuery=new QueryWrapper<User>()
		.select("id", "name", "age")
		.like("name","zhang")
		.gt("age",10);
List<User> users=userMapper.selectList(userQuery);


   List<Address> addresses = Db.lambdaQuery(Address.class)
            .eq(Address::getUserId, userId)
            .list();




@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}
​
@Test
void testDbList() {
    // 利用Db实现复杂条件查询
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
}
​
@Test
void testDbUpdate() {
    Db.lambdaUpdate(User.class)
            .set(User::getBalance, 2000)
            .eq(User::getUsername, "Rose");
}

         

五、IPage的泛型转换(entity转换为vo)

        就是将查出来的实体类转换成VO类,也可以使用循环拷贝,也可以使用下面的方法:

Page<User> userPage = new Page<>(1, 10);
Page<User> page = userService.page(userPage);
IPage<UserVO> convert = page.convert(item -> Convert.convert(UserVO.class,item));


  Page<User> userPage = new Page<>(1, 10);
  Page<User> page = userService.page(userPage);
  IPage<UserVO> convert = page.convert(item -> {
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(item, userVO);
        userVO.setSerial(1);
        return userVO;
    });

六、SimpleQuery工具类

        Simplequery可以对selectList查询后的结果用stream流进行了一些封装,使其可以返回一些指定结果,简洁api的调用。并且这种方式不需要调用service以及mapper中的接口方法,就可以返回数据查询的结果,极大的简化了开发效率,也简化了代码,并且也对数据的结果做出了一定的封装。

        这个工具类常用的情况有三种:

        1、处理列表,也就是当返回结果是一个列表时,可以对该列表直接进行一些 Stream 流的操作。例如:

        // 封装某一列数据 通过SimpleQuery.list()来拿到某一个数据的集合 我这里是查询年龄为18的全部数据
        // 将姓名为张三的数据全部查询出来 并根据id来封装
        List<Long> list = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "张三"), User::getId);
        for (Long aLong : list) {
            System.out.println("姓名为张三的id编号: --> " + aLong);
        }


        // 可以简写
         SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "张三"), Student::getId).forEach(System.out::println);


        //也可以先查出来,然后再对列表操作
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().eq(User::getName, "张三");
        List<User> list1 = userService.list(wrapper);
        List<Long> list = SimpleQuery.list2List(list1, User::getId);
        for (Long aLong : list) {
            System.out.println("姓名为张三的id编号: --> " + aLong);
        }

        使用消费性接口处理列表:

        /**
         * 代码意思 -->  不设置条件查询内容 那么就查询所有的数据 将查询出来的所有的数据
         *             根据姓名封装筛选出来 那么查询结果只返回姓名
         *             在根据消费性接口 进行处理
         * 以下可以简写成Lambda表达式的形式
         */
        List<String> list = SimpleQuery.list(new LambdaQueryWrapper<User>(), User::getName, new Consumer<User>() {
            @Override
            public void accept(User user) {

                /**
                 * 调用Optional.of返回所有的name属性 在将name的值通过map()方法里面的toUpperCase全部替换成大写
                 * 然后在通过ifPresent(student::setName)将参数设置为大写
                 */
                Optional.of(user.getName()).map(String::toUpperCase).ifPresent(user::setName);
            }

        });

        list.forEach(System.out::println);

        2、将返回列表 处理成 map 集合:

    /**
     * map重载的许多 我只解释其中一种
     * 第一个参数条件对象
     * 第二个参数筛选的key值
     * 第三个参数筛选的value值
     * 第四个参数是消费性接口
     * public static <E, A, P> Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, Consumer<E>... peeks) {
     * return list2Map(Db.list(wrapper.setEntityClass(getType(keyFunc))), keyFunc, valueFunc, peeks);
     * }
     */
//        条件查询未设置 查询所有条件
//        在所有条件中
//        我将  key设置为 id 将value设置name 那么返回的结果就是以 key、value的形式进行封装
//        查询结果 :aLong == 》 1 s == 》xiaohong
//        aLong == 》 2 s == 》xaioming
//        aLong == 》 3 s == 》xiaohu
//        aLong == 》 5 s == 》Model测试数据1

        Map<Long, String> map = SimpleQuery.map(new LambdaQueryWrapper<User>(), User::getId, User::getName);
        map.forEach((aLong, s) -> System.out.println("aLong == 》 " + aLong+ " s == 》" + s));
        /**
         * 第一个参数条件对象
         * 第二个参数根据结果筛选
         * 第三个参数消费性接口
         *     public static <E, A> Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
         *         return list2Map(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, Function.identity(), peeks);
         *     }
         */
        //将name设置为key 返回的结果就是以name当作key键
        //返回结果 -- 》 {xiaohong=Student(id=1, name=xiaohong, age=18, gender=0), xaioming=Student(id=2, name=xaioming, age=18, gender=0), Model测试数据1=Student(id=5, name=Model测试数据1, age=18, gender=0), xiaohu=Student(id=3, name=xiaohu, age=20, gender=0)}
        Map<String, User> map = SimpleQuery.keyMap(new LambdaQueryWrapper<User>(), User::getName);
        System.out.println(map);
        map.forEach((aLong, s) -> System.out.println("aLong == 》 " + aLong+ " s == 》" + s));

        3、将结果根据条件进行分组

        /**
         * group重载的许多 我只解释其中一种
         * 第一个参数是条件对象
         * 第二个参数是对条件查询对象查询出来的值进行筛选
         * 第三个参数消费性接口
         * public static <E, A> Map<A, List<E>> group(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
         * return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, peeks);
         * }
         */
        //分组查询 我未设置具体的查询条件 直接返回所有的数据
        //并且我根据姓名进行分组 那么就会把相同的姓名作为一组进行划分
        //   name == 姓名
        //   user == User对象
        //   查询结果:name == > 李四  user == >[User(super=com.test.demo.model.User@1292f814, id=2, name=李四, job=程序员, salary=2800.0, status=null, createTime=2024-08-19T17:15:34, updateTime=2024-08-19T17:15:34, deleted=0)]
        //name == > 张三  user == >[User(super=com.test.demo.model.User@662cf045, id=1, name=张三, job=随便, salary=2000.0, status=null, createTime=2024-08-19T17:15:34, updateTime=2024-08-19T17:15:34, deleted=0)]
        Map<String, List<User>> group = SimpleQuery.group(new LambdaQueryWrapper<User>(), User::getName);
        group.forEach((name, user) -> System.out.println("name == > " + name + "  user == >" + user));

        以上就是基本的使用,总而言之,就是使用SimpleQuery工具类可以让开发更加的快,并且代码美观,简洁。

七、ActiveRecord

        ActiveRecord(活动记录,简称AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。ActiveRecord 一直广受解释型动态语言( PHP 、 Ruby 等)的喜爱,通过围绕一个数据对象进行CRUD操作。而 Java 作为准静态(编译型)语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索,仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅。

        其实简单来说就是可以直接使用 实体类对象进项增删改查等操作。

        步骤 一:让实体类继承Model类

@Getter
@Setter
@ToString(callSuper = true)
@Accessors(chain = true)
@TableName("user")
public class User  extends Model<User> {

    private static final long serialVersionUID = 1L;


    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

    private String name;

    private String job;

    private Double salary;


    private OrderState status;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableLogic
    private Long deleted;
    
}

        步骤二:就可以直接使用实体类对象调用这些增删改查方法了,简化了操作的语法,但是他的底层依然是需要UserMapper的,所以持久层接口并不能省略【2】测试ActiveRecord模式的增删改查添加数据

        User user = new User();
        user.setName("测试a");
        user.insert();

        Model抽象类方法:

     

     注意: 必须存在对应的原始mapper并继承baseMapper并且可以使用

八、自定义ID生成器

        自 3.3.0 开始,默认使用雪花算法+​UUID​(不含中划线),一般我们也会重写生成id的方法。
       
        原本雪花算法的生成规则:

        雪花算法是一个分布式id生成算法,它生成的id一般情况下具有唯一性。由64位01数字组成,第一位是符号位,始终为0。接下来的41位是时间戳字段,根据当前时间生成。然后中间的10位表示机房id+机器id,也可以是单独的机器id。最后12位是序列号。

        我们一般重写也就是使用模仿雪花算法的规则进行改写,一般不改也可以正常使用。

        如果自定义的话,可以通过实现IdentifierGenerator接口来创建自定义的ID生成器。以下是一个简单的自定义ID生成器的例子,使用雪花算法(Snowflake)生成ID。

        首先,创建自定义的ID生成器类:

@Slf4j
@Primary
public class yourIdentifierGenerator implements IdentifierGenerator {


    @Override
    public synchronized Number nextId(Object entity) {
         return ;
    }


}

        重写nextId()方法,就是我们自己的id生成策略,我们可以借鉴一下mybatisPlus原始的方法:

public class Sequence {
    private static final Log logger = LogFactory.getLog(Sequence.class);
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = 31L;
    private final long maxDatacenterId = 31L;
    private final long sequenceBits = 12L;
    private final long workerIdShift = 12L;
    private final long datacenterIdShift = 17L;
    private final long timestampLeftShift = 22L;
    private final long sequenceMask = 4095L;
    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    private InetAddress inetAddress;

    public Sequence(InetAddress inetAddress) {
        this.inetAddress = inetAddress;
        this.datacenterId = this.getDatacenterId(31L);
        this.workerId = this.getMaxWorkerId(this.datacenterId, 31L);
    }

    public Sequence(long workerId, long datacenterId) {
        Assert.isFalse(workerId > 31L || workerId < 0L, String.format("worker Id can't be greater than %d or less than 0", 31L), new Object[0]);
        Assert.isFalse(datacenterId > 31L || datacenterId < 0L, String.format("datacenter Id can't be greater than %d or less than 0", 31L), new Object[0]);
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    protected long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (StringUtils.isNotBlank(name)) {
            mpid.append(name.split("@")[0]);
        }

        return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);
    }

    protected long getDatacenterId(long maxDatacenterId) {
        long id = 0L;

        try {
            if (null == this.inetAddress) {
                this.inetAddress = InetAddress.getLocalHost();
            }

            NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
            if (null == network) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;
                    id %= maxDatacenterId + 1L;
                }
            }
        } catch (Exception var7) {
            logger.warn(" getDatacenterId: " + var7.getMessage());
        }

        return id;
    }

    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (timestamp < this.lastTimestamp) {
            long offset = this.lastTimestamp - timestamp;
            if (offset > 5L) {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
            }

            try {
                this.wait(offset << 1);
                timestamp = this.timeGen();
                if (timestamp < this.lastTimestamp) {
                    throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
                }
            } catch (Exception var6) {
                throw new RuntimeException(var6);
            }
        }

        if (this.lastTimestamp == timestamp) {
            this.sequence = this.sequence + 1L & 4095L;
            if (this.sequence == 0L) {
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = ThreadLocalRandom.current().nextLong(1L, 3L);
        }

        this.lastTimestamp = timestamp;
        return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp;
        for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
        }

        return timestamp;
    }

    protected long timeGen() {
        return SystemClock.now();
    }
}

九、修改批量保存为自定义批量插入

        在mybatisPlus中其实批量保存时并不是真正的保存,而是循环保存得。可以看一下保存200000条数据的耗时:

        List<User> userList = new ArrayList<>();
        for(int i = 0; i < 199999; i++){
            User user = new User();
            user.setName("张三");
            user.setJob("工作");
            user.setSalary(200000D);
            userList.add(user);
        }
        long s = System.currentTimeMillis();
        userService.saveBatch(userList);
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");

        

        1、添加数据库连接配置

        还是比较慢的,mybatisPlus为我们提供了一种优化方式,就是在连接url上配置批量操作的属性,如下:

jdbc:mysql://数据库地址/数据库名?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true&rewriteBatchedStatements=true

        就是在末尾加上 &rewriteBatchedStatements=true 这个配置,加上之后,可以再试一下批量保存200000条数据:

        

        可以发现时间将近缩短了一半,这只是其中一种方式,我们也可以通过自定义批量保存的方式来进行优化。

        2、自定义批量保存

        分为四步:

        1、首先我们自定义一个RootMapper, 继承BaseMapper,自定义自己的批量插入或者更新方法,如下:

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

    /**
     * 批量新增
     * @param batchList
     * @return
     */
    int insertBatch(@Param("list") Collection<T> batchList);

    /**
     * 批量跟新
     * @param batchList
     * @return
     */
    int updateBatch(@Param("list")Collection<T> batchList);

}

        2、定义InsertBatchColumn 继承 AbstractMethod ,下面基本就是一些通用的写法,不同的Mybatis-plus有一点点区别,本文用的版本为3.5.3.1版本,代码如下:

@Slf4j
public class InsertBatchColumn extends AbstractMethod {

    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;

    public InsertBatchColumn() {
        super("insertBatch");
    }

    public InsertBatchColumn(Predicate<TableFieldInfo> predicate) {
        // 此处的名称必须与后续的RootMapper的新增方法名称一致
        super("insertBatch");
        this.predicate = predicate;
    }

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true,false) +
                this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true,ENTITY_DOT, false) +
                this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(modelClass.getName(), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        // 注意第三个参数,需要与后续的RootMapper里面新增方法名称要一致,不然会报无法绑定异常
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, keyGenerator, keyProperty, keyColumn);
    }
}

        定义 UpdateBatchColumn 继承 AbstractMethod ,代码如下:

public class UpdateBatchColumn extends AbstractMethod {

    public UpdateBatchColumn(String methodName) {
        super(methodName);
    }

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>";
        String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true);
        String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, "item", "item.");
        String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        // 第三个参数必须和RootMapper的自定义方法名一致
        return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource);
    }

        3、自定义sql注入,MysqlInjector继承DefaultSqlInjector ,代码如下:

public class MysqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methods = super.getMethodList(mapperClass,tableInfo);
        // 自定义的insert SQL注入器
        methods.add(new InsertBatchColumn());
//        // 自定义的update SQL注入器,参数需要与RootMapper的批量update名称一致
        methods.add(new UpdateBatchColumn("updateBatch"));
        return methods;
    }
}

        4、定义MybatiesPlus的配置文件,将 MysqlInjector 注入进去,代码如下

@Configuration
public class MybatiesPlusConfig {
    @Bean
    public MysqlInjector sqlInjector(){
        return new MysqlInjector();
    }
}

        5、再次执行200000条数据:

        List<User> userList = new ArrayList<>();
        for(int i = 0; i < 199999; i++){
            User user = new User();
            user.setName("张三");
            user.setJob("工作");
            user.setSalary(200000D);
            userList.add(user);
        }
        long s = System.currentTimeMillis();
        userService.saveOrUpdateBatch(userList);
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");

                

        比只改数据库配置还要再快一些。

Mybaties-Plus saveBatch()、自定义批量插入、多线程批量插入性能测试和对比_mybatisplus savebatch-CSDN博客

        

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值