Arthur
Arthur

Reputation: 1548

Adding to collection creates record, but does not link it to parent

I have two very simple entities:

@Entity
public class CanonUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String crm;
    private String name;
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Address> address;

    // getters and setters omitted
}

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String city;
    private String street;

    @ManyToOne
    private CanonUser user;

    // getters and setters omitted
}

and a basic repository:

@RepositoryRestResource(path = "users", collectionResourceRel = "users", itemResourceRel = "user")
public interface CanonUserRepo extends CrudRepository<CanonUser, Long> {
}

I'm trying to add an item to the addresses with this request:

curl 'http://localhost:8080/companion-backend/api/users/3' -i -X PATCH \
    -H 'Accept: application/hal+json' \
    -H 'Content-Type: application/json-patch+json; charset=ISO-8859-1' \
    -d '[{"op":"add", "path":"/address/-","value":{"city":"Jim", "street":"Jim Grove"}}]'

This creates the address record but does not link it to the user:

2018-03-13 11:05:04.242 DEBUG 18533 --- [nio-8080-exec-1] jdbc.sqltiming                           :  com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
1. select canonuser0_.id as id1_1_0_, canonuser0_.crm as crm2_1_0_, canonuser0_.name as name3_1_0_, address1_.user_id as user_id4_0_1_, address1_.id as id1_0_1_, address1_.id as id1_0_2_, 
address1_.city as city2_0_2_, address1_.street as street3_0_2_, address1_.user_id as user_id4_0_2_ from canon_user canonuser0_ left outer join address address1_ on canonuser0_.id=address1_.user_id 
where canonuser0_.id=3 
 {executed in 0 msec}
2018-03-13 11:05:04.353 DEBUG 18533 --- [nio-8080-exec-1] jdbc.sqltiming                           :  com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
1. insert into address (id, city, street, user_id) values (null, 'Jim', 'Jim Grove', NULL) 
 {executed in 2 msec}
2018-03-13 11:05:04.355 DEBUG 18533 --- [nio-8080-exec-1] jdbc.sqltiming                           :  com.zaxxer.hikari.pool.ProxyStatement.getGeneratedKeys(ProxyStatement.java:230)
1. getGeneratedKeys on query: insert into address (id, city, street, user_id) values (null, 'Jim', 'Jim Grove', NULL) 
 {executed in 2 msec}

What am I doing wrong?

<spring.version>5.0.4.RELEASE</spring.version>
<spring-data-releasetrain.version>Kay-SR4</spring-data-releasetrain.version>

Upvotes: 0

Views: 43

Answers (1)

Cepr0
Cepr0

Reputation: 30309

As I said in the comment the Spring Data author explanation of why bidirectional one-to-many doesn't work in SDR you can find here.

But you can use a compromise variant to avoid 3rd, the joining, table in one-to-many association with combination of @JoinColumn annotation:

@Data
@Entity
public class Parent implements Serializable {

  @Id @GeneratedValue private Integer id;

  private String name;

  @OneToMany(cascade = ALL, orphanRemoval = true)
  @JoinColumn(name = "parent_id", foreignKey = @ForeignKey(name = "fk_children_parent"))
  private List<Child> children;
}

@Data
@Entity
public class Child implements Serializable {
  @Id @GeneratedValue private Integer id;
  private String name;
}

public interface ParentRepo extends JpaRepository<Parent, Integer> {
}

In this case you will get only two tables:

CREATE TABLE PARENT
(
    ID integer PRIMARY KEY NOT NULL,
    NAME varchar(255)
)

CREATE TABLE CHILD
(
    ID integer PRIMARY KEY NOT NULL,
    NAME varchar(255),
    PARENT_ID integer,
    CONSTRAINT FK_CHILDREN_PARENT FOREIGN KEY (PARENT_ID) REFERENCES PARENT (ID)
);

I wrote 'compromise variant' because this combination produce an excessive number of queries, compared to the bidirectional one. For example, creating one parent with two children produces five queries:

POST http://localhost:8080/parents
{
    "name": "parent1",
    "children": [
        {
            "name": "child1"
        },
        {
            "name": "child2"
        }
    ]
}
insert into parent (name, id) values ('parent1', 1)
insert into child (name, id) values ('child1', 2) 
insert into child (name, id) values ('child2', 3) 
update child set parent_id=1 where id=2 
update child set parent_id=1 where id=3

Updating this parent with one child produce the following sequence of queries:

PATCH http://localhost:8080/parents/1
{
    "name": "parent1",
    "children": [
        {
            "name": "child3"
        }
    ]
}
select c.parent_id, c.id, c.name from child c where c.parent_id=1
update child set name='child3' where id=2 
update child set parent_id=null where parent_id=1 and id=3 
delete from child where id=3 

Upvotes: 1

Related Questions