views:

46

answers:

3

I have two entities: Vehicle and Weapon classes. They are mapped respectively to Vehicle.hbm.xml and Weapon.hbm.xml. Both have their respective DAOs. All are working fine.

Here's my idea. I want to create a class VehicleWeapon that has all the properties of the Vehicle and Weapon entities. I want to declare a Hibernate mapping file: VehicleWeapon.hbm.xml. I want the properties in the mapping file to match the properties of the Weapon and Vehicle entities.

So that when I add, delete, or save a VehicleWeapon entity, the corresponding changes will reflect on the backing Vehicle and Weapon tables. (I could probably do an HQL query, but I want to know first if the idea I'm asking is doable).

Is this possible? If yes, can you show me an example? (Of course, the Vehicle and Weapon classes are hypothetical classes. I do have an actual scenario where I need this idea to be implemented).

Or what better options are there? Can you provide a direct link or concrete example? Thanks a lot.

A: 

Not sure whether this is the answer, but Hibernate does allow for “compound” relationships, whereby you embed one class within another, and that gets mapped.

For example, here we create the "embeddable" class (Vehicle) and then "embed" it in VehicleWeapon:

import javax.persistence.Embeddable;

@Embeddable
public class Vehicle
{
    ...
}

Embedded here:

@Entity
public class VehicleWeapon
{
  private long id;  
  private String name;
  private Vehicle vehicle;

  ...

    @Embedded
    public Vehicle getVehicle()
    {
      return vehicle;
    }

    public void setVehicle(Vehicle vehicle)
    {
        this.vehicle = vehicle;
    }

    ...
}

Would that help in your scenario?

Ben Poole
Thanks for the reply. This didn't directly answer my question but it did help me (by inspiring me) point to the right direction. Because of your suggestion, I was able to find an answer. I'm gonna post it as an answer on this page as well.
chris
A: 

I found a working solution. As I mentioned earlier, I'm not allowed to modify the Vehicle and Weapon entities and the corresponding mapping files. Not even a single annotation is allowed.

I found this answer by Googling Ben's suggestion above which lead me to these articles: http://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example/ and http://www.vaannila.com/hibernate/hibernate-example/hibernate-mapping-one-to-one-1.html

The suggestion from mkyong is good but I didn't like the idea of keeping and assigning primary keys both ways as I have no way of editing the mapping files from the Vehicle and Weapon classes. See the comment "the main difficulty with one-to-one relationship is ensuring both are assigned the same primary key"

The suggestion from vaannila is somehow intriguing. It does a one-to-one relationship but without requiring any edits on other mapping files.

First, I declared my VehicleWeapon.hbm.xml as follows:

<hibernate-mapping package="com.armory">

    <id name="id" type="long" >
        <column name="id"  />
        <generator class="native" />
    </id>

    <many-to-one name="vehicle" class="Vehicle" 
            column="vehicle_column"  cascade="all" unique="true" />

    <many-to-one name="weapon" class="Weapon" 
            column="weapon_column"  cascade="all" unique="true" />

Then I declared my standard VehicleWeapon POJO class. And the corresponding DAO implementation.

I run a couple of Junit/Spring tests. I was able to save, delete, retrieve without problems. All actions cascade to the corresponding Weapon and Vehicle tables.

The only downside with this method is Hibernate will create a third table vehicle_weapon that contains the id for the vehicle and weapon tables respectively as a reference.

The good thing is I didn't edit any existing entities or mapping files. I created a new one and compose a new object from two tables.

I still like to be able to map directly to the Weapon and Vehicle's properties instead of mapping directly to the Weapon and Vehicle entities. But for now, I think this is acceptable.

chris
A: 

Hibernate provides support to persist one entity class across multiple tables using the <join> element (or the @SecondaryTable annotation when using annotations). From the documentation:

5.1.20. Join

Using the <join> element, it is possible to map properties of one class to several tables that have a one-to-one relationship. For example:

<join
        table="tablename"                        (1)
        schema="owner"                           (2)
        catalog="catalog"                        (3)
        fetch="join|select"                      (4)
        inverse="true|false"                     (5)
        optional="true|false">                   (6)

        <key ... />

        <property ... />
        ...
</join>
  1. table: the name of the joined table.
  2. schema (optional): overrides the schema name specified by the root element.
  3. catalog (optional): overrides the catalog name specified by the root <hibernate-mapping> element.
  4. fetch (optional - defaults to join): if set to join, the default, Hibernate will use an inner join to retrieve a <join> defined by a class or its superclasses. It will use an outer join for a defined by a subclass. If set to select then Hibernate will use a sequential select for a <join> defined on a subclass. This will be issued only if a row represents an instance of the subclass. Inner joins will still be used to retrieve a <join> defined by the class and its superclasses.
  5. inverse (optional - defaults to false): if enabled, Hibernate will not insert or update the properties defined by this join.
  6. optional (optional - defaults to false): if enabled, Hibernate will insert a row only if the properties defined by this join are non-null. It will always use an outer join to retrieve the properties.

For example, address information for a person can be mapped to a separate table while preserving value type semantics for all properties:

<class name="Person"
    table="PERSON">

    <id name="id" column="PERSON_ID">...</id>

    <join table="ADDRESS">
        <key column="ADDRESS_ID"/>
        <property name="address"/>
        <property name="zip"/>
        <property name="country"/>
    </join>
    ...

This feature is often only useful for legacy data models. We recommend fewer tables than classes and a fine-grained domain model. However, it is useful for switching between inheritance mapping strategies in a single hierarchy, as explained later.

Using composition as you did is another option but it will obviously introduce another table (for the "aggregating" entity).

Pascal Thivent
Thanks for the response. You are indeed correct. The composition I suggested does introduce a new table. I have read previously the solution you've provided. The reason why I didn't try it is because I thought it's going to create another table anyway as suggested by the syntax: <class name="Person" table="PERSON"> . Or is this table-"PERSON" only exist in memory? Also, another deterrent was "This feature is often only useful for legacy data models. We recommend fewer tables than classes and a fine-grained domain model."
chris
Either way, I'm gonna try this. So that I don't have to recursive manually the properties of the Weapon and Vehicle entities.
chris
@chris The `Person` class would be `VehicleWeapon`, the table PERSON would be VEHICLE and the table ADDRESS would be WEAPON in your case (in other words, `VehicleWeapon` would be mapped on the existing VEHICLE and WEAPON tables). This solution assumes there is a way to do the join between both tables (i.e. a FK in VEHICLE). Regarding the note about "legacy database", it simply means that the recommendation would be to avoid joins and merge tables (storage is cheap now), if possible of course. But it doesn't mean the support provided by Hibernate is not recommended.
Pascal Thivent
@Pascal, thanks for the info. I've tried your suggestion. It does work. I don't need to re-create the properties for the VehicleWeapon. By the way, VehicleWeapon.hbm.xml will map to two join tables: VEHICLE and WEAPON. Vehicle and Weapon do not have any relation of whatsoever. The downside of using join is it creates a foreign key constraint in VEHICLE and WEAPON tables respectively.
chris
The problem with this foreign key constraint is my separate DAO for VEHICLE and WEAPON throws a foreign key constraint error when inserting and deleting. The program's requirement is that the VehicleDAO and WeaponDAO should still work as stand alone. The solution I posted earlier, the composition, doesn't add any foreign key constraint; HOWEVER, I would have to map manually the properties, using a mapper like Dozer. I guess there are PROs and CONs. I'm gonna remember both techniques just in case I need them.
chris
The downside of having no foreign key constraint is that an external class can delete/update a VEHICLE or WEAPON entries. The VEHICLEWEAPON has no knowledge or control of this action which may result to orphan or missing entries. I'm not sure if I'm making myself clear. In any case, I'm noting my observation here for future readers.
chris
@chris The link between a VEHICLE record and a WEAPON record **has to** exist somewhere and I assumed it was existing in the WEAPON table (so to delete of a WEAPON, you'd have to nullify the FK first, that's the only problematic case). What I don't get is that you should have the exact same problem with the composition approach, the VEHICLE_WEAPON should have a foreign keys for both the VEHICLE and the WEAPON, no?
Pascal Thivent
@chris Or maybe you don't use referential integrity for VEHICLE_WEAPON (now that I reread your comment, this seems to be the case). I can't say I'm comfortable with not using referential integrity. But I don't have all the requirements :)
Pascal Thivent
@Pascal, yes you're right. There's no referential integrity in VEHICLE_WEAPON. It's just a plain Hibernate mapping file. The problem with including referential integrity is it will break the other features of the existing project as the standalone VEHICLE and WEAPON daos will throw foreign key constraints error. Probably from the start, the database design was designed very simplistically. Unfortunately, there's no turning back and the original developer has left the project.
chris