-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Polymorphic query/Inheritance question
PostPosted: Fri Nov 02, 2007 2:48 pm 
Newbie

Joined: Fri Oct 12, 2007 5:18 pm
Posts: 10
Hi,

Below is the basic skeleton of part of my business model (I only kept the primary key attributes/relationships and removed all the other trivial stuff):

Code:
@MappedSuperclass
public abstract class Person implements Serializable {

    private Long id;

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="tableGenerator")
    public Long getId() {
        return this.id;
    }
   
    private void setId(Long id) {
           this.id = id;
    }

}

@Entity
@Inheritance
@DiscriminatorColumn(name = "DISCRIMINATOR")
public abstract class Administrator extends Person {

    private AdministratorAppUser appUser;

    @OneToOne(mappedBy = "administrator", cascade = ALL)
    public AdministratorAppUser getAppUser() {
        return appUser;
    }

    public void setAppUser(AdministratorAppUser appUser) {
        appUser.setAdministrator(this);
        this.appUser = appUser;
    }
}

@Entity
public class SystemAdministrator extends Administrator {
}

@Entity
public class CustomerSupport extends Administrator {
}


@Entity
public abstract class Customer extends Person {

    private CustomerAppUser appUser;

    @OneToOne(mappedBy = "customer", cascade = ALL)
    public CustomerAppUser getAppUser() {
        return appUser;
    }

    public void setAppUser(CustomerAppUser appUser) {
        appUser.setCustomer(this);
        this.appUser = appUser;
    }
}


@Entity
@Inheritance
@DiscriminatorColumn(name = "DISCRIMINATOR")
public abstract class AppUser implements Serializable {

    private Long id;

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="tableGenerator")
    public Long getId() {
        return this.id;
    }
   
    private void setId(Long id) {
           this.id = id;
    }
   
    @Transient
    public abstract Person getPerson();
}


@Entity
public class AdministratorAppUser extends AppUser {

    private Administrator administrator;

    @OneToOne(optional = false)
    @JoinColumn(name = "ADMINISTRATOR_ID")
    public Administrator getAdministrator() {
        return administrator;
    }

    public void setAdministrator(Administrator administrator) {
        this.administrator = administrator;
    }

    @Override
    @Transient
    public Person getPerson() {
        return getAdministrator();
    }
}

@Entity
public class CustomerAppUser extends AppUser {

    private Customer customer;

    @OneToOne(optional = false)
    @JoinColumn(name = "CUSTOMER_ID")
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    @Transient
    public Person getPerson() {
        return getCustomer();
    }
}


And here's the physical model:

table ADMINISTRATOR(contains entities SystemAdministrator and CustomerSupport)
table CUSTOMER (contains entity Customer)
table APP_USER (contains entities AdministratorAppUser and CustomerAppUser)

In other words, I have customers (Customer) and administrators (Administrator) that can login to a site. Administrators are divided in types: system administrators (SystemAdministrator) and customer support people (CustomerSupport). All of these entities share a mapped superclass (Person) (for a reason that I don't want to get into right now, this cannot be an Entity). The credentials of each of these people are stored in a separate table (AppUser).

If I kept the relationships between the people and their assocatied user unidirectionnal, the login process would have to do many EQL queries to find the person whose actually logging base solely on his credentials because querying the AppUser entity directly wouldn't allow my to go back to its "owner". So I figured I would make the relationship bi-directionnal. All the people who own a user do not share a parent entity (Person being a mapped superclass, it cannot be the target of a relationship), so I cannot have a polymorphic relation from AppUser back to any of its owner. I figured I would sub-class the AppUser entity for each of the people who own it (AdministratorAppUser, CustomerAppUser) and create the bi-directionnal relation in each subclass back to the proper owner (AdministratorAppUser => Administrato and CustomerAppUser => Customer). Now, when I query the AppUser entity, it instantiates the proper sub-class (AdministratorAppUser or CustomerAppUser), on which I can invoke the abstract method "getPerson", which is overrided in each of the sub-class to return the true owner using a typed relation in each one (AdministratorAppUser returns getAdministrator() and CustomerAppUser returns getCustomer()). This all works great but I can't get it to optimize the number of queries.

Let's say I do this:

Code:
AppUser user = (AppUser)entityManager.createQuery("select u from AppUser as u where u.username = 'blabla' and u.password = 'potato'").getSingleResult();
Person owner = user.getPerson();


Here are the 2 SQL queries generated (again I removed all the trivial columns):

Code:
/* load org.mcgill.moxxi.entity.user.AppUser */ select
       appuser0_.ID as ID1_3_,
       appuser0_.DISCRIMINATOR as DISCRIMI1_1_3_,
       administra1_.ID as ID0_0_,
       administra1_.DISCRIMINATOR as DISCRIMI1_0_0_,
       customer2_.ID as ID8_1_,
       customer2_.DISCRIMINATOR as DISCRIMI1_8_1_
   from
       APP_USER appuser0_
   left outer join
       ADMINISTRATOR administra1_
           on appuser0_.ADMINISTRATOR_ID=administra1_.ID
   left outer join
       CUSTOMER customer2_
           on appuser0_.CUSTOMER_ID=customer2_.ID
   where
       appuser0_.ID=?|#]


Code:
/* load org.mcgill.moxxi.entity.administrator.AdministratorAppUser */ select
       administra0_.ID as ID1_1_,
       administra1_.DISCRIMINATOR as DISCRIMI1_0_0_
   from
       APP_USER administra0_
   left outer join
       ADMINISTRATOR administra1_
           on administra0_.ADMINISTRATOR_ID=administra1_.ID
   where
       administra0_.ADMINISTRATOR_ID=?
       and administra0_.DISCRIMINATOR='AdministratorAppUser'|#]


You see that in the first query, it saw the the AppUser had 2 sub-classes that each defined a OneToOne with fetch = EAGER (the default for @OneToOne) so it generated 2 left outer joins, one for each of them (CustomerAppUser.customer pointing to the CUSTOMER table and AdministratorAppUser.administrator pointing to the ADMINISTRATOR table). What I cannot explain is the 2nd query. When debugging, I found that the 1st query hydrated 2 entities (AdministratorAppUser and Administrator). Since my test was with an admin user, this makes sense. What I dont' understand is that when it initializes the Administrator entity, it tries to reload the invert part of the relation that points back to the AdministratorAppUser (the Administrator.appUser property) but using the primary key of the Administrator itself. That one, I can't figure out. Would anyone know why?

I also have a problem with auto generation of the database schema. For my AppUser class hierarchy, I have a single table inheritance strategy (the default value of @Inheritance). In the CustomerAppUser and AdministratorAppUser classes, I would like to make the relations that point back to the proper owner (CustomerAppUser.customer and AdministratorAppUser.administrator) optional = false because from their point of view, it doesn't make sense for those values to be null. The thing is if I do, both foreign key columns are generated not null. Since they are stored in the same table and are mutually exclusive, this is a problem. How come the DDL tool didn't realize that even though each relation is defined as mandatory, since they are defined in a specific sub-class of a single table inheritance strategy and not in the base class, it should generate a nullable column because they are mutually exclusive?

Sorry of this is a little bit hard to follow. I can provide more details if necessary. Thank you very much for any help you can provide.

Hibernate Core version: 3.2.5
Hibernate Entity Manager version: 3.3.1
Hibernate Annotations version: 3.3.0


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.