Ir ao conteúdo

Posts recomendados

Postado

 

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).

 

Postado
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);
    }

 

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...

LANÇAMENTO!

eletronica2025-popup.jpg


CLIQUE AQUI E BAIXE AGORA MESMO!