In my spring boot project I have a Document class that has a bi-directional OneToOne relationship to an Invoice class, which share the same ID.
Document
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Invoice invoice;
Invoice
public class Invoice {
@Id
@Column(name = "document_id")
private Long documentId;
@OneToOne(mappedBy = "invoice")
@MapsId
@JoinColumn(name = "document_id")
private Document document;
The document entity is created prior to the invoice entity. Later on I create an invoice entity via a MapStruct DTO-Mapping. I then save the entity to "generate" the document_id value.
After saving the invoice entity, I assign the invoice entity to the document entity and save the document entity via the repository. However, the relation to the invoice entity is not persisted in the database.
The invoice entity persists as should be with the corresponding document_id as primary key.
Service code
Invoice newInvoice = invoiceMapper.fromDto(dto);
newInvoice = invoiceRepository.save(newInvoice);
document.setInvoice(newInvoice);
documentRepository.save(document);
InvoiceMapper
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, uses = {DocumentService.class})
public interface InvoiceMapper {
@BeanMapping(ignoreByDefault=true)
@Mapping(source = "document", target = "document")
Invoice fromDto(Dto dto);
Previously, I tried mapping the document_id in the MapStruct mapper aswell, but then I received an "attempted to assign id from null one-to-one property" exception on save (even though document and document_id were correctly defined).
When debuggin the code, it correctly shows that the invoice entity was set on the document entity, but unfortunately it is not persisted in the database.
Curiously, I am almost certain that at some point in the coding process it did work as intended. But I can not figure out where the issue is. Help would be much appreciated.
CodePudding user response:
This is not setup correctly as you have not specified anything to set the Invoice's document_id column - you'd have to set this yourself from the documentId long. You must pick one side to have a foreign key to the other - presumably the Invoice has the foreign key to Document and is going to use that as its primary key as well. If that is the case, this needs to be:
public class Invoice {
@Id
@Column(name = "document_id")
private Long documentId;
@OneToOne //this is the owning side of the relationship!
@MapsId
@JoinColumn(name = "document_id")
private Document document;
..
}
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "document")
private Invoice invoice;
..
}
MappedBy indicates that the other side controls setting the foreign key values. Note though that your Invoice will not have a documentId value set, and that you do not need to manually set it. The MapsId annotation tells JPA to pull the value from the Document ID when it is generated and to use that for the document_id column, and will set the documentId Long at the same time.
This then will allow you to create a new Document and Invoice and just call save on the document - once. It isn't enough to just add an invoice to the document - the ID within Invoice is entirely controlled by the Invoice.document reference, so if it isn't set, it will be left null. You must maintain both sides of bidirectional relationships yourself to have the model in synch with what you want in the database. Or at least set the owning side of any bidirectional relationship.