1

I'm using Spring Boot with Spring Data and H2 in-memory db and wanted to try using Value Objects instead of Strings everywhere and I found this neat library: AutoValue to create Value Objects. So here is my entity:

@Entity
@Access(AccessType.FIELD) // so I can avoid using setters for fields that won't change
class Address {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long addressId;

  @Embedded private State state;
  @Embedded private ZipCode zipCode;
  @Embedded private City city;
  @Embedded private Street street;

  // many customers might live at the same address
  @OneToMany(mappedBy = "address")
  private final Set<Customer> customers = new HashSet<>();

  // jpa requirement
  public Address() {
  }

  Address(State state, ZipCode zipCode, City city, Street street) {
    this.state = state;
    this.zipCode = zipCode;
    this.city = city;
    this.street = street;
  }

  public Long getAddressId() {
    return addressId;
  }

  public State getState() {
    return state;
  }

  public ZipCode getZipCode() {
    return zipCode;
  }

  public City getCity() {
    return city;
  }

  public Street getStreet() {
    return street;
  }

  public Set<Customer> getCustomers() {
    return customers;
  }

}

And here is one of the Value Objects (they are all similiar thanks to AutoValue):

@AutoValue
abstract class State {

  static State create(String stateName) {
    return new AutoValue_State(stateName);
  }

  abstract String stateName();
}

And when I check my database there are no Columns for Object Values: nocolumns

How can I fix that?

EDIT1: Here is AddressTest confirming that AutoValue actually creates fields and works ok:

  public class AddressTest {

    private static final String ANY_STATE = "ANY STATE";
  private static final String ANY_ZIP_CODE = "ANY ZIP CODE";
  private static final String ANY_CITY = "ANY CITY";
  private static final String ANY_STREET = "ANY STREET";

  @Test
  public void shouldConstructValidAddress() {
    // given
    State anyState = State.create(ANY_STATE);
    ZipCode anyZipCode = ZipCode.create(ANY_ZIP_CODE);
    City anyCity = City.create(ANY_CITY);
    Street anyStreet = Street.create(ANY_STREET);

    // when
    Address address = new Address(State.create(ANY_STATE), ZipCode.create(ANY_ZIP_CODE),
        City.create(ANY_CITY), Street.create(ANY_STREET));

    // then
    assertThat(address.getState()).isEqualTo(anyState);
    assertThat(address.getZipCode()).isEqualTo(anyZipCode);
    assertThat(address.getCity()).isEqualTo(anyCity);
    assertThat(address.getStreet()).isEqualTo(anyStreet);
  }

EDIT 2: State as value object:

    final class State {

  private final String stateName;

  State(String stateName) {
    this.stateName = stateName;
  }

  public String getStateName() {
    return stateName;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    State state = (State) o;
    return stateName != null ? stateName.equals(state.stateName) : state.stateName == null;
  }

  @Override
  public int hashCode() {
    return stateName != null ? stateName.hashCode() : 0;
  }

  @Override
  public String toString() {
    return "State{" +
        "stateName='" + stateName + '\'' +
        '}';
  }
}
doublemc
  • 3,021
  • 5
  • 34
  • 61
  • 1
    State doesn't have any field, any no-arg constructor, and isn't a concrete class. Why would Hibernate generate any column for it? And how is its supposed to create instances of State? Embedded classes have JPA constraints, too, and you're not respecting them. – JB Nizet Feb 26 '17 at 19:27
  • AutoValue does all those things. I added AddressTest class that confirms AutoValue working. – doublemc Feb 26 '17 at 19:32
  • Rer-read my comment. The problem is not with missing fields in Address. The problem is that State (and the other AutoValue classes) don't have any field, no-arg constructor, and are not concrete classes. – JB Nizet Feb 26 '17 at 19:36
  • So I simpy can't use AutoValue as a way to create value objects for my entities? I have to create them 'by hand'? – doublemc Feb 26 '17 at 20:46
  • You can create them the way you want, provided they respect the constraints JPA imposes. – JB Nizet Feb 26 '17 at 20:50
  • So how exactly would you do that using AutoValue? Or the simplest way would be to just not use AutoValue and create them on my own? – doublemc Feb 26 '17 at 20:53
  • I wouldn't, since that wouldn't produce classes that respect the constraints JPA imposes. Yes, I write my classes myself. – JB Nizet Feb 26 '17 at 20:54
  • Alright, removed that from my pom.xml. I'm very new to creating value objects which are supposed to be immutable so could you check if my State in EDIT 2 is ok? – doublemc Feb 26 '17 at 21:15
  • It's immutable, but it doesn't fulfill the JPA requirements. It must have a no-arg constructor, may not be final, and may not have final fields. – JB Nizet Feb 26 '17 at 21:28
  • Ok, changed that. Is this a good idea to override equals & hashcode and toString when my class has only one field which is a String? I've read somewhere that value objects should be immutable and override those methods but I'm not sure with such simple ones. – doublemc Feb 26 '17 at 21:35
  • Well, if you easily want to know what your state contains when printing it, and to be able to compare it with another state, then yes, you want these methods, whether it has just one field or not. – JB Nizet Feb 26 '17 at 22:12

0 Answers0