Java 需要Spring服务、存储库、实体设计模式建议
我有一个(希望是)简单的软件设计问题:我想让我的实体(=持久化到DB的域对象)保持不变。意思是:实体应该只由服务创建,并且应用程序的每个其他部分都使用只有getter方法的接口 示例:Java 需要Spring服务、存储库、实体设计模式建议,java,spring,design-patterns,entity,Java,Spring,Design Patterns,Entity,我有一个(希望是)简单的软件设计问题:我想让我的实体(=持久化到DB的域对象)保持不变。意思是:实体应该只由服务创建,并且应用程序的每个其他部分都使用只有getter方法的接口 示例: root |--- service | |--- MyService.java | |--- MyServiceImpl.java | | | |--- MyEntity.java | |--- MyEn
root
|--- service
| |--- MyService.java
| |--- MyServiceImpl.java
| |
| |--- MyEntity.java
| |--- MyEntityImpl.java
| |
| |--- MyEntityRepository.java
|
|
|------- web
|--- MyController.java
MyController
想要用id=5检索MyEntity
MyController
必须询问MyService
才能获取对象:MyService.getMyEntityById(5)
MyService
将要求MyEntityRepository
从数据库获取对象MyService
将MyEntityInterface
返回给MyController
root
|--- service
| |--- MyService.java
| |--- MyServiceImpl.java
| |
| |--- MyEntity.java
| |--- MyEntityImpl.java
| |
| |--- MyEntityRepository.java
|
|
|------- web
|--- MyController.java
创意:
root
|--- service
| |--- MyService.java
| |--- MyServiceImpl.java
| |
| |--- MyEntity.java
| |--- MyEntityImpl.java
| |
| |--- MyEntityRepository.java
|
|
|------- web
|--- MyController.java
我的第一个想法是在MyEntityImpl
中简单地使用一个包保护的构造函数,但这不适用于我正在使用的其他库(即Orika)。所以它必须是公共的
下一个想法是使用MyEntity
接口。但现在我遇到了一些问题:
问题:
root
|--- service
| |--- MyService.java
| |--- MyServiceImpl.java
| |
| |--- MyEntity.java
| |--- MyEntityImpl.java
| |
| |--- MyEntityRepository.java
|
|
|------- web
|--- MyController.java
MyService(Impl)
有一个名为:updateMyEntityData(myEntitye,Data Data)
的方法。现在我无法确定在我的服务中,这个MyEntity
对象是否真的是MyEntityImpl
的实例。当然我可以做如果(MyEntityImpl的实例)…
但这正是我不想做的
下一个问题是:此服务方法使用MyEntityRepository
,它可以保存和检索MyEntityImpl
对象,但不能处理MyEntity
接口。作为一种解决方法,我可以进行额外的DB查询,但这不是我想要的:
void updateMyEntityData(MyEntity e, Data data) {
MyEntityImpl impl = repo.findOne(e.getId());
impl.setData(data);
repo.saveToDB(impl);
}
这是一个不必要的DB查询,因为我知道MyEntity
是MyEntityImpl
的一个实例,并且它是由该服务创建的,因此它必须是DB中的一个对象。另一种可能是使用铸件:
void updateMyEntityData(MyEntity e, Data data) {
MyEntityImpl impl = (MyEntityImpl) e;
impl.setData(data);
repo.saveToDB(impl);
}
摘要:
root
|--- service
| |--- MyService.java
| |--- MyServiceImpl.java
| |
| |--- MyEntity.java
| |--- MyEntityImpl.java
| |
| |--- MyEntityRepository.java
|
|
|------- web
|--- MyController.java
- 仅允许服务构造
MyEntityImpl
之后必须能够修改MyService(Impl)
的字段(意味着:必须有setter)MyEntityImpl
- 避免不必要的数据库查询
提前谢谢你 我认为你需要克服公共构造函数。如果只有从repository/DB检索到的对象才能分配有效标识,则可以使用该标识控制更新 是的,你可以猜测身份,但是你可以做大量愚蠢的事情来解决你认为你正在实施的任何保护——当然,如果我选择的话,我可以创建一个实例并分配带有重新影响的字段 现在,不变性是一个更崇高的目标,至少在多线程环境中是如此(如果您不是在多线程执行更新的环境中,那么好处就不那么明显,而且,依我看,不值得付出代价) 问题是不可变性与通常会发生变异的域实体发生冲突。解决这个问题的一种常见方法是包含一个时间戳,指示最后一次突变发生的时间,并使用突变拷贝。下面是一个使用生成器模式创建变异副本的干净方法示例: 调用代码如下所示:
MyEntity mutatedMyEntity = myEntity.copy().setData(new Data()).build();
root
|--- service
| |--- MyService.java (public interface)
| |--- MyServiceImpl.java (package protected class implements MyService)
| |
| |--- MyEntity.java (public class)
| |
| |--- MyEntityRepository.java (package protected)
|
|
|------- web
|--- MyController.java
虽然这种方法使事情相对干净,但它引入了多个线程同时创建多个变异副本的问题
根据您的具体需求,这意味着您需要在提交更改时检测冲突(您的
saveToDB
方法),方法是检查更改后的时间戳是否为最新版本(为了避免两次数据库命中,最好在存储过程中进行大量操作,尽管替代方法是在执行写操作的类中保留一个身份缓存,以替换经过修改的时间戳)。冲突的解决将再次取决于您的具体要求,以及对同一实体的其他实例进行的更改。我认为您需要放弃公共构造函数。鉴于只有从存储库/DB检索的对象才能分配有效标识,您可以使用它来控制更新
是的,你可以猜测身份,但是你可以做大量愚蠢的事情来解决你认为你正在实施的任何保护——当然,如果我选择的话,我可以创建一个实例并分配带有重新影响的字段
现在,不变性是一个更崇高的目标,至少在多线程环境中是如此(如果您不是在多线程执行更新的环境中,那么好处就不那么明显,而且,依我看,不值得付出代价)
问题是不可变性与通常会发生变异的域实体冲突。解决此问题的常见方法是包含一个时间戳,指示上一次变异的提交时间,并使用变异副本。以下是使用生成器模式创建变异副本的干净方法示例:
调用代码如下所示:
MyEntity mutatedMyEntity = myEntity.copy().setData(new Data()).build();
root
|--- service
| |--- MyService.java (public interface)
| |--- MyServiceImpl.java (package protected class implements MyService)
| |
| |--- MyEntity.java (public class)
| |
| |--- MyEntityRepository.java (package protected)
|
|
|------- web
|--- MyController.java
虽然这种方法使事情相对干净,但它引入了多个线程同时创建多个变异副本的问题
根据您的具体需求,这意味着您需要在提交更改时检测冲突(您的saveToDB
方法),方法是检查更改后的时间戳是否为最新版本(为了避免两次数据库命中,最好在存储过程中进行大量操作,尽管替代方法是在执行写操作的类中保留一个身份缓存,以替换经过修改的时间戳)。冲突的解决将再次取决于您的具体需求,正如建议的那样