管理实体/代理与 ORM 的良好使用
Good usage of managed entities / proxies with ORM
我目前正在考虑如何处理我的域对象以及休眠,考虑以下因素:
- 我的模型对象直接用JPA注解,没有实体层。
- 在一些数据库繁重的操作中,我不介意调整我的代码,所以我充分利用了代理,即使我们可以将其视为 abstraction/implementation 屏蔽的泄漏。当然,我更喜欢我可以做的其他事情。
- 因为我没有实体层,所以我没有DAO层,实体管理器被认为是DAO层(相关:I found JPA, or alike, don't encourage DAO pattern)
但是我正在考虑改进我正在做的事情,以减少一点复杂性,或者至少,将复杂性重新定位到更适合的地方,比如实体的相关服务。也许更抽象的事实是我正在使用 ORM。
这是一个通用的 CRUD 服务,我的所有业务服务都继承自该服务。这段代码是为了向您展示当前是如何完成的(注释,为清楚起见删除了日志):
public void create(T entity) {
this.entityManager.persist(entity);
}
@Transactional(value = TxType.REQUIRED, rollbackOn=NumeroVersionException.class)
public void update(T entity) throws NumeroVersionException{
try{
this.entityManager.merge(entity);
}catch(OptimisticLockException ole){
throw new NumeroVersionException("for entity "+entity, ole);
}
}
public T read(int id) {
return this.entityManager.find(entityClass, id);
}
public void delete(int id) {
T entity = this.entityManager.getReference(entityClass, id);
this.entityManager.remove(entity);
// edit : removed null test thanks to @JBNizet
}
这种实现的问题是,如果我想创建一个对象,然后利用代理的优势,我基本上必须创建它然后重新获取它。当然,查询可能不会访问数据库,而只会访问 hibernat 的缓存(虽然不确定)。但这意味着我仍然不能忘记重新获取代理。
这意味着我泄露了我在幕后使用 ORM 和代理的事实。
所以我正在考虑将我的界面更改为:
public T read(int id);
public T update(T t)throws NumeroVersionException;
public T create(T object);
public void delete(int id);
List<T> list();
意思是一旦我将一个对象传递给这一层,我将不得不使用返回值。
并具体实施更新,如:
public T update(T t){
if(!(t instanceof [Proxy class goes there])){
//+ check if it is a detached proxy
entityManager.merge(t);
}
}
由于每次调用合并都会命中数据库,对于一些只涉及大约 10 个实体的操作,这可能很烦人,我不会在带有代理的更新方法中调用它。
当然,我希望有一些边缘情况需要 entityManager 来刷新等等。但我认为这会显着降低我的代码当前的复杂性并更好地隔离问题。
简而言之,我正在尝试的是在服务中重新定位 ORM 代码,这样我就可以隐藏我正在使用 ORM 和代理的事实,并像使用任何其他实现一样使用接口,而不会丢失使用 ORM 的好处。
问题是这样的:
- 这个新设计对这个想法来说是个好主意吗?
- 我是否遗漏了有关如何正确处理此问题的任何信息?
注意:即使我在谈论性能,我也关心关注点的隔离、可维护性以及不熟悉 ORM 和我所使用的 Java 的开发人员的易用性。
感谢@JBNizet,我现在看得更清楚了:
- 我应该使用 merge() 方法返回的值。
- 托管实体并不总是代理。
- 我不必抽象化我使用托管实体的事实,这将导致代码复杂且低效
- 我选择了 JPA,我不会换成它,这是真的,除非重写整个模型以代表基于非关系数据库的东西。
所以我只更改原始代码的更新方法,其余的我会保留。
我目前正在考虑如何处理我的域对象以及休眠,考虑以下因素:
- 我的模型对象直接用JPA注解,没有实体层。
- 在一些数据库繁重的操作中,我不介意调整我的代码,所以我充分利用了代理,即使我们可以将其视为 abstraction/implementation 屏蔽的泄漏。当然,我更喜欢我可以做的其他事情。
- 因为我没有实体层,所以我没有DAO层,实体管理器被认为是DAO层(相关:I found JPA, or alike, don't encourage DAO pattern)
但是我正在考虑改进我正在做的事情,以减少一点复杂性,或者至少,将复杂性重新定位到更适合的地方,比如实体的相关服务。也许更抽象的事实是我正在使用 ORM。
这是一个通用的 CRUD 服务,我的所有业务服务都继承自该服务。这段代码是为了向您展示当前是如何完成的(注释,为清楚起见删除了日志):
public void create(T entity) {
this.entityManager.persist(entity);
}
@Transactional(value = TxType.REQUIRED, rollbackOn=NumeroVersionException.class)
public void update(T entity) throws NumeroVersionException{
try{
this.entityManager.merge(entity);
}catch(OptimisticLockException ole){
throw new NumeroVersionException("for entity "+entity, ole);
}
}
public T read(int id) {
return this.entityManager.find(entityClass, id);
}
public void delete(int id) {
T entity = this.entityManager.getReference(entityClass, id);
this.entityManager.remove(entity);
// edit : removed null test thanks to @JBNizet
}
这种实现的问题是,如果我想创建一个对象,然后利用代理的优势,我基本上必须创建它然后重新获取它。当然,查询可能不会访问数据库,而只会访问 hibernat 的缓存(虽然不确定)。但这意味着我仍然不能忘记重新获取代理。
这意味着我泄露了我在幕后使用 ORM 和代理的事实。
所以我正在考虑将我的界面更改为:
public T read(int id);
public T update(T t)throws NumeroVersionException;
public T create(T object);
public void delete(int id);
List<T> list();
意思是一旦我将一个对象传递给这一层,我将不得不使用返回值。 并具体实施更新,如:
public T update(T t){
if(!(t instanceof [Proxy class goes there])){
//+ check if it is a detached proxy
entityManager.merge(t);
}
}
由于每次调用合并都会命中数据库,对于一些只涉及大约 10 个实体的操作,这可能很烦人,我不会在带有代理的更新方法中调用它。
当然,我希望有一些边缘情况需要 entityManager 来刷新等等。但我认为这会显着降低我的代码当前的复杂性并更好地隔离问题。
简而言之,我正在尝试的是在服务中重新定位 ORM 代码,这样我就可以隐藏我正在使用 ORM 和代理的事实,并像使用任何其他实现一样使用接口,而不会丢失使用 ORM 的好处。
问题是这样的:
- 这个新设计对这个想法来说是个好主意吗?
- 我是否遗漏了有关如何正确处理此问题的任何信息?
注意:即使我在谈论性能,我也关心关注点的隔离、可维护性以及不熟悉 ORM 和我所使用的 Java 的开发人员的易用性。
感谢@JBNizet,我现在看得更清楚了:
- 我应该使用 merge() 方法返回的值。
- 托管实体并不总是代理。
- 我不必抽象化我使用托管实体的事实,这将导致代码复杂且低效
- 我选择了 JPA,我不会换成它,这是真的,除非重写整个模型以代表基于非关系数据库的东西。
所以我只更改原始代码的更新方法,其余的我会保留。