Configuration and Dependencies.
Depot currently requires JDK 1.6 or greater.
Depot is published to the Maven Central repository and can be added as a dependency using the
following configuration: com.samskivert:depot:1.7
This will automatically include the Google Guava and samskivert dependencies. You can add Ehcache
as well via net.sf.ehcache:ehcache:1.6.0
(or a newer version, if available).
Depot depends on a small number of external libraries:
- Google Guava - http://code.google.com/p/guava-libraries/
- samskivert - http://code.google.com/p/samskivert/
- Ehcache (optional) - http://ehcache.sourceforge.net/
Depot also requires a JDBC driver for the database with which you plan to operate. Depot currently supports three database backends:
- Postgresql - http://jdbc.postgresql.org/
- MySQL - http://www.mysql.com/products/connector/j/
- HSQLDB - http://hsqldb.org/ (useful for unit testing)
The two main components that require configuration are the JDBC connection provider and the cache implementation.
For testing and other simple systems that don't require connection pooling, the
StaticConnectionProvider
is a simple way to provide JDBC connections to Depot. It is used as
follows (this example uses MySQL):
Properties props = new Properties();
// you'd probably load these properties from a file, but for the purposes
// of this example, we'll set them directly in the code
props.setProperty("default.driver", "com.mysql.jdbc.Driver");
props.setProperty("default.url", "jdbc:mysql://localhost:3306/dbname");
props.setProperty("default.username", "username");
props.setProperty("default.password", "password");
PersistenceContext perCtx = new PersistenceContext(
"default", new StaticConnectionProvider(props), null);
Production systems are more likely to use a JDBC DataSource
to obtain their connections as those
provide connection pooling and integrate with JNDI and such. The only non-obvious aspect of
configuring Depot with a DataSource
is that you can provide two datasources: one for read-only
connections and one for read-write connections. Depot will obtain connections from the appropriate
source depending on whether or not it is doing a query that is safe to be performed against a
read-only mirror of your data or if it's doing a query that must talk to a database master.
What follows is a simple example of manually creating and configuring a Postgresql pooling
DataSource
:
PoolingDataSource readSource = new PoolingDataSource();
readSource.setDataSourceName("MyReadSource");
readSource.setServerName("myReadOnlyServerHost");
readSource.setDatabaseName("myDatabaseName");
readSource.setPortNumber(5432);
readSource.setUser("myUsername");
readSource.setPassword("myPassword");
readSource.setMaxConnections(4); // tune to your applications needs
PoolingDataSource writeSource = new PoolingDataSource();
writeSource.setDataSourceName("MyWriteSource");
writeSource.setServerName("myReadWriteServerHost");
writeSource.setDatabaseName("myDatabaseName");
writeSource.setPortNumber(5432);
writeSource.setUser("myUsername");
writeSource.setPassword("myPassword");
writeSource.setMaxConnections(1); // tune to your applications needs
PersistenceContext perCtx = new PersistenceContext(
"notused", new DataSourceConnectionProvider("jdbc:postgresql", readSource, writeSource), null);
See the note below on lifecycle management.
You may have noticed the second argument to the PersistenceContext
constructor in the above
examples was always null. That is where the CacheAdapter
is provided. By passing null, Depot will
not use caching. Depot comes with integration for Ehcache and implementing additional cache
integrations is as simple as implementing the CacheAdapter
interface and supplying an instance to
the PersistenceContext
constructor.
The following example assumes that you have an ehcache.xml
configuration file in your classpath.
There are other ways to configure Ehcache but we'll leave that explanation to their documentation.
CacheManager cacheMgr = CacheManager.getInstance();
ConnectionProvider conProv = // ...
PersistenceContext perCtx = new PersistenceContext("ident", conProv, new EHCacheAdapter(cacheMgr));
See the note below on lifecycle management.
When your application is shutting down it should shutdown its PersistenceContext
. However, to
avoid integration headaches, Depot does not take responsibility for shutting down certain of its
dependencies as those may be used by other parts of your application and you may wish to shut Depot
down independently of these other components.
Depot will shutdown its connection provider when the PersistenceContext
is shutdown, however the
two ConnectionProvider
implementations have different shutdown behavior as explained below.
StaticConnectionProvider
will close all JDBCConnection
instances it has created when it is shutdown. If you are using Depot withStaticConnectionProvider
you can simply shutdown yourPersistenceContext
and you're done.DataSourceConnectionProvider
will not shutdown its underlyingDataSource
implementations (indeed there is no API for doing so). As long as no queries are executing at the time thatPersistenceContext
is shutdown, then all JDBCConnection
instances will have been closed and returned to theDataSource
connection pool, so the application can shutdown its data sources in whatever way is appropriate.
Depot will shutdown its CacheAdapter
when the PersistenceContext
is shutdown, however the
CacheAdapter
implementation is free to do nothing in its shutdown
call.
EHCacheAdapter
does not shutdown its underlyingCacheManager
when it is shutdown to avoid conflict with other aspects of the application that may use Ehcache. Thus the application is responsible for shutting down theCacheManager
itself when it is known to no longer be needed.
We use Guice around these parts for dependency injection. Using injection allows you to inject the
PersistenceContext
into your repository implementations:
@Singleton
public class FooRepository extends DepotRepository {
@Inject public FooRepository (PersistenceContext perCtx) {
super(perCtx);
}
}
and then inject your repositories wherever you need them.
We also find the following pattern to be very effective:
public class FooModule extends AbstractModule {
@Override protected void configure () {
super.configure();
// depot dependencies (we will initialize this persistence context later when the
// server is ready to do database operations; not initializing it now ensures that no
// one sneaks any database manipulations into the dependency resolution phase)
bind(PersistenceContext.class).toInstance(new PersistenceContext());
}
}
public class WhateverHandlesAppServerLifecycle {
public void init () {
// initialize our persistence context
ConnectionProvider conProv = // ...
_perCtx.init("ident", conProv, new EHCacheAdapter(_cacheMgr));
// initialize our depot repositories; this runs all of our schema and data migrations
_perCtx.initializeRepositories(true);
}
public void shutdown () {
_perCtx.shutdown();
_cacheMgr.shutdown();
}
@Inject protected PersistenceContext _perCtx;
protected CacheManager _cacheMgr = CacheManager.getInstance();
}
One major benefit to the approach of delaying the initialization of your persistence context until
the dependency resolution phase is complete is to ensure that no code accidentally (or
intentionally) starts talking to the database during that phase. You almost certainly want to
resolve all of your injection dependencies and then before you turn your application server loose,
call initializeRepositories
to cause all of your schema and data migrations to be run (or to fail
and abort the initialization of your application).
If you don't call initializeRepositories
then Depot will lazily initialize each
PersistentRecord
class when it is first accessed and run any schema migrations for that record.
Data migrations will be disabled if you choose this lazily initialized approach.
Another note on initializeRepositories
is that this will initialize all repositories that have
been constructed with the supplied PersistenceContext
up to that point. Any repositories
constructed after initializeRepositories
has been called will be initialized at that time
(running schema and data migrations for their records) and a warning will be generated to alert you
to this undesirable behavior. Again, experience has shown that you generally want to get all of
your schema and data migrations out of the way immediately and before the application server starts
normal operation.