Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » EclipseLink » Inheritance bug?: super class instantiated instead of sub class
Inheritance bug?: super class instantiated instead of sub class [message #637377] Fri, 05 November 2010 06:06 Go to next message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
Hello,

I have three tables:

Contacts
Persons
Players

Persons extends Contacts, Players extends Persons by identifying relationship (simple shared ID across all contacts).

These are my entity classes (relevant parts only):

@Entity
@Table(name = "Contacts")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class Contact implements Serializable
{
	@Id
	@Column(name = "id")
	private Integer id;

	...
}


@Entity
@Table(name = "Persons")
@DiscriminatorValue(value = "person")
public class Person extends Contact implements Serializable
{
	@Column(name = "first_name")
	private String firstName;

	@Column(name = "last_name")
	private String lastName;

	...
}


@Entity
@Table(name = "Players")
public class Player extends Person implements Serializable
{
	@Column(name = "registration_nbr")
	private String registrationNbr = null;

	...
}


As you can see the Contact class is abstract, contact entities shouldn't be instantiatable. Persons however should, like players. I execute the following code on an EntityManager:

// returns Person instance -> correct
Contact co = em.find(Contact.class, 1);
System.out.println("Loaded contact = " + co + "</p><p>");

// returns Person instance -> correct
Person pe = em.find(Person.class, 1);
System.out.println("Loaded person = " + pe + "</p><p>");

// returns Person instance -> wrong (ClassCastException)
Player pl = em.find(Player.class, 1);
System.out.println("Loaded player = " + pl + "</p><p>");


The first two finds are okay, both return Person instances as expected.

Loaded contact = tld.persons.model.Person@7f636e4e[id=1,firstName=Michael,lastName=Jordan]
Loaded person = tld.persons.model.Person@7f636e4e[id=1,firstName=Michael,lastName=Jordan]


However, the third also returns a Person instance, which is wrong, giving an exception:

Exception in thread "main" java.lang.ClassCastException: tld.persons.model.Person cannot be cast to tld.persons.model.Player
    at tld.persons.Main.main(Main.java:82)


What's wrong? Is my code incorrect? Is this an EclipseLink bug?

I'm not sure about it, but shouldn't the working two objects be two different objects? The hashcode displayed hints that these are the same, so maybe my hashCode and equals are wrong?

You can find a standalone JavaSE/HSQLDB test app here:
http://www.kawoolutions.com/media/persons.zip

I'd be glad if anybody could comment on this.

Karsten
Re: Inheritance bug?: super class instantiated instead of sub class [message #637414 is a reply to message #637377] Fri, 05 November 2010 09:28 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris Delahunt
Messages: 1015
Registered: July 2009
Senior Member
Hello,

This is a problem with your expectations, and potentially a bug that might have already been fixed. Which version of EclipseLink are you using, and can you try the latest nightly build?

When you query on an EntityManager, it must maintain object identity, so that each time you query for an object you get the same instance back. JPA Inheritance allows a Person object to be a Contact, so when you call em.find(Contact.class, 1) JPA requires that all subclasses also be found and returned as well - in this case the row representing the Person is found and returned. When you call em.find(Person.class, 1), you will get the same Person instance back, since it has to return the same managed instance from the last time.

The problem is with em.find(Player.class, 1). You are expecting to get a Player instance back which is wrong - the row in the database is a Person. EclipseLink should filter out non-Player instances, but because of a caching detail is returning the existing Person object causing the classcast. EclipseLink should be returning null, as there is not a row in the Contact table with ID 1 representing a Player. Please try the latest EclipseLink nightly build and ifle a bug if its still returning a Person instance instead of null.

Best Regards,
Chris
Re: Inheritance bug?: super class instantiated instead of sub class [message #637428 is a reply to message #637414] Fri, 05 November 2010 09:44 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris Delahunt
Messages: 1015
Registered: July 2009
Senior Member
Also note, JPA requires queries on root nodes in an inheritance tree to also return all subclasses matching the selection criteria, but this can be disabled in EclipseLink if required. See InheritancePolicy's setShouldReadSubclasses method, which can be configured through a descriptor customizer.

Best Regards
Chris
Re: Inheritance bug?: super class instantiated instead of sub class [message #637927 is a reply to message #637377] Tue, 09 November 2010 06:36 Go to previous messageGo to next message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
I understand returning sub entities in case of abstract classes, but returning a super entity of a requested sub class really doesn't make much sense. In my design, any (instantiable) person can have a corresponding referee, a coach, *and* a player instance (it can be none or all three of the subs at the same time, disjoint hierarchical relationship).

If I request a person, I want a Person instance if found, if I query for a player, I want a Player instance. It's never a good idea to return the super entity for a requested sub class IMO. It doesn't even make sense (to me) and causes a ClassCassException.

Here's the generalized rule I'd expect:

If the requested entity's class is abstract, find an instantiable, non-ambiguous sub class. This can only be a non-abstract sub class (is distinguished by a discriminator). If the class doesn't
have a discriminator, there can be several sub entities, so the easiest would be to return null here. Alternatively, you could look into all sub tables for the existence of a lone entity and return that.

Pseudo code:

current class = requested class

while current class is abstract
{
	// return nearest instantiable sub entity
	
	if discriminator on current class
	{
		// follow non-ambiguous path... continue
		current class = discriminator sub class
	}
	else
	{
		// possibly ambiguous!
		return null
		
		// or alternatively, find a lone sub entity in any of the subs
	}
}


if entity found of type current class
{
	return entity
}
else
{
	return null
}


I'm not sure if I expect too much here. Does the JPA define what to do in the scenario at hand?

Karsten
Re: Inheritance bug?: super class instantiated instead of sub class [message #637941 is a reply to message #637414] Tue, 09 November 2010 07:26 Go to previous messageGo to next message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
Chris Delahunt wrote on Fri, 05 November 2010 09:28
Which version of EclipseLink are you using, and can you try the latest nightly build?

Best Regards,
Chris


I have used version 2.1.1 and I've just downloaded the nightly build version 2.2.0 from 2010-11-09. Trying now.

Karsten
Re: Inheritance bug?: super class instantiated instead of sub class [message #637958 is a reply to message #637377] Tue, 09 November 2010 08:22 Go to previous messageGo to next message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
I have uploaded three standalone (JavaSE/HSQLDB) test apps using Hibernate 3.6, EclipseLink 2.1.1, and EclipseLink 2.2.0 nightly from 2010-11-09 here:

http://www.kawoolutions.com/media/standalone-eclipselink-2.1 .1.zip
http://www.kawoolutions.com/media/standalone-eclipselink-2.2 .0-r8480.zip
http://www.kawoolutions.com/media/standalone-hibernate-3.6.z ip

A PDF to view the model visually is here:

http://www.kawoolutions.com/media/bbstats.pdf

I still think all ORMs do a bad job here, at least from what I expect. Since EclipseLink always returns an existing super class entity I can never get entities from the concrete sub classes, making EL entirely useless. Hibernate isn't much better, but at least it returns the concrete sub class entities.

See the comments in any of the main().

Karsten
Re: Inheritance bug?: super class instantiated instead of sub class [message #637998 is a reply to message #637958] Tue, 09 November 2010 10:16 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris Delahunt
Messages: 1015
Registered: July 2009
Senior Member
Hello Karsten,

As mentioned, I believe the Find returning other nodes when querying for a root on the inheriatance tree was fixed in 2.2 through bug
327900 - "conform in unit of work queries return subclasses when read subclasses not enabled", which is why you see behavior changes in the test case on 2.1 vs 2.2.

I could not reproduce any problems using a simple inheritance model, and so looked closer at yours. In the test, you are performing:
a)Contact co1 = em.find(Contact.class, 1);
b)Person pe1 = em.find(Person.class, 1);
c)Player pl1 = em.find(Player.class, 1);

and from the comments expecting a Person from a+b, but a Player entity from c. This is expectation is incorrect, as Player, Person and Contact all share the "CONTACT" table - they are using joined table inheritance, the default.
If you check the row in the Contact table, you will see that the "descriminator" field for row id=1 has a value of "Person", and so only a Person entity can be constructed. Looking at the Mysql ddl you included,
the only possible values for the Descriminator field are 'person', 'club', 'arena', so I do not see how it is possible for you to insert a Player or Referee, as they will try to insert a value of "player" and "referee" into the field.

JPA inheritance is strict. If there exists a Person with ID=1, because Person is also a Contact due to inheritance, it means there cannot exist another Contact type or subclass with an ID=1 (since the
discriminator can only have 1 value). I believe you have mapped your object model incorrectly, as I assume a Person can dynamically become a referee or a Player. Java object inheritance does not allow you to 'change' a Person object into a Player object.
if a Person can independently have rows in the Referee and Player tables. Instead, I would create a 1:1 mapping from Referee to Person, and from Player to Person (and if it makes more sense, also have Person->Referee and Person->Player back references).

Best Regards,
Chris
Re: Inheritance bug?: super class instantiated instead of sub class [message #638196 is a reply to message #637377] Wed, 10 November 2010 07:20 Go to previous messageGo to next message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
Hello,

please see the link with the PDF for a visual model. (the darker blue means abstract)

Quote:
JPA inheritance is strict. If there exists a Person with ID=1, because Person is also a Contact due to inheritance, it means there cannot exist another Contact type or subclass with an ID=1 (since the
discriminator can only have 1 value). I believe you have mapped your object model incorrectly, as I assume a Person can dynamically become a referee or a Player.


The person instance with ID = 1 is the one in the DB representing a Contact's discriminator value. This is what I modeled. If you request a Person, return the person, if I query for Contact, it's class is abstract , return the Person then (upcast to Contact okay).

Maybe the JPA is just too strict. I just don't see why EL and Hibernate don't just return a Player instance when I request a Player instance which does exist in the DB:

Player pl1 = em.find(Player.class, 1);


Why should I expect anything else but an existing player row/instance to be returned?

The question is, does the JPA spec, EL, or Hibernate have problems because the Persons table is non-abstract (partial inheritance) or because the Referees, Coaches, and Players sub tables can contain entities in any possible combination (non-disjoint inheritance), resulting in a partial, non-disjoint inheritance tree?

The problem in my design is, that I can't add 'player', 'referee', and 'coach' to the discriminator of Contact, because all persons can be all or none of Persons' subs.

Maybe JPA and/or ORMs don't handle this model yet?

I'm pretty frustrated right now, as I believe it has to do with the existence of the sub entities of Persons, as seen in the latest 2.2.0 fix mentioned by you. It's still not 100% correct IMHO. Again, I fail to understand why EL doesn't just return a Player instance when I request a Player instance which does exist in the DB.

It's also a problem when retrieving TeamMember instances (see PDF), which just reference the Player class. Because EL returns null when requesting a Player, finding a TeamMember sets the player reference to null. It can't be correct IMO.

The pseudo code posted reflects what I believe should happen.

Karsten

[Updated on: Wed, 10 November 2010 07:43]

Report message to a moderator

Re: Inheritance bug?: super class instantiated instead of sub class [message #638216 is a reply to message #638196] Wed, 10 November 2010 09:41 Go to previous messageGo to next message
Chris Delahunt is currently offline Chris Delahunt
Messages: 1015
Registered: July 2009
Senior Member
Hello Karsten,

It is not JPA, nor EclipseLInk and probably not Hibernate. Java cannot handle it - you are describing something that goes against the Java language. A Java object cannot be both a Player and a Referee at the same time - they share Person as their inheritance parent. If you instantiate a Person object, that Person can not be a Player object. So the model you are using is flawed.

As for Find(Player.class, 1) returning Person instances. As mentioned, this is due to the Datase telling EclipseLInk that there is a Person instance to instantiate. There cannot be two instances for the same row - that breaks object identity. What would happen if you changed the Person enitty's name, but kept the Player's name unchanged or changed it to something different? Object identity helps prevent these situations from arising. As such Player does not exist in the database. Yes, there maybe a row in the database table with ID=1. But it does not correspond to the Context row with ID=1, and is issolated from your object model.


As mentioned, your model needs to change. The object model you are attempting does not match the database design. If a person can be a Player independently, it should be mapped as such - as a role or state of the Person object, not a subclass of it. Ie, an isPlayer method rather than checking if it is an instance of a Player object, returns true if there is a non-null player relation. This allows the Person to exist despite its state as a Player changing. I would suggest mapping the Player and Referee table to new Player and Referee entities that only contain Player and referee data, and 1:1 references to the Person entity (which could have references back if you wish).
TeamMember would then also reference Player, which would have a link to its Person Entity.

Regards,
Chris
Re: Inheritance bug?: super class instantiated instead of sub class [message #638239 is a reply to message #638216] Wed, 10 November 2010 10:39 Go to previous message
Karsten Wutzke is currently offline Karsten Wutzke
Messages: 112
Registered: July 2009
Senior Member
Chris Delahunt wrote on Wed, 10 November 2010 09:41

It is not JPA, nor EclipseLInk and probably not Hibernate. Java cannot handle it - you are describing something that goes against the Java language. A Java object cannot be both a Player and a Referee at the same time - they share Person as their inheritance parent. If you instantiate a Person object, that Person can not be a Player object. So the model you are using is flawed.

As for Find(Player.class, 1) returning Person instances. As mentioned, this is due to the Datase telling EclipseLInk that there is a Person instance to instantiate. There cannot be two instances for the same row - that breaks object identity. What would happen if you changed the Person enitty's name, but kept the Player's name unchanged or changed it to something different? Object identity helps prevent these situations from arising. As such Player does not exist in the database. Yes, there maybe a row in the database table with ID=1. But it does not correspond to the Context row with ID=1, and is issolated from your object model.



The model I use isn't flawed, I mean data model here. The model is flawed when used with a Java ORM. Don't get me wrong, I'm not a Java newbie, I know that

Quote:
A Java object cannot be both a Player and a Referee at the same time


..., but: when I query for Player, I want a JPA ORM to return a Player instance with the same data as the Person, Referee or Coach instances. The Player, Referee, and Coach instances on the same ID should be different objects!

Maybe my equals() etc. implementation on that is still wrong, but it should be possible, shouldn't it?

If any of the Person properties get updated, the referee's, coach's person properties should be updated, too. That's what I'd expect. Whether that's possible I don't know. I'm rather new to JPA and ORM, so chances are I don't understand the exact problem here.

What you said basically means, that Java/JPA ORMs can't generally handle any non-disjoint inheritance, at least the way I expressed it via pure Java inheritance, which it is...

Chris Delahunt wrote on Wed, 10 November 2010 09:41

As mentioned, your model needs to change. The object model you are attempting does not match the database design. If a person can be a Player independently, it should be mapped as such - as a role or state of the Person object, not a subclass of it. Ie, an isPlayer method rather than checking if it is an instance of a Player object, returns true if there is a non-null player relation. This allows the Person to exist despite its state as a Player changing. I would suggest mapping the Player and Referee table to new Player and Referee entities that only contain Player and referee data, and 1:1 references to the Person entity (which could have references back if you wish).
TeamMember would then also reference Player, which would have a link to its Person Entity.



Hmmm. A player is-a person, and a referee is-a person, and a coach is a person, too... again, I don't understand why they all shouldn't be distinct classes in a Java class hierarchy. I'm still not convinced, because the DB model "as is" works perfectly. Maybe I just have to reimplement my sub classes to return different objects.

I still fail to understand why an ORM doesn't just return different objects for each Person, Player, Referee, and Coach having the same ID in the DB. After all, they're just different sub entities sharing the same ID "by coincidence", too.

It must be something related to object and entity identity that I'm having problems with. I have to think about it...

Again thanks for helping me
Karsten
Previous Topic:em.persist trigger duplicate insertion
Next Topic:Support for XML column types?
Goto Forum:
  


Current Time: Sat Jul 12 22:34:55 EDT 2014

Powered by FUDForum. Page generated in 0.01947 seconds