25

We have the following classes

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // optional annotation as this is default
@DiscriminatorColumn(name = "apType", discriminatorType = DiscriminatorType.STRING, length = 255)
@DiscriminatorValue("AP")
public class ApplicationProcess {
}

And this

@Entity
@DiscriminatorValue("APS")
public class ApplicationProcessScheme extends ApplicationProcess {
}

Now I need to know at runtime if the ApplicationProcess is of DiscriminatorValue AP or APS. Since this is automatically handled by jpa, I have no way of getting this value.

We are calling a method that takes an ApplicationProcess as parameter, and I want to avoid using instanceof to check what type it is. Would be cooler if I could do something like

applicationProcess.getApType().equals("AP");
Shervin Asgari
  • 22,044
  • 28
  • 92
  • 138

6 Answers6

46

You can map your discriminator as a read-only property:

public class ApplicationProcess { 

    ...

    @Column(name = "apType", insertable = false, updatable = false)
    private String apType;

}
Yonatan
  • 2,335
  • 2
  • 17
  • 18
axtavt
  • 228,184
  • 37
  • 489
  • 472
  • @Shervin: You can, but it's not a good idea, your logic shouldn't rely on that. – Pascal Thivent Jun 09 '10 at 12:48
  • 1
    @Pascal: I think I have to. On the xhtml side I have no way in the EL expression to say `instanceof` (I think). So I might need this. – Shervin Asgari Jun 09 '10 at 13:45
  • 1
    @Shervin: I'm sorry to say so but it seems that you have a major flaw in your design, the **view** should NEVER have to rely on a discriminator value (this is true for business code but even more for the view). – Pascal Thivent Jun 09 '10 at 15:41
  • If you don't want to map `apType` you can set it to `@Transient` and set its value in constructor: `apType = getClass().getAnnotation(DiscriminatorValue.class).value()` – gamliela Jan 26 '16 at 16:23
  • But if you use a transient, you wont be able to use it in you JPA queries – Thermech Jan 28 '17 at 14:38
  • @PascalThivent, just to add to the commentary... I understand and agree that it's bad design, but I'm currently mirroring and existing data schema into Hibernate, so I need this to be public data. Thank for the solution! – Jeff Fairley May 16 '17 at 06:50
  • this approach has one big flaw: it does not work properly in case when you create an entity, save it and try to use newly saved entity. In this case apType==null. And off course, manual updating of apType for this specific case is even worse. Solution provided by @Wirus is much better. – Oleksandr_DJ Oct 31 '18 at 07:02
41

I can imagine few cases where it might be helpful, but despite the reason why you need this, you could create on your abstract class method like

@Transient
public String getDiscriminatorValue(){
    DiscriminatorValue val = this.getClass().getAnnotation( DiscriminatorValue.class );

    return val == null ? null : val.value();
}
Wirus
  • 1,080
  • 10
  • 10
  • 1
    A use case might be: some system wide translation table with types (country, language, gender, ...) and per type multiple keys ("pl", "uk", "us", ...) and values ("Poland", "United Kingdom", ...). Some of the types might be used in `@OneToMany` relationships, like country might be used in an `@Entity Address`, but other types might only be used by querying the database. So: the base class would handle most types, but a few specific ones would have their own derived class. – Arjan Feb 12 '13 at 18:53
  • for me the problem with the accepted answer was when the objected is just created and not yet saved, discriminator is not present. In test cases where data is really not persisted to db, i was issues with that. This worked great. Thanks – Rajani Karuturi Mar 06 '13 at 10:58
  • I like this a lot. Performance is important in my current use case though, so I will have to use a quicker solution. For others, [consider performance](http://stackoverflow.com/q/8098648/1756430) before implementing this. – Sam Berry Oct 02 '16 at 23:57
  • Great idea, but used like this I wont be able to use the discriminator value in my jpa query. For example: findByDiscriminatorIn() wont work – Thermech Jan 28 '17 at 14:36
  • 1
    This is a good solution as long as you're not using it on a lazy loaded / proxy object -- in which case the class with be a proxy class, and will not contain the `@DiscriminatorValue` annotation. – jlb Apr 04 '17 at 16:18
4

We are calling a method that takes an ApplicationProcess as parameter, and I want to avoid using instanceof to check what type it is. Would be cooler if I could do something like (...)

I don't think it would be cooler, this seems worse than calling instanceOf to me: if for whatever reason you change the discriminator value, it would break your code.

If you need to check the type, use instanceOf. A trick using the discriminator is not going to make things nicer, it would just make your code less robust.

Pascal Thivent
  • 535,937
  • 127
  • 1,027
  • 1,106
  • 1
    +1 I totally agree. Perhaps this refactoring my be applicable: http://www.c2.com/cgi/wiki?ReplaceConditionalWithPolymorphism – chickeninabiscuit Jun 09 '10 at 11:55
  • But then I would have to do something like this: pseudo code: `if(not applicationProcess instanceof ApplicationProcessScheme)`, because I cannot check against ApplicationProcess since ApplicationProcessScheme would also match that So the code would be `if(obj instanceof ApplicationProcessScheme) {//because there is no !instanceof} else { //We have a child!}` – Shervin Asgari Jun 09 '10 at 12:01
  • 1
    @Shervin: Yes, you would have to do that. I'm not saying it's ideal, I'm saying relying on metadata would be worse. Actually, I'd dig @Ash's suggestion. – Pascal Thivent Jun 09 '10 at 12:07
  • For those interested in ReplaceConditionalWithPolymorphism here's a better explaination: http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism – chickeninabiscuit Jun 09 '10 at 12:29
1

I just came across this question and had to post an answer. IMO this is clear cut case for using Java reflection API

DiscriminatorValue annotation = ApplicationProcess.class.getAnnotation(DiscriminatorValue.class);
annotation.getValue().equals("AP");
Jan Hruby
  • 1,390
  • 1
  • 12
  • 25
0

You can use Formula Annotation. If you're using Hibernate, you can use the code below according to this link:

private String theApType;

@Formula("apType")
String getTheApType() {
    return theApType;
}

Of course you would be able to use it in your queries.

0

I have used the following solution to get this value at runtime, assuming that you don't know beforehand what is the inheritance type:

SessionFactoryImpl sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactoryImpl.class);
EntityPersister entityPersister = sessionFactory.getEntityPersister( Task.class.getPackage().getName()+"."+param.getValue().get(0) );
int clazz_ = 0;
if(UnionSubclassEntityPersister.class.isInstance(entityPersister)) {
    clazz_ = (Integer) ((UnionSubclassEntityPersister) entityPersister).getDiscriminatorValue();
} else if(JoinedSubclassEntityPersister.class.isInstance(entityPersister)) {
    clazz_ = (Integer) ((JoinedSubclassEntityPersister) entityPersister).getDiscriminatorValue();
}

You should change the type of clazz_ according to your discriminatorType annotations (Integer is the default for union and join strategies). This solution can be used for SINGLE_TABLE too, if you add such case; or you can use the other solutions mentioned here.

Bharata
  • 11,779
  • 6
  • 27
  • 42
AngelVel
  • 11
  • 2