Spring Boot and relational databases - part 4: Inserting data
In the previous part of this series, we added an entity for address data to the database. So far the application provides only support for reading data. In this part, we make changes to make it possible to add contacts.
Changing the code
At first, let’s add a new mothod to the `ContactController
@PostMapping("")
public void createContact(@RequestBody final Contact contact){
contactService.createContact(contact);
}
This method adds a new REST endpoint for POST requests.
In the class ContactService
, the following method is added:
public void createContact(final Contact contact){
contactRepository.save(contact);
}
In the createContact
method, the save
method of contactReposity
is called. This save method is nothing we need to implement ourselves because it is provided by the JpaRepository
.
A test run
When the application is started, the new endpoint can be called for example with the following curl command:
curl -H "Content-Type: application/json" -X POST -d '{"firstName": "John", "lastName": "Doe"}' http://localhost:8080/contact
However, the request fails with HTTP status code 500 and the following error message:
"Internal Server Error","message":"could not execute statement; SQL [n/a]; constraint [\"PRIMARY KEY ON PUBLIC.CONTACT(ID)\"; SQL statement:\ninsert into contact (address_id, email, firstname, lastname, phonenumber, id) values (?, ?, ?, ?, ?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException:
Fixing the error
According to the message there is some constraint getting violated. More precisely, a value for the primary key is missing.
The primary key in this case is an id. Usually this is something that the database generates. To achieve that, we need to change the Contact
class:
@Entity
public class Contact implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
...
The id
member is now annotated with @GeneratedValue
, which will make the database generate an id automatically.
When the curl command is run again, the request is succesful and the contact is added to the database.
Inserting contact and address at the same time
So now new contacts can be inserted. However, what happens if the client posts contact data including an address? Let´s try with the following curl command:
curl -v -H "Content-Type: application/json" -X POST -d '{"firstName": "John", "lastName": "Doe22", "address":{"street":"Street","postalCode":"77588", "city":"Town"}}' http://localhost:8080/contact
The request fails and results in the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
This happens because we try to save a contact with an associated address, while the address is not present in the database yet. What is needed here is a way to save address and contact in the right order. This can be solved with a minor change in the Contact
class:
@Entity
public class Contact implements Serializable {
...
@OneToOne(cascade = {CascadeType.ALL})
private Address address;
...
}
When CascadeType.ALL
is used, both address and contact are stored.
Further resources
The code example can be found here