Wednesday, September 10, 2008

Some notes about tomcat/hibernate/servlet

Tomcat

For tomcat applications, resources (configuration files...) should be put in directory WEB-INF/classes. The files cannot be symbolic links. They MUST be regular files in order for tomcat to handle them correctly. Of course, you can embed resources into your .jar files.
For Tomcat, the regular CLASSPATH environment variable is IGNORED. The resources (jars, configuration files...) must be put into your tomcat app directory. Read this post to see what is under the hood.
Resource/class search order for web apps: 
  • Bootstrap classes of your JVM
  • System class loader classses (described above)
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application
  • $CATALINA_HOME/lib
  • $CATALINA_HOME/lib/*.jar
The System class loader is the only place where CLASSPATH can be parsed and used. However, tomcat startup script(bin/catalina.sh) totally ignore environment variable CLASSPATH. You will figure it out if you read file bin/setclasspath.sh. In my case, first several lines are :
#  Set CLASSPATH and Java options
# 
#  $Id: setclasspath.sh 589060 2007-10-27 08:19:24Z jfclere $
# -----------------------------------------------------------------------------

# First clear out the user classpath
CLASSPATH= 
Now, you know the root of the problem right? If you want to include CLASSPATH, you can modify the bash file. I have not tried it. It should work.

Hibernate

For hibernate app, corresponding mapping tables must be created before you can run your app. Hibernate will NOT create those tables automatically for you. For the primary key property/column, If you use generator "native", you must set property of the primary key column correctly in order to let database generate primary key values correctly. For example, you can use AUTO_INCREMENT to make database automatically generate primary key values in MySQL. You can choose to use generator "assigned" to assign primary key values in your programs.
For database operations in Hibernate, you should always EXPLICITLY start and commit a transaction.

Servlet/JSP

Method Forward declared in RequestDispatcher should be understood correctly. It is an INTERNAL control transfer. It means this control transfer is TRANSPARENT to end users. One consequence is that the links in the target page, to which you forward control, MUST use absolute path instead of relative path.
For example, first a user sends request to http://domain/users/gerald.jsp. At server side, your servlet internally transfers control to /users/profiles/g/gerald.jsp. In the target page, if you have links with relative path, the paths are relative to the original request path (/users/ in this example) instead of path of the target page (/users/profiles/g/ in this example).
Your servlet should not have generated any response ( in output stream to request sender ) when redirection or forwarding is carried out.

Sunday, September 07, 2008

Hibernate installation (run on MySQL)

Recently, I tried to install Hibernate on gridfarm machine. The version I used was 3.3.0 SP1.
Configuration document can be found here. However, the document seems not to be updated.
(1) Configuration file hibernate.properties vs. hibernate.cfg.xml.
In the document, either one can be used. If both are used, configurations in hibernate.cfg.xml would override configurations in hibernate.properties. The document says using hibernate.properties is an easy way to configure Hibernate. So I chose it. However, after trial I found that in Hibernate 3.3.0 SP1 hibernate.cfg.xml. In other words, you MUST have a configuration file "hibernate.cfg.xml". Of course, you can use programmatical configuration which is not covered in this post.
(2) Logger
In documents, I saw "Hibernate logs various events using Apache commons-logging.". However, when I tried to run my Hibernate app, it complained that logger class can not be found. After inspection, I figured out that Hibernate 3.3.0 does NOT use Apache common-logging any more. Instead it uses slf4j. But in Hibernate 3.3.0 SP1 distribution only slf4j-api jar is included while an implementation is needed to run Hibernate(If you turn off logging, I don't know whether it will still complain). In other words, to use logging functionality, you must download an implementation of logging service supported by slf4j. I used slf4j-simple.
(3) JNDI bindings (for session factory and data source).
Because I just wanted to write a simple prototype without deploying in J2EE server, I chose not to use JNDI bindings for data source or session factory. After several hour trial, I could not get it to work!! I always got the error "org.hibernate.impl.SessionFactoryObjectFactory - Could not bind factory to JNDI".It seemed that Hibernate always was always trying to use JNDI to bind Session Factory. However, the official document says

"A JNDI bound Hibernate SessionFactory can simplify the lookup of the factory and the creation of new Sessions. Note that this is not related to a JNDI bound Datasource, both simply use the same registry!

If you wish to have the SessionFactory bound to a JNDI namespace, specify a name (eg. java:hibernate/SessionFactory) using the property hibernate.session_factory_name. If this property is omitted, the SessionFactory will not be bound to JNDI. (This is especially useful in environments with a read-only JNDI default implementation, e.g. Tomcat.)

When binding the SessionFactory to JNDI, Hibernate will use the values of hibernate.jndi.url, hibernate.jndi.class to instantiate an initial context. If they are not specified, the default InitialContext will be used.

Hibernate will automatically place the SessionFactory in JNDI after you call cfg.buildSessionFactory(). This means you will at least have this call in some startup code (or utility class) in your application, unless you use JMX deployment with the HibernateService (discussed later).

If you use a JNDI SessionFactory, an EJB or any other class may obtain the SessionFactory using a JNDI lookup.

We recommend that you bind the SessionFactory to JNDI in a managend environment and use a static singleton otherwise. To shield your application code from these details, we also recommend to hide the actual lookup code for a SessionFactory in a helper class, such as HibernateUtil.getSessionFactory(). Note that such a class is also a convenient way to startup Hibernate—see chapter 1. "

It turned out that a property called "hibernate.current_session_context_class" must be set.
My hibernate.cfg.xml looks like:
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="show_sql">true</property>
        <property name="current_session_context_class">thread</property>
        <property name="connection.username">username</property>
        <property name="connection.password">password</property>
        <property name="connection.url">jdbc:mysql://localhost/hibernate</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <mapping resource="User.hbm.xml"/> 
    </session-factory>
</hibernate-configuration>
Then, I tried a very very simple read-only JNDI service provider - simple-jndi. My simple-jndi configuration file (jndi.properties which should be put into root of your classpath) looks like:
#The first (required) parameter, org.osjava.sj.root, is the location 
#of your simple-jndi root, which is the location in which simple-jndi
#looks for values when code asks for them.

org.osjava.sj.root=/home/zhguo/share/simple-JNDI/config
java.naming.factory.initial=org.osjava.sj.SimpleContextFactory
Under directory pointed to by org.osjava.sj.root property, I have a jndi data file called hibernate_jndi.properties:
mysql.type=javax.sql.DataSource
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost/hibernate
mysql.user=username
mysql.password=password
So, my new version of hibernate.cfg.xml looks like:
<hibernate-configuration>
    <session-factory>
        <property name="show_sql">true</property>
        <property name="current_session_context_class">thread</property>
        <property name="connection.datasource">hibernate_jndi.mysql</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <mapping resource="User.hbm.xml"/> 
    </session-factory>
</hibernate-configuration>
Note: in the file, session factory name is not set! This is because we are using a read-only JNDI. If you use a read-write JNDI, you can set the session factory name in one of the following ways:
(*) In hibernate.properties file:
hibernate.session_factory_name hibernate/session_factory
(*) In hibernate.cfg.xml file:
<session-factory name="your name goes here">