How to get the DiscriminatorValue at run time
Asked Answered
S

6

31

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");
Semilunar answered 9/6, 2010 at 11:34 Comment(0)
F
54

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

public class ApplicationProcess { 

    ...

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

}
Farad answered 9/6, 2010 at 12:15 Comment(7)
@Shervin: You can, but it's not a good idea, your logic shouldn't rely on that.Yazzie
@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.Semilunar
@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).Yazzie
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()Antinomy
But if you use a transient, you wont be able to use it in you JPA queriesHanyang
@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!Dagley
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.Overseas
B
47

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();
}
Bedfellow answered 26/11, 2012 at 8:42 Comment(5)
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.Barbie
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. ThanksBeatabeaten
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 before implementing this.Transpadane
Great idea, but used like this I wont be able to use the discriminator value in my jpa query. For example: findByDiscriminatorIn() wont workHanyang
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.Inspired
Y
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.

Yazzie answered 9/6, 2010 at 11:42 Comment(4)
+1 I totally agree. Perhaps this refactoring my be applicable: c2.com/cgi/wiki?ReplaceConditionalWithPolymorphismBelford
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!}Semilunar
@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.Yazzie
For those interested in ReplaceConditionalWithPolymorphism here's a better explaination: sourcemaking.com/refactoring/…Belford
W
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");
Wharve answered 26/2, 2019 at 14:51 Comment(1)
it is, haven't noticed that oneWharve
W
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.

Wop answered 9/8, 2017 at 15:15 Comment(0)
S
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.

Stellular answered 11/7, 2018 at 11:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.