I'm working on adding a feature to an already developed spring boot web application. The primary entity that has child entities is a Record. It has a few columns/variables that I want to now be in its own, separate entity (CustomerOrder) and exist in a one-to-one relationship with the Record. To summarize:
Record {
-thing 1
-thing 2
-thing 3
}
is now becoming:
CustomerOrder {
-thing 1
-thing 2
-thing 3
}
Record { CustomerOrder }
I'm having some issues with what I've produced. Here is the CustomerOrder model's relevant relationship data:
@Entity
@Table(name="customer_orders")
public class CustomerOrder {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
@OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy="customerOrder", fetch = FetchType.EAGER)
private Record record;
}
And then here is the Record model's relevant data:
@Entity
@Table(name="records")
public class Record extends Auditable<String> implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
... other columns
@OneToOne
@JoinColumn(name="customer_order_id", nullable = false, unique = true)
private CustomerOrder customerOrder;
}
My issue exists when I try to POST a record, when a user tries creating one in the ui. Here is the POST method for a record:
@PostMapping
public ResponseEntity<?> saveRecord(@RequestBody Record recordBody, BindingResult result) {
if(!result.hasErrors()) {
if(recordBody.getHardwareItems().isEmpty()) {
record = recordsService.save(recordBody);
} else {
// Save the record first, recordId is required on hardwareItems
// TODO: investigate Spring Hibernate/JPA rules - is there a way to save parent before children to avoid a null recordId
CustomerOrder customerOrder = recordBody.getCustomerOrder();
recordBody.setCustomerOrder(new CustomerOrder());
customerOrder.setRecord(record);
customerOrder = customerOrdersService.save(customerOrder);
record = recordsService.save(recordBody);
}
} else {
return new ResponseEntity<>(result.getAllErrors(), HttpStatus.BAD_REQUEST);
}
// Return the location of the created resource
uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{recordId}").buildAndExpand(record.getId()).toUri();
return new ResponseEntity<>(uri, HttpStatus.CREATED);
}
The error I receive is the following:
2021-02-19 02:35:50.989 WARN 31765 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1364, SQLState: HY000
2021-02-19 02:35:50.989 ERROR 31765 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : Field 'record_id' doesn't have a default value
This makes sense to me at least, since I'm trying to save the CustomerOrder object that depends on a Record object, which has yet to have been persisted. So, how do I go about changing up the order and/or creating and persisting a Record object so that I can then save the CustomerOrder object to it?
Also, I am using mysql and here is the migration script that I already have. Must I add something here for the customer_orders table?
-- Add a sample user
INSERT IGNORE INTO users (first_name, last_name, email, password, enabled, role)
VALUES ('Sample', 'User', '[email protected]', 'sample password', true, 'ROLE_ADMIN');
-- Customer Reference Values
INSERT IGNORE INTO customers (name) VALUES ('value1');
INSERT IGNORE INTO customers (name) VALUES ('value2');
INSERT IGNORE INTO customers (name) VALUES ('value3');
INSERT IGNORE INTO customers (name) VALUES ('value4');
INSERT IGNORE INTO customers (name) VALUES ('value5');
INSERT IGNORE INTO customers (name) VALUES ('value6');
INSERT IGNORE INTO customers (name) VALUES ('value7');
INSERT IGNORE INTO customers (name) VALUES ('value8');
Here is the mysql script for the Records table and CustomerOrders table:
-- -----------------------------------------------------
-- Table `myapp`.`records`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `myapp`.`records` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`customer_order_id` BIGINT NOT NULL,
`record_id` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UK_7m7wsqy68b7omkufvckoqv2hf` (`customer_order_id` ASC) VISIBLE,
INDEX `FKta31a9q1llknlo2n0jw741987` (`customer_id` ASC) VISIBLE,
CONSTRAINT `FK3q3clytyrx7s8edp9ok821j3`
FOREIGN KEY (`customer_order_id`)
REFERENCES `myapp`.`customer_orders` (`id`),
CONSTRAINT `FKta31a9q1llknlo2n0jw741987`
FOREIGN KEY (`customer_id`)
REFERENCES `myapp`.`customers` (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 27
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;
-- -----------------------------------------------------
-- Table `myapp`.`customer_orders`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `myapp`.`customer_orders` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`record_id` BIGINT NOT NULL,
`record_id_test` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UK_ilew9pg8y4qnyhmjg38k1fev2` (`record_id_test` ASC) VISIBLE,
INDEX `FK5rpb3u59bblj7h70wjr5mvb01` (`record_id` ASC) VISIBLE,
CONSTRAINT `FK5rpb3u59bblj7h70wjr5mvb01`
FOREIGN KEY (`record_id`)
REFERENCES `myapp`.`records` (`id`),
CONSTRAINT `FKk7a0g7djyhymr54ehoftkhyfw`
FOREIGN KEY (`record_id_test`)
REFERENCES `myapp`.`records` (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;
If my understanding is correct, you are having a two way mapping in one to one relationship. So,
customer_order
has arecord_id
column andrecord
has acustomer_order_id
column. This is not ideal and hibernate doesn't support this type of mapping automatically. If you think about it, if both columns are not nullable, then you can't save the data b/c you can't save both simultaneously. You have to save one first.If you want to keep this, you have to make one of them non-nullable. Then save one, then the other, then re-save the first one with the id of the other that you now have generated.
Here hibernate is trying to save the
customer_order
first, b/c you have statedcustomer_order_id
is in therecords
table and is not nullable. This is the default. I would recommend keeping this, and remove the otherrecord_id
column incustomer_orders
table.UPDATE: For clarification, here is one solution. Since record_id and record_id_test are not nullable, set their value in the code, then save. It should work.
If I missed something, please provide more information, like the underlying DB, the location of the
record_id
column in your model, etc.