Aspects of using Hibernate with CaptainCasa Enterprise Client We all know: there are a lot of frameworks that deal with persistence in the Java environment one of them being Hibernate. And there are a lot of different ways how to work with Hibernate. So this document only does not tell you the one and only way to use Hibernate within a CaptainCasa environment, but it talks about one way of using it. Nevertheless there are some principal thoughts that should always be of interest and, please note: the server side of CaptainCasa is based on JSF. So, what we talk about in this document is nothing, which is specific to CaptainCasa. Adding Hibernate to your Project Libraries Hibernate consist out of quite a lot libraries + dependent libraries. Add all these libraries into the WEB-INF/lib folder of your web content. The following screen shot shows the WEB-INF/lib folder after having added all Hibernate libraries: 1
You also see a hsqldb.jar which is a driver to the database that we are using (a small HSQL database). Configuration Files Add the following configuration files to the source folder of your project: A typical content of the log4j.properties files is: ### 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=%dabsolute %5p %c1:%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=%dabsolute %5p %c1:%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 A typical content of the hibernate.cfg.xml file is: <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> 2
<session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.hsqldb.jdbcdriver</property> <property name="connection.url">jdbc:hsqldb:hsql://localhost/isa</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.hsqldialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.nocacheprovider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">update</property> <mapping resource="entities/database.hbm.xml"/> </session-factory> </hibernate-configuration> The hibernate configuration points to locally running hypersonic database (HSQL). There is a pointing to a mapping file contained in the source directory entities/database.hbm.xml - so all this is normal Hibernate The typical HibernateUtil class Add the typical HibernateUtil class to the project for having a singleton-access to the Hibernate SessionFactory: package hibernate; import org.hibernate.sessionfactory; import org.hibernate.cfg.configuration; public class HibernateUtil private static final SessionFactory s_sessionfactory; static try // Create the SessionFactory from hibernate.cfg.xml s_sessionfactory = new Configuration().configure().buildSessionFactory(); catch (Throwable ex) // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); public static SessionFactory getsessionfactory() return s_sessionfactory; 3
First test your Environment After having done the copying, it's not too bad to do some testing, if you can access the database Write an entity bean (in example below PersonEntity ) Set up the mapping (or use Hibernate annotations) Write a mini test page with an action listener doing some Hibernate operations Deploy this by reloading the application in the Layout Editor The entity bean might look like: package entities; public class PersonEntity String m_id; String m_firstname; String m_lastname; Boolean m_sex; String m_geburtsland; public String getgeburtsland() return m_geburtsland; public void setgeburtsland(string geburtsland) m_geburtsland = geburtsland; public Boolean getsex() return m_sex; public void setsex(boolean sex) m_sex = sex; public String getid() return m_id; public void setid(string id) m_id = id; public String getfirstname() return m_firstname; public void setfirstname(string firstname) m_firstname = firstname; public String getlastname() return m_lastname; public void setlastname(string lastname) m_lastname = lastname; The mapping file (in the example: entities/database.hbm.xml) might look like: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="entities.personentity" table="person"> <id name="id" length="25" column="p_id" type="string" /> <property name="firstname" length="100" column="p_firstname"/> <property name="lastname" length="100" column="p_lastname"/> <property name="sex" column="p_sex"/> </class> </hibernate-mapping> And the simple bean that is bound to user interface ( managed bean ) might have the following action listener: public class TestUI public void ontest(actionevent event) try Session s = HibernateContext.getCurrentSession(); // query List<PersonEntity> pes = (List<PersonEntity>)s.createQuery 4
( "from PersonEntity" ).list(); for (PersonEntity pe: pes) System.out.println(pe); // update Transaction t = HibernateContext.beginTransaction(); PersonEntity pe = new PersonEntity(); pe.setid(""+system.currenttimemillis()); pe.setfirstname("first"); pe.setlastname("last"); s.save(pe); HibernateContext.commit(); catch (Throwable t) t.printstacktrace(); When executing the action listener from the UI several times, you should see an increasing number of persons that are output to the console Typical Usage Patterns In principal you now can work with Hibernate in any way you like. You have the UI-classes (managed beans) that are bound to the UI processing, within these classes you can invoke any Java function that internally operated with the Hibernate objects, such as SessionFactory, Session, Transaction, Query etc. But: of course there are some usage patterns that you should be aware of: Typically there is one (and exactly one) SessionFactory for the database that you access. This is ensured due the HibernateUtil class that was mentioned in a previous chapter. Typically the life cycle of a Session-object should be very short. The Session-object should be valid during one request only, so it should be closed when the request that comes from the user interface client is finished and the response to the client is sent. - It is an absolute anti-pattern to have Session-objects that span multiple requests. Don't do this by accident! Typically all the code that is executed during a request should access the same Sessionobject. Same with transactions: everyone typically should used a shared transaction for one request, so that all updates that are done are in the same transaction. Now adding some typical Hibernate Usage Patterns Bind Hibernate Context Info to Thread In order to achieve the goals listed in the text above, it is a common pattern that these objects that are be shared during request processing are bound to the request thread. The concrete objects that we take about are: Session Transaction The binding to the thread is done by a class HibernateContext: 5
package hibernate; import java.util.hashtable; import java.util.map; import org.hibernate.session; import org.hibernate.transaction; public class HibernateContext static Map<Thread,HibernateContext> s_contextperthread = new Hashtable<Thread, HibernateContext>(); Session m_currentsession; Transaction m_currenttransaction; public static Session getcurrentsession() HibernateContext hc = getcurrentcontext(true); if (hc.m_currentsession == null) hc.m_currentsession = HibernateUtil.getSessionFactory().openSession(); return hc.m_currentsession; public static Transaction begintransaction() HibernateContext hc = getcurrentcontext(true); if (hc.m_currentsession == null) getcurrentsession(); if (hc.m_currenttransaction == null) hc.m_currenttransaction = hc.m_currentsession.begintransaction(); return hc.m_currenttransaction; public static void commit() HibernateContext hc = getcurrentcontext(false); if (hc == null) return; if (hc.m_currenttransaction!= null) hc.m_currenttransaction.commit(); hc.m_currenttransaction = null; public static void rollback() HibernateContext hc = getcurrentcontext(false); if (hc == null) return; if (hc.m_currenttransaction!= null) hc.m_currenttransaction.rollback(); hc.m_currenttransaction = null; public static void close() HibernateContext hc = getcurrentcontext(false); if (hc == null) return; if (hc.m_currenttransaction!= null) hc.m_currenttransaction.rollback(); if (hc.m_currentsession!= null) hc.m_currentsession.close(); s_contextperthread.remove(thread.currentthread()); System.out.println("HibernateContext - closed"); private static HibernateContext getcurrentcontext(boolean createnewifnotexists) HibernateContext hc = s_contextperthread.get(thread.currentthread()); if (createnewifnotexists == true && hc == null) hc = new HibernateContext(); s_contextperthread.put(thread.currentthread(),hc); return hc; 6
The typical lifecycle within the request processing is: HibernateContext.getCurrentSession() <== the session for querying etc. HibernateContext.beginTransaction() <== start update HiberanateContext.commit() / rollback() HibernateContext.close() <== remove the thread binding and clean up Session Because the methods are static they can be accessed everywhere within the call stack that is processed during a request processing. Of course there should be only limited functions that really are responsible for committing a transaction typically the most outside function that triggers an update. Enforced closing of Session at End of Request Now that during the request processing we made sure that we have a shared Session and a shared Transaction instance due to thread binding, we have to make sure as next step that these shared objects are reliably closed when a request processing is finished. An easy way to do so, is the usage of a servlet filter: package hibernate; import java.io.ioexception; import javax.servlet.filter; import javax.servlet.filterchain; import javax.servlet.filterconfig; import javax.servlet.servletexception; import javax.servlet.servletrequest; import javax.servlet.servletresponse; public class HibernateContextFilter implements Filter public void init(filterconfig config) throws ServletException public void dofilter(servletrequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException try chain.dofilter(request,response); catch(throwable t) finally HibernateContext.close(); public void destroy() Please note: you need to embed servlet-api.jar (typically in tomcat/lib-directory) into your project libraries. The filter just makes sure that at the end of request processing the Hibernate instances 7
are closed. You have to register the filter in your web.xml file: <?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="webapp_id" version="2.5"> <display-name>jsf1</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <filter> <filter-name>hibernate.hibernatecontextfilter</filter-name> <filter-class>hibernate.hibernatecontextfilter</filter-class> </filter> <filter> <filter-name>org.eclnt.jsfserver.util.compressionfilter</filter-name> <filter-class>org.eclnt.jsfserver.util.compressionfilter</filter-class> </filter> <filter-mapping> <filter-name>hibernate.hibernatecontextfilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> Passing Hibernate POJOs into your Screens Now that you can access Hibernate in some structured way, you might look for the best option to directly bind the POJO instances coming from Hibernate into your screen. Example: you may want to render a list of Persons, each person being an instance of calls PersonEntity. 8
The logic for accessing the database is: public static List<PersonEntity> readallpersons(string firstname, String lastname) if (firstname == null) firstname = "%"; else firstname = "%" + firstname + "%"; if (lastname == null) lastname = "%"; else lastname = "%" + lastname + "%"; Session s = HibernateContext.getCurrentSession(); List<PersonEntity> pes = (List<PersonEntity>)s.createQuery ( "from PersonEntity where " + "firstname LIKE '"+firstname+"' and " + "lastname LIKE '"+lastname+"'" ).list(); return pes; The grid of the page is defined / filled in the following way: <t:row id="g_12"> <t:fixgrid id="g_13" height="100%" objectbinding="#d.personenlisteui.grid" sbvisibleamount="25" width="100%"> <t:gridcol id="g_14" text="column" width="100"> <t:label id="g_15" text=".pe.id" /> </t:gridcol> <t:gridcol id="g_16" text="column" width="50%"> <t:label id="g_17" text=".pe.firstname" /> </t:gridcol> <t:gridcol id="g_18" text="column" width="50%"> <t:label id="g_19" text=".pe.lastname" /> </t:gridcol> </t:fixgrid> </t:row> package managedbeans; import hibernate.hibernatecontext; 9
import java.io.serializable; import java.util.list; import javax.faces.event.actionevent; import logic.commitable; import logic.logic; import org.eclnt.editor.annotations.ccgenclass; import org.eclnt.jsfserver.elements.impl.fixgriditem; import org.eclnt.jsfserver.elements.impl.fixgridlistbinding; import org.eclnt.jsfserver.pagebean.pagebean; import org.hibernate.session; import org.hibernate.transaction; import entities.personentity; @CCGenClass (expressionbase="#d.personenlisteui") public class PersonenListeUI extends PageBean implements Serializable public class GridItem extends FIXGRIDItem implements java.io.serializable PersonEntity i_pe; public GridItem(PersonEntity pe) i_pe = pe; public PersonEntity getpe() return i_pe; FIXGRIDListBinding<GridItem> m_grid = new FIXGRIDListBinding<GridItem>(); String m_searchlastname; String m_searchfirstname; // ------------------------------------------------------------------------ // constructors & initialization // ------------------------------------------------------------------------ public PersonenListeUI() public String getpagename() return "/isa/personenliste.jsp"; public String getrootexpressionusedinpage() return "#d.personenlisteui"; public String getsearchlastname() return m_searchlastname; public void setsearchlastname(string value) this.m_searchlastname = value; public String getsearchfirstname() return m_searchfirstname; public void setsearchfirstname(string value) this.m_searchfirstname = value; public FIXGRIDListBinding<GridItem> getgrid() return m_grid; public PersonEntity getnewperson() return m_newperson; public void onsearchaction(actionevent event) fillgrid(); private void fillgrid() m_grid.getitems().clear(); List<PersonEntity> pes = Logic.readAllPersons(m_searchFirstName,m_searchLastName); for (PersonEntity pe: pes) m_grid.getitems().add(new GridItem(pe)); 10
You see: The grid item class refers to the POJO instance (member i_pe ). In the grid column cell definition the properties of the POJO instance are referenced via.pe.<propertyname>. By using the POJO object directly you do not have to repeat all set/get-method definitions on item level, but directly bind the user interface controls to the POJO instance coming from the database. 11