Kaskadowy zapis dzieci z kluczem rodzica

Odpowiedz Nowy wątek
2019-05-09 09:07
0

Na wstępie uprzedzam że nie jestem dobrze obeznany z Spring i JPA/Hibernate dopiero się wdrażam, więc mój kod może być "brzydki architektonicznie" nie mam opanowanego wzorca budowy aplikacji dla Spring :) . W kontrolerze otrzymuję zmapowany z JSONa obiekt. Który chcę zapisać do bazy. Fragment który chcę zapisać wygląda w ten sposób:

REATE TABLE IF NOT EXISTS `journal_entry` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `vehicle_journal` bigint(20) NOT NULL,
  `cost` int(10) unsigned DEFAULT NULL,
  `type` tinyint(3) unsigned DEFAULT NULL,
  `name` varchar(25) DEFAULT NULL,
  `fuel_amount_added` float DEFAULT NULL,
  `unit_price` int(11) DEFAULT NULL,
  `fuel_type` tinyint(4) DEFAULT NULL,
  `full_filled` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_journal_entry_type` (`type`),
  KEY `fk_ft` (`fuel_type`),
  CONSTRAINT `fk_ft` FOREIGN KEY (`fuel_type`) REFERENCES `fuel_type` (`id`),
  CONSTRAINT `fk_vehicle_journal` FOREIGN KEY (`vehicle_journal_id`) REFERENCES `vehicle_journal` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `vehicle_journal` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `vehicle` bigint(20) NOT NULL,
  `created` timestamp NOT NULL DEFAULT current_timestamp(),
  `updated` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
  `milage` int(10) unsigned DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  `entry_time` timestamp NULL DEFAULT NULL,
  `place` varchar(25) DEFAULT NULL,
  `total_cost` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_vehicle` (`vehicle`),
  CONSTRAINT `fk_vehicle` FOREIGN KEY (`vehicle`) REFERENCES `vehicle` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

A w kodzie wygląda to tak:

@Table
@Entity
public class JournalEntry {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Integer cost;

    @ManyToOne(fetch=FetchType.EAGER, optional=false)
    @JoinColumn(name="vehicle_journal", referencedColumnName="id", nullable=false)
    private VehicleJournal vehicleJournal;

    @Size(max=25)
    private String name;

    private Float fuelAmountAdded;

    private Integer unitPrice;

    @ManyToOne
    @JoinColumn(name="fuel_type", referencedColumnName="id")
    private FuelType fuelType;

    @Column(name="full_filled")
    private Boolean fullfiled;
//...

@Table
@Entity
public class VehicleJournal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name="vehicle", referencedColumnName="id")
    private Vehicle vehicle;

    @DateTimeFormat
    @CreationTimestamp
    private LocalDateTime created;

    @DateTimeFormat
    @UpdateTimestamp
    private LocalDateTime updated;

    private Integer milage;

    @Size(max=255)
    private String description;

    @DateTimeFormat
    private LocalDateTime entryTime;

    @Size(max=25)
    private String place;

    private Integer totalCost;

    @OneToMany(mappedBy="vehicleJournal", cascade= { CascadeType.ALL, CascadeType.PERSIST}, fetch=FetchType.LAZY)
    private Set<JournalEntry> journalEntries = new HashSet<>(0);

//Fragment kontrolera

    @RequestMapping(path = "/create", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize("isAuthenticated()")
    public ResponseEntity<?> create(Principal principal, @RequestBody VehicleJournal vehicleJournal) 
            throws JsonProcessingException{
        logger.debug("Adding new journal entry:" + jsonCustomMapper.writeValueAsString(vehicleJournal));

        //TODO:Add validation
        VehicleJournal savedVehicleJournal = vehicleJournalRepo.save(vehicleJournal); //interface VehicleJournalRepo extends CrudRepository<VehicleJournal, Long>

        if(savedVehicleJournal==null) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }else {
            return new ResponseEntity<VehicleJournal>(savedVehicleJournal, HttpStatus.ACCEPTED);
        }
    }

Pobieranie obiektów z bazy nie stanowi problemu, problem występuje tylko podczas zapisu. JPA/Hibernate wyrzuca błąd podczas zapisu obiektu dziecka journalEntry ponieważ pole rodzica vehicleJournal jest wymagane a JPA/Hibernate ma tam wartość null i baza odrzuca zapis. JPA/Hibernate powinien otworzyć transakcję zapisać rodzica następnie pobrać jego id i umieścić u dzieci. Pytanie czy mam sknocone adnotacje i powiązania czy nie mam wyjścia i muszę ręcznie utworzyć transakcję i zapisywać w odpowiedniej kolejności?

edytowany 1x, ostatnio: GarryMoveOut, 2019-05-09 10:15

Pozostało 580 znaków

2019-05-09 11:04
0

Pokaż logi błędu.

Pozostało 580 znaków

2019-05-09 11:16

Log:

2019-05-09 11:08:08.158 DEBUG 5330 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : POST "/autobook/api/v1/journal/create", parameters={}
2019-05-09 11:08:08.190 DEBUG 5330 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<?> ....autobook.api.v1.controller.JournalController.create(java.security.Principal,e....autobook.db.model.VehicleJournal) throws com.fasterxml.jackson.core.JsonProcessingException
2019-05-09 11:08:08.648 DEBUG 5330 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [[email protected]]
Hibernate: insert into vehicle_journal (created, description, entry_time, milage, place, total_cost, updated, vehicle) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into journal_entry (cost, fuel_amount_added, fuel_type, full_filled, name, unit_price, vehicle_journal) values (?, ?, ?, ?, ?, ?, ?)
2019-05-09 11:08:09.145 WARN 5330 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
2019-05-09 11:08:09.147 ERROR 5330 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : (conn=112) Column 'vehicle_journal' cannot be null
2019-05-09 11:08:09.196 DEBUG 5330 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2019-05-09 11:08:09.241 ERROR 5330 --- [nio-8080-exec-3] o.s.b.w.servlet.support.ErrorPageFilter : Forwarding to error page from request [/api/v1/journal/create] due to exception [could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement]

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:296) ~[spring-orm-5.1.5.RELEASE.jar:5.1.5.RELEASE]
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
...
... 113 common frames omitted
Caused by: java.sql.SQLIntegrityConstraintViolationException: (conn=112) Column 'vehicle_journal' cannot be null
at org.mariadb.jdbc.internal.util.exceptions.ExceptionMapper.get(ExceptionMapper.java:229) ~[mariadb-java-client-2.3.0.jar:na]
...
... 179 common frames omitted
Caused by: java.sql.SQLException: Column 'vehicle_journal' cannot be null
Query is: insert into journal_entry (cost, fuel_amount_added, fuel_type, full_filled, name, unit_price, vehicle_journal) values (?, ?, ?, ?, ?, ?, ?), parameters [7000,40.35,5,0,'Orlen',218,<null>]
java thread: http-nio-8080-exec-3
...
... 184 common frames omitted

Zapomniałem dodać że przy pobieraniu rodzica VehicleJournal miałem błąd Infinite recursion dlatego do gettera w JournalEntry dodałem @JsonIgnore. Nie wiem czy to może mieć jakiś wpływ.

        @JsonIgnore
    public VehicleJournal getVehicleJournal() {
        return vehicleJournal;
    }

    public void setVehicleJournal(VehicleJournal vehicleJournal) {
        this.vehicleJournal = vehicleJournal;
    }

A więc znalazłem przyczynę, jednak to stanowiło problem. Usunąłem adnotację @JsonIgnore a dodałem:

public class JournalEntry {

//...

    **@JsonBackReference**
    @ManyToOne(fetch=FetchType.EAGER, optional=false, cascade= {CascadeType.ALL, CascadeType.PERSIST})
    @JoinColumn(name="vehicle_journal", referencedColumnName="id", nullable=false)
    private VehicleJournal vehicleJournal;

//...

public class VehicleJournal {

//...

    **@JsonManagedReference**
    @OneToMany(mappedBy="vehicleJournal", cascade= { CascadeType.ALL, CascadeType.PERSIST}, fetch=FetchType.LAZY)
    private Set<JournalEntry> journalEntries = new HashSet<>(0);

Wyniknął z tego inny problem, a mianowicie journalEntry zapisuje się bez klucza do fuelType.

edytowany 4x, ostatnio: GarryMoveOut, 2019-05-09 12:35

Pozostało 580 znaków

2019-05-09 12:50
1

Był problem bo @JsonIgnore powoduje że pole w ogóle nie jest mapowane z jsona na obiekt. Spróbuj dodać @Transactional do metody kontrolera.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0