Problem with aggregate root with more than one value object of the same type

Viewed 238

I'm new to Spring Data JDBC and create a Customer aggregate with two Address values using Spring-Boot 2.5.0, Java 11 and Lombok (code examples simplified).

I have one Customer entity (aggregate root) and one Address value object

@Data
@Builder
@AllArgsConstructor
class Customer {
    @Id Long id;
    String name;
    Address address1;
    Address address2;
}

@Data
@Builder
@AllArgsConstructor
class Address {
    String city;
}

and one Repository for the Customer entity

@Repository
public interface CustomeRepository extends CrudRepository<Customer, Long> {
}

Using Postgres the db schema looks like this

CREATE TABLE "customer" (
  "id"                  BIGSERIAL       NOT NULL,
  "name"                VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE "address" (
  "id"                  BIGSERIAL       NOT NULL,
  "customer"            BIGINT,
  "city"                VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

Creating and storing a Customer

        var address1 = Address.builder().city("New York").build();
        var address2 = Address.builder().city("Chicago").build();
        var customer = Customer.builder().name("Joe").address1(address1).address2(address2).build();
        var result = customerRegistry.save(customer);

So far so good, also the entries in the database looking fine

 id | name 
----+------
  1 | Joe


 id | customer |   city   
----+----------+----------
  1 |        1 | Chicago
  2 |        1 | New York

So expecting one Customer, but when doing this

var customers = customerService.findAll();
customers.forEach(c -> log.debug("Customer: {}", c));

the output will be

Customer(id=1, name=Joe, address1=Address(city=New York), address2=Address(city=Chicago))
Customer(id=1, name=Joe, address1=Address(city=Chicago), address2=Address(city=Chicago))
Customer(id=1, name=Joe, address1=Address(city=New York), address2=Address(city=New York))
Customer(id=1, name=Joe, address1=Address(city=Chicago), address2=Address(city=New York))

and doing this

var customer = customerRepository.getById(result.getId());

will result in

org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 4

Btw. If the Customer has only one address field everything works as expected.

So do I miss something or is this a bug?

1 Answers

You can put a @Column annotation on one or both attributes, specifying different columns to use for the back-reference to Customer.

For example:

class Customer {
    @Id Long id;
    String name;
    @Column("first")
    Address address1;
    @Column("second")
    Address address2;
}

would expect the following address table

CREATE TABLE "address" (
  "id"                  BIGSERIAL       NOT NULL,
  "first"               BIGINT,
  "second"              BIGINT,
  "city"                VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

For more background and other alternatives that go beyond the specific problem at hand see Why is a entity - value relationship implemented as a back reference in Spring Data JDBC

Related