我创建了一个Spring MVC转换器来处理实体映射:(对不起,这段代码在Kotlin中,应该不难理解)
class BaseEntityConverterFactory(val em: EntityManager) : ConverterFactory<String, BaseEntity?> {
override fun <T : BaseEntity?> getConverter(targetType: Class<T>)
= BaseEntityConverter(targetType) as Converter<String, T>
private inner class BaseEntityConverter internal constructor(private val baseEntityClass: Class<*>) : Converter<String, BaseEntity?> {
override fun convert(source: String): BaseEntity? {
if (source.isEmpty()) {
return null
} else {
return em.find(baseEntityClass, source) as BaseEntity? ?: throw EntityNotFoundException("Entity $baseEntityClass with $source was not found.")
}
}
}
}
这很棒,它可以起作用:
@GetMapping @Transactional
fun myEndPoint(@RequestParam entity: MyEntity) {
...
em.persist(entity)
}
像这样,我直接在方法中得到正确的实体。问题在于该实体为detached
,而em.persist
指向detached entity passed to persist
。
我当然可以在之前打电话给em.merge
,但我希望将实体加载为attached
。这可行吗?
编辑:似乎em.merge
并没有帮助。实体仍处于分离状态。我猜在ConverterFactory
内必须做些什么。
答案 0 :(得分:1)
这里的问题是,您已经在Web范围内初始化了实体(实际上不应该知道您的持久层实现的细节),然后传递给了服务层,这可能使用了不同的线程...
您应该应用长期存在的“ OpenSessionInView”模式,现在将其视为“不良做法”或“反模式”。这种模式将确保您的实体从您的Web请求开始到Web请求发送所有响应数据之前,都将使用相同的会话实例。
或另一个选择:修改应用程序以引入纯DTO层,并创建映射。 DTO在WEB层中处理,然后传递到Service层,在此它被映射到实体。在这种情况下,我建议使用Mapstruct生成映射器
更新:
您有2个实体。
@Entity
class MyEntity {
// ...properties
@Id
private Long id;
String stringProperty;
LocalDate dateProperty;
}
@Entity
class MyDependentEntity {
@Id
Long id;
@ManyToOne(targetEntity=MyEntity.class)
MyEntity entity;
// ... properties
}
现在,您希望您的服务能够更新您的MyEntity
和MyDependentEntity
为此,您创建了DTO,它将直接表示您的实体:
class MyEntityDTO {
Long id;
String stringProperty;
LocalDate dateProperty;
}
class MyDependentEntityDTO {
Long id;
// here we will be transferring the ID of the entity. Or we can pass the DTO.
// it depends on usage scenario.
Long entity;
}
对于此更新,您将创建服务层,该服务层将使用此DTO。或者我称这些“用户模型”对象,即对我的服务/应用/微服务/您命名的客户端开放的数据模型。
@Service
class ManagementService {
MyEntityRepository entityRepository;
MyDependentEntityRepository dependentEntityRepository;
MapStructMapper mapper;
@Transactional
public MyEntityDTO saveOrUpdate(@Valid MyEntityDTO dto) {
MyEntity managedEntity = Optional.ofNullable(dto.id).map(entityRepository::findOne).orElse(new MyEntity());
// now we need to copy values from dto to managed entity. We can write set of setters by own hands, or use the [mapstruct][1]
mapper.map(dto, managedEntity);
managedEntity = entityRepository.saveAndFlush(managedEntity);
return mapper.map(managedEntity);
}
@Transactional
MyDependentEntityDTO saveOrUpdate(MyDependentEntityDTO dto) {
// first of all we assume, that if there is no passed id, the entity should be created
// and if it is present the entity, already exists.
MyDependentEntity managedEntity =
Optional.ofNullable(dto.id).map(dependentEntityRepository::findOne)
.orElse(new MyDependentEntity());
// it is up to you which logic to apply if ID exist, but no entity found.
// Current implementation will throw nullpointer exception in this case. Which for me is quite fine.
// now we need to copy values from dto to managed entity. We can write set of setters by own hands, or use the [mapstruct][1]
mapper.map(dto, managedEntity);
managedEntity = dependentEntityRepository.saveAndFlush(managedEntity);
return mapper.map(managedEntity);
}
}
现在到映射器。您可以自己创建它。但这将需要大量的monkeycoding,并且当您更新实体和dto却忘记更新映射器时,可能会成为问题的解决之道。 Mapstruct来到这里,并提供了极大的支持。它是注释处理器,它允许生成样板代码。您只需写:
// componentModel="spring" will make the generated MapStructMapperImpl class
// into a spring bean, available for injection in your services.
@Mapper(componentModel="spring")
abstract class MapStructMapper {
@Autowired
MyEntityRepository entityRepository;
abstract MyEntityDTO map(MyEntity entity);
// MappingTarget annotation marks the destination object.
abstract void map(MyEntityDTO dto, @MappingTarget MyEntity entity);
abstract MyDependentEntityDTO map(MyDependentEntity entity);
abstract void map(MyDependentEntityDTO dto, @MappingTarget MyDependentEntity entity);
// This method will be picked up by mapstruct automatically to
// map your Long property to MyEntity, during mapping of MyDependentEntity.
// NOTE: here you have to chose how to deal with NULL values.
// Mapstruct does not imply any rule: you write custom mapping on your own.
MyEntity mapMyEntityFromId(Long id) {
return Optional.ofNullable(id).map(entityRepository::findOne).orElse(null);
}
}
使用上述方法,您可以轻松地创建Web层,并分离存储实现和Web逻辑。
例如:如果将SQL用JPA替换为MongoDB。您完全不需要重写Web层。只是为了重新构建您的存储库,以创建那些MongoDB存储库。服务层,在这种情况下,不应重新创建地图服务。...NICE :)