Help

We were doing some work with a customer with a very large project recently, and they were concerned about traceability of the SQL issued by Hibernate. Their problem is one that I guess is common: suppose I see something wong in the Hibernate log (say, some N+1 selects problem), how do I know which of my business classes is producing this? All I've got in the Hibernate log is org.hibernate.SQL, line 224 as the source of the log message!

I started to explain how Hibernate3 can embed comments into the generated SQL, so you could at least track the problem back to a particular HQL query. But then Steve remembered that log4j provides the /nested diagnostic context/. Now, I've seen a lot of projects using log4j, but I've never actually seen this used anywhere. I think it might be a better alternative to adding entry and exit logging everywhere, since we can see this context even if the entry/exit log categories are disabled. It's a good way to track the source of SQL in the Hibernate log. All you need to do is add calls to push() and pop() in your DAO:


public List getCustomersByName(String pattern) {
    NDC.push("CustomerDAO.getCustomersByName()");
    try {
        return getSession()
            .createQuery("from Customer c where c.name like :pattern")
            .setString("pattern", pattern)
            .list();
    }
    finally {
        NDC.pop();
    }
}

Then, if I set my pattern right:


log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m (%x)%n

I'll get a log message like this:


20:59:38,249 DEBUG [=>SQL:244] - select .... like ? (CustomerDAO.getCustomersByName())

Just thought I'd mention it, in case it helps someone.

8 comments:
 
25. Aug 2004, 16:59 CET | Link
Rafael Alvarez
An example of an application that needs to map an object to several tables: Versionable Objects. In the Insurance world, you need to store every version of each policy so if there is a claim then it 's processed using the state of the policy at the date of the claim.

Simplifying a bit, a Policy Object will have field that can change (like the Insurance Amount, or the Validity) and fields that won't change (like the Product/branch of the policy). To avoid redundancy, we stored the things that changed in one table and the things that don't change in another table, thus our Policy object map to two tables.

Given the "limitation" of Hibernate 2.0, we created an auxiliary object to handle the variable part. This is like the Mememto pattern, but it's another object to store in memory, and we have a very object-intensive application so we would like to remove it.

Hibernate 3.0 is what we're waiting for :)
ReplyQuote
 
26. Aug 2004, 02:39 CET | Link
Gavin
(1) Yeah, if method name is all you want, its fine for AOP. All I mean is I think you might want something different to that in a real system. Maybe I'm wrong.

(3) was not in response to you, but to Jeff. Sorry for confusement.
 
25. Aug 2004, 16:50 CET | Link
Jeff Mesnil | jmesnil(AT)gmail.com
Gavin, I haven't had a look at the internals of Hibernate butone has to pay attention that NDC does not work if you create threads (the child won't automatically inherit the NDC of its parent). It has do be done explicitely with NDC.cloneStack() and NDC.inherit().

However, if you want threads to automatically inherit from the parent context, you can use MDC (M is for Mapped).
It uses a Map to store the contexts.
Then in your log4j pattern, you can do interesting things such as %X{ip} %X{sessionId}.
this will log the context values associated with the keys "ip" and "sessionId" that you've put with MDC.put(name, value) regardless of the thread hierarchy.
It's quite useful for multithreaded web app and may be interesting for Hibernate.

hope it helps,
jeff
 
25. Aug 2004, 15:20 CET | Link
Ryan Breidenbach | ryan_breidenbach(AT)hotmail.com
This looks to be an appropriate case for AOP, no? Around advice the pushes/pops the NDC based on the method name.

Also, if you are simply using the method name as the context for the log message, you can change the log4j pattern to include this instead. Simply change the %c{1}:%L to %c{1}.%M:%L in your log4j.properties file. This may be slower, but that is not really that important when you are trying to diagnose a bug, right?
 
25. Aug 2004, 23:40 CET | Link
Ryan Breidenbach | ryan_breidenbach(AT)hotmail.com
Gavin,

(1) I don't follow why this would be a bad candidate for AOP. In the example you gave, the context you suggested was the business method name. For me, this makes sense as a context - it let's me know from where in *my* code the Hibernate call originated.

To take this a step further - say I want to use the method name as my log4j context for all of my business methods. Applying this with an aspect makes perfect sense to me. I am talking about pushing/popping the log4j context via an aspect, not the actual logging. Yes, the logging itself needs semantic context to be of any use. What am I missing here?

(2) Nevermind on #2. It clicked with me afterwards why this doesn't help. I am interested in *my* business class method, not the method in which Hibernate is issuing the log statement.

(3) Huh?
 
12. Sep 2004, 09:42 CET | Link
eepstein
Not convenient? I thought NDC could have layers of context. So couldn't you have the existing user tracking/context and push the classname/methodname/whatever as Gavin shows? Maybe I missed the gist...
 
06. Sep 2004, 12:23 CET | Link
Jean-Pol
Gavin,
I guess this is not a convenient solution for some of us as the NDC is already often used to track the users when the application is tested in multi-users mode : storing the session ID in the NDC, you can follow the actions from the users in your logs. And this can be done elegantly (it becomes truly transparent) if you do it in a Servlet Filter that you set on the Struts servlet.
 
25. Aug 2004, 17:00 CET | Link
Gavin
(1) I'm not sure that you would usually be wanting just a method name, normally with logging you want something with more semantic content, Logging is a really bad usecase for AOP.

(2) %M doesn't help us at all! The method that called Log.debug() is org.hibernate.jdbc.AbstractBatcher.log(), absolutely useless to the reader of the log. That's the point - we want some way of associating the SQL statement with the transaction script, ie. with some more "interesting" context.

(3) Hibernate does not start any threads. :-)
Post Comment