Java主数据案例

wen java案例 4

本文目录导读:

Java主数据案例

  1. 案例背景
  2. 数据模型设计 (Entity)
  3. 核心服务层逻辑 (Service)
  4. 数据变更捕获与分发 (Event & MQ)
  5. 缓存策略 (Redis)
  6. 数据一致性保障(关键点)
  7. 总结案例图谱

这是一个非常典型的企业级数据治理场景,在Java技术栈中,主数据管理(MDM, Master Data Management)通常不只是一个简单的CRUD应用,而是一个涉及数据模型治理、数据标准化、数据同步、版本控制及缓存策略的复杂系统。

下面我将构建一个“客户主数据(Customer Master)” 的案例,涵盖从模型设计到代码实现的核心逻辑。

案例背景

假设公司有多个业务系统(CRM、ERP、BI),需要对“客户”这同一个实体进行统一管理,需求是:

  1. 唯一索引:通过身份证号/统一社会信用代码保证唯一。
  2. 数据编码:自动生成全局唯一的“客户编码”(格式:CUST-202405-XXXX)。
  3. 数据版本:每次修改都会生成一个新版本,支持数据血缘追溯。
  4. 数据分发:修改后通过消息队列通知下游系统(如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);
    }
}

数据一致性保障(关键点)

在分布式环境下,为了防止主数据混乱,注意以下几点:

  1. 最终一致性:不允许下游系统直接修改主数据表,必须通过接口调用。
  2. 使用分布式IDCustomerCode 可以用 雪花算法(Snowflake)美团Leaf 生成,保证全局唯一且有序。
  3. 异常监控:当主数据因并发产生版本冲突时,使用乐观锁。
// 在实体中添加版本号字段用于乐观锁
@Version
private Integer optimisticLockVersion;

总结案例图谱

[CRM系统] ----> [Java MDM Service] ----> [MySQL/MongoDB]
                   |                          |
                   | (版本管理)               | (存储主数据)
                   |                          |
                   v                          v
            [Kafka/RabbitMQ]           [Redis Cache]
                   |                          |
                   v                          v
            [ERP系统]                   [BI系统]

这个案例展示了在 Java 中如何构建一个结构化的主数据管理系统,重点在于模型设计、版本迭代、数据分发和缓存一致性,你可以根据业务类型(客户、产品、供应商)复用这个模式。

抱歉,评论功能暂时关闭!