Solucionado (ver solução)
Solucionado
(ver solução)
25
respostas

[Dúvida] API Criada dando problema com POSTMAN

Professor, ou se alguem conseguir me ajudar. Criei uma aplicação utilizando o framework do Jhipster. Eu consegui fazer o método de salvar informações na interface front-end que ele gera. Contudo ao executar via postman a inserção de dados me apresenta o seguinte erro: "type": "https://www.jhipster.tech/problem/problem-with-message", "title": "Internal Server Error", "status": 500, "detail": "could not execute batch; SQL [insert into sira_report (advance_speed, cutting_time, datetime_begin, datetime_end, final_state, grinding_wheel_speed, id_process, profile_roll_end_advance_distance, profile_roll_end_diameter, profile_roll_start_advance_distance, profile_roll_start_diameter, step_numbers, tag_roll, tag_rollgrinder, temperature_grinding_wheel_step_numbers, temperature_grinding_wheel_temperature, temperature_roll_end_advance_distance, temperature_roll_end_temperature, temperature_roll_start_advance_distance, temperature_roll_start_temperature, total_time, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute batch", "path": "/api/reports", "message": "error.http.500"

25 respostas

Segue código da minha classe Controller

@RestController
@RequestMapping("/api")
public class ReportController {

    private final Logger log = LoggerFactory.getLogger(reportController.class);

    private static final String ENTITY_NAME = "Report";

    @Value("${jhipster.clientApp.name}")
    private String applicationName;

    private final ReportService reportService;

    private final reportRepository reportRepository;

    public ReportController(ReportService ReportService, ReportRepository ReportRepository) {
        this.reportService = reportService;
        this.reportRepository = reportRepository;
    }

    @GetMapping("/reports")
    public ResponseEntity<List<ReportDTO>> getAllReports(Pageable pageable) {
        log.debug("REST request to get a page of Reports");
        Page<ReportDTO> page = reportService.getAllReports(pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
        return ResponseEntity.ok().headers(headers).body(page.getContent());
    }

    @PostMapping("/reports")
    public ResponseEntity<ReportDTO> saveReports(@Valid @RequestBody ReportDTO reportDTO) {
        reportService.save(reportDTO);

        return ResponseEntity.status(HttpStatus.CREATED).body(reportDTO);
    }
}

O acesso ao banco de dados, estou utilizando o Liquibase que foi gerado pelo Jhipster quando eu criei minha Entidade. Se eu executo via o Front-End gerado pelo Jhipster ele cria normalmente no banco de dados, porém via postman não.

Oi Tiago,

Pelo log o erro foi: nested exception is org.hibernate.exception.ConstraintViolationException:.

Então é bem provavél que no Postman você esteja esquecendo de enviar algum campo que é obrigatório no banco de dados, sendo que no frontend ele está sendo enviado e por isso não causa o erro.

Oi Professor bom dia,

A principio ele quer que eu coloque o id, porem este atributo foi criado na classe Entity como GenerationType.SEQUENCE. O Jhispter criou o DTO também com esse atributo Long Id. Porém ele não me deixa colocar numero de ID. Pois, no front-end ele é gerado automaticamente, mas no postman não.

Como frontend gera de maneira automática, se você quiser disparar requisições no postman vai precisar passar esse id também. Aí no caso vai precisar entender como é essa geração automática para enviar algum id via postman.

Pelo changelog do Liquibase ele cria assim <changeSet id="00000000000000" author="jhipster"> <createSequence sequenceName="sequence_generator" startValue="1050" incrementBy="50"/> </changeSet>

Uma sugestão é disparar a requisição pelo browser, com o Devtools aberto na aba Network, e clicar na requisção post para ver como o frontend está enviando os parâmetros. Assim você vai saber exatamente todos os parâmetros que precisam ser enviados no postman.

Exemplo:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Oi Professor, fiz o que você me recomendou via browser.

Na verdade o ID_process eu tava passando um que ja havia criado. Ai ele dava esse erro 500. Quando eu crio com novo ID_Process ele da certo no postman.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Porém no corpo ele ta pegando o ID, acredito que deve ser por causa da minha Classe DTO, que tem esse ID, porém no projeto só deveria eu ter que passar o ID_process, sendo o ID gerado Sequencialmente Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Se eu enviar sem o ID no corpo da requisição, aparece Null conforme a imagem, porém no DB e no front-end o ID é gerado automaticamente, pois este ID trata-se do ID da Entidade, que tem a lógica de GenerationType.SEQUENCE

Show de bola!

Então agora o único problema é o id que não está voltando no dto de resposta, certo?

Altera o método de salvar no controller para ver se resolve:

@PostMapping("/reports")
@Transactional
public ResponseEntity<ReportDTO> saveReports(@Valid @RequestBody ReportDTO reportDTO) {
    reportDTO = reportService.save(reportDTO);

    return ResponseEntity.status(HttpStatus.CREATED).body(reportDTO);
}

E altere o método save da service para devolver o objeto.

Oi Professor, deu certo viu? Agradeço imensamente sua ajuda! Gostaria de tirar só uma dúvida com você. Para esse método Post eu preciso que o método consiga receber um ou mais reports. Ou seja, pode receber uma lista de DTOs. Para isso o caminho seria assim?:

@PostMapping("/reports")
@Transactional
public ResponseEntity<ReportDTO> saveReports(@Valid @RequestBody List<ReportDTO>  listReportDTO) {
    //codigo necessário

    return ...;
}

Sendo necessário na classe DTO, ter um atributo passando uma lista com ArrayList? E ai desenvolver o método para pegar um ou mais DTOs passados?

Meu problema está em gerar um JSON também que possui mais de uma lista de Requisição, Tenho que usar {[atributos],[atributosOutros]} no JSON?

Eu cheguei a criar o código classe Controller:

@PostMapping("/report")
    public ResponseEntity<Object> saveReport(@RequestBody @Valid List<ReportDTO> reportDTOList){
        reportDTOList.forEach(reportDTO -> {
            var report = new Report();
            BeanUtils.copyProperties(teportDTO, new Report());
            reportDTO = reportMapper.toDto(report);
            reportService.save(reportDTO);
        });


        return ResponseEntity.status(HttpStatus.CREATED).body(reportDTOList);
    }

Codigo da classe service para salvar:

public Report save(ReportDTO reportDTO) {
        log.debug("Request to save Report : {}", reportDTO);
        Report report = reportMapper.toEntity(reportDTO);
        return this.reportRepository.save(report);
    } 

Quando executo o código tomo o 400

Bad Request: JSON parse error: Cannot deserialize value of type java.util.ArrayList<br.com.service.dto.ReportDTO> from Object value (token JsonToken.START_OBJECT); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<br.com.service.dto.ReportDTO> from Object value (token JsonToken.START_OBJECT)_ at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.ArrayList<br.com.service.dto.ReportDTO> from Object value (token JsonToken.START_OBJECT); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<br.com.service.dto.ReportDTO> from Object value (token JsonToken.START_OBJECT) at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]]

O JsonToken é um Enum criado pelo próprio Jhipster e não me deixa alterar:

public enum JsonToken {
    NOT_AVAILABLE((String)null, -1),
    START_OBJECT("{", 1),
    END_OBJECT("}", 2),
    START_ARRAY("[", 3),
    END_ARRAY("]", 4),
    FIELD_NAME((String)null, 5),
    VALUE_EMBEDDED_OBJECT((String)null, 12),
    VALUE_STRING((String)null, 6),
    VALUE_NUMBER_INT((String)null, 7),
    VALUE_NUMBER_FLOAT((String)null, 8),
    VALUE_TRUE("true", 9),
    VALUE_FALSE("false", 10),
    VALUE_NULL("null", 11);

    final String _serialized;
    final char[] _serializedChars;
    final byte[] _serializedBytes;
    final int _id;
    final boolean _isStructStart;
    final boolean _isStructEnd;
    final boolean _isNumber;
    final boolean _isBoolean;
    final boolean _isScalar;

    private JsonToken(String token, int id) {
        if (token == null) {
            this._serialized = null;
            this._serializedChars = null;
            this._serializedBytes = null;
        } else {
            this._serialized = token;
            this._serializedChars = token.toCharArray();
            int len = this._serializedChars.length;
            this._serializedBytes = new byte[len];

            for(int i = 0; i < len; ++i) {
                this._serializedBytes[i] = (byte)this._serializedChars[i];
            }
        }

        this._id = id;
        this._isBoolean = id == 10 || id == 9;
        this._isNumber = id == 7 || id == 8;
        this._isStructStart = id == 1 || id == 3;
        this._isStructEnd = id == 2 || id == 4;
        this._isScalar = !this._isStructStart && !this._isStructEnd && id != 5 && id != -1;
    }

    public final int id() {
        return this._id;
    }

    public final String asString() {
        return this._serialized;
    }

    public final char[] asCharArray() {
        return this._serializedChars;
    }

    public final byte[] asByteArray() {
        return this._serializedBytes;
    }

    public final boolean isNumeric() {
        return this._isNumber;
    }

    public final boolean isStructStart() {
        return this._isStructStart;
    }

    public final boolean isStructEnd() {
        return this._isStructEnd;
    }

    public final boolean isScalarValue() {
        return this._isScalar;
    }

    public final boolean isBoolean() {
        return this._isBoolean;
    }

Você vai precisar enviar o json assim:

[
{
"id_process" : 11,
"final_state" : "aaa"
},
{
"id_process" : 11,
"final_state" : "bbb"
}
]

E passar as informações em cada objeto.

No controller não sei se funciona receber um List<ReportDto> ou se precisa receber um DTO que dentro dele ter um atributo List.

Entendi obrigado professor. Eu fiz o JSON como você me falou e tomei o seguinte erro:

{
    "type": "https://www.jhipster.tech/problem/problem-with-message",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction",
    "path": "/api/v1/sira-report",
    "message": "error.http.500"
}

No console retornou:

Caused by: javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)
    ... 128 common frames omitted
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [br.com.domain.Report] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='não deve ser nulo', propertyPath=tag_rollgrinder, rootBeanClass=class br.com.report.domain.Report, messageTemplate='{javax.validation.constraints.NotNull.message}'}
ConstraintViolationImpl{interpolatedMessage='não deve ser nulo', propertyPath=temperatureRollStartTemperature br.com.report.domain.Report, messageTemplate='{javax.validation.constraints.NotNull.message}'}
...

Acredito que terei que criar um atributo que recebe uma lista de ReportsDTO.

Coloquei o @Transaction, porém ele também da erro.

Acho que vai precisar sim de um novo DTO encapsulando essa lista:

public ResponseEntity<Object> saveReport(@RequestBody @Valid ReportsDTO dto)
public class ReportsDTO {

    @NotNull @Valid
    private List<ReportDto> reports;

}

E o json no postman você envia assim:

{
    "reports": [
        {
            "id_process" : 11,
            "final_state" : "aaa"
        },
        {
            "id_process" : 11,
            "final_state" : "bbb"
        }
    ]
}

Entendi professor, blz. Contudo, quando mudo ali no controller pra receber o DTO e não mais uma lista, a lógica muda, pois o Foreach não consegue mais usar quando mudo pra dto o foreach não possibilita mais o uso.

@PostMapping("/report")
   public ResponseEntity<Object> saveReport(@RequestBody @Valid ReportsDTO dto){
        dto.forEach(reportDTO -> {
            var report = new Report();
            BeanUtils.copyProperties(teportDTO, new Report());
            reportDTO = reportMapper.toDto(report);
            reportService.save(reportDTO);
        });


        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }

Agora é só questão de adaptar o código:

dto.getReports().forEach(reportDTO -> {
        // resto do codigo
    });
return ResponseEntity.status(HttpStatus.CREATED).body(dto);

Entendi professor. Eu estar com os construtor do Model e do DTO default pode estar contribuindo para este erro? As classes estão só com os métodos getters e setters.

Oi professor, adaptei o código, executei o JSON conforme mencionado e agora consigo ver que estão vindo nulos:

{
    "type": "https://www.jhipster.tech/problem/constraint-violation",
    "title": "Method argument not valid",
    "status": 400,
    "path": "/api/v1/sira-report",
    "message": "error.validation",
    "fieldErrors": [
        {
            "objectName": "report",
            "field": "temperatureRollStartAdvanceDistance",
            "message": "não deve ser nulo"
        },
        {
            "objectName": "report",
            "field": "grinding_wheel_speed",
            "message": "não deve ser nulo"
        },
        {
            "objectName": "report",
            "field": "id_process",
            "message": "não deve ser nulo"
        },
        {
            "objectName": "report",
            "field": "temperatureRollEndTemperature",
            "message": "não deve ser nulo"
        },
        {
            "objectName": "report",
            "field": "datetime_begin",
            "message": "não deve ser nulo"
        },
        {
            "objectName": "report",
            "field": "reports[0].reports",
            "message": "não deve ser nulo"
        }


                ...
    ]
}

Código que adaptei

@PostMapping("/report")
    public ResponseEntity<Object> saveReport(@RequestBody @Valid ReportDTO dto){
        dto.getReports().forEach(reportDTO -> {
            var report = new Report();
            BeanUtils.copyProperties(reportDTO, new Report());
            reportDTO = reportMapper.toDto(report);
            reportService.save(reportDTO);
        });


        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }

Pode estar vindo nulo, devido ao Construtor Default no reportDTO?

Acho que entendi um dos erros. No Beans ali properties eu dou New Report(), de uma variavel que eu criei, porém ela vem vazia. Ai eu estou convertendo algo vazio para Dto. Só que está tudo nulo, pois não tem nenhuma informação vindo.

Eu retirei o new Report() do Beans, mas ainda retorna tudo vazio.

@PostMapping("/report")
    public ResponseEntity<Object> saveReport(@RequestBody @Valid ReportDTO dto){
        dto.getReports().forEach(reportDTO -> {
            var report = new Report();
            BeanUtils.copyProperties(reportDTO, siraReport());
            reportDTO = reportMapper.toDto(report);
            reportService.save(reportDTO);
        });


        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }

Nos DTOs você precisar criar os getters/setters. Construtor pode ter outros com parâmetros, desde que tenha também o construtor default.

Acho que seu código no controller deveria ser assim:

@PostMapping("/report")
public ResponseEntity<Object> saveSiraReport(@RequestBody @Valid ReportsDTO reports) {
    var response = reportService.save(reports);
    return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

E seu método na service:

public List<ReportDto> save(ReportsDTO dto) {
    log.debug("Request to save Report : {}", dto);

    var response = new ArrayList<ReportDto>();

    dto.getReports().forEach(reportDto -> {
        var report = new Report();
        BeanUtils.copyProperties(reportDto, report);
        report = this.reportRepository.save(report);

        var r = new ReportDto();
        BeanUtils.copyProperties(report, r);
        response.add(r);
    });

    return response;
}

Oi Professor agradeço muito por você estar me ajudando, eu refiz os métodos no Controller e Service, porém tenho o mesmo erro, vou te enviar minhas classes Model e DTO.

Model:

/**
 * A Report.
 */
@Entity
@Table(name = "report")
@SuppressWarnings("common-java:DuplicatedBlocks")
public class Report implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    @Column(name = "id")
    private Long id;

    @NotNull
    @Size(max = 15)
    @Column(name = "id_process", length = 15, nullable = false, unique = true)
    private String id_process;

    @NotNull
    @Column(name = "final_state", nullable = false)
    private String final_state;

    @NotNull
    @Column(name = "datetime_begin", nullable = false)
    private Instant datetime_begin;

    @NotNull
    @Column(name = "datetime_end", nullable = false)
    private Instant datetime_end;

    @NotNull
    @Column(name = "step_numbers", nullable = false)
    private Integer step_numbers;

    @NotNull
    @Column(name = "advance_speed", nullable = false)
    private Float advance_speed;

    @NotNull
    @Column(name = "cutting_time", nullable = false)
    private String cutting_time;



    //Construtor Default
    //...getters and setters
    //Métodos Override equals, hasCode e to String

Classe DTO:

/**
 * A DTO for the {@link br.com.domain.Report} entity.
 */
@SuppressWarnings("common-java:DuplicatedBlocks")
public class ReportDTO {

    @NotNull
    @Size(max = 15)
    private String id_process;

    @NotNull
    private String final_state;

    @NotNull
    private String tag_rollgrinder;

    @NotNull
    private String tag_roll;

    @NotNull
    private Instant datetime_begin;

    @NotNull
    private Instant datetime_end;

    @NotNull
    private Integer step_numbers;

    @NotNull
    private Float advance_speed;

    @NotNull
    private String cutting_time;

    //outros atributos

    @NotNull @Valid
    private List<SiraReportDTO> reports;

    public List<SiraReportDTO> getReports() {
        return reports;
    }

    public void setReports(List<SiraReportDTO> reports) {
        this.reports = reports;
    }

    //Construtor do DTO está Default não adicionei nenhum Construtor com parâmetro
    //outros getters e setters
    //Override toString.

O Erro que eu tomo ao executar o postman para o endpoint é o mesmo:

Bad Request: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<java.lang.Object> br.com.web.rest.ReportController.saveReport(br.com.service.dto.SiraReportDTO) with 23 errors: [Field error in object 'reportDTO' on field 'profileRollEndAdvanceDistance': rejected value [null]; ...entre outros atributos com erro.
solução!

Oi Professor consegui resolver o problema: O problema estava naquele conversor toDto. Pois ele estava fazendo validações, só que estava resultando tudo Null. Esse conversor foi criado pelo próprio Jhipster.

Ai eu voltei um pouco e como eu já estava salvando um objeto DTO corretamente, para salvar um ou mais objeto DTO, pra retornar uma lista eu iterei com o foreach mesmo:

Controller:

@PostMapping("/report")
    public ResponseEntity<Object> saveReport(@RequestBody @Valid List<ReportDTO> listDto){
        listDto.getReports().forEach(reportDTO -> {

            reportService.save(reportDTO);
        });


        return ResponseEntity.status(HttpStatus.CREATED).body(listDto);
    }