Search code examples
javaspring-bootspring-data-jpaspring-data-restspring-hateoas

Using Spring-Data-Rest, how to update FK on both sides of OneToOne relationship from one REST call?


I have a Spring-Boot app with

  • spring-boot-starter-data-rest
  • spring-boot-starter-data-jpa
  • h2

I have entities with one-to-one relationship like this:

@Entity
public class Address {

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

    @OneToOne(cascade = ALL)
    @JoinColumn(name = "party")
    private Party party;

    @Column
    private String street;

    ....
}
@Entity
public class Party {

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

    @Column
    private String name;

    @OneToOne(cascade = ALL)
    @JoinColumn(name  = "address")
    private Address address;

    ...

}

And repositories for each entity:

@RepositoryRestResource
public interface AddressRepository extends JpaRepository<Address, Long> { }
@RepositoryRestResource
public interface PartyRepository extends JpaRepository<Party, Long> { }

I create an instance of each entity:

curl -X POST \
     -H 'Content-Type: application/json' \
     -d '{ "name": "John Smith" }' \
     http://localhost:8080/parties
curl -X POST \
     -H 'Content-Type: application/json' \
     -d '{ "street": "456 main st" }' \
     http://localhost:8080/addresses

On the address instance, I create a HATEOAS association to party:

curl -X PUT \
     -H 'Content-Type: text/uri-list' \
     -d http://localhost:8080/parties/1 \
     http://localhost:8080/addresses/1/party

When I check the associations of address:

curl -X GET http://localhost:8080/addresses/1/party -i

I see the correct party:

HTTP/1.1 200 
{
  "key" : 1,
  "name" : "John Smith",
  ....
}

But, when I check the associations of address:

curl -X GET http://localhost:8080/parties/1/address -i

It does not exist:

HTTP/1.1 404 

How do I create both associations from a single call using Spring-Data-Rest?


Solution

  • I found a solution for this using @PrePersist and @PreUpdate on my Party class:

    @Entity
    public class Party {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long key;
    
        @Column
        private String name;
    
        @OneToOne(cascade = ALL)
        @JoinColumn(name  = "address")
        private Address address;
    
        @PrePersist
        @PreUpdate
        public void updateAddressAssociation() {
            if (address != null)
                address.setParty(this);
        }
    
    }
    

    Here is my app flow:

    1. Create the address:
    curl -X POST \
         -H 'Content-Type: application/json' \
         -d '{ "street": "456 main st" }' \
         http://localhost:8080/addresses
    
    1. Create the party
    curl -X POST \
         -H 'Content-Type: application/json' \
         -d '{ "name": "John Smith" }' \
         http://localhost:8080/parties
    
    1. Associate address with party
    curl -X PUT \
         -H 'Content-Type: text/uri-list' \
         -d http://localhost:8080/parties/1 \
         http://localhost:8080/addresses/1/party
    
    1. Confirm party has address association
    curl http://localhost:8080/parties/1/address
    
    1. Confirm address has party association
    curl http://localhost:8080/addresses/1/party