views:

586

answers:

3

I'm new to hibernate, as you'll soon see. I apologize if this question has an easy answer, but I'm just not familiar enough with all of the terminology to find it easily.

Let's say I have a base class "A" and one subclass "B" that I'm mapping with Hibernate, perhaps using the table per subclass strategy. The base class is not abstract. All Bs are As, but not all As are Bs. This is reflected in the database, where table B references table A.

Ok, now suppose I have a program of some sort that displays a list of A objects. The user can select any A object and be taken to a screen to modify it...BUT, if the A object is also a B, the screen will allow the user to modify B instead of just A.

How in the world do I approach this?

Note: I'm not asking about how to determine what class an object is. What I'm asking is how do I get hibernate to return a list of objects that are of the proper class.

+1  A: 

You could use RTTI, with instanceof, but that's not object-oriented.

Instead, give the base class, A a method that pertains to what extra a B does, e.g, if A is Customer and B is PreferredCustomer, the method might be isPreferredCustomer().

In A, the method return false, in B it returns turn.

It's important to note that we're not asking if an object is of a certain class; we're asking a business question, asking an object if it can do something. It's a subtle but important distinction.

In particular, it means that your code below won't have to change when and if you add more Customer subclasses, so long as each subclass truthfully answers the question, isPreferredCustomer().

In your code:

if( a.isPreferredCustomer() ) {
  showPreferredCustomerPage( a) ;
else { 
  show CustomerPage(a);
}

You might think it even better to give Customer a showPage() method, but that too tightly binds your models to your views.

Instead, you put the above code in some ViewDispatcher class, which can vary orthogonally with Customer and its subclasses.

For example, you might have several ViewDispatcher subclasses, some of which care about PreferredCustomers and some which don't.

tpdi
Good point not to mess with types in business logic - never realy thought about that before. +1
Daniel Brückner
But in this case A doesn't know anything about B. There are no properties of A, either in the class or the table being mapped, that indicate whether A is also a B.
Boden
One point of subclassing is to be able to add additional functionality to the class without having to modify the baseclass. This solution muddies up A with details of B. What happens when you get C which has a different customer type. Now you need to modify isPreferredCustomer and it's not boolean.
digitaljoel
null, your point is a good one, and I don't want to dismiss it. Do you see a better way to do this?
tpdi
Boden, A doesn't have to know about B. It does, for my solution to work, have to know if it's a PreferredCustomer, or really, if it can support PrefferedCustomer operations.
tpdi
@tpdi - right, but I'm not sure how I'd map that. I would have to do something like query for A left join B and then return an A or B object where appropriate. It's really the mapping part that I'm asking about.
Boden
You're using Hibernate. Assuming you set up your Hibernate XML/Annotations/whatever, Hibernate will do the querying for you.
tpdi
@tpdi Unfortuntely, other than instanceof, which you already addressed, I don't have a better solution. If I did, I would have posted it as an answer. Not sure why I'm showing up as 'null' that's not my name.
digitaljoel
null's a pretty cool name.
tpdi
+2  A: 

I apologize again for this question. I'm very surprised at how hibernate works, it's really cool. I didn't think it would do all of this automagically, and I really wasn't even sure what I was trying to ask. As I responded to comments I started to refine the question in my head and was able to then find the answer I was looking for. Thanks to everyone who helped.

The answer is: hibernate does this automatically.

Suppose you have in your database table A with a primary key "id", and a table B that has a primary key called "a_id" that references table A.

So you create the following classes (abbreviated):

public class A {
  private String aProperty;
  // ... getter and setter, etc
{

public class B extends A {
  private String bProperty;
  // ... getter and setter, etc
}

Then map them like so:

<hibernate-mapping>
    <class name="A" table="a" catalog="testokdelete">
        <id name="id" type="java.lang.Integer">
            <column name="id" />
            <generator class="identity" />
        </id>
        <property name="aProperty" column="a_property"/>
        <joined-subclass name="B" table="b">
            <key column="a_id"/>
            <property name="bProperty" column="b_property"/>
        </joined-subclass>
    </class>
</hibernate-mapping>

And you can return A and B objects using a simple "from A" query, as in the following code:

Query query = session.createQuery("from A");
List<Object> objects = query.list();
for (Object object: objects) {
    System.out.print("A property: ");
    System.out.print(((A)object).getAProperty());
    System.out.print(", B property: ");
    System.out.println( (object.getClass() == B.class) ? ((B)object).getBProperty() : "not a B");
}

All it does is return a list of objects using the query "from A", and then walks through them printing out aProperty from our A class and, if the class is of type B, the bProperty from our B class.

The hibernate query in this case is automatically polymorphic and will give you a B object when appropriate.

Boden
Yes, Hibernate does this. But using (object.getClass() == B.class) ? ((B)object) is RTTI. You shouldn't have to do this, and doing it is indicative that your classes are poorly designed.
tpdi
This looks like simple debugging code. It doesn't necessarily imply the "real" classes would be used this way. However, sometimes RTTI is needed, and in those cases the proper idiom for testing is `object instanceof B`, not `object.getClass() == B.class`.
erickson
+1  A: 

Alright well I'm going to assume that you have your classes mapped properly. If you have done this you should be able to do the following, I think:

public List<A> getAllClassA(){
      Session session = HibernateUtil.getSession();
      Criteria criteria = session.createCriteria(A.class);

      List<A> ret = criteria.list();

      return ret;
}

If you aren't familiar with Criteria, its pretty simple to pick up. This snippet of code will retrieve all objects of class A out of the database. If you have it mapped properly I think that Hibernate will also maintain the the B attributes of your A objects if they are in fact B objects (sorry if that was confusing).

Let me know if this is was you were looking for or if you need the code for HibernateUtil.

tkeE2036