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

Java实现DDD中UnitOfWork的更多相关文章

  1. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  2. JAVA WEB项目中各种路径的获取

    JAVA WEB项目中各种路径的获取 标签: java webpath文件路径 2014-02-14 15:04 1746人阅读 评论(0) 收藏 举报  分类: JAVA开发(41)  1.可以在s ...

  3. 揭开Java IO流中的flush()的神秘面纱

    大家在使用Java IO流中OutputStream.PrintWriter --时,会经常用到它的flush()方法. 与在网络硬件中缓存一样,流还可以在软件中得到缓存,即直接在Java代码中缓存. ...

  4. JAVA安装过程中出现的“javac不是内部或外部指令”的解决方法

    近来重新安装了JAVA,安装过程中出现问题,网上找到解决办法,汇总发布. 解决流程: 1.确定自己的环境变量设置没问题,没有出现遗漏 : . 等情况 (具体环境变量设置百度) 2.环境变量设置后 ,d ...

  5. Java Web开发中MVC设计模式简介

    一.有关Java Web与MVC设计模式 学习过基本Java Web开发的人都已经了解了如何编写基本的Servlet,如何编写jsp及如何更新浏览器中显示的内容.但是我们之前自己编写的应用一般存在无条 ...

  6. Java EE 编程中路径

    版权声明:未经博主允许,不得转载 首先我们要限定一个范围,是一个项目,或是以个访问地址..就先以一个项目为限定的范围 前述: 学过物理学的都知道相对运动和绝对运动, 虽然是相似的概念,但这里的要简单得 ...

  7. 【java】jackson 中JsonFormat date类型字段的使用

    为了便于date类型字段的序列化和反序列化,需要在数据结构的date类型的字段上用JsonFormat注解进行注解具体格式如下 @JsonFormat(pattern = "yyyy-MM- ...

  8. DDD~Unity在DDD中的使用

    回到目录 上一讲介绍了DDD中的领域层,并提到下次要讲Unity,所以这篇文章当然就要介绍它了,呵呵,Unity是Microsoft.Practices中的一部分,主要实现了依赖注入的功能,或者叫它控 ...

  9. Java线程并发中常见的锁

    随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想 ...

随机推荐

  1. 探索ORACLE之ASM概念(完整版)

    探索ORACLE之ASM概念(完整版) 本文出自https://www.jb51.net/article/43527.htm ASM是Oracle 10g R2中为了简化Oracle数据库的管理而推出 ...

  2. 解决Idea配置文件不显示中文的问题

    1.首先我们的IDEA文件编码一般都修改为utf-8(setting-->file encodings--->Global Encoding 和 Project Encoding 都设置为 ...

  3. vuex-persist数据持久化存储插件

    Vuex 解决了多视图之间的数据共享问题.但是运用过程中又带来了一个新的问题是,Vuex 的状态存储并不能持久化.也就是说当你存储在 Vuex 中的 store 里的数据,只要一刷新页面,数据就丢失了 ...

  4. Week-1 linux命令行重点整理

    ①仅对当前用户: ~/.bashrc ②对所有用户有效: /etc/bashrc screen命令:创建新screen会话screen –S [SESSION]加入screen会话screen –x ...

  5. 《OSPF和IS-IS详解》一1.5 ARPANET内的路由选择

    本节书摘来异步社区<OSPF和IS-IS详解>一书中的第1章,第1.5节,作者: [美]Jeff Doyle 译者: 孙余强 责编: 傅道坤,更多章节内容可以访问云栖社区"异步社 ...

  6. 无向图双连通分量BCC(全网最好理解)

    不是标题党,之前我也写过一篇比较全的,但是对于初学者不友好.传送门? 双连通分量(Biconnected component):     1.边双联通 E-BCC     2.点双连通 V-BCC 双 ...

  7. OSG程序设计之Hello World 3.0

    直接上代码: #include <osgDB/ReadFile> #include <osgViewer/Viewer> #include <osgViewer/View ...

  8. 数据可视化:使用python代码实现可视数据随机漫步图

    #2020/4/5 ,是开博的第一天,希望和大家相互交流学习,很开森,哈哈~ #像个傻子哟~       #好,我们进入正题, #实现功能:利用python实现数据随机漫步,漫步点数据可视化 #什么是 ...

  9. OKR新手入门指南 (第一部分)

    什么是OKR? OKR(目标和关键结果)是Google和其他公司使用的目标系统.这是一个简单的工具,围绕可衡量的目标进行调整和互动. OKR:Google的目标设定方法 与传统的规划方法有何不同? O ...

  10. 深入理解Mybatis插件

    Mybatis插件实现原理 本文如有任何纰漏.错误,请不吝指出,谢谢! 首先,我并没有使用过 Mybatis的插件,但是这个和我写这篇文章并不冲突,估计能真正使用到插件的人也比较少,写这篇文章的目的主 ...