views:

629

answers:

4

A polymorphic association is similar to a foreign key or many-to-one relationship, with the difference being that the target might be one of a number of types (classes in the language, tables in the db).

I'm porting a database design I've been using for some years from PHP to Java. In the old code, I had rolled my own ORM, which wasn't optimal for a number of reasons. Although I might start to tweak things later, and maybe end up implementing things myself again, for now I'd like to use an off-the-shelf ORM and JPA on my entity classes.

Now, there's one thing about the database layout that I don't know how to express in JPA:

I have a Node and an Edge table storing a graph (a DAG, if it matters). Each node may optionally reference one other entity from the database. These entites may be refrenced multiple times throughout the graph and there may also be "orphaned" entites, which wouldn't be accesible for the user, but which may make sense to keep at least for a while.

These objects are not at all related in terms of inheritance etc. but have a natural hierarchy, similar to Customer->Site->Floor->Room. In fact, years ago, I started out with just foreign key fields pointing to the "parent" objects. However, this hierarchy isn't flexible enough and started falling apart.

For example, I want to allow users to group objects in folders, some objects can have multiple "parents" and also the relations change over time. I need to keep track of how the relations used to be, so the edegs of the graph have a timespan associated with them, that states from when to when that edge was valid.

The link from a node to an object is stored in two columns of the node table, one carries the id in the foreign table, one carries its name. For example (some columns omitted):

table Node:
+--------+-------+----------+
| ixNode | ixRef | sRefType |
+--------+-------+----------+
|    1   |  NULL |   NULL   |  <-- this is what a "folder" would look like
|    2   |   17  |  Source  |
|    3   |   58  |  Series  |  <-- there's seven types of related objects so far
+--------+-------+----------+

table Source (excerpt):
+----------+--------------------+
| ixSource |        sName       |
+----------+--------------------+
|    16    | 4th floor breaker  |
|    17    | 5th floor breaker  |
|    18    | 6th floor breaker  |
+----------+--------------------+

There might be a different solution than using JPA. I could change something about the table layout or introduce a new table etc. However, I have thought about this a lot already and the table structure seems OK to me. Maybe there's also a third way that I didn't think of.

A: 

How much information is stored in the Source and Series tables? Is it just a name? If so, you could combine them into one table, and add a "type" column. Your Node table would lose its sRefType, and you would have a new table that looks like this:

ixSource        sName                  sType
  16            4th floor breaker      SOURCE
  17            5th floor breaker      SOURCE
  18            6th floor breaker      SOURCE
  19            1st floor widget       SERIES
  20            2nd floor widget       SERIES

This table would replace the Source and Series tables. Do Source and Series both belong to a superclass? That would be a natural name for this table.

Peter Recore
Sorry, but that won't work for me. There's more information in the tables and there's also more tables, the examples are simplified. There's many objects of multiple types and I need a graph to be more flexible in how they relate to each other.
Hanno Fietz
Just maybe, I could conceive some kind of abstract RelatedEntity and a generic Property table that stores key-value pairs, but that seems like taking it a little too far. I'd have almost no structured information in the db anymore.
Hanno Fietz
+1  A: 

I think you've already hit on an answer. Create an abstract class (either @Entity or @MappedSuperclass) and have the different types extend it.

Something like this might work

@MappedSuperclass
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Edge { 
    // . . .
    @OneToMany
    Collection<Node> nodes; 
}

@Entity 
public class Source extends Edge { 
}

@Entity public class Series extends Edge { 
}

@Entity
public class Node { 
    // . . .
    @ManyToOne
    Edge edge; 
}

I understand you might not want to imply a relationship between the Source and Series, but extending a common abstract (table-less) class is the only way I can think of to do what you want.

InheritanceType.TABLE_PER_CLASS will keep Source and Series in separate tables (you could use SINGLE_TABLE to do something like the previous answer).

If this isn't what you're looking for, many JPA providers provide a tool that creates mappings based on an existing set of tables. In OpenJPA it's called the ReverseMappingTool [1]. The tool will generate Java source files that you can use as a starting point for your mappings. I suspect Hibernate or EclipseLink have something similar, but you could just use the OpenJPA one and use the entity definitions with a different provider (the tool doesn't generate any OpenJPA specific code as far as I know).

[1] http://openjpa.apache.org/builds/latest/docs/manual/manual.html#ref_guide_pc_reverse

Mike
This would be very easy to implement, but what I don't get is: how does the persistency provider know which class to instantiate as the referenced object for a particular row in Node?
Hanno Fietz
Good question. I've modeled this solution with OpenJPA (other vendors may differ in their approach). OpenJPA stores a string in the Node table, the string contains the classname and PK for the edge. Basically it combines ixRef + sRefType into a single column. I'm not sure how one would convince a JPA provider to use the table definitions you currently have (ReverseMappingTool or something similar might figure it out though). I'm assuming you need to keep existing tables intact, and this approach won't work for you?
Mike
No, I can tweak the schema as much as I want, I was just curious (or call it sceptical :-) ). Seems like this is the solution, then. I still wonder what happens if you let one provider set up the database and then change the provider. But I guess that's one of the things you just defer until you really have to know.
Hanno Fietz
You *should* be able to switch providers at any time. Naturally there are some caveats with it though. Hibernate and OpenJPA may handle generated values differently (OpenJPA creates an OPENJPA_SEQUENCE_TABLE table for example). They might make different assumptions about constraints too.
Mike
Meanwhile, I tested this, and sadly, it doesn't work. Using @MappedSuperclass, the base type is not accepted as a target for the @ManyToOne annotation by my provider (eclipselink). Using @Entity, I get a superflous table that's always empty and reference keys that are not qualified (i. e. only the ids, no key as to where the id points to)
Hanno Fietz
+1  A: 

Have you looked at the @Any annotation? It's not part of JPA but is a Hibernate Annotation extension to it.

mtpettyp
While this seems to be at least close to what I want, I'm not using Hibernate at the moment and I neother want to switch nor would I like being tied to a particular provider for such a core element of my application. +1 for "good to know", though.
Hanno Fietz
+1  A: 

The answer would be:

  • inheritance (as suggested already by Mike)
  • plus @DiscriminatorColumn to provide information which column stores the information about which subclass should be used: sxRef. The only doubt I see is the "sxRef" being a nullable column. I guess that it's forbidden.
Grzegorz Oledzki