1

Given the following three tables describing an item that can be either a toy, a food or unknown:

CREATE TABLE item (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    type VARCHAR(255) NOT NULL CHECK (type IN ('TOY', 'FOOD', 'UNKNOWN'))
);

CREATE TABLE item_toy (
    item INTEGER NOT NULL REFERENCES item(id),
    color VARCHAR(255) NOT NULL,
    material VARCHAR(255) NOT NULL
);

CREATE TABLE item_food (
    item INTEGER NOT NULL REFERENCES item(id),
    smell VARCHAR(255) NOT NULL,
    taste VARCHAR(255) NOT NULL
);

Toy items would have only toy properties, food items only food properties and uknown items neither of them.

We can model these tables in Java, using Spring Data JDBC, in the following way:

public class Item {
    private int id;
    private String name;
    private ItemType type;
    private ItemToy toyProperties;
    private ItemFood foodProperties;
}

public enum ItemType {
    TOY,
    FOOD,
    UNKNOWN
}

public class ItemToy {
    private String color;
    private String material;
}

public class ItemFood {
    private String smell;
    private String taste;
}

public class ItemRepository extends CrudRepository<Item, Integer> {
    // ...
}

The problem I have with this implementation is that the fields toy and food may be null or not regardless of the value of type: ideally I would like unknown items to not have any property, toy items to only have toyProperties and food items to only have foodProperties.

Is there a more elegant way to model this relationship, either by changing the database structure and/or Java classes, using inheritance or composition? How would the repository class/es look like in that case?

Marco Lackovic
  • 6,077
  • 7
  • 55
  • 56
  • Take a look at https://stackoverflow.com/questions/16772370/when-to-use-discriminatorvalue-annotation-in-hibernate – Jim Garrison May 08 '23 at 20:59
  • Look up JPA and `@DiscriminatorColumn` – Bill Mair May 08 '23 at 21:03
  • @JimGarrison The question is about Spring Data JDBC not JPA or Hibernate. – Jens Schauder May 09 '23 at 07:10
  • @BillMair The question is about Spring Data JDBC not JPA or Hibernate. – Jens Schauder May 09 '23 at 07:11
  • What does Sping Data "JDBC" use? The Spring team didn't invent their own stack for SQL. That is why there is a JpaRepository in Spring Data. The OP asked about SQL tables, and unless he want to start down the path of implementing his own CrudRepository or even staring from scratch with nothing, then your comment and solution is overly complex. See: https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#repositories.core-concepts – Bill Mair May 09 '23 at 11:05

1 Answers1

1

The underlying problem here is that you have redundant information in your model and therefore run the risk that the different representations contradict each other. The type is really determined by the presence or absence of the different properties. I therefore recommend to remove it completely from both the database and the Java model. Then change the one-to-one relationships to @Embedded ones so you only have a single table with all the columns.

If you need the type in the database you might add it as a function based virtual column if your database supports such a thing. Or have it as a normal column and fill it using a trigger.

If you need the type in Java you can create a getter that calculates it or keep it in a @Transient field.

If this doesn't work for you for what ever reason you are left with two issues here. Consistency on the database level and consistency on the application/Java side.

Database

Let's start with the database side. I see three options:

  1. You could change the model to have all properties in a single table and instead of one-to-one relationships mark the references as @Embedded you could then create a check constraint in the database that ensures only valid combinations are created.

  2. If you don't want to merge the tables into one you could create a materialized view based on the tables and put constraints on that. For this to work as intended you need materialized views that automatically update. I do know that Oracle supports those and also know that at least some databases don't.

  3. The last alternative is to use triggers to verify the constraint.

Since it is the easiest to implement, works on all databases, and also leads to simpler/faster reading and writing I recommend the first approach.

Java

For the Java side you can simply rely on standard Java code to check the constraints. In order to make this fails save and simple I recommend making your model immutable. It allows you to check the constraints in the constructor and be done with it.

If that leads to an API that is hard to use in your application, you could create a builder that gathers all the data for an instance before building it.

Marco Lackovic
  • 6,077
  • 7
  • 55
  • 56
Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • This is no solution for a JDBC based problem. "Spring Data modules accept either third-party annotations (such as JPA’s @Entity) or provide their own annotations (such as @Document for Spring Data MongoDB and Spring Data Elasticsearch)." Spring utilizes JPA to interact with SQL databases. – Bill Mair May 09 '23 at 11:00
  • @BillMair You still seem to confuse Spring Data JPA and Spring Data JDBC https://spring.io/projects/spring-data-jdbc/ – Jens Schauder May 09 '23 at 13:11
  • Spring Data JDBC does not utilise JPA annotations or any other part of JPA. – Jens Schauder May 09 '23 at 13:12
  • @JensSchauder I agree `type` is redundant and should be removed. I can check whether `toyProperties` and `foodProperties` are `null` to infer the type. I don't like the idea of having a single table with all the columns: (1) to keep data consistent I would need to add complex non-scalable constraints - e.g. when I need to add a new type or a new field to an existing type, (2) could lead to having a table with a lot of columns, (3) it's difficult to get a grasp of the domain sections by looking at the table, (4) many columns would contain `NULL` values. – Marco Lackovic May 09 '23 at 19:27
  • 1 if you don't have duplicate information you can't really have inconsistent data. So you don't need constraints, as far as the discussed requirement is concerned. – Jens Schauder May 10 '23 at 10:54
  • 2 lots of columns shouldn't be a problem until you get really lots. – Jens Schauder May 10 '23 at 10:54
  • 3 true, although with different prefixes for the different groups that shouldn't be to bad. – Jens Schauder May 10 '23 at 10:55
  • 4 true, but shouldn't be a problem. DBs can handle that quite well. Certainly more efficient then writing/loading to/from all the different tables. – Jens Schauder May 10 '23 at 10:56