1

I'm building an application using Hibernate. I work with a database schema that contains (amongst others) two tables called assignment and assignment_type. The latter contains primary key / value pairs that describe the type of the assignment (e.g. "written", "practical").

assignment, however, is an abstract class extended by such classes as written_assignment and practical_assignment. For this I'm using the class table inheritance approach (some details on the topic).

Now, it would make sense to set a reference to the appropriate object in the assignment_type during the creation of instances of classes like written_assignment, something like this:

public WrittenAssignment() {
    AssignmentType assignmentType = ...; // Create or acquire appropriate instance
    this.setAssignmentType(assignmentType);
}

As far as I understand it, going with the "create instance" approach will only work once, that one time being the state when there is no object in the database yet with that value (since the value of the assignment_type table is set to be unique).

Is there a way to set a reference to an object from assignment_type in the constructor of WrittenAssignment without acquiring it first? Bonus question: can it be created if no matching object exists yet?

Community
  • 1
  • 1
koala
  • 133
  • 8

1 Answers1

1

You can use the column that references the assignment_type table from the assignment table as a discriminator column, and then map the AssignmentType relation as a read only @ManyToOne, setting insertable and updatable to false.

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="ASSIGNMENT_TYPE_ID")
@Table(name="assignment")
public abstract class Assignment {
    ...
    @ManyToOne
    @JoinColumn(name = "ASSIGNMENT_TYPE_ID", insertable = false, updatable = false)
    private AssignmentType assignmentType;
}

In the child class, put the appropiate discriminator column value with the @DiscriminatorValue annotation:

@Entity
@DiscriminatorValue("W")
@Table(name="written_assignment")
public class WrittenAssignment extends Assignment {
    ...
}

When you need to create a new child entity, use the constructor and the assignment type would be implicit due to the child entity discriminator. When you read an existing entity instance and fetch the associated assignment type, the corresponding assignment type will be loaded.

JMSilla
  • 1,326
  • 10
  • 17
  • Thanks, this works like a charm! Any chance the discriminator can be used to automatically fetch the appropriate child class when dealing with a query on the superclass? – koala Jul 30 '16 at 21:13
  • 1
    Try to execute a query of `Assignment` entities, the returned real class of the result instances must be of the concrete type (not the parent abstract type). – JMSilla Jul 31 '16 at 14:34
  • Thanks again! That would be the behaviour I'd expect, and it works partially. I tweaked the `toString` methods so that they identify the real class, which works. However, I can't check for the child class by using `instanceof`, nor can I typecast it. `getClass().getName()` returns the name of the abstract class, followed by a bunch of underscores, dollar signs and other characters. I suppose this may be something about reflections that I don't understand. – koala Jul 31 '16 at 14:56
  • The `instanceof` operator must be working. Another way you can try to check the real class is to make a debug session in your IDE and inspect the results: it must show you the real class of the result instances. – JMSilla Jul 31 '16 at 15:01
  • The debugger indeed reveals the real classname that I expect, but with a trailing `@` followed by a number. `instanceof` doesn't work, I'm checking it like this: `for (Assignment a : user.getAssignments()) System.out.println(a instanceof WrittenAssignment);` -- which always prints `false` (`user` being an instance of another mapping class containing a `Set` of `Assignment`s). – koala Jul 31 '16 at 15:30
  • 1
    If you are lazy fetching the `Set` of `Assignment` of the user object, Hibernate may create `Assignment` proxies for every entity on the `Set`, and the `instanceof` operator won't work with that proxies. But if you directly make a HQL query like `"SELECT a FROM Assignment a WHERE a.user = :user"` using the desired `user` object as a query parameter, the returned list elements will be real instances of the child classes (`WrittenAssignment`, etc). – JMSilla Aug 01 '16 at 07:33