8

I'm starting a new project, trying to do things right this time(so more than one question), I might need some help, I'm not sure what I'm doing wrong :

  1. Spring context
  2. Controller
  3. Service Interface
  4. Service Implementation
  5. DAO interface
  6. DAO implementation

I want to utilize spring MVC as much as possible, how do I make session opening/closing handled by @Transactional?

How do I catch the exceptions(i.e. non existing record or database failed) if any. i.e My database doesn't accept duplicate entries like this one :

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry

How can I catch this?

And for every next request I make I get this exception :

org.hibernate.AssertionFailure: null id in com.test.spring.ws.service.impl.TestObject entry (don't flush the Session after an exception occurs)

What I'm doing wrong? Can anyone suggest some improvements in my project?

London
  • 14,986
  • 35
  • 106
  • 147

4 Answers4

12

Component Scan

First things first: you're using @Controller, @Service, @Repository, and an @Autowired, but you don't do anything with them. I recommend using classpath scanning. Remove the "testServiceDAO" and "testService" beans from your spring context file, and instead use:

<context:component-scan base-package="com.test.spring.ws"/>

That will find and create those beans by their annotations instead of requiring you to declare them in the XML. Add @Autowired to the testServiceDAO field in your service and to the sessionFactory field in your DAO. Remove the setters for these fields. They're no longer needed. The component-scan tag will also do the autowiring for you. To use the context namespace, you need to add it to your root element. For example:

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">

Transaction Management

To use @Transactional, as Sean said, you need to add an element to your spring context file:

<tx:annotation-driven/>

Since your transaction manager bean is named "transactionManager", it will find it automatically. You also need to add the "tx" namespace to your root element, so it should look something like:

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
         http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/tx 
         http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.0.xsd">

For this to have any chance of working, you need to remove both session.beginTransaction() and session.close() from your DAO method. Opening your own transaction in that way is mixing programmatic and declarative transaction demarcation, and the declarative way is usually better. Also, you should never ever close a session in a DAO in a real project. That will get you into all kinds of trouble.

Exception Handling

Your MySQLIntegrityConstraintViolationException, being a database-specific exception, would be caught by Hibernate and wrapped in a ConstraintViolationException, which is what would come out of your DAO; however, since your DAO is a @Repository now, you can benefit from Spring's exception translation. With this, the Hibernate exception will be caught by Spring and translated to a DataIntegrityViolationException. Database exception handling is always fun!

Session Management

Are you using an OpenSessionInViewFilter or OpenSessionInViewInterceptor? If so, a Hibernate session is opened when a request is first received and closed after the response is written. If not, then the session doesn't start until a transaction begins (at an @Transactional method), and it's closed when that transaction finishes. With the filter/interceptor, you can do things in the "view" tier that require calling back to the database--specifically when you have lazy relationships or lazy-loaded objects that you need for rendering the view. If the session isn't available--as it isn't if it only exists for the length of the transactional service method--you can't do those things in the view and you'll get the infamous LazyInitializationException.

As for the "don't flush the Session after an exception occurs" error you're getting, I don't see anything immediately that would make me think that should happen. Perhaps something in your web-tier spring context is misconfigured, or maybe there's some weird interplay in how you're handling the transaction and session directly in the DAO.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • i assumed "don't flush the Session after an exception occurs" was on account of his closing the session in the DAO. – Nathan Hughes Jul 15 '11 at 17:58
  • I thought about that and even looked through the source of SessionImpl a little, but I'm fairly certain that close() doesn't cause a flush(). I could be wrong. If someone can prove it, I'll update the answer. – Ryan Stewart Jul 15 '11 at 18:19
  • 1
    See http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-basics-issues, which describes how an "exception thrown by Hibernate means that you have to rollback your database transaction and close the Session immediately." Once you have this exception your session is dead. – Will Iverson Jul 16 '11 at 07:15
2

how do I make session opening/closing handled by @Transactional?

You need <tx:annotation-driven /> (and the tx namespace) in your spring context.

(see Using @Transactional)

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
1

I would suggest extending HibernateDaoSupport and using HibernateTemplate rather than using the SessionFactory (and creating transactions) explicitly in your DAO code.

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 1
    That's old-school. Current best practice is more or less as he has the DAO written: inject a SessionFactory and use getCurrentSession(). – Ryan Stewart Jul 15 '11 at 16:43
  • 2
    HibernateTemplate et al is no longer recommended in Spring 3.x. See http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/orm.html#orm-hibernate-straight – Sean Patrick Floyd Jul 15 '11 at 16:43
  • Interesting, I was not aware of this. Although the documentation at the link doesn't mention HibernateTemplate not being recommended anymore, it just seems to barely mention it. Personally I prefer would prefer HibernateTemplate to translate HibernateException to DataAccessException family – matt b Jul 15 '11 at 18:54
  • You can translate HibernateException to DataAccessException using @Repository annotation. – danny.lesnik Jul 15 '11 at 22:38
-1

I would strongly recommend staying far, far away from the @Transactional in Spring. For your usage, stick with an open-session-in-view pattern. When a request comes in, a session is opened and a transaction started. If a exception or other error is encountered, roll back the transaction. Otherwise commit. That way, Hibernate takes care of all of the heavy lifting for you.

If you go down the path of @Transactional, you will enter into a murky realm of lazy loading exceptions and other strange behavior as you try to sort out what came from where. At the worst, you will need to keep careful track of the order in which methods are called (i.e. pay strict attention to your stack) to ensure that you have the right permissions.

Worst of all, if you put @Transactional on things that are already in the Hibernate first or second level cache, you will wind up getting lots and lots of BEGIN/END transactions going to your database with no actual queries being executed (because they are in the cache). This can kill your performance and are almost impossible to quash.

After the transaction in your session blows up, you need to rollback. You may need to redo your session, depending on the semantics around that. The fix for the first one is simple - do a check first to see if the entity already exists before doing the save. That will fix the second one.

Check out this article comparing Hibernate/JPA and myBatis for some more commentary.

Will Iverson
  • 2,009
  • 12
  • 22
  • 2
    The open-session-in-view pattern isn't a replacement for declarative transactions. The OSIVFilter doesn't start transactions. Spring's transaction management and its OSIVFilter are complementary to each other, but each fulfills a very specific role. The problems you're describing result from misunderstanding and/or misusing them. – Ryan Stewart Jul 15 '11 at 16:39
  • Ok... how would you describe their roles? How would you address the concerns? I'm not saying that they do the same thing, I'm saying that adding Spring transaction management (especially for typical web apps as described by the OP) is a lot of complexity and headache with no particular gain. – Will Iverson Jul 16 '11 at 06:51
  • I briefly described the roles earlier today in [answer](http://stackoverflow.com/questions/6709750/proper-usage-of-spring-mvc-3-with-hibernate-spring-orm/6710885#6710885) to another post. Spring transaction management is about the simplest you can find and is designed exactly for the type of app being discussed here. It's so simple, I could practically give a tutorial on it in the constraints of this comment. One bean: the transaction manager, wired with your persistence mechanism of choice; one line of XML to enable the annotations; and annotations on the service methods. Done. – Ryan Stewart Jul 16 '11 at 07:04
  • Well, that's the way the tutorials describe it. Once you start doing anything with complexity, you are back in the land of conflicting transactional annotations, with different implications across the stack as you hit different access with different settings. Trivial, one-off method requests are not the issue. As soon as you start trying to cross boundaries across methods with different transactions, you're done for - the results can only be debugged (or understood) by carefully comparing the transactions emitted by the code and walking through the stack with a debugger. – Will Iverson Jul 16 '11 at 07:08