SQL Zone is brought to you in partnership with:

J2EE developer with over 7 years of experience in designing and implementing enterprise j2ee solutions based on open source technologies like Tapestry, Hibernate, Spring. Current interests include Tapestry, Plastic, Spock, Scala. Taha is a DZone MVB and is not an employee of DZone and has posted 40 posts at DZone. You can read more from them at their website. View Full User Profile

Connecting to Multiple Databases Using Hibernate

03.07.2012
| 25762 views |
  • submit to reddit

In a recent project, I had a requirement of connecting to multiple databases using hibernate. As tapestry-hibernate module does not provide an out-of-box support, I thought of adding one. https://github.com/tawus/tapestry5

Now that the application is in production, I thought of writing a simple “How to”.

I have cloned the latest stable(5.3.2) tapestry project at

https://github.com/tawus/tapestry5

and have added multiple database support to it.

Single Database

It is almost fully compatible with the previous integration when using a single database except for a few things

1) HibernateConfigurer has changed

public interface HibernateConfigurer
{
   /**
    * Passed the configuration so as to make changes.
    */
   void configure(Configuration configuration);

   /**
    * Factory Id for which this configurer is meant for
    */
   Class<? extends Annotation> getMarker();

   /**
    * Entity package names
    * 
    * @return
    */
   String[] getPackageNames();
}

2) There is no HibernateEntityPackageManager, as the packages can be contributed by adding more HibernateConfigurers with the same Markers.

Multiple databases

For multiple database, a marker has to be used for accessing Session or HibernateSessionManager

@Inject
@XDB
private Session session;

@Inject
@YDB
private HibernateSessionManager sessionManager;

@XDB
@CommitAfter
void myMethod(){

}

Also you have to define a HibernateSessionManager and a Session for the secondary database in the Module class.

@Scope(ScopeConstants.PERTHREAD)
@Marker(DatabaseTwo.class)
public static HibernateSessionManager buildHibernateSessionManagerForFinacle(
    HibernateSessionSource sessionSource,
    PerthreadManager perthreadManager)
{
    HibernateSessionManagerImpl service = new HibernateSessionManagerImpl(sessionSource,
        DatabaseTwo.class);

    perthreadManager.addThreadCleanupListener(service);

    return service;
}

@Marker(DatabaseTwo.class)
public static Session buildSessionForFinacle(
    @Local HibernateSessionManager
        sessionManager,
    PropertyShadowBuilder propertyShadowBuilder)
{
    return propertyShadowBuilder.build(sessionManager, "session", Session.class);
}

Notice an annotation @DatabaseTwo.class. This is a Factory marker and is used to identify a service related to a particular SessionFactory.

@Retention(RetentionPolicy.RUNTIME)
@Target( {ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@FactoryMarker
@Documented
public @interface DatabaseTwo
{

}

A typical AppModule for two databases will be

public class AppModule
{

    public static void bind(ServiceBinder binder)
    {
        binder.bind(DemoService.class, DemoServiceImpl.class);
    }

    @Contribute(HibernateSessionSource.class)
    public static void configureHibernateSources(OrderedConfiguration<HibernateConfigurer>
        configurers)
    {
        configurers.add("databaseOne", new HibernateConfigurer()
        {
            public void configure(org.hibernate.cfg.Configuration configuration)
            {
                configuration.configure("/databaseOne.xml");
            }

            public Class<? extends Annotation> getMarker()
            {
                return DefaultFactory.class;
            }

            public String[] getPackageNames()
            {
                return new String[] {"org.example.demo.one"};
            }
        });

        configurers.add("databaseTwo", new HibernateConfigurer()
        {
            public void configure(org.hibernate.cfg.Configuration configuration)
            {
                configuration.configure("/databaseTwo.xml");
            }

            public Class<? extends Annotation> getMarker()
            {
                return DatabaseTwo.class;
            }

            public String[] getPackageNames()
            {
                return new String[] {"org.example.demo.two"};
            }
        });
    }

    @Contribute(SymbolProvider.class)
    @ApplicationDefaults
    public static void addSymbols(MappedConfiguration<String, String> configuration)
    {
        configuration.add(HibernateSymbols.DEFAULT_CONFIGURATION, "false");
        configuration.add("tapestry.app-package", "org.example.demo");
    }


    @Scope(ScopeConstants.PERTHREAD)
    @Marker(DatabaseTwo.class)
    public static HibernateSessionManager buildHibernateSessionManagerForFinacle(
        HibernateSessionSource sessionSource,
        PerthreadManager perthreadManager)
    {
        HibernateSessionManagerImpl service = new HibernateSessionManagerImpl(sessionSource,
            DatabaseTwo.class);

        perthreadManager.addThreadCleanupListener(service);

        return service;
    }

    @Marker(DatabaseTwo.class)
    public static Session buildSessionForFinacle(
        @Local HibernateSessionManager
            sessionManager,
        PropertyShadowBuilder propertyShadowBuilder)
    {
        return propertyShadowBuilder.build(sessionManager, "session", Session.class);
    }

}

Injecting into Services

You can inject a session in a service using the marker. As DatabaseOne is being used as the default configuration, in order to inject its Session, you have to annotate it with @DefaultFactory. For DatabaseTwo, you can use @DatabaseTwo annotation.

public class DemoServiceImpl implements DemoService
{
    private Session sessionOne;

    private Session sessionTwo;

    public DemoServiceImpl(
        @DefaultFactory Session sessionOne,
        @DatabaseTwo Session sessionTwo)
    {
        this.sessionOne = sessionOne;
        this.sessionTwo = sessionTwo;
    }

    @SuppressWarnings("unchecked")
    public List<EntityOne> listOnes()
    {
        return sessionOne.createCriteria(EntityOne.class).list();
    }

    @SuppressWarnings("unchecked")
    public List<EntityTwo> listTwos()
    {
        return sessionTwo.createCriteria(EntityTwo.class).list();
    }

    public void save(EntityOne entityOne)
    {
        sessionOne.saveOrUpdate(entityOne);
    }

    public void save(EntityTwo entityTwo)
    {
        sessionTwo.saveOrUpdate(entityTwo);
    }
}


Using @CommitAfter

You can add an advice the same way you used to. The only change is in @CommitAfter. You have to additionally annotate the method with the respective marker.

public interface DemoService
{
    List<EntityOne> listOnes();

    List<EntityTwo> listTwos();

    @CommitAfter
    @DefaultFactory
    void save(EntityOne entityOne);

    @CommitAfter
    @DatabaseTwo
    void save(EntityTwo entityTwo);
}

Here is an example.

 

From http://tawus.wordpress.com/2012/03/03/tapestry-hibernate-multiple-databases/

Published at DZone with permission of Taha Siddiqi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: