spring-data-mongo分表
这里是续:建议先读上一边文章。
一、为什么会使用分表
我们知道mognoDB,支持集群分布式部署,支持分片。这也就是MongoDB使用ObjectId作为主键的原理。
ObjectId是每个文档的唯一标识。是一个24位的字符串(12字节)。
0-3字节:时间戳
4-6字节:机器Machine主机唯一标识
7-8: PID进程标识符
9-11: 计数器
我们可以按照一定的规则和分片算法去做海量数据的分片存储,例如常见的主键取余等。
那么既然已经支持分片了,为什么还要分表呢?这里是这样的:
- 原mongoDB采用单机部署,暂无集群配置条件,且暂未达到瓶颈。
- 希望能够较为简单的根据不同字段自定义分表规则
- 希望做到同库分表,而非分库分表,利于维护。
二、项目配置
见上一篇文章
三、如何做分表
这里还是采用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自带的两种数值转换方法
- 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);
}
}
- 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下的同库分表业务。
其他
-
上篇回顾: ORM-Mapping MongoDB的使用配置
-
演示代码项目Gitee地址:longer-spring-boot-study-diary: 我的spring-boot学习记录 - Gitee.com
-
参考文章|官方Guide传送门:Spring Data MongoDB
码字不易,多多支持