Saturday, January 5, 2013

Hibernate Many to Many with Additional Columns

So we had a slight hiccup in our plans to dynamically place products according to their location points on a screen. We had to associate each view with a set of products and each product, a location, easy. But here's the fun part, products could be part of many views and views could be part of many products. This meant we had to accompany location data along with each product that belongs to a view. Which boils down to adding additional columns to a M-M association.

I did, I guess what anyone would do in a situation quite like this, break the problem down in to manageable parts.  First map the association to an entity class, use a composite primary key for the new entity (you don't have to do this, you can go about it the usual way i.e having two individual entities), add the additional columns to the new entity and finally decorate the classes involved with annotations. 

Ok let's get down to business then. Here are my two entity classes, 
@Entity()
@Table(name = "assortment")
public class Assortment {
 private Long id;
 private String imgURL;
 private Set<AssortmentProduct> products = new HashSet<AssortmentProduct>(0);
       @OneToMany(fetch = FetchType.LAZY, mappedBy = "assortmentProductId.assortment", cascade = {
   CascadeType.PERSIST, CascadeType.MERGE }, orphanRemoval = true)
 @Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
 public Set<AssortmentProduct> getProducts() {
  return products;
 }
 // Other Fields, accessors and mutators

And as for the Product entity,
@Entity
@Table(name = "product")
public class Product implements java.io.Serializable {
 private static final long serialVersionUID = 1L;
 private int id;
 private Set<AssortmentProduct> assortmentProducts = new HashSet<AssortmentProduct>(
   0);
@OneToMany(fetch=FetchType.LAZY,mappedBy="assortmentProductId.product")
 public Set<AssortmentProduct> getAssortmentProducts() {
  return assortmentProducts;
 }
 // Other Fields, accessors and mutators


Here is my composite primary key class,
@Embeddable
public class AssortmentProductId implements Serializable {
 private static final long serialVersionUID = -3712895453750701217L;
 private Assortment assortment;
 private Product product;
        /**
  * other stuff 
  */

Make sure you override the equals and hashcode methods of your composite primary key class as its important to do so.
And now the association entity,

import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name = "assortment_product")
@AssociationOverrides({
  @AssociationOverride(name = "assortmentProductId.product", joinColumns = { @JoinColumn(name = "product_id") }),
  @AssociationOverride(name = "assortmentProductId.assortment", joinColumns = { @JoinColumn(name = "assortment_id") }) })
public class AssortmentProduct {

 private AssortmentProductId assortmentProductId = new AssortmentProductId();
 private PlaceHolder placeHolder;

 @EmbeddedId
 public AssortmentProductId getAssortmentProductId() {
  return assortmentProductId;
 }

 public void setAssortmentProductId(AssortmentProductId assortmentProductId) {
  this.assortmentProductId = assortmentProductId;
 }

 @Transient
 public Assortment getAssortment() {
  return assortmentProductId.getAssortment();
 }

 @Transient
 public Product getProduct() {
  return assortmentProductId.getProduct();
 }

 @OneToOne
 public PlaceHolder getPlaceHolder() {
  return placeHolder;
 }
}
And that's it. 

Friday, January 4, 2013

Using DB4o

db4o is an excellent Database Management System, it's a fresh breath of air to us relational database users. For years we have been battling with Object/Relational impedance mismatches. And I am not about revive an age old debate here, but I have been giving this some serious thought ever since our last client rejected using a really popular ORM outright(sadly I think you know what it is), in his words "it's too slow". A little part of me died that day. His solution, a home grown framework, a quite amazing one at that. Maintainability was a little price to pay for much sought after performance gains, it wasn't too difficult too once you get the hang of it.

I digressed a little, apologies, anyways getting back to db4o. What I love about it is it's simplicity. Ok lets talk basic necessities, what do you need to get this baby up and running, a cuppa coffee or a can of redbull, you sit right down after a sip, get your data model pinned down. And that's it. Yeah that really is it. There's no more complex mappings, annotations or configurations. It really is that simple.

Lets talk querying, db4o offers three types of querying techniques, There's Query By Example(QBE), Native Queries(NQ) and SODA Query API. For a newbie QBE offers the perfect solution, its pretty much the same as Hibernate, apart from a few trivial differences you can totally wrap your head around. Native queries are even easier, they offer type safety, compile-time checks and refactorability, how cool is that. SODA Query API is db4o's low level querying API,it's pretty verbose for my liking. 

db4o is perfect for freely distributed applications under proper licences, although stepping off this boundary would cost you. How much you ask, well it depends. Is it worth it, well it depends too, but that's a topic best left for another post, if the suspense is killing you, http://www.db4o.com/ offers very compelling reasons why you should.

Let's get dirty with a sample application then, apologies in advance for my data model, I was merely testing this out in an actual application and too lazy to whip up a quick starter pack.

Ok then, here's the maven dependency you need, now there are a few complaints about the unavailability of this particular repository (and yes you won't find this in the default maven repository) although I haven't personally been at the receiving end of this. You need to add the following to your maven configuration.


<repositories>
 <repository>
  <id>source.db4o</id>
  <url>http://source.db4o.com/maven/</url>
 </repository>
</repositories>

And now we are all set to resolve the dependency.

<dependency>
 <groupId>com.db4o</groupId>
 <artifactId>db4o-core-java5</artifactId>
 <version>8.1-SNAPSHOT</version>
</dependency>

As for the data model, now this is a model class I created to persist image downloads be warned as it's simplicity might astound you.

public class DownloadImage{
 public String imageUrl;
 public DownloadImage(String imageUrl) {
  this.imageUrl = imageUrl;
 }

 public String getImageUrl() {
  return imageUrl;
 }
}

Here comes the heavy works, now it's best if you keep the connection awake/open as long as possible, why you ask, simples, for performance, when you open and close connections, db4o has to reload data, recreate class metadata/structures etc etc there's a lot of re-'s, although these are standard procedures and hence fast, you don't need this additional overhead, you opted to use db4o for its performance gains after all right. Right.

Now in the code to follow I am using the singleton pattern and my ObjectContainer (definition to follow) is created/opened only once, and it comes with a neat built-in shutdown hook, so I thought what the heck. 


import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.constraints.UniqueFieldValueConstraint;
import com.kohls.strategies.DownloadImage;

public class DatabaseConnection {
 private DatabaseConnection() {

 }

 private static final ObjectContainer OBJECT_CONTAINER = Db4oEmbedded
   .openFile(Configuration.getConfig(), PropertyLoader.getDbLocation());

 private static class Configuration {
  private static EmbeddedConfiguration getConfig() {
   EmbeddedConfiguration CONFIGURATION = Db4oEmbedded
     .newConfiguration();
   CONFIGURATION.common().objectClass(DownloadImage.class)
     .objectField("imageUrl").indexed(true);
   CONFIGURATION.common().add(new UniqueFieldValueConstraint(DownloadImage.class,"imageUrl"));
   return CONFIGURATION;
  }
 }

 public static ObjectContainer getInstance() {
  return OBJECT_CONTAINER;
 }

 private Object readResolve() {
  // Return the one true instance and let the garbage collector
  // take care of the impersonator.
  return OBJECT_CONTAINER;
 }
}

Hmm now what's that shifty bit there with the private static class, well by default db4o won't check for uniqueness of your records, hence you can store multiple, yeah multiple identical records. To avoid that you need to add constraints. But no, this alone won't guarantee uniqueness. You need to be little clever and put additional checks in place during persistence, a small price to pay if you ask me. 

At this point I think I should tell you what an Object Container is. An Object Container represents your database and all operations would be executed using this. You can have multiple Object Containers.

And finally for the DAO class, cleverly disguised as a service class, I dropped the backing interface definition for this for simplicity.



import java.util.Collection;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.constraints.UniqueFieldValueConstraintViolationException;
import com.kohls.service.DataService;
import com.kohls.strategies.DownloadImage;
import com.kohls.utils.DatabaseConnection;

public class DataServiceImpl {
 private ObjectContainer db = null;

 public void addDownloadable(DownloadImage downloadable) {
  try {
   db = DatabaseConnection.getInstance();
   db.store(downloadable);
   db.commit();
  } catch (UniqueFieldValueConstraintViolationException e) {
   db.rollback();
  } finally {
  }
 }

 public Collection<DownloadImage> getDownloadables() {
  ObjectSet<DownloadImage> downloadables = null;
  try {
   db = DatabaseConnection.getInstance();
   downloadables = db.queryByExample(new DownloadImage(null));
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
  }
  return downloadables;
 }

 public Collection<DownloadImage> getDownloadableByUrl(String url) {
  ObjectSet<DownloadImage> downloadables = null;
  try {
   db = DatabaseConnection.getInstance();
   downloadables = db.queryByExample(new DownloadImage(url));
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
  }
  return downloadables;
 }

 public DownloadImageupdateDownloadable(DownloadImage downloadable) {
  ObjectSet<DownloadImage> result = null;
  DownloadImagefound = null;
  try {
   db = DatabaseConnection.getInstance();
   result = db.queryByExample(downloadable);
   found = result.next();
   //do something with it
   db.commit();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return found;
 }

 public void delete(DownloadImage downloadable) {
  ObjectSet<DownloadImage> result = null;
  DownloadImagefound = null;
  try {
   db = DatabaseConnection.getInstance();
   result = db.queryByExample(downloadable);
   found = result.next();
   db.delete(found);
   db.commit();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Every operation happens in a transaction hence the explicit commits. Apart from this , explicit commits also allow you to capture exceptions, allowing you to, yeah you guessed it, rollback. It's good practice when you are storing or deleting records, to grab the record in question first.

Ok that's all I have time for.