• Java实现DDD中UnitOfWork


    Java实现DDD中UnitOfWork


    背景

    Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
    Unit of Work --Martin Fowler

    Unit Of Work模式,由马丁大叔提出,是一种数据访问模式。UOW模式的作用是在业务用例的操作中跟踪对象的所有更改(增加、删除和更新),并将所有更改的对象保存在其维护的列表中。在业务用例的终点,通过事务,一次性提交所有更改,以确保数据的完整性和有效性。总而言之,UOW协调这些对象的持久化及并发问题。

    Uow的本质

    1. UOW跟踪变化
    2. UOW维护了一个变更列表
    3. UOW将跟踪到的已变更的对象保存到变更列表中
    4. UOW借助事务一次性提交变更列表中的所有更改
    5. UOW处理并发

    对于以上这些点,在C#的EF框架中,DBContext已经实现。
    而这里主要描述如何用java实现以上要点。

    Repository

    • 将仓储Repo作为聚合的范型类
    • 在Repo中维护一个聚合与聚合状态的集合
    • 在Repo中每次add/update/delete等操作时,将操作的聚合对象,和其最终状态存入集合中
    • 在Repo中的retrieve方法,将聚合检索出来并存入Repo的集合中
    • 代码如下:
    public class RepositoryBase<T extends IBusinessObjectRoot> implements IRepository<T> {
    
        private HashMap<IBusinessKey, RepositoryComponent<T>> map = new HashMap<IBusinessKey, RepositoryComponent<T>>();
    
        private Class<T> tClass;
    
        public RepositoryBase(Class<T> tClass) {
            RepositoryThreadLocalHelper.newInstance().put(tClass.getSimpleName(), this);
            this.tClass = tClass;
        }
    
        public void add(T t) {
            IBusinessKey bk = t.getBusinessKey();
            if (map.containsKey(bk) && RepositoryComponentState.DELETED.equals(map.get(bk).getState())) {
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.UNCHANGED));
            } else {
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.ADDED));
            }
        }
    
        public void update(T t) {
            IBusinessKey bk = t.getBusinessKey();
            if (map.containsKey(bk) && RepositoryComponentState.ADDED.equals(map.get(bk).getState())) {
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.ADDED));
            } else {
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.MODIFIED));
            }
        }
    
        public void delete(IBusinessKey bk) {
            if (map.containsKey(bk) && RepositoryComponentState.ADDED.equals(map.get(bk).getState())) {
                map.get(bk).setState(RepositoryComponentState.UNCHANGED);
            } else {
                map.put(bk, new RepositoryComponent<T>(retrieve(bk), RepositoryComponentState.DELETED));
            }
        }
    
        public void delete(T t) {
            IBusinessKey bk = t.getBusinessKey();
            if (map.containsKey(bk) && RepositoryComponentState.ADDED.equals(map.get(bk).getState())) {
                map.get(bk).setState(RepositoryComponentState.UNCHANGED);
            } else {
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.DELETED));
            }
        }
    
        public T retrieve(IBusinessKey bk) {
            if (map.containsKey(bk)) {
                return map.get(bk).getT();
            } else {
                RepositoryBuilder<T> builder = new RepositoryBuilder<T>();
                T t = builder.buildBo(tClass, bk);
                map.put(bk, new RepositoryComponent<T>(t, RepositoryComponentState.UNCHANGED));
                return t;
            }
        }
    }
    

    RepositoryComponentState

    • 聚合存在于内存中的状态,主要分为以下5个状态
    Added	4	
    The entity is being tracked by the context but does not yet exist in the database.
    Deleted	2	
    The entity is being tracked by the context and exists in the database. It has been marked for deletion from the database.
    Detached	0	
    The entity is not being tracked by the context.
    Modified	3	
    The entity is being tracked by the context and exists in the database. Some or all of its property values have been modified.
    Unchanged	1	
    The entity is being tracked by the context and exists in the database. Its property values have not changed from the values in the database.
    

    Uow

    • 增加自定义注解UowTransaction
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UowTransaction {
    }
    
    • 增加注解切面,在所注解的方法上执行完毕后调用uowService.saveChange()
    @Slf4j
    @Component
    public class UowService {
    
        @Transaction
        public void saveChange() {
            SqlSessionFactory factory = (SqlSessionFactory) SpringContextUtil.getBean("sqlSessionFactory");
            SqlSession session = factory.openSession();
    
            Collection<IRepository> repos = RepositoryThreadLocalHelper.newInstance().getRepositories();
            for (IRepository repo : repos) {
                repo.flushDb(session);
            }
        }
    }
    

    RepositoryFlush

    • flushDb时将内存中的聚合集合统一刷新入数据库
    • flushAdd:将聚合从聚合根到所有子节点依次入库
    • flushModify:更改数据库聚合时,先检索出数据库中原聚合originEntity,然后将原聚合与更改后聚合依次对比,子节点中根据对比内容做新增/删除/修改
    • flushDelete:将聚合从聚合根到所有子节点依次删除
    • 具体参考代码如下:
    @Slf4j
    public class RepositoryFlush {
    
        private SqlSession session;
        private RepositoryConfigHelper configHelper = new RepositoryConfigHelper();
    
        RepositoryFlush(SqlSession session) {
            this.session = session;
        }
    
        void flushAdd(IBusinessEntity bo) {
            RepositoryConfig repoConfig = configHelper.getRepositoryConfig(bo);
            //bo->do
            Object dataObject = TypeConversion.unWrapPrimitiveType(bo, repoConfig.getEntityClass());
            BaseMapper mapper = this.session.getMapper(repoConfig.getEntityMapperClass());
    
            mapper.insert(dataObject);
    
            List<String> childNames = BusinessObjectManager.getEntityNamesByComposition(
                    bo.getBusinessObjectRoot().getArtifactName(),
                    bo.getArtifactName());
            if (childNames.size() > 0) {
                for (String name : childNames) {
                    List<IBusinessEntity> childBes = bo.getEntitiesByComposition(name);
                    for (IBusinessEntity be : childBes) {
                        flushAdd(be);
                    }
                }
            }
        }
    
        void flushModify(IBusinessEntity originBo, IBusinessEntity bo) {
            RepositoryConfig repoConfig = configHelper.getRepositoryConfig(bo);
            // bo->do
            Object dataObject = TypeConversion.unWrapPrimitiveType(bo, repoConfig.getEntityClass());
            BaseMapper mapper = this.session.getMapper(repoConfig.getEntityMapperClass());
    
            //1. gdt->基本类型
            //2. bk:驼峰形式改为下划线形式
            Map<String, String> bkMap = RepositoryUtil.MapCamelCaseToUnderscore(
                    UnWrapMapUtil.getGdtValue(
                            bo.getBusinessKey().getBusinessKeyMap()));
    
            // update root
            UpdateWrapper updateWrapper = new UpdateWrapper();
            updateWrapper.allEq(bkMap);
            mapper.update(dataObject, updateWrapper);
    
            // 遍历子be
            List<String> originChildNames = BusinessObjectManager.getEntityNamesByComposition(
                    originBo.getBusinessObjectRoot().getArtifactName(),
                    originBo.getArtifactName()
            );
            if (originChildNames.size() > 0) {
                for (String name : originChildNames) {
                    List<IBusinessEntity> originChildBes = originBo.getEntitiesByComposition(name);
                    List<IBusinessEntity> childBes = bo.getEntitiesByComposition(name);
                    for (IBusinessEntity be : childBes) {
                        // be在数据库中,从originChildBes列表中删除,所有删除完剩下的->需要delete的
                        Optional<IBusinessEntity> optional = originChildBes.stream()
                                .filter(
                                        x -> {
                                            // 判断bk是否相同
                                            return x.getBusinessKey().equals(be.getBusinessKey());
                                        }
                                ).findFirst();
                        if (optional.isPresent()) {
                            // 数据库中存在:修改
                            IBusinessEntity originBe = optional.get();
                            originChildBes.remove(originBe);
                            flushModify(originBe, be);
                        } else {
                            // 数据库中不存在:新增
                            flushAdd(be);
                        }
                    }
                    // 数据库中存在,但modifyBo中没有:删除
                    if (originChildBes.size() > 0) {
                        for (IBusinessEntity originDeleteBe : originChildBes) {
                            flushDelete(originDeleteBe);
                        }
                    }
                }
            }
        }
    
        void flushDelete(IBusinessEntity bo) {
            RepositoryConfig repoConfig = configHelper.getRepositoryConfig(bo);
            BaseMapper mapper = this.session.getMapper(repoConfig.getEntityMapperClass());
    
            IBusinessKey bk = bo.getBusinessKey();
            Map<String, String> bkMap = RepositoryUtil.MapCamelCaseToUnderscore(
                    UnWrapMapUtil.getGdtValue(
                            bo.getBusinessKey().getBusinessKeyMap()));
    
            mapper.deleteByMap(bkMap);
    
            List<String> childNames = BusinessObjectManager.getEntityNamesByComposition(
                    bo.getBusinessObjectRoot().getArtifactName(),
                    bo.getArtifactName()
            );
            if (childNames.size() > 0) {
                for (String name : childNames) {
                    List<IBusinessEntity> childBes = bo.getEntitiesByComposition(name);
                    for (IBusinessEntity be : childBes) {
                        flushDelete(be);
                    }
                }
            }
        }
    }
    
    • 在repo中增加flushDb方法,如下:
    public void flushDb(SqlSession session) {
        RepositoryFlush flush = new RepositoryFlush(session);
        for (Map.Entry<IBusinessKey, RepositoryComponent<T>> entry : map.entrySet()) {
            RepositoryComponentState state = entry.getValue().getState();
            T t = entry.getValue().getT();
            if (RepositoryComponentState.ADDED.equals(state)) {
                flush.flushAdd(t);
            } else if (RepositoryComponentState.MODIFIED.equals(state)) {
                RepositoryBuilder<T> builder = new RepositoryBuilder<T>();
                T rootT = builder.buildBo(tClass, t.getBusinessKey());
                flush.flushModify(rootT, t);
            } else if (RepositoryComponentState.DELETED.equals(state)) {
                flush.flushDelete(t);
            }
        }
    }
    

    Retrieve&&RepositoryBuilder

    • repo中提供retrieve方法用于检索聚合
    • 若聚合在repo的集合中已存在则直接返回聚合,若无聚合,则通过RepoBuilder从数据库中捞取聚合
    public class RepositoryBuilder<T extends IBusinessObjectRoot> {
    
        private RepositoryConfigHelper configHelper = new RepositoryConfigHelper();
        private Map<String, List<IBusinessEntity>> beValues = new HashMap<>();
    
        T buildBo(Class<T> tClass, IBusinessKey businessKey) {
            SqlSessionFactory factory = (SqlSessionFactory) SpringContextUtil.getBean("sqlSessionFactory");
            SqlSession session = factory.openSession();
            try {
                RepositoryConfig config = configHelper.getRepositoryConfig((IBusinessEntity) tClass.newInstance());
                BaseMapper mapper = session.getMapper(config.getEntityMapperClass());
                QueryWrapper queryWrapper = new QueryWrapper();
                queryWrapper.allEq(
                        RepositoryUtil.MapCamelCaseToUnderscore(
                                UnWrapMapUtil.getGdtValue(
                                        businessKey.getBusinessKeyMap()))
                );
                // 获取到当前do object
                Object object = mapper.selectOne(queryWrapper);
                if (object == null) {
                    throw new RuntimeException("未找到数据库对应DO数据。");
                }
                // build child
                IBusinessObjectRoot rootT = (IBusinessObjectRoot) TypeConversion.wrapPrimitiveType(object, tClass);
                buildChildBe(session, tClass, object, rootT);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            } finally {
                session.close();
            }
            // beValues->bo
            return (T) BusinessObjectManager.newInstance().createBusinessObjectInstance(
                    tClass.getSimpleName(),
                    beValues
            );
        }
    
        /**
         * 1. do->beValue
         * 2. beValues.add(beValue)
         * 3. bo查子bo
         * 4. 返回
         */
        private void buildChildBe(SqlSession session, Class<T> tClass, Object object, IBusinessObjectRoot rootT)
                throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
            T t = TypeConversion.wrapPrimitiveType(object, tClass);
            if (t == null) {
                throw new RuntimeException("DO数据类型转BE异常");
            }
            // 加入beValues
            if (beValues.containsKey(t.getArtifactName())) {
                beValues.get(t.getArtifactName()).add(t);
            } else {
                beValues.put(t.getArtifactName(), new ArrayList<IBusinessEntity>() {{
                    add(t);
                }});
            }
            //
            IBusinessKey bk = t.getBusinessKey();
            List<String> childClassNames = BusinessObjectManager.getEntityNamesByComposition(
                    rootT.getArtifactName(),
                    t.getArtifactName()
            );
            if (childClassNames.size() > 0) {
                for (String childClassName : childClassNames) {
                    Class childClass = Class.forName(childClassName);
                    // 构造函数:包含父be(无结构构造函数)
                    IBusinessEntity nullChildBe = (IBusinessEntity) childClass
                            .getConstructor()
                            .newInstance();
                    RepositoryConfig childConfig = configHelper.getRepositoryConfig(rootT, nullChildBe);
                    BaseMapper childMapper = session.getMapper(childConfig.getEntityMapperClass());
                    List dbList = childMapper.selectByMap(
                            RepositoryUtil.MapCamelCaseToUnderscore(
                                    UnwrapMapUtil.getGdtValue(bk.getBusinessKeyMap())
                            )
                    );
                    for (Object dbObject : dbList) {
                        buildChildBe(session, childClass, dbObject, rootT);
                    }
                }
            }
        }
    }
    

    最后

    • 以上代码只包含Uow、Repo等关键代码,完整代码使用还需要配合聚合的建模,全局统一类型的使用
    • 代码仅供学习,以后有机会会上传到github中

    资料参考
    EntityFrameworkCore
    UnitOfWork知多少

  • 相关阅读:
    修改 jenkins 主目录
    pom中Maven插件 配置 maven-dependency-plugin maven-surefire-plugin
    Oracle解决ora-01653 无法通过1024扩展
    windows 定时备份linux 上oracle 数据库
    oracle drop 表后 恢复
    jenkins 安装
    关于RedHat Linux无法使用yum命令安装gcc-c++问题
    eolinker 安装时遇到的坑
    mysql linux安装教程
    JWT ajax java spingmvc 简洁教程
  • 原文地址:https://www.cnblogs.com/llicat/p/12924591.html
Copyright © 2020-2023  润新知