30

I have the following entity:

@Entity
public class TestCaseStep implements JPAEntity<Integer> {

        ...

    @Column(name="STEP_NUMBER")
    private Integer stepNumber;

    @Enumerated(EnumType.STRING)
    @Column(name="ACTION")
    private Action action;

    **@ManyToOne
    @JoinColumn(name="connector")
    private ScriptItem connector;**

My attribute ScriptItem is a interface for 3 other classes. Is it possible to configure JPA to set the correct class id in runtime execution?

Other resources:

public interface ScriptItem {

    String getValue();
    ScriptItemType getType();
}

@Entity
@Table(name="DAT_FEED_XML")
public class FeedXml implements JPAEntity<Integer>, ScriptItem {
    ...
}

@Entity
@Table(name="DAT_DB_STMT")
public class DbStatement implements JPAEntity<Integer>, ScriptItem {
       ...
}

Which annotations should I use to let JPA understand that I want to save the id of one of the 3 classes?

Thanks in advance,

Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60
Fernando
  • 315
  • 1
  • 3
  • 9
  • 2
    Yes but the three implementations of scriptitem need to inherit from a common superclass Entity, not just a common interface. (The reference type of the mapped property can still be the interface type) – Affe Sep 27 '12 at 18:19
  • Sounds like @MappedSuperClass may be helpful here (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.repositories). – Woodchuck Feb 11 '22 at 23:54

4 Answers4

15

It is really a good idea but unfortunately directly mapping interfaces as an entity attribute is not supported by JPA.

You can only map top level classes directly annotated with @Entity. This top level class may implement an interface though.

This feature has been requested and discussed for a long time.

Also take a look at this and this.

Depending on what you're trying to accomplish, @Inheritance annotation with table-per-class strategy could be an option.

I hope it helps.

Community
  • 1
  • 1
rbento
  • 9,919
  • 3
  • 61
  • 61
8

It is possible with one caveat - you have to point JPA to a target entity which should be a concrete class. However, it can be an abstract class which implements your interface. So one principle of good design you'll have to break and in particular - "favour composition over inheritance".

Here's how to do it:

  1. In your user class (which references your interface Task):
    @OneToOne(targetEntity = BaseTask.class)
    private Task task;

so here Task is an interface, but you have to declare an abstract class BaseTask.

  1. In your BaseTask:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="task_type")
@Table(name="Task")
public abstract class BaseTask implements Task{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    }

Here the important thing is @DiscriminatorColumn - since all fields of the inheritance tree will be stored in 1 table (you specified that with the @Inheritance(strategy = InheritanceType.SINGLE_TABLE) annotation above. So this discriminator column will contain a label which will allow JPA to differentiate what kind of task you're talking about

  1. Your concrete classes:
@Entity
@DiscriminatorValue("api")
public class ApiTask extends BaseTask {

or

@Entity
@DiscriminatorValue("ssh")
public class SshTask extends BaseTask{

As you can see the discriminator value tells JPA what task it is going to load (what class to instantiate).

ACV
  • 9,964
  • 5
  • 76
  • 81
5

No, not possible with JPA or Hibernate.

It does seem strange, when coding in the Java language which allows for attributes to be interfaces, that a persistence standard like JPA, intended for Java, does not support persisting attributes that are interfaces.

I always found it really very frustrating when my ORM would force me to refactor my 'pure' OO model just so that it could persist it.

It's not that it's technically impossible to implement persisting of interface attributes - in fact JDO has supported persistence of interfaces since forever which is why I started using it years ago for all of my own projects.

I've been tempted to switch to JPA, not because it is technically superior (in fact, quite the opposite) but just because of "herd mentality".

In recent contract work I have been forced to gain experience with JPA/Hibernate and in doing so, have lived out the many limitations and inefficiencies of that combination compared with JDO/DataNucleus. This was a great experience because it helped me quell my desire to join "the herd" :)

Volksman
  • 1,969
  • 23
  • 18
  • Can you please detail *"the many limitations and inefficiencies of that combination"* ? – Stephan May 03 '21 at 08:48
  • @Stephan Read up on the proxy implementation that Hibernate uses to implement lazy loading. It's the alternative (but inferior) implementation based on BCE which DataNucleus has always used and which Hibernate fought against for years (but now supports). It's some vital "under the hood" detail that every Hibernate developer should be aware of but, in my experience, I've seen even team leads with 10 years of Hibernate experience look at me blankly when I explain that their "lazy" annotation on a Blob field has no effect unless BCE is used. Their reply: "What's BCE?" - OMG! – Volksman May 18 '21 at 09:26
  • Please go further, what is BCE ? :) Before Common Era ?? – Stephan Jun 03 '21 at 20:44
  • @Stephan BCE: Byte Code Enhancement – Volksman Jun 05 '21 at 04:53
0

You need to setup your inheritance of ScriptItem correctly in JPA using whatever strategy you prefer (see the docs) and then JPA will be smart about it.

digitaljoel
  • 26,265
  • 15
  • 89
  • 115