Monday, April 2, 2007

Hibernate

Before going to Hibernate, i need to brief you on ORM although i'm sure you already have a good idea.. so here goes --

Object Relational Mapping is a programming technique for converting between data in the form of Objects in Object Oriented programming languages to relational database entities.

The above definition, is just …well for the sake of giving a definition, and the example below will convert the above ‘Martian Tongue’ into sense ;-)

Say, you’re modeling a Person entity in Java. Your POJO(Plain Old Java Object) would consist of variables for Name, Address and say SSN. Where Address in turn could be a POJO composed of Street, City and Pin. When persisting this object, the programmer has to dedicate code (effort, thinking, blood, sweat and pain!) to saving this to the Database, since Person is not a scalar value that a database understands (String, int..), but an Object.

So the problem is, persisting this object to the DB, and retrieving it easily, yet preserving the relations it maintains with other Objects (Address relates to Person in our example).

Well, Hibernate, is one such Persistence Solution.

Quoting from www.hibernate.org since I cannot put it across any better, Hibernate is:

A powerful, high performance object/relational persistence and query service. Hibernate lets you develop persistent classes following object-oriented idiom - including association, inheritance, polymorphism, composition, and collections. Hibernate allows you to express queries in its own portable SQL extension (HQL), as well as in native SQL, or with an object-oriented Criteria and Example API.

Now, if you’re anything like me you’ll want to start coding and not let long tutorials bog you down! So let me take you through a simple Hibernate Application, that will get your essentials in place, and then you and google can take it from there ;-)

You will need the following before you get busy coding—

  • JDK 1.5
  • Eclipse
  • Database (I used MySql 5.0), and its driver(a .jar that you will need to add to your classpath)
  • The .jar files for Hibernate available from www.hibernate.org (will also need adding to the classpath)

To write your first Hibernate Using Application you need to code the following essential entities—

    1. POJO to be persisted-- This is structured like a Java Bean. It has a bunch of private variables, their accessors are mutators (which you can get eclipse to generate for you) and a no-argument constructor. A simple such class is sh

package hello;


/**

*

* @author shalini_israni

*

*/

public class Message {

private long id;

private String text;

public Message(){}

public long getId()

{

return this.id;

}

public String getText()

{

return this.text;

}

private void setId(long id)

{

this.id = id;

}

public void setText(String text)

{

this.text = text;

}

}

    1. Hibernate Configuration File (hibernate.cfg.xml)-- Which is used to configure the Database settings, and used to generate a Session Factory as I will later explain. Shown below is the Hibernate Config file. There is an element named Session Factory within which there is a set of properties which describe various aspects of a DB Connection. (Sorry for doing this via a screenshot... pasting the XML snippets was causing only the Tag Text to remain..alternate suggestions welcome )


The first 4 things are self explanatory. The fifth is setting a connection pool size- you do this since opening a Database Connection is generally more expensive than executing an SQL statement. Next, setting show_sql to true is useful for debugging purposes, since it shows you the SQL that hibernate is generating. Next, the dialect needs to be set according to whatever DB you are using, in this case MySql. After this comes'hibernate.hbm2ddl.auto'. Setting this to 'update' as done above, will cause Hibernate to simply update tables (with names as in the mapping files as shown below) with new data if they already exist, and not drop and recreate them. Although, it will create tables for you if not already present. You could also set this to 'create', and this will cause the tables to be dropped and re-created on every run. And then,'hibernateInstallation' set to log4j, which is for debugging. If you don't set this, you will get a warning, and not setting this, will NOT prevent your application from running. Contents of log4j.properties are shown below:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=warn, stdout

#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=debug

### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug

### log just the SQL
#log4j.logger.org.hibernate.SQL=debug

### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug

### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=debug

### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug

### log cache activity ###
#log4j.logger.org.hibernate.cache=debug

### log transaction activity
#log4j.logger.org.hibernate.transaction=debug

### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug

### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
Finally, you specify the mapping file in the 'mapping' entry. There can be multiple such entries referencing multiple mapping files.

    1. The mapping file(whatever.hbm.xml) Which is used to define the mapping between the POJO to be persisted and the Database Schema. An entry in the Hibernate Config File (above) makes a mention of this file. As mentioned earlier,you can split this mapping into multiple xml files and write separate entries for each in the hibernate config file.
As shown, this maps the POJO to be persisted to a particular table in the database. Here, we have a 'class' element, in which the 'name' attribute specifies the fully qualified name of the class. The 'table' attribute is optional. If not specified, Hibernate creates a table using the class name as the table name. Within this, is the' id' element. This is important and serves as the table's primary key. This maps to the private variable ID of the Message class. Again, 'column' attribute here, just like table, is optional. Within this is the 'generator' element. The 'class' attribute is used to specify how the id is generated. Here, having selected 'native', i have delegated this responsibility to Hibernate. Now, internally Hibernate uses sequences to generate IDs. Sequences, in Oracle are on a schema level,where each user has their own schema. In this case, you can specify the sequence you want hibernate to use. Now, watch your step here because certain DBs, donot have the concept of a sequence. MySql for instance, uses 'autoincrement'. Getting back to generator, had i selected 'assigned', it would be my responsibility to set the ID before i chose to persist the object. Here is a list of values that the class attribute can assume. These are pre-written classes. You only have to use them. Now, the 'property' element is used to define variables of the class that you want to persist. These become columns in the table. Here we have chosen to persist the TEXT variable.

    1. Driver Class—This class will contain your Main method. Here, the session factory is created and the session object is used to persist the message object.

package hello;

import java.util.List;

import org.hibernate.*;

import org.hibernate.cfg.*;

import com.tombrus.javaParser.Token.Iterator;

import util.*;

/**

*

* @author shalini_israni

*

*/

public class MessageExample {

/**

* @param args

*/

public static void main(String[] args) {

SessionFactory sessionFactory =new Configuration().configure().buildSessionFactory();

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

new MessageExample().insert("Inserted Message", session);

new MessageExample().listAll(session);

new MessageExample().update(new Long(3), session, "Updated Message");

new MessageExample().delete(new Long(1), session);

new MessageExample().listAll(session);

tx.commit();

session.close();

}

/**

*

* @param text

* @param session

*/

public void insert(String text,Session session)

{

Message message = new Message();

message.setText(text);

session.save(message);

}

/**

*

* @param id

* @param session

* @param newText

*/

public void update(Long id,Session session,String newText)

{

Message message = (Message) session.get(Message.class, id);

message.setText(newText);

session.save(message);

}

/**

*

* @param id

* @param session

*/

public void delete(Long id,Session session)

{

Message message = (Message) session.get(Message.class, id);

session.delete(message);

System.out.println("Deleted Message with ID = " + id);

}

/**

*

* @param session

*/

public void listAll(Session session)

{

List messages = session.createQuery("from hello.Message").list();

for(java.util.Iterator it=messages.iterator(); it.hasNext();)

{

Message m =(Message) it.next();

System.out.println(m.getText());

}

}

}



The code is fairly easy, so i'll just give you a 'Things to Remember' about it.

  • Note, i have not set the ID anywhere, because i had set the 'generator class' to 'native'.
  • Session factory creation is done using Configuration, and buildSessionFactory() which searches for a hibernate.cfg.xml in the classpath and uses it to create the session factory. It is possible to specify the absolute path to the config file as a parameter to the configure() method.
  • A transaction needs to be created, although retrieval operations are possible without a Transaction. All inserts and updates become effective only on a transaction commit, not otherwise.
  • To persist an object, you pass its referance to session.save()
  • To retrieve an object, you pass the ClassName.class, and a serializable id (the ID) to session.get() or session.load(). But, load should be used only when you are sure that the instance with that ID exists in the DB.
  • Notice, how easy updating & deleting an existing entry is.
  • List all gives you a sneak peak of HQL- Hibernate's own query Language. The intention of this query is to retrieve all entries of type 'Message'. So 'From hello.Message' does that. Note here, that you are not specifying the Table name, but the Class name whose objects you want to retrieve.
The above is a very basic account, and your questions will tell me if i've missed anything! :-)
There are many many other things about Hibernate, like HQL and persisting classes which have associations with other classes. But that area is so vast that it will probably need a separate post ... :) which i don't really promise.... :-)

0 comments: