Generics with Hibernate – part II

In the previous post I have shown how to couple Hibernate and Generics to write all the code needed for CRUD operations in (basically) one class.
Forgive me a little digression. Few months ago I was working on a project (written by someone else, of course); one of the admin recreated the database we where using on a different place and: BANG! the application stopped working. To cut a long (and sad) story short, the previous developer have had the brilliant idea to put a lot of triggers inside the database to store backup data during CRUD operations. But when the admin recreated the database, he used a different “user”, so all the nice triggers previously created could not be accessed by the application, and it stop working.
My opinion on this story is that we are developer, and all the database work needed for the business domain of our application should be driven by our code – otherwise who can tell what could happen?
Now, in one of my project the data versioning, I mean keeping track of all the modifications done to the stored record, was part of the application domain, so I begun to search I smart way to do that (of course, without triggers!).
You know what, that was how I begun to implement the Generic Hibernate access I have shown in the previous post.
But now, stop chit-chat, and do some real work.
Basically, we have an entity, for example a customer, with a property, let’s say his mood, that may change over time, but we want to keep track of all his moods, and when he changed it. Of course, the easiest way would be to just save all modifications in the same table, but then a couple of problem arise:
1) maybe some part of data can not change: for example our customer has always the same name, but with this solution we would copy again and again the same information – useless;
2) most time we would need to access just the current customer state, but the table would be bigger because it holds also all the previous ones, so access would be slower.
A different approach could be to have two entities, one representing the current state and one representing the previous (let’s call it “version”) state. The minimum set of properties needed are the ids and the creation/modification times:

@MappedSuperclass
public abstract class AbsCurrentDTO {

	protected long id;
	protected Date creationDate;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(unique = true, nullable = false)
	public long getId() {
		return id;
	}

	public void setId(long idParam) {
		id = idParam;
	}

	@Column(name = "CREATION_DATE", nullable = false)
	public Date getCreationDate() {
		return creationDate;
	}

	public void setCreationDate(Date creationDateParam) {
		creationDate = creationDateParam;
	}
}

@MappedSuperclass
public abstract class AbsVersionDTO {

	protected long id;
	protected Date modificationDate;
	protected long currentId;

        @Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(unique = true, nullable = false)
	public long getId() {
		return id;
	}

	public void setId(long idParam) {
		id = idParam;
	}

	@Column(name = "MODIFICATION_DATE", nullable = false)
	public Date getModificationDate() {
		return modificationDate;
	}

	public void setModificationDate(Date modificationDateParam) {
		modificationDate = modificationDateParam;
	}

	public long getCurrentId() {
		return currentId;
	}

	public void setCurrentId(long currentIdParam) {
		currentId = currentIdParam;
	}

Here, currentId is needed to link all the VersionsDTO belonging to the same CurrentDTO. Remember, @MappedSuperclass here means that this classes would not be mapped to any real table.
Here’s the concrete entities:

@Entity
@Table(name = "CUSTOMER_CURRENT")
public class CustomerCurrentDTO extends AbsCurrentDTO {

	protected String name;
	protected String mood;

        @Column(name = "NAME", nullable = false)
	public String getName() {
		return name;
	}

	public void setName(String nameParam) {
		name = nameParam;
	}

	@Column(name = "MOOD", nullable = true)
	public String getMood() {
		return mood;
	}

	public void setMood(String commentParam) {
		mood = commentParam;
	}
}

@Entity
@Table(name = "CUSTOMER_VERSION")
public class CustomerVersionDTO extends AbsVersionDTO {

	protected String mood;

	public CustomerVersionDTO() {
		super();
	}

	public CustomerVersionDTO(CustomerCurrentDTO customerCurrentDTO) {
		currentId = customerCurrentDTO.getId();
		mood = customerCurrentDTO.getMood();
	}

	@Column(name = "MOOD", nullable = true)
	public String getMood() {
		return mood;
	}

	public void setMood(String commentParam) {
		mood = commentParam;
	}
}

As you see, ids and dates are not here, because they are inherited from parents. Customer define a couple of properties, name and mood; name is the one that never change, and mood is the changing one. Version define only the changing one, mood. Of course, this two entities are mapped to different tables.
While implementation of Current customer does not have anything fancy in it, something is going on with the Version.
There are two constructors, the default one and the other that accept CustomerCurrentDTO as parameter. That way, we could copy variable states from the Current to the Version entity in the constructor. The default constructor is neede by Hibernate as we will see later.
All this talking of Current and Version is nice and good, but what about DAO ?
Well, we just need to add some modifications to the one I have illustrated in the other post.
First, we have to pass both type to the AbstractDAO:

public abstract class AbsDAO<T, E> {

	private Class currentType;
	private Class versionType;

	public AbsDAO() {
		currentType = (Class) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		versionType = (Class) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[1];

	}
}

Now we have two generics types, T and E, representing respectively the Current and the Version DTO. We also have two Class variables, currentType and versionType.
Take notice that currentType is instantiated with the .getActualTypeArguments()[0], while versionType is instantiated with the .getActualTypeArguments()[1].

Then, we have to modify the update method so that when we call it a new Version will be stored and the modificated Current will be updated

public boolean update(T myDTO) throws IllegalArgumentException,
			SecurityException, java.lang.InstantiationException,
			IllegalAccessException, InvocationTargetException,
			NoSuchMethodException {
		boolean toReturn = false;
		try {
			AbsCurrentDTO previousDTO = 
                          (AbsCurrentDTO) findById(((AbsCurrentDTO) myDTO).getId());
			AbsVersionDTO version = 
                          (AbsVersionDTO) versionType.getConstructor(currentType).newInstance(previousDTO);
			version.setModificationDate(new Date());
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			session.save(version);
			session.update(myDTO);
			tx.commit();
			session.close();
			toReturn = true;
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

The update method receive the current (just modified) object. We retrieve its saved instance (not yet modified) by its id, and then with this we create the version object. Then, we save the version (a new record will be created for each modification) and we update the current (only one record will be stored in the current table). I think a little bit of magic is in that line (:
AbsVersionDTO version =
(AbsVersionDTO) versionType.getConstructor(currentType).newInstance(previousDTO);
that allows us to create a new instance of an object whose type is known only at runtime, using a constructor that require as parameter another object whose type is also known only at runtime… where were we?

Last, we want to retrieve all the versions, of course:

public List listVersions(T myDTO) {
		List toReturn = null;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			long currentId = ((AbsCurrentDTO) myDTO).getId();
			toReturn = session.createCriteria(versionType)
					.add(Restrictions.eq("currentId", currentId)).list();
			tx.commit();
			session.close();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

This is where the default constructor is needed by Hibernate . otherwise it can not instantiate the CustomerVersionDTO to put in the return list.

Here’s the complete class

public abstract class AbsDAO<T, E> {

	protected SessionFactory sessionFactory;

	private Class currentType;
	private Class versionType;

	public AbsDAO() {
		currentType = (Class) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		versionType = (Class) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[1];
		sessionFactory = new AnnotationConfiguration().configure()
				.buildSessionFactory();
	}
	public T findById(long id) {
		T toReturn = null;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			toReturn = (T) session.get(currentType, id);
			tx.commit();
			session.close();
		} catch (NonUniqueResultException e) {
			e.printStackTrace();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public List list() {
		List toReturn = null;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			toReturn = session.createCriteria(currentType).list();
			tx.commit();
			session.close();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public List listVersions(T myDTO) {
		List toReturn = null;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			long currentId = ((AbsCurrentDTO) myDTO).getId();
			toReturn = session.createCriteria(versionType)
					.add(Restrictions.eq("currentId", currentId)).list();
			tx.commit();
			session.close();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public Long save(T myDTO) {
		Long toReturn = null;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			toReturn = (Long) session.save(myDTO);
			tx.commit();
			session.close();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public boolean update(T myDTO) throws IllegalArgumentException,
			SecurityException, java.lang.InstantiationException,
			IllegalAccessException, InvocationTargetException,
			NoSuchMethodException {
		boolean toReturn = false;
		try {
			AbsCurrentDTO previousDTO = (AbsCurrentDTO) findById(((AbsCurrentDTO) myDTO)
					.getId());
			AbsVersionDTO version = (AbsVersionDTO) versionType.getConstructor(
					currentType).newInstance(previousDTO);
			version.setModificationDate(new Date());
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			session.save(version);
			session.update(myDTO);
			tx.commit();
			session.close();
			toReturn = true;
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public boolean delete(T myDTO) {
		boolean toReturn = false;
		try {
			Session session = sessionFactory.openSession();
			Transaction tx = session.beginTransaction();
			session.delete(myDTO);
			tx.commit();
			session.close();
			toReturn = true;
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return toReturn;
	}
}

This is the class to test it all:

public class Main {

	/**
	 * (Insert comments here)
	 * 
	 * @param args
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 */
	public static void main(String[] args) throws IllegalArgumentException,
			SecurityException, InstantiationException, IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		VersionedDAO dao = new VersionedDAO();
		CustomerCurrentDTO current = new CustomerCurrentDTO();
		current.setMood("I am easy");
		current.setCreationDate(new Date());
		current.setName("FOO BAR");
		System.out.println(current.toString());
		dao.save(current);
		current.setMood("Now I've changed my mind");
		System.out.println(current.toString());
		dao.update(current);
		current.setMood("And now I'm tired");
		System.out.println(current.toString());
		dao.update(current);
		List versions = dao.listVersions(current);
		for (CustomerVersionDTO version : versions) {
			System.out.println(version);
		}
	}

This is the database structure (I called it versiondb):

mysql> show tables;
+---------------------+
| Tables_in_versiondb |
+---------------------+
| CUSTOMER_CURRENT    |
| CUSTOMER_VERSION    |
+---------------------+
2 rows in set (0.00 sec)

mysql> describe CUSTOMER_CURRENT;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| CREATION_DATE | datetime     | NO   |     | NULL    |                |
| MOOD          | varchar(255) | YES  |     | NULL    |                |
| NAME          | varchar(255) | NO   |     | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> describe CUSTOMER_VERSION;
+-------------------+--------------+------+-----+---------+----------------+
| Field             | Type         | Null | Key | Default | Extra          |
+-------------------+--------------+------+-----+---------+----------------+
| id                | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| currentId         | bigint(20)   | NO   |     | NULL    |                |
| MODIFICATION_DATE | datetime     | NO   |     | NULL    |                |
| MOOD              | varchar(255) | YES  |     | NULL    |                |
+-------------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

And here’s the sql to generate it (actually, I used Hibernate’s hbm2ddl.auto=create feature)

CREATE TABLE `CUSTOMER_CURRENT` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `CREATION_DATE` datetime NOT NULL,
  `MOOD` varchar(255) DEFAULT NULL,
  `NAME` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`)
);

CREATE TABLE `CUSTOMER_VERSION` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `currentId` bigint(20) NOT NULL,
  `MODIFICATION_DATE` datetime NOT NULL,
  `MOOD` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`)
);
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s