Can I remove the discriminator column in a Hibernate single table inheritance?

Pete picture Pete · Jan 2, 2012 · Viewed 8.5k times · Source

We use single table inheritance for every table in our application. This allows different instances of the same application stack to work with the same DAOs while their entities might differ slightly potentially containing information unique to that instance. An abstract class defines the basic table structure and an extension defines additional columns, if needed by that instance:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "client")
public abstract class Client extends AbstractPersistable<Long> {
    // ...
}

application A:

@Entity
public class ClientSimple extends Client {
    private String name;
    // getter, setter
}

application B:

@Entity
public class ClientAdvanced extends Client {
    private String description;
    // getter, setter
}

Now a DAO can work with Client objects for application A and B but application B can define additional information for its client object that may be read by a manager method unique to application B:

application A:

Client client = new ClientSimple();
clientDao.save(client);

application B:

Client client = new ClientAdvanced();
clientDao.save(client);

Unfortunately this means there is a DTYPE column in every table (or any other name that I might choose). Is there any way to get rid of this? We don't need it and it's using up DB space...

Thanks!


EDIT

Important to note: @MappedSuperclass won't work. We're using QueryDSL as our HQL abstraction layer. This requires automatically generated Query Type classes for type save querying. These however will only be generated correctly if the abstract class is annotated with @Entity.

This is neccessairy because we want to query against the abstract class Client while in truth querying ClientSimple in application A and ClientAdvanced in application B:

So in any application this will work:

query.where(QClient.client.name.equals("something");

and in application B this will work:

query.where(QClientSimple.client.description.equals("something else");

EDIT2 - boil down

It seems to boil down to this: Can I configure hibernate at deploy time to set the discriminator type for an inhertited entity to a fixed value. So going with my example a Client will always be ClientSimple in one application and ClientAdvanced in the other so that I don't have to store that information in the database?

Like I said: Each application will be an instance of the base application stack. Each application might define additional columns for their local database but ALL objects will be of the same type for that instance so we guarantee that the discriminator is always the same making it redundant in the database and a use case for hibernate configuration.

Answer

Bhashit Parikh picture Bhashit Parikh · Jan 24, 2014

I know, this is a very old question, but I encountered this problem recently and this might prove useful to someone.

This can be done using Hibernate's @DiscriminatorFormula annotation. The following description is based on the book Java Persistence with Hibernate, section 5.1.3; the relevant part begins at page the last paragraph on page 202.

With @DiscriminatorFormula you can provide an SQL statement that determines the value of the discriminator while fetching the relevant rows from the database. In your case, it would have to be a simple string that evaluates to some arbitrarily selected value. For this to work, you need to decide upon a name that would be used for your Client entity. Suppose that you select 'GenericClient' as the name of the entity. This is the name that should appear within @Entity annotation as the value of the name attribute. So, the complete example, in your case would look like the following.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "client")
@DiscriminatorFormula("'GenericClient'")  // *1*
public abstract class Client extends AbstractPersistable<Long> {
    // ...
}

// Application A
@Entity
@DiscriminatorValue("GenericClient")  // *2*
public class SimpleClient extends Client {
    // ...
}


// Application B
@Entity
@DiscriminatorValue("GenericClient")  // *3*
public class AdvancedClient extends Client {
    // ...
}

The line that is denoted by '1' is a part of the SQL snippet that will always return 'GenericClient' as its value. The subclasses of the Client should always be annotated with the @DiscriminatorValue("GenericClient"). What this means is that when Hibernate fetches the rows from the DB, the type of the object to be constructed would always be the specific subclass of Client.

If the package where the subclasses of Client reside, and the name of the subclasses are fixed:

In that case, the @DiscriminatorValue("GenericClient") on the sub-classes wouldn't be required, all you would need to do is:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "client")
@DiscriminatorFormula("'com.example.fixed.path.FixedSubClassName'")
public abstract class Client extends AbstractPersistable<Long> {
    // ...
}

The subclasses wouldn't need any annotations. The discriminator-value defaults to the entity-name, which itself defaults to the fully-qualified class-name.

Note: The SQL statement inside @DiscriminatorFormula() can be any valid SQL statement for your targeted DB server.