Jens Krämer

Container Managed Database Connections With Rails

 |  jruby, tomcat, ruby, rails

This is a follow up to my recent post about running Redmine on Tomcat.

One of the more useful features of Java application servers is their ability to provide services to the applications they host, freeing them up from the task to set up these services themselves. Most commonly this is used for database connections - instead of each application managing its own connections, the application server becomes the central place where database connections are configured.

So how do we get hold of such a container-managed connection for our Rails app to use?

This happens through JNDI, which in general is a protocol used to resolve resources in application servers by their name. So the only piece of configuration that needs to be shared between server and application is the JNDI name of the configured connection resource.

Connection pool setup

First things first, if we want the app server to manage our DB connections, it needs the JDBC driver, which until now was only bundled in our WAR file. You can download it from the PostgreSQL web pages, or simply copy the jar from the jdbc-postgres gem:

$ cp $GEM_HOME/gems/jdbc-postgres-9.4.1200/lib/postgresql-9.4-1200.jdbc41.jar ~/apache-tomcat-8.0.24/lib

Resources like the connection pool can either be configured globally (in case you want to share the same database between applications), or just for a given application, which is what we do here by creating an XML file corresponding to the context our application runs in. Simply put: if your Rails app runs at /redmine, name the file redmine.xml:

$ vi ~/apache-tomcat-8.0.24/conf/Catalina/localhost/redmine.xml

Yay, finally some XML! Here you go:

<Context>
  <Resource name="jdbc/redmine_db" auth="Container"
    factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
    type="javax.sql.DataSource"
    driverClassName="org.postgresql.Driver"
    url="jdbc:postgresql://127.0.0.1:5432/jruby_redmine_dev"
    username="redmine" password="secret" maxTotal="20" maxIdle="10"
    maxWaitMillis="-1"
    timeBetweenEvictionRunsMillis="30000"
    removeAbandoned="true"
    removeAbandonedTimeout="60"
    testWhileIdle="true"
    validationQuery="select 1"
    />
</Context>

This declares a connection pool managing connections to our PostgreSQL instance. The name attribute holds the JNDI name of our connection, which we may freely choose. Just remember it for the next step and amend database connection string, username and password as needed. Check out the docs for more information about the other attributes.

JNDI Resource lookup

Inside the application, we need to reference the JNDI name in two places. First of all in web.xml, which is the central piece of metadata describing the application. Luckily Warbler has us covered here - just uncomment the config.webxml.jndi line at the very end of config/warble.rb and set the JNDI name:

config.webxml.jndi = 'java:comp/env/jdbc/redmine_db'

That java:comp/env/ prefix is probably different in other application servers, for Tomcat (and AFAIR also JBoss) it has to be like that.

Also be sure to change config/database.yml so it points to the connection pool:

production:
  adapter: postgresql
  jndi: java:comp/env/jdbc/redmine_db

Turn off the built in connection pooling of ActiveRecord

With Tomcat providing the connection pooling it is a good idea to disable Rails’ connection pooling altogether. That’s not totally trivial, as suggested in the ActiveRecord-JDBC docs, we use Bogacs for that. After adding it to the Gemfile (Gemfile.local in the case of Redmine) we initialize the FalsePool in config/application.rb:

if Rails.env.production?
  pool_class = ActiveRecord::Bogacs::FalsePool
  ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = pool_class
end

Congratulations! You may now call your Rails app Enterprise Ready.

Comments

You can use Markdown here.

For the sake of spam checking any data you submit, including your IP address, will be transferred to the US based Akismet web service (akismet.com). If that's not acceptable for you, you can also reach me by other means.