分离的实体已传递以保留在Spring MVC中

时间:2018-08-03 15:21:45

标签: hibernate spring-mvc kotlin

我创建了一个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内必须做些什么。

1 个答案:

答案 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
}

现在,您希望您的服务能够更新您的MyEntityMyDependentEntity

为此,您创建了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 :)