本文目录导读:

这是一个非常典型的企业级数据治理场景,在Java技术栈中,主数据管理(MDM, Master Data Management)通常不只是一个简单的CRUD应用,而是一个涉及数据模型治理、数据标准化、数据同步、版本控制及缓存策略的复杂系统。
下面我将构建一个“客户主数据(Customer Master)” 的案例,涵盖从模型设计到代码实现的核心逻辑。
案例背景
假设公司有多个业务系统(CRM、ERP、BI),需要对“客户”这同一个实体进行统一管理,需求是:
- 唯一索引:通过身份证号/统一社会信用代码保证唯一。
- 数据编码:自动生成全局唯一的“客户编码”(格式:CUST-202405-XXXX)。
- 数据版本:每次修改都会生成一个新版本,支持数据血缘追溯。
- 数据分发:修改后通过消息队列通知下游系统(如ERP)。
数据模型设计 (Entity)
我们使用 JPA / Hibernate 来定义主数据模型。
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "mdm_customer",
uniqueConstraints = @UniqueConstraint(columnNames = "idCard", name = "uk_id_card"))
public class CustomerMaster {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 全局唯一编码,由系统生成
@Column(name = "customer_code", nullable = false, unique = true, length = 32)
private String customerCode;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "id_card", nullable = false, length = 18)
private String idCard; // 身份证号作为唯一标识
@Column(name = "phone", length = 20)
private String phone;
@Column(name = "email", length = 100)
private String email;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private MasterDataStatus status; // ACTIVE, INACTIVE, MERGED
@Column(name = "version_number", nullable = false)
private Integer versionNumber; // 数据版本号
@Column(name = "created_by", length = 50)
private String createdBy;
@Column(name = "created_time", nullable = false, updatable = false)
private LocalDateTime createdTime;
@Column(name = "updated_time")
private LocalDateTime updatedTime;
// 用于记录当前数据是否是最新版本(用于历史追溯)
@Column(name = "is_latest_version")
private Boolean latestVersion;
// Getters and Setters...
}
enum MasterDataStatus {
ACTIVE, INACTIVE, MERGED
}
核心服务层逻辑 (Service)
这是主数据管理的核心,包含了防重、编码生成、版本控制。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class CustomerMasterService {
@Autowired
private CustomerMasterRepository repository;
@Autowired
private CustomerCodeGenerator codeGenerator;
/**
* 创建或更新主数据(基于幂等性设计)
* 如果身份证号已存在,则创建新版本并标记旧版本为过期
*/
@Transactional
public CustomerMaster createOrUpdate(CustomerMaster request) {
// 1. 查找是否存在该身份证号的主数据
Optional<CustomerMaster> existingOpt = repository.findByIdCard(request.getIdCard());
if (existingOpt.isPresent()) {
// 存在则进行版本迭代更新
return createNewVersion(existingOpt.get(), request);
} else {
// 不存在则新建
return createNewCustomer(request);
}
}
private CustomerMaster createNewCustomer(CustomerMaster request) {
CustomerMaster entity = new CustomerMaster();
// 生成全局唯一编码
entity.setCustomerCode(codeGenerator.generateCode());
entity.setName(request.getName());
entity.setIdCard(request.getIdCard());
entity.setPhone(request.getPhone());
entity.setEmail(request.getEmail());
entity.setStatus(MasterDataStatus.ACTIVE);
entity.setVersionNumber(1); // 初始版本为1
entity.setCreatedTime(LocalDateTime.now());
entity.setUpdatedTime(LocalDateTime.now());
entity.setLatestVersion(true);
return repository.save(entity);
}
/**
* 版本更新策略
* 将旧记录标记为历史,插入新记录并版本号+1
*/
private CustomerMaster createNewVersion(CustomerMaster oldEntity, CustomerMaster newData) {
// 1. 将旧版本标记为"非最新"
oldEntity.setLatestVersion(false);
repository.save(oldEntity);
// 2. 创建新版本
CustomerMaster newEntity = new CustomerMaster();
// 继承旧的唯一编码
newEntity.setCustomerCode(oldEntity.getCustomerCode());
newEntity.setName(newData.getName() != null ? newData.getName() : oldEntity.getName());
newEntity.setIdCard(oldEntity.getIdCard());
newEntity.setPhone(newData.getPhone() != null ? newData.getPhone() : oldEntity.getPhone());
newEntity.setEmail(newData.getEmail() != null ? newData.getEmail() : oldEntity.getEmail());
// 版本号递增
newEntity.setVersionNumber(oldEntity.getVersionNumber() + 1);
newEntity.setStatus(MasterDataStatus.ACTIVE);
newEntity.setCreatedTime(oldEntity.getCreatedTime()); // 保留原创建时间
newEntity.setUpdatedTime(LocalDateTime.now());
newEntity.setLatestVersion(true);
return repository.save(newEntity);
}
// 获取最新版本(下游系统查询时使用)
public CustomerMaster getLatestByIdCard(String idCard) {
return repository.findByIdCardAndLatestVersionTrue(idCard)
.orElseThrow(() -> new RuntimeException("主数据未找到"));
}
}
数据变更捕获与分发 (Event & MQ)
主数据修改后,需要通知下游系统,使用 Spring Event + RocketMQ/Kafka。
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.kafka.core.KafkaTemplate;
@Component
public class MasterDataEventDispatcher {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
private static final String TOPIC = "MDM_CUSTOMER_CHANGE";
@EventListener
public void handleCustomerChange(CustomerChangeEvent event) {
// 构建数据变更消息体
CustomerChangeMessage message = CustomerChangeMessage.builder()
.customerCode(event.getCustomerMaster().getCustomerCode())
.action(event.getActionType()) // CREATE, UPDATE, DELETE
.dataSnapshot(event.getCustomerMaster())
.build();
// 发送到消息队列
kafkaTemplate.send(TOPIC, message.getCustomerCode(), message);
}
}
缓存策略 (Redis)
主数据访问频率极高,但变更频率较低(相对于业务交易数据),非常适合使用 Redis 旁路缓存。
public class CustomerCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_PREFIX = "MDM:CUST:";
public CustomerMaster getCustomer(String customerCode) {
String key = CACHE_PREFIX + customerCode;
// 1. 查缓存
CustomerMaster cache = (CustomerMaster) redisTemplate.opsForValue().get(key);
if (cache != null) {
return cache;
}
// 2. 查数据库
CustomerMaster db = repository.findByCustomerCode(customerCode);
if (db != null) {
// 3. 设置缓存(TTL 24小时)
redisTemplate.opsForValue().set(key, db, 24, TimeUnit.HOURS);
return db;
}
return null;
}
// 数据更新后,删除缓存
public void evictCache(String customerCode) {
redisTemplate.delete(CACHE_PREFIX + customerCode);
}
}
数据一致性保障(关键点)
在分布式环境下,为了防止主数据混乱,注意以下几点:
- 最终一致性:不允许下游系统直接修改主数据表,必须通过接口调用。
- 使用分布式ID:
CustomerCode可以用雪花算法(Snowflake)或美团Leaf生成,保证全局唯一且有序。 - 异常监控:当主数据因并发产生版本冲突时,使用乐观锁。
// 在实体中添加版本号字段用于乐观锁 @Version private Integer optimisticLockVersion;
总结案例图谱
[CRM系统] ----> [Java MDM Service] ----> [MySQL/MongoDB]
| |
| (版本管理) | (存储主数据)
| |
v v
[Kafka/RabbitMQ] [Redis Cache]
| |
v v
[ERP系统] [BI系统]
这个案例展示了在 Java 中如何构建一个结构化的主数据管理系统,重点在于模型设计、版本迭代、数据分发和缓存一致性,你可以根据业务类型(客户、产品、供应商)复用这个模式。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播