Friday, November 17, 2006
Spring into Hibernate - and it hurts
It's been a long Spring debugging, google searching, forum scrubbing session. I just wanted sipXconfig to talk to CDR database in addition to SIPXCONFIG database. I thought I understood well what has to be done. New data source, new LocalSessionFactory, new HibernateInterceptor fine tuned for read only connection. I had everything wired in 10 minutes. I ran my test and spring surprised me with the following exception:
Initialization of bean failed; nested exception is org.hibernate.HibernateException: No local DataSource found for configuration - dataSource property must be set on LocalSessionFactoryBean
I double checked everything, I borrowed Douglas, which usually helps. Nothing.
Soon I was browsing in search of similar problems. I found out how to set up transaction spanning multiple sessions, but I did not need it. I found how to switch your data source based on what who is logged in, but I did not need it either. I just wanted to connect to the second database and it did not work.
Finally I found SPR-1633 which was good in a "misery loves company" kind of way. Looks like Hibernate does not really provide any easy way for Spring to inject data source, so Spring has to define a static function getConfigTimeDataSource in order for Hibernate to retrieve the data source. As soon as Hibernate is done Spring would reset configTimeDataSource. It does seem strange (or as some people call it: ingenious) but as long as you do not want to initialize two LocalSessionFactory objects in the same thread at the same time it should work.
Right. But it did not work for me. It was getting personal now: I know it's impossible to initialize 2 object at the same time from the same single thread. Everybody knows that...
Unless... Unless of course in the middle of initializing the first object, we explicitly ask the other object to initialize. Which - as it turns out - is exactly what we do.
We are way too smart to use standard Spring LocalSessionFactoryBean. We write our own extended factories. The coolest among them (the most ingenious one) is DynamicSessionFactoryBean. It extends LocalSessionFactoryBean to dynamically create Hibernate mappings for all gateway and phone plug-ins. Thanks to this trick plug-in developers do not have to modify Hibernate mapping files when adding new phone or gateway type. They do not even have to know sipXconfig uses Hibernate. In order for that to work, in the middle of its initialization phase, DynamicSessionFactoryBean asks Spring to enumerate all the beans: to find all these plugged in phones. Well, whenever you call getBeanNamesForType, Spring will initialize the remaining beans (even the lazy ones). Which of course triggers initialization of the other LocalSessionFactoryBean, which promptly resets carefully wired data source. When we try to complete initialization of DynamicSessionFactoryBean it does not have DataSource any more...
Now for the blame part. Hibernate should work better with Spring, so that Spring does not restore to ugly hacks. Spring should not restore to ugly hacks - it should find a way to influence Hibernate community to improve data source injecting. sipXconfig should find a better way to inject hibernate mappings - something that does not trigger initializing all the beans.
For now I am just going to use JDBC for CDRs. This cool lazy data source proxy that I just noticed probably cannot wait to be abused...