Wednesday, February 15, 2006

Sharing attributes across Entities

One way to share common attributes across entities is to use inheritance, and put the common attributes in a super class. Another way is to abstract the common attributes into a separate Embeddable class and then embed it within the regular Entity classes. The latter approach has been used for some of the entities in the EJB3Demo project.

The entities Customer, District and Warehouse all contain some very similar columns for storing address information. I created a new class called Address to hold this common data. The Address class is marked as @Embeddable and contains the getters/setters for the attributes. Here is an extract:
@Embeddable public class Address implements Serializable {
String street1;
String city;
@Column(length=20)
public String getCity() {
return city;
}
@Column(length=20)
public String getStreet1() {
return street1;
}
}
Note that the getters have been annotated using @Column - this is useful for capturing the bits that are shared across the entities.

When you embed the Address object within an Entity, you can fine-tune the column mapping. Example:
@Entity
public class Customer {
Address address;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="street1", column=@Column(name="C_STREET_1")),
@AttributeOverride(name="city", column=@Column(name="C_CITY")),
})
public Address getAddress() {
return address;
}
}
The getter for the Address object is decorated with the @Embedded annotation. The address object can be shared amongst multiple entities, and each entity can define its own column names, etc.

When you access embedded objects in EJBQL, you use the property name in the Query statement. For example, to access the city field in Customer, you would write:
SELECT c.address.city FROM Customer c
This syntax is also used when you have relationship mappings. For example, a District belongs to a Warehouse, and hence the District object contains an instance of the Warehouse object. To access the fields within Warehouse, you would write:
SELECT d.warehouse.name FROM District d
Although one could use inheritance to model above, I prefer to use the Embeddable class option as it more accurately reflects what is happening here. A Customer is not an instance of Address - a Customer has an Address. In this situation, Has-A relationship is more natural than Is-A relationship.

3 comments:

Geoff Granum said...

A lot of wonderful tips in your blog, you've already helped quite a bit in my little quest to figure out what I am doing. Maybe you would be so kind as to answer a question for me :~)

I am getting the impression that my DB design is bad: basically I am using pointers to an address column and pretending at some sick, twisted level that a database can be sort of object oriented.

So, a tbl_workSite Table has a whole bunch of information about a job site, and a site_address field which is an int, and points to the PK on tbl_Address.

I also have an 'owner_address' field in tbl_workSite which also points to the PK of tbl_Address.

How in the world to connect them? Embedded looks like it would work great... but I cannot find in the spec where it allows for the use of the @Table annotation. In fact, the spec states 'Each of the persistent properties or fields of the
embedded object is mapped to the database table for the entity'


Of course it isn't shockingly hard to do a manual query and grab the object on my own. But man it is pretty to not have to. To the point where I will gladly rebuild the db schema, as it is a work in progress anyway. I just LIKE having a nice, abstract 'Address' table. Every bloody thing I work with has an address. Most of them have two! Some have three! But the impression I am getting is that doing so is bad DB design. I can see, logically, why. But I am happy to kill a tiny bit of efficiency to get a whole lot of sanity...
Says the man using 4 different programs to build JSF pages. (Dreamweaver, Textpad, JBuilder and DBManager)

Anyway :~)
If you answer the question on your blog I will see it, or my e-mail addy is geoff_9@granum.biz. And if you don't have time, I'm sure I'll figure it out eventually ;~)

Thanks for the tips you've got up!

Cheers,
Geoff

Dibyendu said...

Hello Geoff,

If I understand you correctly, you are storing addresses in a separate table. I think that this is fine.

In this case, you should probably use regular join semantics. That is, embed an Address object in your class, provide getter/setter for it, and then annotate the join condition.

Address should be another Entity.

For example:
@Entity
class Container {
Address address;
@ManyToOne(..)
public Address getAddress() {}
}

@Entity
class Address {
}

I hope this helps.

Regards

Geoff Granum said...

Ok, I'm happier now. I was under the mistaken impression that all @(DBRelation) annotations required an owner and a child. You can do it with JUST an owner, e.g. define the table:

Customer
id INT PK
name VarChar
addr1 INT
addr2 INT
--
Address
id INT PK
street1
street2
zip

Define the java:
@Entity
@Table(name = "address")
public class Addy {
Integer id;
String street1;
String street2;
public Addy() { }
@Id
public Integer getId() {
return id;
} // setters and getters for street1, street2. Thats it.
}

Define the Customer class:

@Entity
@Table(name = "customer")
public class Customer {
private int id;
private String name;
private Addy addr1;
private Addy addr2;
@Id
public int getId() {
return id;
}
@OneToOne()
@JoinColumn(name = "addr1")
public Addy getAddr1() {
return addy1;
}
@OneToOne()
@JoinColumn(name = "addr2")
public Addy getAddr2() {
return addy2;
}
// the rest of getters and setters; no more annotation needed
}


I post this back mostly on the thought that, since I googled too here, someone else might :~).

Thanks for your response, it is much appreciated.

For the random visitor who cares to check: I *might* put up a write up on the pain of the first week of JSF with resource injection on my site; it's worth a click anyway. If I make one, it will be obvious on the main page.

I'm sure I'll need to reference it when I need to start a new site ;~)

Cheers,
Geoff