代码之家  ›  专栏  ›  技术社区  ›  Vojtěch

传递到Spring MVC中持久化的分离实体

  •  0
  • Vojtěch  · 技术社区  · 6 年前

    我已经创建了一个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 . 这可行吗?

    编辑 好像是在打电话 合并 没用。实体仍处于分离状态。我想一定是在 ConverterFactory .

    1 回复  |  直到 6 年前
        1
  •  1
  •   Ilya Dyoshin    6 年前

    这里的问题是,您已经在web范围内初始化了实体(实际上不应该知道持久层实现的细节),然后传递给服务层,服务层可能使用不同的线程。。。

    您应该应用长寿的“OpenSessionInView”模式,该模式现在被认为是“不良实践”或“反模式”。此模式将确保您的实体将使用相同的会话实例,从您的web请求的最开始,直到web请求发送所有响应数据。

    或者另一个选择:重新设计应用程序,引入纯DTO层,并创建映射。DTO在WEB层进行处理,并传递到服务层,在服务层将其映射到实体。在这种情况下,我建议使用Mapstruct生成映射程序

    更新:

    你有两个实体。

    @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。或者我称之为“Usermodel”对象,即为我的服务/app/microservice/的客户端打开的数据模型。

    @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存储库。在这种情况下,不应重新创建服务层和映射服务。。。。不错:)