Pergunta

Having one abstract class NodeBase and several extending classes (NodeAnnounce, NodeTransfer, ...), I'd like to apply inheritance with JPA's annotation @Inheritance and @DiscriminatorColumn.

Here are my classes (I condensed it a bit, it's not my style though getters and setters are obvious anyway):

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="nodeType")
@Table(name="node_base")
@NamedQueries({
  @NamedQuery(name="NodeBase.findAll", query="SELECT n FROM NodeBase n"),
  @NamedQuery(name="NodeBase.findByTarget", query="SELECT n FROM NodeBase n JOIN Target t ON t.firstNode = n.id WHERE t.id = :targetID")
})
public abstract class NodeBase implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  private int id;
  private int customer;
  private String exitNames;
  private int nodeType;
  private int diagramPosX;
  private int diagramPosY;

  public NodeBase() {}

  public int getId() { return this.id; }
  public void setId(int id) { this.id = id; }

  public int getCustomer() { return this.customer; }
  public void setCustomer(int customer) { this.customer = customer;}

  public String getExitNames() { return this.exitNames; }
  public void setExitNames(String exitNames) { this.exitNames = exitNames; }

  public int getDiagramPosX() { return diagramPosX; }
  public void setDiagramPosX(int diagramPosX) { this.diagramPosX = diagramPosX; }

  public int getDiagramPosY() { return diagramPosY; }
  public void setDiagramPosY(int diagramPosY) { this.diagramPosY = diagramPosY; }

  public int getNodeType() { return nodeType; }
  public void setNodeType(int nodeType) { this.nodeType = nodeType; }  
}

One inheriting class (only an example)

@Entity
@Table(name="node_announce")
@DiscriminatorValue(value="2")
@NamedQuery(name="NodeAnnounce.findAll", query="SELECT n FROM NodeAnnounce n")
public class NodeAnnounce extends NodeBase implements Serializable {
  private static final long serialVersionUID = 1L;

  private String wavefile;

  public NodeAnnounce() {}

  public String getWavefile() { return this.wavefile; }
  public void setWavefile(String wavefile) { this.wavefile = wavefile; }
}

I'm now calling the named query previously defined

NodeBase nodeBase =
(NodeBase) em.createNamedQuery("NodeBase.findByTarget")
  .setParameter("targetID", target.getId())
  .getSingleResult();

I can assure target isn't null and the id it returns is existing. In the above function call, a pretty long exception is thrown:

[EL Warning]: 2014-05-02 18:39:31.287--UnitOfWork(138297553)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: The multi-part identifier "node_base.nodeType" could not be bound.
Error Code: 4104
Call: SELECT DISTINCT node_base.nodeType FROM target t0, node_base t1 WHERE ((t0.ID = ?) AND (t0.FIRSTNODE = t1.ID))
  bind => [1 parameter bound]
Query: ReadObjectQuery(name="NodeBase.findByTarget" referenceClass=NodeBase sql="SELECT DISTINCT node_base.nodeType FROM target t0, node_base t1 WHERE ((t0.ID = ?) AND (t0.FIRSTNODE = t1.ID))")
  at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:340)
  at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:679)
  at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:558)
  (...)
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The multi-part identifier "node_base.nodeType" could not be bound.
  at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:216)
  at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1515)
  (...)

This line of the stack trace looks strange to me:

SELECT DISTINCT node_base.nodeType FROM target t0, node_base t1 WHERE ((t0.ID = ?) AND (t0.FIRSTNODE = t1.ID))

Why is t0.ID = ?? It shouldn't.

I know, it's yet another The multi-part identifier question. But I think it is related to JPA. The field nodeType in the database has a relation to the table node_type - which I'm not using in my Java project. But the value of the field is important anyway.

The backend is SQL Server. Here the relevant table definition:

CREATE TABLE [dbo].[node_base](
  [id] [int] IDENTITY(1,1) NOT NULL,
  [nodeType] [int] NOT NULL,
  [exitNames] [varchar](20) NOT NULL,
  [customer] [int] NOT NULL,
  [diagramPosX] [int] NULL,
  [diagramPosY] [int] NULL,
 CONSTRAINT [PK_node_base] PRIMARY KEY CLUSTERED 
(
  [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Foi útil?

Solução

As mentioned in the comment, the problem in your query is not with the t0.ID = ? as this is expected from the JPQL SELECT n FROM NodeBase n JOIN Target t ON t.firstNode = n.id WHERE t.id = :targetID. The problem is that when querying on NodeBase, EclipseLink first will check all the subclasses that are to be returned from the query and so select only the DiscriminatorColumn, "nodeType". This would return all the subclasses that are required to be built due to inheritance, which EclipseLink would then use for subsequent queries.
Unfortunately you are hitting EclipseLink bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=344721 with inheritance.

I the workaround is to have EclipseLink use an outerjoin strategy to bring in subclasses rather than separate queries. This allows everything to be brought in using a single query, and can be configured using a customizer as described here: https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Inheritance#Outer_Joining_Subclasses

The pros and cons of using one query vs multiple queries for inheritance are discussed here: http://en.wikibooks.org/wiki/Java_Persistence/Inheritance

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top