• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 783
  • Last Modified:

Hibernate & Tomcat: One Application, Multiple Contexts

I've created an application (call it Portal) which uses Hibernate and runs under Tomcat. I need to run Portal under two different contexts with two different web.xmls (each one refers to a different database (obviously, each database has the same schema)).

Originally, I tried to use the standard by-the-tutorial HibernateUtil class (http://www.hibernate.org/hib_docs/reference/en/html/quickstart.html). This worked fine for the first context - however, when I tried to access the second context, Hibernate broke with "no persistant class mapped," and from that point on, neither context worked until I restarted Tomcat.

I couldn't find a way to force Tomcat to all the classes twice, so I resorted to a static HashMap to store the two different SessionFactories. In each web.xml, I created an Environment entry naming each context differently. Here's the relevant portion of my new HibernateUtil:

private static Hashmap hash = new HashMap();

public static Session currentSession () throws HibernateException {
    Session s = (Session) threadLocal.get();
    if (s == null) {
        s = getSessionFactory().openSession();
        threadLocal.set(s);
    }
    return s;
}
   
private static SessionFactory getSessionFactory() throws HibernateException {
    String sContext;
    try {
        Context iCtx = new InitialContext();
        sContext = (String)iCtx.lookup("java:comp/env/servletContext");
    } catch (NamingException e) {
        e.printStackTrace();
        sContext = "whoops";
    }
   
    SessionFactory sessionFactory = (SessionFactory) hash.get(sContext);
    if (sessionFactory == null) {
        Configuration conf = new MyConfiguration();
        conf.configure("/efp.cfg.xml");
        sessionFactory = conf.buildSessionFactory();
        hash.put(sContext, sessionFactory);
    }
    return sessionFactory;
}

Now, whichever context I go to first works fine, and it keeps working fine even once I visit the second context. However, the second context now throws ClassCastExceptions when I try to load something and cast it.

How can I make this work?
0
Nazrax
Asked:
Nazrax
  • 10
  • 9
1 Solution
 
aozarovCommented:
You should not use static for that purpose because each web application will use different class loader.
Store Hibernate SessionFactory in the application context instead. (call to ServletContext#setAttribute)
0
 
NazraxAuthor Commented:
I've started to try to make my utility class non-static but it's proving difficult - this class has to be called from places that don't have access to the servlet's context.

How much of this thing can I make static, and how much of it has to be per-instance?
0
 
aozarovCommented:
You can still use static variables but don't expect different web application (war) to share that static variable.
Each application will have its own instances of that static variable which should be initialized with its own
(and probably different) Hibernate SessionFactory.
Another aproach whould be to add each SessionFactory to the servlet context and then to have
a servlet Filter (e.g. http://www.onjava.com/lpt/a/4361) per application that will associate
the SessionFactory with a ThreadLocal (e.g. http://javaalmanac.com/egs/java.lang/TLocal.html) for each request.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
NazraxAuthor Commented:
Ok, so right now I have the SessionFactory stored in the ServletContext. The following ServletFilter (cut down for this post) is called on every request:

public class HibernateFilter implements Filter
{
    private FilterConfig filterConfig;
    private static ThreadLocal local = new ThreadLocal();
...    

    public static ServletContext getContext()  { return (ServletContext)local.get(); }
   
    public void doFilter (ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException  {
        try {
            local.set(filterConfig.getServletContext());
            chain.doFilter(request, response);
        } catch (Exception e) { ... }
    }

...
}

And the HibernateUtil itself (cut down):

public class HibernateUtil
{
    private static ThreadLocal local = new ThreadLocal();
   
    public static void closeSession () throws HibernateException
    {
        Session session = (Session)local.get();
        if (session!= null)
            session.close();
    }

    public static Session currentSession () throws HibernateException
    {
        Session session = (Session)local.get();

        if (session == null || !session.isOpen()) {
            session = getSessionFactory().openSession();
            local.set(session);
        }
        return session;
    }
 
    private static SessionFactory getSessionFactory() throws HibernateException
    {
        ServletContext context = HibernateFilter.getContext();
        SessionFactory sessionFactory = (SessionFactory) context.getAttribute("hibernateSession");
        if (sessionFactory == null) {
            Configuration conf = new MyConfiguration();
            conf.configure("/efp.cfg.xml");
            sessionFactory = conf.buildSessionFactory();
            context.setAttribute("hibernateSession", sessionFactory);
        }
       
        return sessionFactory;
    }
}

On the first context I go to, everything works fine.
Once I go to the second context, I start off with some cached values; after a few queries, I end up with "no persistent classes found for query class" errors. At that point, both contexts are broken.

Somehow, I need something static somewhere - if I have more than one session per thread, I'll end up with connection leaks. I'm just having difficulty figuring out what needs to be nonstatic and what I can make static.

While I'm very familiar with Java, this is the first time I've been in a situation with classes being loaded by multiple classloaders into the same JVM (I guess that's what's happening here?). Is there anywhere I can read up on what this actually means for my code? I've looked at Tomcat's classloading section, but that only seems to talk about the order in which classes are loaded.
0
 
aozarovCommented:
Each time the servlet process a request it should be done by the container via one thread. Are you creating your own threads?
I think in some cases you are not closing the Hibernate session.
A good place to close it would be in HibernateFilter  after chain.doFilter(request, response); and in a finally clause.
change:
public static void closeSession () throws HibernateException
    {
        Session session = (Session)local.get();
        if (session!= null)
            session.close();
    }
to
public static void closeSession () throws HibernateException
    {
        Session session = (Session)local.get();
        if (session!= null)
        {
            session.close();
            local.set(null);
        }
    }
and
public static Session currentSession () throws HibernateException
    {
        Session session = (Session)local.get();

        if (session == null || !session.isOpen()) {
            session = getSessionFactory().openSession();
            local.set(session);
        }
        return session;
    }
to
public static Session currentSession () throws HibernateException
    {
        Session session = (Session)local.get();

        if (session == null) {
            System.out.println("DEBUG: creating a new Hibernate session....");
            session = getSessionFactory().openSession();
            local.set(session);
        }
        return session;
    }

Now, if you call to HibernateUtil .currentSession() from the Filter after " local.set(filterConfig.getServletContext());"
and you don't see the debug printout then that shows you are not closing the session in some cases.
0
 
NazraxAuthor Commented:
Whoops - in my attempts to make the class fit in a web post, I deleted too much. HibernateFilter's doFilter is actually:

        try {
            local.set(filterConfig.getServletContext());
            chain.doFilter(request, response);

            System.out.println("Request finished, closing session");
            HibernateUtil.closeSession();
        } catch (Exception e) {
            try {
                HibernateUtil.closeSession();
            } catch (HibernateException he) {
                e.printStackTrace();
            }
            throw new ServletException(e);
        }


But that doesn't explain why accessing the second context breaks everything.
0
 
aozarovCommented:
>>  HibernateUtil.closeSession();
        } catch (Exception e) {
            try {
                HibernateUtil.closeSession();
            } catch (HibernateException he) {
                e.printStackTrace();
            }

Probably it is cleaner to do it once in a finally block.
Can you apply my other suggestions about " local.set(null);"
and  HibernateUtil .currentSession() from the Filter after " local.set(filterConfig.getServletContext());"
and " System.out.println("DEBUG: creating a new Hibernate session....");"

Just to verify sure that each new request starts with a new Hibernate Session.
Also, do you create any user threads in your webapp?
Can you also add to your HibernateFilter
static
{
System.out.println("DEBUG: my HibernateFilter static initializer....");
}
and check how many times it is being called when you deploy both applications.
Waiting for your response....
0
 
NazraxAuthor Commented:
The static initializer (and the init() method) are called the moment the context descriptor is read:

Apr 22, 2005 2:33:58 PM org.apache.catalina.core.StandardHostDeployer install
INFO: Processing Context configuration file URL file:C:\Program Files\Apache Software Foundation\Tomcat 5.0\conf\Catalina\localhost\demo.xml
DEBUG: my HibernateFilter static initializer....
DEBUG: In Init
Apr 22, 2005 2:34:00 PM org.apache.catalina.core.StandardHostDeployer install
INFO: Processing Context configuration file URL file:C:\Program Files\Apache Software Foundation\Tomcat 5.0\conf\Catalina\localhost\portal.xml
DEBUG: my HibernateFilter static initializer....
DEBUG: In Init
Apr 22, 2005 2:34:00 PM org.apache.catalina.core.StandardHostDeployer install
INFO: Installing web application at context path /portal from URL file:C:/Program Files/Apache Software Foundation/Tomcat 5.0/webapps/https/portal
DEBUG: my HibernateFilter static initializer....
In Init

The session is closed every time when it should be. For every request, I get

DEBUG: In Filter
DEBUG: Creating new session
...
DEBUG: Request finished, closing session
DEBUG: Closing session


As long as only one context is visited (or, more precisely, uses Hibernate), everything's fine. When the second context is visited, both break.
15:50:04,218  WARN QueryTranslator:951 - no persistent classes found for query class: from net.iss.portal.bean.Employee where username=?

0
 
aozarovCommented:
Can you provide the content MyConfiguration and each of efp.cfg.xml?
0
 
NazraxAuthor Commented:
I created MyConfiguration because Configuration was having trouble finding my XML files:

public class MyConfiguration extends Configuration
{
    protected InputStream getConfigurationInputStream (String resource) throws HibernateException
    {
        return this.getClass().getResourceAsStream(resource);
    }
}

The configuration file:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>
      <session-factory>
            <property name="connection.datasource">java:comp/env/jdbc/PortalDB</property>
            <property name="dialect">net.sf.hibernate.dialect.PostgreSQLDialect</property>
            <property name="hibernate.use_outer_join">true</property>

            <mapping resource="Portal/AVP.hbm" />
            <mapping resource="Portal/Answer.hbm" />
            <mapping resource="Portal/Employee.hbm" />
.......
            <mapping resource="Legacy/ReviewType.hbm" />
      </session-factory>
</hibernate-configuration>
0
 
aozarovCommented:
Can you try this:
1. Make sure MyConfiguration is included as part of the WEB-INF/lib or classes (and not found elsewhere)
2. Change the local jndi referene, java:comp/env/jdbc/PortalDB, to the full jndi referecne (without java:/com/env and basically the jndi name java:comp/env/jdbc/PortalDB referes to).
0
 
NazraxAuthor Commented:
MyConfiguration only shows up in one place: portal/WEB-INF/classes

JNDI isn't something I've played with a lot - I'm not sure what you mean. In my Tomcat Context, I have:
<Resource name="jdbc/PortalDB" type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/PortalDB">...</ResourceParams>

What are you looking to be there?
0
 
aozarovCommented:
>> MyConfiguration only shows up in one place: portal/WEB-INF/classes
Good.

Try
<property name="connection.datasource">jdbc/PortalDB</property>
instead
<property name="connection.datasource">java:comp/env/jdbc/PortalDB</property>
0
 
NazraxAuthor Commented:
That doesn't work:
net.sf.hibernate.HibernateException: Could not find datasource
...
Caused by: javax.naming.NameNotFoundException: Name jdbc is not bound in this Context
0
 
aozarovCommented:
Do you have this in your web.xml:
<resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/TestDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
</resource-ref>

I am trying to avoid "java:/com/env" but if that does not work then I think I have only two more suggestions.

1. override the HibernateFilter init method  "public void init(FilterConfig filterConfig) throws ServletException"
and after calling super.init(filterConfig); call local.set(filterConfig.getServletContext());
 and HibernateUtil.getSessionFactory to pre-initialize the configuration part.
2. Or instead, don't use Tomcat DataSource but rather configure  Hibernate with the jdbc driver settings.
0
 
NazraxAuthor Commented:
I've already tried #1 - that doesn't work, because init is called at the same time as static:
DEBUG: my HibernateFilter static initializer....
DEBUG: In HibernateFilter Init

#2 I'd rather not do, since Hibernate's connection pooling is (according to its documentation) less that production ready.

But, why would the JDBC connection have anything to do with this? Hibernate seems to be complaining that it's loosing its configuration. That exception is the same I'd get if I tried session.find("from a.class.that.doesn't.exist").
0
 
aozarovCommented:
>> I've already tried #1 - that doesn't work, because init is called at the same time as static:
Right, but this time you store it in your application context.

>> But, why would the JDBC connection have anything to do with this?
Because I assume (maybe wrongly) that the problem might be one of two problems (loose configuration or configuration uses the wrong DS).
0
 
NazraxAuthor Commented:
I'd really love to get this working - anybody else have any ideas?
0
 
NazraxAuthor Commented:
Well, I upgraded to Hibernate 3, and it fixed the problem.
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

  • 10
  • 9
Tackle projects and never again get stuck behind a technical roadblock.
Join Now