Moved wiki docs into main project.
Why have them separate? Github allows reading docs right in the main tree.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
_Overview of Caching._
|
||||
|
||||
Records are cached by primary key. Lookups by primary key first check the cache and on a miss, load
|
||||
the record from the database and insert it into the cache.
|
||||
|
||||
Queries for records that have primary keys are automatically split into phases:
|
||||
|
||||
* A query is made to the database for the primary keys of all rows that match the query.
|
||||
* Any records in the cache are obtained from the cache.
|
||||
* All remaining records are loaded by primary key in a single additional query and placed into
|
||||
the cache.
|
||||
|
||||
For deletions using a `Where` clause, first the primary keys that match the deletion clause are
|
||||
loaded, then those records are deleted from the database and the cache using their primary key.
|
||||
|
||||
Decomposition for updates using a `Where` clause is not yet implemented, but a fallback mechanism
|
||||
to invalidate the cache manually is provided for those cases.
|
||||
|
||||
If one already has the primary keys for the records they desire, it is possible to avoid the first
|
||||
phase of the decomposed query using `loadAll()` instead of `findAll()`:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class MemberNameRecord extends PersistentRecord
|
||||
{
|
||||
/** This member's unique id. */
|
||||
@Id public int memberId;
|
||||
|
||||
/** The name by which this member is known. */
|
||||
public String name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up members' names by id.
|
||||
*/
|
||||
public Map<Integer, MemberName> loadMemberNames (final Set<Integer> memberIds)
|
||||
{
|
||||
final Map<Integer, MemberName> names = Maps.newHashMap();
|
||||
for (MemberNameRecord name : loadAll(MemberNameRecord.class, memberIds)) {
|
||||
names.put(name.memberId, name.name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
```
|
||||
|
||||
This will efficiently fetch all records that it can from the cache and then load and cache any
|
||||
remaining records.
|
||||
@@ -0,0 +1,66 @@
|
||||
_Overview of Computed Records._
|
||||
|
||||
Note: computed records are largely deprecated in favor of ad-hoc queries or the use of
|
||||
`selectInto`. See [the example queries page](ExampleQueries) for examples of such use.
|
||||
|
||||
## Computed Records
|
||||
|
||||
You can easily define record with computed fields or records that represent a join across multiple
|
||||
tables (see below). It is very easy to select a subset of a record's fields:
|
||||
|
||||
```java
|
||||
@Entity @Computed(shadowOf=PersonRecord.class)
|
||||
public PersonNameRecord extends PersistentRecord
|
||||
{
|
||||
public int personId;
|
||||
public String name;
|
||||
}
|
||||
|
||||
List<PersonNameRecord> allNames = findAll(PersonNameRecord.class);
|
||||
List<PersonNameRecord> youngNames = findAll(
|
||||
PersonNameRecord.class, new Where(PersistentRecord.AGE.lessEq(18)));
|
||||
```
|
||||
|
||||
You can also define computed records that calculate information:
|
||||
|
||||
```java
|
||||
@Entity @Computed
|
||||
public CountRecord extends PersistentRecord
|
||||
{
|
||||
@Computed(fieldDefinition="count(*)")
|
||||
public int count;
|
||||
}
|
||||
|
||||
int personCount = load(CountRecord.class, new FromOverride(PersonRecord.class)).count;
|
||||
|
||||
// or if you want to be less general purpose
|
||||
|
||||
@Entity @Computed(shadowOf=PersonRecord.class)
|
||||
public PersonCountRecord extends PersistentRecord
|
||||
{
|
||||
@Computed(fieldDefinition="count(*)")
|
||||
public int count;
|
||||
}
|
||||
|
||||
int personCount = load(PersonCountRecord.class).count;
|
||||
|
||||
// or something more specific
|
||||
|
||||
@Entity @Computed(shadowOf=PersonRecord.class)
|
||||
public PersonAvgAgeRecord extends PersistentRecord
|
||||
{
|
||||
@Computed(fieldDefinition="avg(*)")
|
||||
public float averageAge;
|
||||
}
|
||||
|
||||
float averageAge = load(PersonAvgAgeRecord.class).averageAge;
|
||||
```
|
||||
|
||||
The reader may be alarmed to notice some hard-coded SQL in those classes. One is advised to stick
|
||||
to very standard SQL in cases like this to avoid introducing portability problems, but we didn't
|
||||
think it was worth the trouble to try to model these simple and mostly standard operations in a
|
||||
more complex way. YMMV.
|
||||
|
||||
## Joins with Computed Records
|
||||
|
||||
TBD
|
||||
@@ -0,0 +1,207 @@
|
||||
_Configuration and Dependencies._
|
||||
|
||||
## JVM Version Requirement
|
||||
|
||||
Depot currently requires JDK 1.6 or greater.
|
||||
|
||||
### Integrate with Ivy or Maven
|
||||
|
||||
Depot is published to the Maven Central repository and can be added as a dependency using the
|
||||
following configuration: `com.samskivert:depot:1.6.4`
|
||||
|
||||
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).
|
||||
|
||||
## Manually Adding Dependencies
|
||||
|
||||
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)
|
||||
|
||||
## Configuration
|
||||
|
||||
The two main components that require configuration are the JDBC connection provider and the cache
|
||||
implementation.
|
||||
|
||||
### StaticConnectionProvider
|
||||
|
||||
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):
|
||||
|
||||
```java
|
||||
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);
|
||||
```
|
||||
|
||||
### DataSourceConnectionProvider
|
||||
|
||||
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`:
|
||||
|
||||
```java
|
||||
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.
|
||||
|
||||
### EHCacheAdapter
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
CacheManager cacheMgr = CacheManager.getInstance();
|
||||
ConnectionProvider conProv = // ...
|
||||
PersistenceContext perCtx = new PersistenceContext("ident", conProv, new EHCacheAdapter(cacheMgr));
|
||||
```
|
||||
|
||||
See the note below on lifecycle management.
|
||||
|
||||
### PersistenceContext Lifecycle
|
||||
|
||||
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.
|
||||
|
||||
#### ConnectionProvider
|
||||
|
||||
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 JDBC `Connection` instances it has created when it is
|
||||
shutdown. If you are using Depot with `StaticConnectionProvider` you can simply shutdown your
|
||||
`PersistenceContext` and you're done.
|
||||
* `DataSourceConnectionProvider` will not shutdown its underlying `DataSource` implementations
|
||||
(indeed there is no API for doing so). As long as no queries are executing at the time that
|
||||
`PersistenceContext` is shutdown, then all JDBC `Connection` instances will have been closed
|
||||
and returned to the `DataSource` connection pool, so the application can shutdown its data
|
||||
sources in whatever way is appropriate.
|
||||
|
||||
#### CacheAdapter
|
||||
|
||||
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 underlying `CacheManager` 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 the `CacheManager` itself when it is known to no longer be
|
||||
needed.
|
||||
|
||||
## Injection
|
||||
|
||||
We use Guice around these parts for dependency injection. Using injection allows you to inject the
|
||||
`PersistenceContext` into your repository implementations:
|
||||
|
||||
```java
|
||||
@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:
|
||||
|
||||
```java
|
||||
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.
|
||||
@@ -0,0 +1,152 @@
|
||||
_Describes various example queries._
|
||||
|
||||
Depot queries are constructed using a builder-pattern.
|
||||
|
||||
## Whole record queries
|
||||
|
||||
A basic query to select all rows from a table looks like so:
|
||||
|
||||
```java
|
||||
from(PersonRecord.class).select();
|
||||
```
|
||||
|
||||
Note that the above query and the others in these examples will use the `PersonRecord` from the
|
||||
SimpleCodeExample page.
|
||||
|
||||
Various query clauses may be added to the basic query above to do filtering, ordering and the like.
|
||||
Here's a query with a simple where clause:
|
||||
|
||||
```java
|
||||
// selects all records with age <= 25
|
||||
from(PersonRecord.class).where(PersonRecord.AGE.lessEq(25)).select();
|
||||
```
|
||||
|
||||
One can also order and limit the results:
|
||||
|
||||
```java
|
||||
// selects the first ten records in ascending alphabetic order on name
|
||||
from(PersonRecord.class).ascending(PersonRecord.NAME).limit(10).select();
|
||||
```
|
||||
|
||||
More complex orderings are also possible:
|
||||
|
||||
```java
|
||||
OrderBy order = OrderBy.ascending(PersonRecord.NAME).thenDescending(PersonRecord.AGE);
|
||||
from(PersonRecord.class).orderBy(order).select();
|
||||
```
|
||||
|
||||
## Ad-hoc queries
|
||||
|
||||
Instead of selecting whole rows from the database, one can select individual columns, or the
|
||||
results of aggregate and other functions. Here's a simple projection:
|
||||
|
||||
```java
|
||||
List<Tuple2<Integer,String>> results =
|
||||
from(PersonRecord.class).select(PersonRecord.ID, PersonRecord.NAME);
|
||||
```
|
||||
|
||||
Depot annotates the `ColumnExp` constants generated in your `PersistentRecord` classes with their
|
||||
type so that queries like the above can be done in a type-safe manner. `Tuple` classes are provided
|
||||
up to `Tuple5` for such ad-hoc queries.
|
||||
|
||||
As an alternative to a `Tuple` class, you can use a type-safe builder to receive the results of
|
||||
your query like so:
|
||||
|
||||
```java
|
||||
public class IdName {
|
||||
public static Builder2<IdName, Integer, String> IDNAME_BUILDER =
|
||||
new Builder2<IdName, Integer, String>() {
|
||||
public IdName build (Integer a, String b) {
|
||||
return new IdName(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
public int id;
|
||||
public String name;
|
||||
public IdName (int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
List<IdName> results =
|
||||
from(PersonRecord.class).select(IDNAME_BUILDER, PersonRecord.ID, PersonRecord.NAME);
|
||||
```
|
||||
|
||||
Such queries will result in compile time error if the types of the columns do not match the types
|
||||
expected by the builder. The `BuilderN` interfaces are also only available up to arity-5.
|
||||
|
||||
For situations where type-safety is not a major concern, and for cases where you wish to select
|
||||
more than five columns, you can use `selectInto` which uses reflection to construct results:
|
||||
|
||||
```java
|
||||
public class NameCount {
|
||||
public String name;
|
||||
public int count;
|
||||
public NameCount (String name, int count) {
|
||||
this.name = name;
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
List<NameCount> results =
|
||||
from(PersonRecord.class).groupBy(PersonRecord.NAME).selectInto(
|
||||
NameCount.class, PersonRecord.NAME, Funcs.countStar());
|
||||
```
|
||||
|
||||
Note that the class supplied to the `selectInto` method must have exactly one public constructor,
|
||||
and the arguments to that constructor must match __in order__, the columns specified in the
|
||||
`selectInto` call. The types of the selected columns (or expressions) must be convertible to the
|
||||
type needed by the constructor (which means they will be widened or unboxed, but not converted from
|
||||
`float` to `int` or other non-automatic conversions). These requirements are unfortunately not
|
||||
checkable at compile time, and instead result in a runtime error when violated. Fortunately,
|
||||
testing tends to catch any such errors before they make it into the wild.
|
||||
|
||||
## Count queries
|
||||
|
||||
The `selectCount` method exists for when you wish to simply select the count of rows that match
|
||||
your query. For example:
|
||||
|
||||
```java
|
||||
int youngins = from(PersonRecord.class).where(PersonRecord.AGE.lessEq(12)).selectCount();
|
||||
```
|
||||
|
||||
You may also wish to group by certain columns and select the counts of rows that match each group.
|
||||
This is done with an ad-hoc query:
|
||||
|
||||
```java
|
||||
List<Tuple2<String,Integer>> results =
|
||||
from(PersonRecord.class).groupBy(PersonRecord.NAME).
|
||||
select(PersonRecord.NAME, Funcs.countStar());
|
||||
```
|
||||
|
||||
## Other functions
|
||||
|
||||
A variety of other functions are defined in
|
||||
[Funcs](http://depot.googlecode.com/svn/apidocs/com/samskivert/depot/Funcs.html),
|
||||
[StringFuncs](http://depot.googlecode.com/svn/apidocs/com/samskivert/depot/StringFuncs.html),
|
||||
[DateFuncs](http://depot.googlecode.com/svn/apidocs/com/samskivert/depot/DateFuncs.html), and
|
||||
[MathFuncs](http://depot.googlecode.com/svn/apidocs/com/samskivert/depot/MathFuncs.html). These can
|
||||
be used in queries, like so:
|
||||
|
||||
```java
|
||||
List<PersonRecord> eldest =
|
||||
from(PersonRecord.class).where(PersonRecord.AGE.eq(Funcs.max(PersonRecord.AGE))).select();
|
||||
```
|
||||
|
||||
And you can select the value of a function in an ad-hoc query:
|
||||
|
||||
```java
|
||||
// note that load() can be used for selections that will only ever return one row
|
||||
Number maxAge = from(PersonRecord.class).load(Funcs.max(PersonRecord.AGE));
|
||||
|
||||
// alternatively
|
||||
List<Number> maxAge = from(PersonRecord.class).load(Funcs.max(PersonRecord.AGE));
|
||||
assert(maxAge.size() == 1);
|
||||
|
||||
// here's a more complex (if somewhat nonsensical) query that groups people by the first
|
||||
// letter of their name and selects the sum of all ages of the people in those groups
|
||||
SQLExpression<String> firstLetter = StringFuncs.substring(PersonRecord.NAME, 0, 1);
|
||||
List<String,Integer> results = from(PersonRecord.class).groupBy(firstLetter).
|
||||
select(firstLetter, Funcs.sum(PersonRecord.AGE));
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
_Overview of the Depot documentation._
|
||||
|
||||
## Docs
|
||||
|
||||
- [Simple code example](wiki/SimpleCodeExample)
|
||||
- [Configuring and integrating Depot](wiki/Configuration)
|
||||
- [Example queries](wiki/ExampleQueries)
|
||||
- [Caching](wiki/Caching)
|
||||
- [Computed records](wiki/ComputedRecords)
|
||||
- [Schema migration](wiki/SchemaMigration)
|
||||
|
||||
## Google Group
|
||||
|
||||
If you have further questions or suggestions, please post to the
|
||||
[OOO Libs Google Group](http://groups.google.com/group/ooo-libs).
|
||||
@@ -0,0 +1,50 @@
|
||||
_Overview of Schema and Data Migration._
|
||||
|
||||
## Schema Migration
|
||||
|
||||
Depot makes simple schema migrations extremely simple and complex schema migrations pretty easy.
|
||||
|
||||
* Automatic schema migration: adding a new column to a persistent record is as simple as adding
|
||||
the new field to the POJO and incrementing the `SCHEMA_VERSION_NUMBER` constant.
|
||||
* Assisted schema migration: dropping, renaming and retyping columns is very easy, and more
|
||||
sophisticated custom migrations can also be easily incorporated into Depot's schema migration
|
||||
system.
|
||||
* Data migration: migrations that do not change record schemas but manipulate their data can also
|
||||
be registered and Depot will ensure that they run successfully and only once.
|
||||
* Distributed migration coordination: Depot is designed so that you can bring up a dozen
|
||||
application servers on a dozen machines and during their initialization they will coordinate
|
||||
(through the database) which server will handle each migration and the other servers will block
|
||||
any database access until those migrations have successfully completed.
|
||||
|
||||
The addition of columns is automatic. Dropping, renaming and retyping are very simple:
|
||||
|
||||
```java
|
||||
public DecorRepository (PersistenceContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
|
||||
ctx.registerMigration(DecorRecord.class,
|
||||
new SchemaMigration.Rename(17004, "scale", DecorRecord.ACTOR_SCALE));
|
||||
ctx.registerMigration(DecorRecord.class, new SchemaMigration.Drop(17004, "offsetX"));
|
||||
ctx.registerMigration(DecorRecord.class, new SchemaMigration.Drop(17004, "offsetY"));
|
||||
ctx.registerMigration(DecorRecord.class,
|
||||
new SchemaMigration.Retype(17004, DecorRecord.FURNI_SCALE));
|
||||
}
|
||||
```
|
||||
|
||||
More complex migrations are also possible, one has to take care if they wish to preserve database
|
||||
agnosticism:
|
||||
|
||||
```java
|
||||
ctx.registerMigration(FooRecord.class, new SchemaMigration(42) {
|
||||
@Override
|
||||
public Integer invoke (Connection conn, DatabaseLiaiason liaison) throws SQLException {
|
||||
// go crazy with your raw JDBC connection or use the DatabaseLiaison to
|
||||
// help you do things in a database agnostic way
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Data Migration
|
||||
|
||||
TBD
|
||||
@@ -0,0 +1,153 @@
|
||||
_A simple Depot code example._
|
||||
|
||||
Here's a simple example to give you a quick overview of what code using Depot looks like.
|
||||
|
||||
Start by defining a persistent record, this maps to a database table:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class PersonRecord extends PersistentRecord
|
||||
{
|
||||
/** Increment this value if you change this record's schema. */
|
||||
public static final int SCHEMA_VERSION = 1;
|
||||
|
||||
/** A unique identifier for this record. Automatically filled in at row insertion time. */
|
||||
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||
public int personId;
|
||||
|
||||
/** This person's name. Note: one difference between EJB3 and Depot is that columns are
|
||||
* non-nullable by default. */
|
||||
@Column(length=100)
|
||||
public String name;
|
||||
|
||||
/** This person's age. */
|
||||
public int age;
|
||||
}
|
||||
```
|
||||
|
||||
Then you run a simple Ant task or Maven plugin that adds some unfortunately non-POJO boilerplate
|
||||
code to your record class, but this code allows you to talk about your record in queries in a way
|
||||
that the compiler can check which is a huge win.
|
||||
|
||||
If and when Java adds field literals, Depot will absolutely take advantage of them and eliminate
|
||||
this undesirable boilerplate.
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class PersonRecord extends PersistentRecord
|
||||
{
|
||||
// AUTO-GENERATED: FIELDS START
|
||||
public static final Class<PersonRecord> _R = PersonRecord.class;
|
||||
public static final ColumnExp<Integer> PERSON_ID = colexp(_R, "personId");
|
||||
public static final ColumnExp<String> NAME = colexp(_R, "name");
|
||||
public static final ColumnExp<Integer> AGE = colexp(_R, "age");
|
||||
// AUTO-GENERATED: FIELDS END
|
||||
|
||||
/** Increment this value if you change this record's schema. */
|
||||
public static final int SCHEMA_VERSION = 1;
|
||||
|
||||
/** A unique identifier for this record. Automatically filled in at row insertion time. */
|
||||
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||
public int personId;
|
||||
|
||||
/** This person's name. Note: one difference between EJB3 and Depot is that columns are
|
||||
* non-nullable by default. */
|
||||
@Column(length=100)
|
||||
public String name;
|
||||
|
||||
/** This person's age. */
|
||||
public int age;
|
||||
|
||||
// AUTO-GENERATED: METHODS START
|
||||
/**
|
||||
* Create and return a primary {@link Key} to identify a {@link PersonRecord}
|
||||
* with the supplied key values.
|
||||
*/
|
||||
public static Key<PersonRecord> getKey (int personId)
|
||||
{
|
||||
return newKey(_R, personId);
|
||||
}
|
||||
// AUTO-GENERATED: METHODS END
|
||||
}
|
||||
```
|
||||
|
||||
Next you define a repository class which will provide an application-specific persistence API. We
|
||||
highly recommend preserving this boundary and having all Depot code inside repository classes and
|
||||
only pass persistent record classes outside to your application.
|
||||
|
||||
```java
|
||||
public class PersonRepository extends DepotRepository
|
||||
{
|
||||
/**
|
||||
* Creates this repository and provides it with a context via which it will obtain JDBC
|
||||
* connections.
|
||||
*/
|
||||
public PersonRepository (PersistenceContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and returns the person with the specified id, or null if no person exists with that
|
||||
* id.
|
||||
*/
|
||||
public PersonRecord loadPerson (int personId)
|
||||
{
|
||||
return load(PersonRecord.getKey(personId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads records for all people with an age less than or equal to the specified maximum.
|
||||
*/
|
||||
public List<PersonRecord> loadYoungPeople (int maxAge)
|
||||
{
|
||||
return from(PersonRecord.class).where(PersonRecord.AGE.lessEq(maxAge)).select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the names of all people in the repository.
|
||||
*/
|
||||
public Set<String> loadNames ()
|
||||
{
|
||||
Set<String> names = new HashSet<String>();
|
||||
names.addAll(from(PersonRecord.class).select(PersonRecord.NAME));
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a newly created person record into the database. If record.personId is non-zero (or
|
||||
* non-null in the case of a non-primitive integer field) an exception will be thrown.
|
||||
*/
|
||||
public void insertPerson (PersonRecord record)
|
||||
{
|
||||
insert(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a person record. If record.personId is zero (or null in the case of a non-primitive
|
||||
* integer field) an exception will be thrown.
|
||||
*/
|
||||
public void updatePerson (PersonRecord record)
|
||||
{
|
||||
update(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a person record. If record.personId is zero (or null in the case of a non-primitive
|
||||
* integer field) a new row will be created for this record, if not the matching row will be
|
||||
* updated.
|
||||
*/
|
||||
public void storePerson (PersonRecord record)
|
||||
{
|
||||
store(record);
|
||||
}
|
||||
|
||||
@Override // from DepotRepository
|
||||
protected void getManagedRecords (Set<Class<? extends PersistentRecord>> classes)
|
||||
{
|
||||
classes.add(PersonRecord.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the [example queries](ExampleQueries) page for examples of other kinds of queries.
|
||||
Reference in New Issue
Block a user