Friday, October 18, 2013

First steps with Hibernate - basic entity

learning through tests

Some time ago, when I started to learn Hibernate, I took a while and thought what would be the best way to learn this ORM. As a fan of test-driven development I decide to try learn it through tests and after a few tests were written I had to admit that choice was good and this is really efficient way of learning new things. Additionaly, I was practicing testing and improves my skills in this subject, which was also great benefit.

Since then, whenever I have to or want to learn something new (language, library, ORM) I have the same approach and it works really well.

Recently I thought about this idea a few times and I decided to share my code with you. Maybe you will like this way of learning :)

let's start with something simple

Let's assume that we have really simple entity in our application which will present a Person. I won't bother the logic under the application, because it's irrelevant for this post. Let's take a look at the code:
package com.smalaca.basicentity;

public class Person {
 
 private String name;
 private String address;
 private IdentityCard identityCard;

 public Person(String name, String address) {
  this.name = name;
  this.address = address;
  
  identityCard = new IdentityCard(name, this.address);
 }

 public IdentityCard getID() {
  return identityCard;
 }

 public void changeAddress(String newAdderss) {
  identityCard = new IdentityCard(name, newAdderss);
 }
}

Class IdentityCard is provided to ensure encapsulation and its code looks like this:
package com.smalaca.basicentity;

public class IdentityCard {

 private String name;
 private String address;
 
 public IdentityCard(String name, String address) {
  this.name = name;
  this.address = address;
 }

 public String getName() {
  return name;
 }

 public String getAddress() {
  return address;
 }
}

Tests for those classes are also irrelevant for the post (but if you really want you can find it here).

If we are talking about Hibernate it would be good to have database to store the data of particular objects:
CREATE TABLE Person (
	person_id MEDIUMINT UNSIGNED AUTO_INCREMENT NOT NULL,
		PRIMARY KEY(person_id),
	name VARCHAR(100) NOT NULL,
	home_address VARCHAR(100),
	UNIQUE INDEX PersonNameUnique (name)
) ENGINE = InnoDB;

let's write couple tests

Before we will proceed further I just want to say that I will omit implementation of HibernateUtil class or hibernate.cfg.xml (just write a comment if you think that was a bad decision). If you will like to, you can look at already made implementation of it on GitHub.

Ok, now we can move on.
Firstly we need to have a few tests to verify if the basic CRUD operations works as they should. I omit the one for read operation, because its tested undirectly in other tests:
package com.smalaca.basicentity;

import static org.junit.Assert.*;

import org.hibernate.Session;
import org.junit.Test;

import com.smalaca.dbhandler.HibernateUtil;

public class PersonIntegrityTest {
 private final Session session = HibernateUtil.getSessionFactory().openSession();
 
 @Test
 public void create() {
  session.beginTransaction();

  Person sebastian = new Person("Sebastian Malaca", "Cracow, Poland");
  Person junior = new Person("Sebastian Junior", "Cracow, Poland");

  Integer sebastianId = (Integer) session.save(sebastian);
  Integer juniorId = (Integer) session.save(junior);

  Person sebastianFromDb = (Person) session.get(Person.class, sebastianId);
  Person juniorFromDb = (Person) session.get(Person.class, juniorId);

  assertTrue(sebastian.equals(sebastianFromDb));
  assertTrue(junior.equals(juniorFromDb));
  assertFalse(sebastianFromDb.equals(juniorFromDb));
  
  session.getTransaction().rollback();
  session.close();
 }
 
 @Test
 public void update() {
  session.beginTransaction();

  Person sebastian = new Person("Sebastian Malaca", "Cracow, Poland");
  Integer sebastianId = (Integer) session.save(sebastian);

  Person sebastianFromDb = (Person) session.get(Person.class, sebastianId);
  
  String newAdderss = "New York, USA";
  sebastian.changeAddress(newAdderss);
  session.update(sebastian);

  Person sebastianFromDbAfterUpdate = (Person) session.get(Person.class, sebastianId);

  assertTrue(sebastian.equals(sebastianFromDb));
  assertTrue(sebastianFromDb.equals(sebastianFromDbAfterUpdate));
  assertTrue(sebastian.equals(sebastianFromDbAfterUpdate));
  assertEquals(newAdderss, sebastian.getID().getAddress());
  
  session.getTransaction().rollback();
  session.close();
 }
 
 @Test
 public void delete() {
  session.beginTransaction();

  Person sebastian = new Person("Sebastian Malaca", "Cracow, Poland");
  Integer sebastianId = (Integer) session.save(sebastian);

  session.delete(sebastian);
  
  Person notExisting = (Person) session.get(Person.class, sebastianId);

  assertNull(notExisting);
  
  session.getTransaction().rollback();
  session.close();
 }
}

What's now?
Let's try to run it:)

our first failed test...

After test exeuction you can find in stack trace message like:
org.hibernate.MappingException: Unknown entity: com.smalaca.basicentity.Person

Great, Hibernate doesn't even know that our entity exists. Let's try to fix it and add following lines into our code:
@Entity
@Table(name="Person")
public class Person {

Before you will execute your tests once again I want to say that annotation @Table is required only if entity name and table name are different. It's mean that for our example this annotation is useless.
Ok, let's execute it.

... and all the others

This time we can notice in console following information:
Initial SessionFactory creation failed.org.hibernate.AnnotationException: No identifier specified for entity: com.smalaca.basicentity.Person

Yeah, well, it make sense, there is no information about identifier in our entity class. One more time, let's try to fix failing test:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="Person")
public class Person {
 
 @Id
 private int id;

As you can see we added id attribute to our class, so take a look if it works.
Run tests and... :
Initial SessionFactory creation failed.org.hibernate.MappingException: Could not determine type for: com.smalaca.basicentity.IdentityCard, at table: Person, for columns: [org.hibernate.mapping.Column(identityCard)]

True, identityCard shouldn't be mapped into database, so we need to inform Hibernate about this fact:
 @Transient
 private IdentityCard identityCard;

Now Hibernate knows that identityCard attribute shouldn't be mapped and we know that one more problem was solved.

Let's check what's next:
Initial SessionFactory creation failed.org.hibernate.HibernateException: Missing column: id in learning_through_test.person

What? Didn't we fix this problem a few lines above? No, we just added an attribute to class, but take a look on table definition and you can see that column is called person_id and attribute is called id. Yeah, it can generate a problems. Fortunatelly solution is simple:
 @Id
 @Column(name="person_id")
 private int id;

And know is obvious that column name is different.

Next stop:
Initial SessionFactory creation failed.org.hibernate.HibernateException: Missing column: address in learning_through_test.person

Basing on past experiences with id column we can fix it in a seconds:
 @Column(name="home_address")
 private String address;

Ok, let's try it once again and... still failed, this time information from stack trace:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.smalaca.basicentity.Person#0]

The problem is with id generation - value is always the same. We can simply fix it by:
 @Id
 @GeneratedValue
 @Column(name="person_id")
 private int id;

I recommend to read something more about @GeneratedValue annotation e.g. here. It's really complex and I believe it would be senseless to explain whole mechanism in here.

execute test and...

Finally! It's green!
And after all modifications our Person class looks like this:
package com.smalaca.basicentity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="Person")
public class Person {

	@Id
	@GeneratedValue
	@Column(name="person_id")
	private int id;
	
	private String name;
	
	@Column(name="home_address")
	private String address;
	
	@Transient
	private IdentityCard identityCard;

	public Person(String name, String address) {
		this.name = name;
		this.address = address;
		
		identityCard = new IdentityCard(name, this.address);
	}

	public IdentityCard getID() {
		return identityCard;
	}

	public void changeAddress(String newAdderss) {
		identityCard = new IdentityCard(name, newAdderss);
	}
}

Hopefully you enjoy it and I can promise that will be more.

Below you can add a comments with your opinions, criticism and suggestion, I will appreciate each:)