spring-data-mongo分表

这里是续:建议先读上一边文章。

一、为什么会使用分表

我们知道mognoDB,支持集群分布式部署,支持分片。这也就是MongoDB使用ObjectId作为主键的原理。

ObjectId是每个文档的唯一标识。是一个24位的字符串(12字节)。

0-3字节:时间戳

4-6字节:机器Machine主机唯一标识

7-8: PID进程标识符

9-11: 计数器

我们可以按照一定的规则和分片算法去做海量数据的分片存储,例如常见的主键取余等。

那么既然已经支持分片了,为什么还要分表呢?这里是这样的:

  1. 原mongoDB采用单机部署,暂无集群配置条件,且暂未达到瓶颈。
  2. 希望能够较为简单的根据不同字段自定义分表规则
  3. 希望做到同库分表,而非分库分表,利于维护。

二、项目配置

见上一篇文章

三、如何做分表

这里还是采用orm-mapping的方式取操作数据库

1. po定义

这里我们定义一个订单PO,其中会根据商品类型和月份自动生成分表。

在ORM操作数据,便于映射生成数据表,所以见过有些方案在同库分表时,分表数量,分表类型时固定时候,会定义多个PO。也有采用了一个PO,但是使用了ShardingSpere去维护分表算法的。

这里基于mongTemplate实现。

package com.longer.springdatamongodemo.po;

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

/**
 * @author lang
 * @description 订单PO
 * @createTime 2021/9/12 21:32
 */
@Data
@Document(collection = "m_order")
public class Order extends BaseDocument{

    /** 商品uuid **/
    @Field("productUuid")
    private String productUuid;

    /** 商品类型 **/
    @Field("productType")
    private String productType;

    /** 订单用户Uuid **/
    @Field("userUuid")
    private String userUuid;

    /** 订单金额  这里没有使用 Java 的Big BigDecimal 不能对应上Mongo3.X+的Decimal128 
     * 因为Mongo不支持,所以需要做读写一个转换
     * 实现 JAVA  BigDecimal  <=>  Mongo Decimal128
     * **/
    /** 订单金额 **/
    @Field("orderAmount")
    private BigDecimal orderAmount;

    /** 订单时间 **/
    @Field("orderTime")
    private Date orderTime;

    /** 订单其他详细信息 **/
    @Field("orderDetail")
    private Map<String, Object> orderDetail;
    
}

2. repository定义

上一章一个po对应了一个repository,基于实现MongoRepository实现了单表操作。但是这里由于要做分表,我们需要一个po,一个repostitory,需要能够映射到多张表。与时基于MongoTemplate实现了自定义方法。

package com.longer.springdatamongodemo.repository.order;

import com.longer.springdatamongodemo.po.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author lang
 * @description 自定义用于orm-mapping的分表DAO层
 * @createTime 2021/9/12 21:45
 */
@Repository
public class OrderCustomRepository {

    private MongoTemplate mongoTemplate;
    @Autowired
    public void setMongoTemplate(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }


    /**
     * 向指定分表插入数据。
     * @param order  订单实体
     * @param collectionName   分表名称
     * @return
     */
    public Order create(Order order, String collectionName){
        return mongoTemplate.save(order, collectionName);
    }


    /**
     * 查询指定分表中的所有订单记录
     * @param collectionName  查询指定分表中所有订单记录
     * @return
     */
    public List<Order> findAll(String collectionName) {
        return mongoTemplate.findAll(Order.class, collectionName);
    }
}

3. 测试类

接下来我们写测试业务类,来测试,是否数据会自动按照规则创建表,并写入指定表中。

package com.longer.springdatamongodemo.repository;

import com.longer.springdatamongodemo.po.Order;
import com.longer.springdatamongodemo.repository.order.OrderCustomRepository;
import org.bson.types.Decimal128;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * @author lang
 * @description 订单测试类
 * @createTime 2021/9/12 22:05
 */
@SpringBootTest
public class OrderRepositoryTests {

    private OrderCustomRepository orderCustomRepository;
    @Autowired
    public void setOrderCustomRepository(OrderCustomRepository orderCustomRepository) {
        this.orderCustomRepository = orderCustomRepository;
    }

    /**
     * 创建订单1, 写入分表1,这里更新商品类型做分表
     */
    @Test
    void createOrder1Test() {
        Order order = new Order();
        order.setProductUuid("商品uuid2");
        order.setProductType("food");
        order.setUserUuid("User123");
        order.setOrderAmount(BigDecimal.valueOf(31411.41));
        order.setOrderTime(new Date());

        /** 根据分表规则,指定写入订单分表, 如这里是食品类 订单记录 **/
        String collectionName = "m_order_" + order.getProductType();
        Order orderSaved = orderCustomRepository.create(order, collectionName);
        System.out.println("orderSaved = " + orderSaved);
    }

    /**
     * 创建订单2, 写入分表2,这里根据商品类型做分表
     */
    @Test
    void createOrder2Test() {
        Order order = new Order();
        order.setProductUuid("商品uuid1");
        order.setProductType("clothes");
        order.setUserUuid("User123");
        order.setOrderAmount(BigDecimal.valueOf(341.462));
        order.setOrderTime(new Date());

        /** 根据分表规则,指定写入订单分表, 如这里是服装类 订单记录 **/
        String collectionName = "m_order_" + order.getProductType();
        Order orderSaved = orderCustomRepository.create(order, collectionName);
        System.out.println("orderSaved = " + orderSaved);
    }

    /**
     * 查询订单2对应分表的所有
     */
    @Test
    void findOrder2Test() {
        /** 指定查询分表, m_order是OrderPo映射的文档,后面的_clothes是分表依据 **/
        String collectionName = "m_order_" + "clothes";
        List<Order> orderList = orderCustomRepository.findAll(collectionName);
        System.out.println("orderList = " + orderList);
    }


}

4. 一点调整

注意到,订单OrderPo订单金额orderAmount 定义的类型是JAVA的BigDecimal,但是在Mongo中并不支持该类型,Mongo中支持的是Decimal128,所以我们这里需要做一个Java BigDecimal <=> MongoDB Decimal12的 读写互转。具体解析可以搜springboot mongo decimal 关键词就可以查到描述,这里不赘述。

见之前定义的配置文件类MonoConfig,将其修改为如下配置

package com.longer.springdatamongodemo.config;

import com.longer.springdatamongodemo.converter.BigDecimalToDecimal128Converter;
import com.longer.springdatamongodemo.converter.Decimal128ToBigDecimalConverter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lang
 * @description mongo配置类
 * @createTime 2021/9/9 23:09
 */
@Configuration
public class MongoConfig implements InitializingBean {

    private MappingMongoConverter mappingMongoConverter;
    @Autowired
    @Lazy
    public void setMappingMongoConverter(MappingMongoConverter mappingMongoConverter) {

        /**
         * 新增 java <=>  decimal数据类型读写 互转
         */
        List<Object> converterList = new ArrayList<>();
        converterList.add(new BigDecimalToDecimal128Converter());
        converterList.add(new Decimal128ToBigDecimalConverter());
        mappingMongoConverter.setCustomConversions(new MongoCustomConversions(converterList));

        this.mappingMongoConverter = mappingMongoConverter;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        // 用于设置保存的mongo中移除 _class列
        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
    }
}

并写两个转化器实现spring.core自带的两种数值转换方法

  1. BigDecimalToDecimal128Converter
package com.longer.springdatamongodemo.converter;

import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;

import java.math.BigDecimal;

/**
 * @author lang
 * @description Java -> MongoDB Decimal128读写转换器
 * @createTime 2021/9/12 22:47
 */

@WritingConverter
public class BigDecimalToDecimal128Converter implements Converter<BigDecimal, Decimal128> {
    @Override
    public Decimal128 convert(BigDecimal bigDecimal) {
        return new Decimal128(bigDecimal);
    }
}
  1. Decimal128ToBigDecimalConverter
package com.longer.springdatamongodemo.converter;

import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;

import java.math.BigDecimal;

/**
 * @author lang
 * @description mongo -> java  Decimal128 转 BigDecimal
 * @createTime 2021/9/12 22:50
 */
@ReadingConverter
public class Decimal128ToBigDecimalConverter implements Converter<Decimal128, BigDecimal> {
    @Override
    public BigDecimal convert(Decimal128 decimal128) {
        return decimal128.bigDecimalValue();
    }
}

添加如上配置后,运行测试类,看到到不同的document根据分表键插入到不同的collection中了,实现我们需要的orm-mapping下的同库分表业务。

其他

  1. 上篇回顾: ORM-Mapping MongoDB的使用配置
  2. 演示代码项目Gitee地址:longer-spring-boot-study-diary: 我的spring-boot学习记录 - Gitee.com
  3. 参考文章|官方Guide传送门:Spring Data MongoDB