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的本质
UOW跟踪变化
UOW维护了一个变更列表
UOW将跟踪到的已变更的对象保存到变更列表中
UOW借助事务一次性提交变更列表中的所有更改
UOW处理并发
对于以上这些点,在C#的EF框架中,DBContext已经实现。
而这里主要描述如何用java实现以上要点。

Repository
将仓储Repo作为聚合的范型类
在Repo中维护一个聚合与聚合状态的集合
在Repo中每次add/update/delete等操作时,将操作的聚合对象,和其最终状态存入集合中
在Repo中的retrieve方法,将聚合检索出来并存入Repo的集合中
代码如下:
public class RepositoryBase implements IRepository {

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 {

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知多少

原文地址https://www.cnblogs.com/llicat/p/12924591.html

相关文章
|
5月前
|
设计模式 Java 开发者
如何在Java项目中实现领域驱动设计(DDD)
如何在Java项目中实现领域驱动设计(DDD)
|
7月前
|
存储 Java 数据库连接
java DDD 领域驱动设计思想的概念与实战
【4月更文挑战第19天】在Java开发中,领域驱动设计(Domain-Driven Design, DDD) 是一种软件设计方法论,强调以领域模型为中心的软件开发。这种方法通过丰富的领域模型来捕捉业务领域的复杂性,并通过软件满足核心业务需求。领域驱动设计不仅是一种技术策略,而且还是一种与业务专家紧密合作的思维方式
443 2
|
Java
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
724 0
|
设计模式 SQL 开发框架
【Java设计模式 面向对象设计思想】六 再谈MVC贫血模式与DDD领域驱动开发
【Java设计模式 面向对象设计思想】六 再谈MVC贫血模式与DDD领域驱动开发
319 1
|
Java 数据安全/隐私保护
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
1284 0
JAVA 实现上传图片添加水印(详细版)(上)
|
网络协议 Java
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
ip地址的分类: 1、ipv4、ipv6 127.0.0.1:4个字节组成,0-255,42亿;30亿都在北美,亚洲就只有4亿 2011年就用尽了。
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
|
Java
Java实现拼图小游戏(7)——查看完整图片(键盘监听实例2)
由于在移动和图片中我们已经添加了键盘监听,也继承了键盘监听的接口,那么我们只需要在重写方法内输入我们的代码即可
221 0
|
存储 Java
Java实现图书管理系统
本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
426 0
Java实现图书管理系统
|
数据可视化 Java
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
548 0
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
|
数据可视化 Java 容器
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
333 0
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现