CBE Examples on GitHub

Cayenne by Example

Illustrating some of the major features of the Apache Cayenne ORM framework using simple examples.

Download this project as a .zip file Download this project as a tar.gz file

Unit Testing

Cayenne Version 3.0.2
Project Directory UnitTesting
Source Directories UnitTesting/src/main/java (Cayenne classes)
UnitTesting/src/test/java (testing classes)
Resource Directory UnitTesting/src/main/resources (Cayenne Model)
Inputs N/A
Compiling/Running cd UnitTesting
mvn clean compile
mvn test
View/Edit Model mvn cayenne-modeler:run
Status Code: Completed
Documentation: In-Progress

One of the difficulties encountered when unit testing Cayenne objects (this example uses JUnit) is that Cayenne objects want to live in a context. You can create a single Cayenne object without issue, but if you try to add a relationship without a context, an exception will be raised because Cayenne is trying to do magic behind the scenes to define the relationships keys. This means your unit tests must create a Cayenne context even if you do not plan on committing any objects to the database. Of course, if you actually want to commit to a testing database (I prefer an in-memory one), you don't want to use your main Cayenne model (which is configured for deployment).

Cayenne includes three data source factories (and database connection pools) natively:

  1. Cayenne Model (top-level XML configuration file, plus DataNode and DataMap XML files).
  2. JNDI (top-level XML configuration file, plus DataMap XML files).
  3. Apache DBCP (top-level XML configuration file, plus DataMap XML files).

As you can see, the Cayenne Model option includes a DataNode file while JNDI and Apache DBCP do not. The JNDI option specifies a JNDI lookup name in the top-level XML configuration file. The Apache DBCP option specifies the configuration file name.

Model Copying

You can copy your Cayenne Model XML files (in src/main/resources) to src/test/resources and then open the model in Cayenne Modeler to configure a different test database. This option is relatively straightforward, but if your model changes, you have to remember to copy the files again. This can be simplified by deleting the DataMap file in src/test/resources, though, and letting the tests load the top-level XML file (and possibly the DataNode) from src/test/resources, but pick up the DataMap from src/main/resources. This way you don't have to remember to copy the DataMap when it changes -- and the DataMap is the most likely item to change as you alter tables/columns. However, if you need to edit the testing Cayenne Model again, Cayenne Modeler won't be able to open the model unless you copy the DataMap file again (and probably delete it afterwards).

JNDI Lookups

If you are using JNDI, which is typical when developing a web application, it is possible to register your own JNDI name and not have to copy any model files to src/test/resources. This has the advantage of the model being used by your test cases being identical to your production/deployment model.

Cayenne (and JNDI) doesn't make this approach easy, though. When Cayenne initializes itself from the model and you are using JNDI, it immediately tries to do a JNDI lookup even if it doesn't need the database connection right away. This throws an exception, of course. In order to keep using JNDI (so you don't have to copy model files around) requires an actual JNDI lookup, but registering your own JNDI resource is non-trivial.

This example uses Simple JNDI to register a data source to a memory-based H2 database in the test suite. You can then safely create Cayenne contexts without an exception. You can also create a test database and perform more advanced tests if you like.

Model Configuration

To be continued...

Setting up JNDI

You need to register the JNDI context once upon test startup.

 1     /**
 2      * @return the suite of tests being tested
 3      */
 4     public static Test suite() throws Exception
 5     {
 6         System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory");
 7         System.setProperty("org.osjava.sj.jndi.shared", "true");
 8 
 9         JdbcDataSource ds = new JdbcDataSource();
10         ds.setURL("jdbc:h2:mem:cbe");
11         ds.setUser("sa");
12         ds.setPassword("sa");
13         Context ctx = new InitialContext();
14         ctx.createSubcontext("java:comp/env");
15 
16         ctx.bind("jdbc/cbe", ds);
17 
18         return new TestSuite(AppTest.class);
19     }

Test Cases

The first test creates a single Cayenne object.

 1     public void testPerson()
 2     {
 3         // Create a new DataContext.
 4         DataContext dataContext = DataContext.createDataContext();
 5 
 6         // Create a new Person object tracked by the DataContext.
 7         Person person = dataContext.newObject(Person.class);
 8 
 9         // Set values for the new person.
10         person.setFirstName("System");
11         person.setLastName("Administrator");
12         person.setUsername("admin");
13 
14         assertEquals(person.getUsername(), "admin");
15     }

The second creates two Cayenne objects and joins them in a relationship.

 1     public void testPersonAndAddress()
 2     {
 3         // Create a new DataContext.
 4         DataContext dataContext = DataContext.createDataContext();
 5 
 6         // Create a new Person object tracked by the DataContext.
 7         Person person = dataContext.newObject(Person.class);
 8 
 9         // Set values for the new person.
10         person.setFirstName("System");
11         person.setLastName("Administrator");
12         person.setUsername("admin");
13 
14         // Create the address (a to-one relationship) for the person.
15         Address address = dataContext.newObject(Address.class);
16 
17         // Set the address attributes.
18         address.setCity("Falls Church");
19         address.setState("VA");
20         address.setStreet("W Broad Street");
21         address.setZip("22046");
22 
23         // Assign the address to the person.  Cayenne will figure out how
24         // to map the relationship based upon the model and also create
25         // the primary keys and foreign keys.
26         person.setAddress(address);
27 
28         assertEquals(address, person.getAddress());
29     }

Running Tests

Running a mvn clean compile test produces:

 1 [INFO] Scanning for projects...
 2 ...
 3 -------------------------------------------------------
 4  T E S T S
 5 -------------------------------------------------------
 6 Running cbe.inserting.AppTest
 7 Dec 08, 2012 4:56:41 PM org.apache.cayenne.conf.RuntimeLoadDelegate startedLoading
 8 INFO: started configuration loading.
 9 Dec 08, 2012 4:56:41 PM org.apache.cayenne.conf.RuntimeLoadDelegate shouldLoadDataDomain
10 INFO: loaded domain: CBEInserting
11 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate loadDataMap
12 INFO: loaded <map name='CBEInsertingMap' location='CBEInsertingMap.map.xml'>.
13 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate shouldLoadDataNode
14 INFO: loading <node name='CBEInsertingNode' datasource='jdbc/cbe' factory='org.apache.cayenne.conf.JNDIDataSourceFactory' schema-update-strategy='org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy'>.
15 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate shouldLoadDataNode
16 INFO: using factory: org.apache.cayenne.conf.JNDIDataSourceFactory
17 Dec 08, 2012 4:56:42 PM org.apache.cayenne.access.QueryLogger logConnect
18 INFO: Connecting. JNDI path: jdbc/cbe
19 Dec 08, 2012 4:56:42 PM org.apache.cayenne.access.QueryLogger logConnectSuccess
20 INFO: +++ Connecting: SUCCESS.
21 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate shouldLoadDataNode
22 INFO: loaded datasource.
23 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate initAdapter
24 INFO: no adapter set, using automatic adapter.
25 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate shouldLinkDataMap
26 INFO: loaded map-ref: CBEInsertingMap.
27 Dec 08, 2012 4:56:42 PM org.apache.cayenne.conf.RuntimeLoadDelegate finishedLoading
28 INFO: finished configuration loading in 239 ms.
29 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.563 sec
30 
31 Results :
32 
33 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
34 
35 [INFO] ------------------------------------------------------------------------
36 [INFO] BUILD SUCCESS
37 [INFO] ------------------------------------------------------------------------
38 ...