0

I've decided to use Value Objects instead of String fields in my entity and I don't know how (and if it's even possible) to validate them using JPA Annotations like @Size, @Pattern and so on. Here is my Book entity:

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

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

  @Embedded
  private Isbn isbn;
  @Embedded
  private Title title;
  @Embedded
  private Author author;
  @Embedded
  private Genre genre;
  @Embedded
  private PublicationYear publicationYear;
  private BigDecimal price;

  // jpa requirement
  public Book() {
  }

  public Book(Isbn isbn, Title title, Author author, Genre genre, PublicationYear publicationYear,
      BigDecimal price) {
    this.isbn = isbn;
    this.title = title;
    this.author = author;
    this.genre = genre;
    this.publicationYear = publicationYear;
    this.price = price;
  }

  public Long getBookId() {
    return bookId;
  }

  public Isbn getIsbn() {
    return isbn;
  }

  public Title getTitle() {
    return title;
  }

  public Author getAuthor() {
    return author;
  }

  public Genre getGenre() {
    return genre;
  }

  public BigDecimal getPrice() {
    return price;
  }

  public PublicationYear getPublicationYear() {
    return publicationYear;
  }

  // setter for price is needed because price of the book can change (discounts and so on)
  public void setPrice(BigDecimal price) {
    this.price = price;
  }

}

And here is my example value object - all are just using Strings.

   public class Isbn {
  private String isbn;

  // jpa requirement
  public Isbn() {
  }

  public Isbn(String isbn) {
    this.isbn = isbn;
  }

  public String getIsbn() {
    return isbn;
  }

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

    Isbn isbn1 = (Isbn) o;

    return isbn != null ? isbn.equals(isbn1.isbn) : isbn1.isbn == null;
  }

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

  @Override
  public String toString() {
    return "Isbn{" +
        "isbn='" + isbn + '\'' +
        '}';
  }
}

Is there a simple way to validate those objects? If it was a String in my entity instead of Isbn object I could just use @Pattern to match correct Isbn and be done with it.

EDIT1: Maybe there is a better way to validate value objects than above one? I'm kinda new to this stuff so would like to know if there is a better option to validate my entities.

doublemc
  • 3,021
  • 5
  • 34
  • 61
  • They are each essentially String's right? Why do you need to create separate classes for each? Just use `javax` validation annotations; http://www.baeldung.com/javax-validation – buræquete Feb 27 '17 at 22:57
  • Also I recommend using `lombok` for getter/setter/constructor generation; https://projectlombok.org/ – buræquete Feb 27 '17 at 22:59
  • 1
    Well, that's exactly why you would use Value Objects: to swap your meaningless String to objects - have a read: https://martinfowler.com/bliki/ValueObject.html – doublemc Feb 27 '17 at 23:00
  • Why would it be meaningless, what meaning are you adding by creating a hollow object? That is essentially wrapping the true `String` field – buræquete Feb 27 '17 at 23:02
  • Have you read the article I just posted? It's explained there. And here is great speech about Value Objects and why you would use them: https://vimeo.com/13549100 I want to use them and am currently looking how to validate them - I could use just Strings but I think that using VOs is superior. – doublemc Feb 27 '17 at 23:10
  • Nothing is simply `superiour`, you have to think for cases, and for your case wrapping each singular field with an Object is meaningless, if you had multiple fields that can be wrapped inside some Object, then I'd agree. What you are doing is not the same thing as the article you've shared. – buræquete Feb 27 '17 at 23:11
  • I'd like to end this offtopic. Here is quote form that article that sums up why I want to use VOs: "It's often a good idea to replace common primitives, such as strings, with appropriate value objects. While I can represent a telephone number as a string, turning into a telephone number object makes variables and parameters more explicit (with type checking when the language supports it), a natural focus for validation, and avoiding inapplicable behaviors (such as doing arithmetic on integer id numbers)." – doublemc Feb 27 '17 at 23:13
  • Value objects are indeed a good way to avoid [primitive obsession](https://sourcemaking.com/refactoring/smells/primitive-obsession), but you might be a bit out of luck if you want to use JPA's built-in validation mechanisms. After all, you have created custom data types and would need custom validation logic for this reason. – Mick Mnemonic Feb 27 '17 at 23:15
  • You can easily enforce the validation with a single annotation of `@Pattern` for a `String` field, you shouldn't have this fanaticism stance towards programming, I am just suggesting you a more readable, and maintainable way – buræquete Feb 27 '17 at 23:15
  • As a side note, Google devs have released a project called [AutoValue](https://github.com/google/auto/tree/master/value) that would at least help you get rid of some boiler-plate. I don't know if there are any hooks for custome validation logic. – Mick Mnemonic Feb 27 '17 at 23:18
  • @MickMnemonic So what would you suggest? Using just simple Strings as bureaquete suggested? Or applying my own validators? About Auto-Value - I couldn't make it work with Hibernate: here is SO Link: http://stackoverflow.com/questions/42473071/columns-for-value-objects-in-entities-are-not-created-on-start-up – doublemc Feb 27 '17 at 23:19
  • @bureaquete, which one is a more readable constructor signature for a person: `public Person(String firstName, String lastName, String personId, String address)` or `public Person(Name name, PersonId id, Address address)`? I think the biggest benefit from _strong typing_ is that you catch errors compile-time instead of run-time; e.g, if you get the String parameters in the wrong order. That can mean a lot. Of course you need to find a balance and simple wrappers for Java's primitives seem a bit overkill (unless you can get rid of the boiler-plate that comes with the verbosity of the language). – Mick Mnemonic Feb 27 '17 at 23:23
  • @MickMnemonic I really like the idea of AutoValue - I just couldn't make it work with Hibernate - posted a link to SO thread from yesterday. Do you mind checking it? It would be great if I could just throw out all this biolerplate code form my VOs and use AutoValue. – doublemc Feb 27 '17 at 23:28
  • @MickMnemonic by utilizing a static builder, there is no need to work with such constructors already, I agree that the order of multiple strings for a constructor would be an issue, but having a `Name` class with only single `String` field, just to be safe from that parameter ordering, cannot be `best practice`, I recommend creating proper JSON like beans with `javax` annotations for validation. If needed wrapper objects can be of course created, but only for logical reasons, not for weird field wrapping. e.g. (A `mail` field of type `Mail`, containing `subject`, `to`, `from` fields) – buræquete Feb 28 '17 at 07:04
  • FWIW `@Size`, `@Pattern` etc are *not* JPA annotations. They are nothing to do with it. They come from the Bean Validation API project – Neil Stockton Feb 28 '17 at 10:02
  • @NeilStockton but I can use them for validating my fields in entities without a problem, right? – doublemc Feb 28 '17 at 20:19
  • of course, that is what the Bean Validation API is for (not what the JPA API is for) – Neil Stockton Mar 01 '17 at 07:14

1 Answers1

1

You can enforce validation of Object fields by utilizing @Valid annotation;

@Entity
@Access(AccessType.FIELD)
public class Book {

  @Embedded @Valid
  private Isbn isbn;
...
}

public class Isbn {
  @Pattern(//Pattern you'd like to enforce)
  private String isbn;
...
}

Then you can validate by yourself using the following;

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(book);
//if set is empty, validation is OK
buræquete
  • 14,226
  • 4
  • 44
  • 89