Ir ao conteúdo
  • Cadastre-se

Java Erro de Conversão com ModelMapper ao Atualizar Objeto


Posts recomendados

 

Estou enfrentando um problema de conversão usando o ModelMapper. Estou desenvolvendo uma API e, ao atualizar o objeto, o ModelMapper está definindo um dos campos como NULL. Tentei configurá-lo de várias maneiras, mas nenhuma delas resolveu o problema. Em uma dessas configurações, até consegui salvar no banco de dados, mas houve um problema de regra de negócios.
 

Basicamente, o objeto tem campos como tempo de criação, tempo de atualização e tempo de exclusão. Quando o objeto é criado, ele vem com os tempos de criação e atualização definidos. No entanto, quando o objeto é atualizado, ocorre um erro. O ModelMapper está definindo o tempo de criação como NULL, o que não deveria acontecer. O framework nem deveria definir o tempo de criação ao converter de UpdateDto para DetailDto e de DetailDto para Produto.
 

Já sei a razão do erro, mas não sei como corrigi-lo. A classe DetailDto tem os três campos de tempo mencionados acima, enquanto a classe UpdateDto tem apenas o campo de atualização. Isso está fazendo com que o ModelMapper defina o campo de criação como NULL. A classe Produto tem todos os três campos. As classes Java são apresentadas abaixo. 

 

@SuppressWarnings("unused")
@Configuration
public class ModelMapperConfig {
  
    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setMatchingStrategy(MatchingStrategies.STRICT)
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
                .setPropertyCondition(Conditions.isNotNull())
                .setSkipNullEnabled(true);  // Configuração para não sobrescrever campos não nulos

        // Adicionar um mapeamento personalizado para ProductUpdateDTO e ProductDetailDTO
        modelMapper.createTypeMap(ProductUpdateDTO.class, ProductDetailDTO.class)
                .addMappings(mapper -> mapper.skip(ProductDetailDTO::setCreatedOn));

        modelMapper.createTypeMap(ProductDetailDTO.class, Product.class)
                .addMappings(mapper -> mapper.skip(Product::setCreatedOn));

        return modelMapper;
    }

    @Bean
    public ModelMapperConverter modelMapperConverter() {
        return new ModelMapperConverter();
    }

}
@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService service;

    @Autowired
    private ModelMapperConverter modelMapperConverter;

    @PostMapping(produces = {"application/json"}, consumes = {"application/json"})
    public ResponseEntity<ProductDetailDTO> create(@RequestBody @Valid ProductCreateDTO productCreateDTO) {
        var productDetails = service.save(modelMapperConverter.map(productCreateDTO, ProductDetailDTO.class));
        productDetails.add(linkTo(methodOn(ProductController.class).detail(productDetails.getId())).withSelfRel());
        return ResponseEntity.created(productDetails.getRequiredLink(IanaLinkRelations.SELF).toUri()).body(productDetails);
    }

    @PutMapping(produces = {"application/json"}, consumes = {"application/json"})
    public ResponseEntity<ProductDetailDTO> update(@RequestBody @Valid ProductUpdateDTO productUpdateDTO) {
        var productDetails = service.update(modelMapperConverter.map(productUpdateDTO, ProductDetailDTO.class));
        productDetails.add(linkTo(methodOn(ProductController.class).detail(productDetails.getId())).withSelfRel());
        return ResponseEntity.ok(productDetails);
    }

    @GetMapping(value = "/{id}", produces = {"application/json"})
    public ResponseEntity<ProductDetailDTO> detail(@PathVariable UUID id) {
        var productDetails = service.findById(id);
        productDetails.add(linkTo(methodOn(ProductController.class).detail(id)).withSelfRel());
        return ResponseEntity.ok(productDetails);
    }

    @GetMapping(produces = {"application/json"})
    public ResponseEntity<Page<ProductListDTO>> listAll(@PageableDefault(size = 15, sort = {"created_on"}) Pageable pageable) {
        var products = service.findAll(pageable);
        products.forEach(product -> product.add(linkTo(methodOn(ProductController.class).detail(product.getId())).withSelfRel()));
        return ResponseEntity.ok(products);
    }

    @DeleteMapping
    public ResponseEntity<Void> remove(@RequestBody List<UUID> ids) {
        service.remove(ids);
        return ResponseEntity.noContent().build();
    }

}
@Table(name = "products")
@Entity(name = "Product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "price", nullable = false)
    private BigDecimal price;
    @Column(name = "description")
    private String description;
    @Column(name = "category", nullable = false)
    @Enumerated(EnumType.STRING)
    private Constants.ProductCategory category;
    @Column(name = "image_url")
    private String imageUrl;
    @Column(name = "created_on", nullable = false)
    private LocalDateTime createdOn;
    @Column(name = "updated_on", nullable = false)
    private LocalDateTime updatedOn;
    @Column(name = "deleted_on")
    private LocalDateTime deletedOn;

    public Product() {
    }

// getters and setters 
// equals and hashcode
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProductDetailDTO extends RepresentationModel<ProductDetailDTO> implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    private UUID id;
    private String name;
    private BigDecimal price;
    private String description;
    private Constants.ProductCategory category;
    private String imageUrl;
    private LocalDateTime CreatedOn;
    private LocalDateTime UpdatedOn;
    private LocalDateTime DeletedOn;

    public ProductDetailDTO() {
    }

// getters and setters

// equals and hashcode 
}
public class ProductUpdateDTO extends RepresentationModel<ProductUpdateDTO> implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @NotNull
    private UUID id;
    private String name;
    private BigDecimal price;
    private String description;
    private Constants.ProductCategory category;
    private String imageUrl;
    private LocalDateTime UpdatedOn;

    public ProductUpdateDTO() {
        setUpdatedOn(LocalDateTime.now());
    }

// getters and setters
// equals and hashcode
}
@Service
public class ProductService implements JpaService<Product, ProductDetailDTO, ProductListDTO> {

    private final JpaCore<Product> jpaCore;
    private final ModelMapperConverter modelMapperConverter;

    @Autowired
    public ProductService(@Qualifier("productJpaCore") JpaCore<Product> jpaCore, ModelMapperConverter modelMapperConverter) {
        this.jpaCore = jpaCore;
        this.modelMapperConverter = modelMapperConverter;
    }

    @Override
    public ProductDetailDTO save(ProductDetailDTO productDetailDTO) {
        return modelMapperConverter.map(jpaCore.save(modelMapperConverter.map(productDetailDTO, Product.class)), ProductDetailDTO.class);
    }

    @Override
    public ProductDetailDTO update(ProductDetailDTO productDetailDTO) {
        Product product = modelMapperConverter.map(productDetailDTO, Product.class);
        if (jpaCore.findById(productDetailDTO.getId()) == null) {
            throw new EntityNotFoundException("O produto que você deseja editar não existe!");
        }
        return modelMapperConverter.map(jpaCore.update(product), ProductDetailDTO.class);
    }

    @Override
    public ProductDetailDTO findById(UUID id) {
        return modelMapperConverter.map(jpaCore.findById(id), ProductDetailDTO.class);
    }

    @Override
    public Page<ProductListDTO> findAll(Pageable pageable) {
        return jpaCore.findAllNotDeleted(pageable).map(product -> modelMapperConverter.map(product, ProductListDTO.class));
    }


    @Override
    public void remove(List<UUID> ids) {
        jpaCore.remove(ids);
    }

}
public class ModelMapperConverter {

    @Autowired
    private ModelMapper modelMapper;

    public <S, T> T map(S source, Class<T> targetClass) {
        return modelMapper.map(source, targetClass);
    }

}
Hibernate: select p1_0.id,p1_0.created_on,p1_0.deleted_on,p1_0.updated_on,p1_0.category,p1_0.description,p1_0.image_url,p1_0.name,p1_0.price from products p1_0 where p1_0.id=?
Hibernate: select p1_0.id,p1_0.created_on,p1_0.deleted_on,p1_0.updated_on,p1_0.category,p1_0.description,p1_0.image_url,p1_0.name,p1_0.price from products p1_0 where p1_0.id=?
Hibernate: update products set created_on=?, deleted_on=?, updated_on=?, category=?, description=?, image_url=?, name=?, price=? where id=?
2024-02-07T16:17:41.672-03:00  WARN 21784 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23502
2024-02-07T16:17:41.673-03:00 ERROR 21784 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERRO: null value in column "created_on" of relation "products" violates not-null constraint
  Detalhe: Registro que falhou contém (2d771dd4-9baa-46bd-844f-0e22285ae555, Macarrão francês, 8.00, Macarrão francês 500 gramas., CATEGORY1, wwww.macarrao_frances.com.br, null, 2024-02-07, null).
2024-02-07T16:17:41.676-03:00  INFO 21784 --- [nio-8080-exec-5] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2024-02-07T16:17:41.685-03:00 ERROR 21784 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [created_on" of relation "products]] with root cause

org.postgresql.util.PSQLException: ERRO: null value in column "created_on" of relation "products" violates not-null constraint
  Detalhe: Registro que falhou contém (2d771dd4-9baa-46bd-844f-0e22285ae555, Macarrão francês, 8.00, Macarrão francês 500 gramas., CATEGORY1, wwww.macarrao_frances.com.br, null, 2024-02-07, null).

 

Link para o comentário
Compartilhar em outros sites

12 horas atrás, Luis Gabriel Goes disse:

 

Estou enfrentando um problema de conversão usando o ModelMapper. Estou desenvolvendo uma API e, ao atualizar o objeto, o ModelMapper está definindo um dos campos como NULL. Tentei configurá-lo de várias maneiras, mas nenhuma delas resolveu o problema. Em uma dessas configurações, até consegui salvar no banco de dados, mas houve um problema de regra de negócios.
 

Basicamente, o objeto tem campos como tempo de criação, tempo de atualização e tempo de exclusão. Quando o objeto é criado, ele vem com os tempos de criação e atualização definidos. No entanto, quando o objeto é atualizado, ocorre um erro. O ModelMapper está definindo o tempo de criação como NULL, o que não deveria acontecer. O framework nem deveria definir o tempo de criação ao converter de UpdateDto para DetailDto e de DetailDto para Produto.
 

Já sei a razão do erro, mas não sei como corrigi-lo. A classe DetailDto tem os três campos de tempo mencionados acima, enquanto a classe UpdateDto tem apenas o campo de atualização. Isso está fazendo com que o ModelMapper defina o campo de criação como NULL. A classe Produto tem todos os três campos. As classes Java são apresentadas abaixo. 

 

@SuppressWarnings("unused")
@Configuration
public class ModelMapperConfig {
  
    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setMatchingStrategy(MatchingStrategies.STRICT)
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
                .setPropertyCondition(Conditions.isNotNull())
                .setSkipNullEnabled(true);  // Configuração para não sobrescrever campos não nulos

        // Adicionar um mapeamento personalizado para ProductUpdateDTO e ProductDetailDTO
        modelMapper.createTypeMap(ProductUpdateDTO.class, ProductDetailDTO.class)
                .addMappings(mapper -> mapper.skip(ProductDetailDTO::setCreatedOn));

        modelMapper.createTypeMap(ProductDetailDTO.class, Product.class)
                .addMappings(mapper -> mapper.skip(Product::setCreatedOn));

        return modelMapper;
    }

    @Bean
    public ModelMapperConverter modelMapperConverter() {
        return new ModelMapperConverter();
    }

}
@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService service;

    @Autowired
    private ModelMapperConverter modelMapperConverter;

    @PostMapping(produces = {"application/json"}, consumes = {"application/json"})
    public ResponseEntity<ProductDetailDTO> create(@RequestBody @Valid ProductCreateDTO productCreateDTO) {
        var productDetails = service.save(modelMapperConverter.map(productCreateDTO, ProductDetailDTO.class));
        productDetails.add(linkTo(methodOn(ProductController.class).detail(productDetails.getId())).withSelfRel());
        return ResponseEntity.created(productDetails.getRequiredLink(IanaLinkRelations.SELF).toUri()).body(productDetails);
    }

    @PutMapping(produces = {"application/json"}, consumes = {"application/json"})
    public ResponseEntity<ProductDetailDTO> update(@RequestBody @Valid ProductUpdateDTO productUpdateDTO) {
        var productDetails = service.update(modelMapperConverter.map(productUpdateDTO, ProductDetailDTO.class));
        productDetails.add(linkTo(methodOn(ProductController.class).detail(productDetails.getId())).withSelfRel());
        return ResponseEntity.ok(productDetails);
    }

    @GetMapping(value = "/{id}", produces = {"application/json"})
    public ResponseEntity<ProductDetailDTO> detail(@PathVariable UUID id) {
        var productDetails = service.findById(id);
        productDetails.add(linkTo(methodOn(ProductController.class).detail(id)).withSelfRel());
        return ResponseEntity.ok(productDetails);
    }

    @GetMapping(produces = {"application/json"})
    public ResponseEntity<Page<ProductListDTO>> listAll(@PageableDefault(size = 15, sort = {"created_on"}) Pageable pageable) {
        var products = service.findAll(pageable);
        products.forEach(product -> product.add(linkTo(methodOn(ProductController.class).detail(product.getId())).withSelfRel()));
        return ResponseEntity.ok(products);
    }

    @DeleteMapping
    public ResponseEntity<Void> remove(@RequestBody List<UUID> ids) {
        service.remove(ids);
        return ResponseEntity.noContent().build();
    }

}
@Table(name = "products")
@Entity(name = "Product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "price", nullable = false)
    private BigDecimal price;
    @Column(name = "description")
    private String description;
    @Column(name = "category", nullable = false)
    @Enumerated(EnumType.STRING)
    private Constants.ProductCategory category;
    @Column(name = "image_url")
    private String imageUrl;
    @Column(name = "created_on", nullable = false)
    private LocalDateTime createdOn;
    @Column(name = "updated_on", nullable = false)
    private LocalDateTime updatedOn;
    @Column(name = "deleted_on")
    private LocalDateTime deletedOn;

    public Product() {
    }

// getters and setters 
// equals and hashcode
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProductDetailDTO extends RepresentationModel<ProductDetailDTO> implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    private UUID id;
    private String name;
    private BigDecimal price;
    private String description;
    private Constants.ProductCategory category;
    private String imageUrl;
    private LocalDateTime CreatedOn;
    private LocalDateTime UpdatedOn;
    private LocalDateTime DeletedOn;

    public ProductDetailDTO() {
    }

// getters and setters

// equals and hashcode 
}
public class ProductUpdateDTO extends RepresentationModel<ProductUpdateDTO> implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    @NotNull
    private UUID id;
    private String name;
    private BigDecimal price;
    private String description;
    private Constants.ProductCategory category;
    private String imageUrl;
    private LocalDateTime UpdatedOn;

    public ProductUpdateDTO() {
        setUpdatedOn(LocalDateTime.now());
    }

// getters and setters
// equals and hashcode
}
@Service
public class ProductService implements JpaService<Product, ProductDetailDTO, ProductListDTO> {

    private final JpaCore<Product> jpaCore;
    private final ModelMapperConverter modelMapperConverter;

    @Autowired
    public ProductService(@Qualifier("productJpaCore") JpaCore<Product> jpaCore, ModelMapperConverter modelMapperConverter) {
        this.jpaCore = jpaCore;
        this.modelMapperConverter = modelMapperConverter;
    }

    @Override
    public ProductDetailDTO save(ProductDetailDTO productDetailDTO) {
        return modelMapperConverter.map(jpaCore.save(modelMapperConverter.map(productDetailDTO, Product.class)), ProductDetailDTO.class);
    }

    @Override
    public ProductDetailDTO update(ProductDetailDTO productDetailDTO) {
        Product product = modelMapperConverter.map(productDetailDTO, Product.class);
        if (jpaCore.findById(productDetailDTO.getId()) == null) {
            throw new EntityNotFoundException("O produto que você deseja editar não existe!");
        }
        return modelMapperConverter.map(jpaCore.update(product), ProductDetailDTO.class);
    }

    @Override
    public ProductDetailDTO findById(UUID id) {
        return modelMapperConverter.map(jpaCore.findById(id), ProductDetailDTO.class);
    }

    @Override
    public Page<ProductListDTO> findAll(Pageable pageable) {
        return jpaCore.findAllNotDeleted(pageable).map(product -> modelMapperConverter.map(product, ProductListDTO.class));
    }


    @Override
    public void remove(List<UUID> ids) {
        jpaCore.remove(ids);
    }

}
public class ModelMapperConverter {

    @Autowired
    private ModelMapper modelMapper;

    public <S, T> T map(S source, Class<T> targetClass) {
        return modelMapper.map(source, targetClass);
    }

}
Hibernate: select p1_0.id,p1_0.created_on,p1_0.deleted_on,p1_0.updated_on,p1_0.category,p1_0.description,p1_0.image_url,p1_0.name,p1_0.price from products p1_0 where p1_0.id=?
Hibernate: select p1_0.id,p1_0.created_on,p1_0.deleted_on,p1_0.updated_on,p1_0.category,p1_0.description,p1_0.image_url,p1_0.name,p1_0.price from products p1_0 where p1_0.id=?
Hibernate: update products set created_on=?, deleted_on=?, updated_on=?, category=?, description=?, image_url=?, name=?, price=? where id=?
2024-02-07T16:17:41.672-03:00  WARN 21784 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 23502
2024-02-07T16:17:41.673-03:00 ERROR 21784 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERRO: null value in column "created_on" of relation "products" violates not-null constraint
  Detalhe: Registro que falhou contém (2d771dd4-9baa-46bd-844f-0e22285ae555, Macarrão francês, 8.00, Macarrão francês 500 gramas., CATEGORY1, wwww.macarrao_frances.com.br, null, 2024-02-07, null).
2024-02-07T16:17:41.676-03:00  INFO 21784 --- [nio-8080-exec-5] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2024-02-07T16:17:41.685-03:00 ERROR 21784 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [created_on" of relation "products]] with root cause

org.postgresql.util.PSQLException: ERRO: null value in column "created_on" of relation "products" violates not-null constraint
  Detalhe: Registro que falhou contém (2d771dd4-9baa-46bd-844f-0e22285ae555, Macarrão francês, 8.00, Macarrão francês 500 gramas., CATEGORY1, wwww.macarrao_frances.com.br, null, 2024-02-07, null).

 

 

Consegui resolver o bug, apenas peguei a data de criação que já estava no banco e dei um set. Caso alguém, tenha uma solução melhor, por favor, responder abaixo.

 

    @Override
    public ProductDetailDTO update(ProductDetailDTO productDetailDTO) {
        // Busca o produto existente apenas uma vez
        Product existingProduct = jpaCore.findById(productDetailDTO.getId());

        if (existingProduct == null) {
            throw new EntityNotFoundException("O produto que você deseja editar não existe!");
        }

        // Mapeia o DTO para a entidade do produto
        Product product = modelMapperConverter.map(productDetailDTO, Product.class);

        // Mantém o valor de createdOn do produto existente
        product.setCreatedOn(existingProduct.getCreatedOn());

        // Atualiza o produto e mapeia o resultado para o DTO
        return modelMapperConverter.map(jpaCore.update(product), ProductDetailDTO.class);
    }

 

Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...

 

GRÁTIS: ebook Redes Wi-Fi – 2ª Edição

EBOOK GRÁTIS!

CLIQUE AQUI E BAIXE AGORA MESMO!