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

Basic Inserts

Cayenne Version 3.1
Project Directory InsertingObjects/BasicInserts
Source Directory InsertingObjects/BasicInserts/src/main/java
Resource Directory InsertingObjects/BasicInserts/src/main/resources
Inputs N/A
Compiling/Running cd InsertingObjects/BasicInserts
mvn clean compile
mvn exec:java -Dexec.mainClass=cbe.inserting.BasicInserts1
mvn exec:java -Dexec.mainClass=cbe.inserting.BasicInserts2
mvn exec:java -Dexec.mainClass=cbe.inserting.BasicInserts3
View/Edit Model mvn cayenne-modeler:run
Status Code: Completed
Documentation: In-Progress (mostly done)

The Basic Inserts example focuses on a single PEOPLE table and a corresponding Person class (the database table being named in the plural and the Java class in the singular). Open the model (located in the resource directory) in Cayenne Modeler to see full class names, optimistic locking attributes, primary key generation strategies, etc.

Database

The PEOPLE table is defined as:

Database Column Java Attribute Other
id: BIGINT N/A Primary Key
email_address: VARCHAR(50) emailAddress: String
first_name: VARCHAR(25) firstName: String
last_name: VARCHAR(25) lastName: String
password: VARCHAR(40) password: String

Cayenne-Generated Classes

When you generate your Java classes using Cayenne Modeler, you end up with an abstract superclass, which you SHOULD NOT EDIT, and a concrete subclass which is actually used for your Java objects. If you want to edit the Cayenne-generated classes, edit the concrete subclass -- Cayenne will only generate this class once when it is missing, but will always generate the abstract superclass (and overwrite any changes made there).

The abstract superclass contains all of the setters and getters for your attributes and relationships (although no relationships are shown in this simple example). Typically, these classes are generated in an "auto" package beneath your concrete subclasses and the class name has a leading underscore. The generated _Person class looks like:

 1 public abstract class _Person extends CayenneDataObject {
 2 
 3     public static final String EMAIL_ADDRESS_PROPERTY = "emailAddress";
 4     public static final String FIRST_NAME_PROPERTY = "firstName";
 5     public static final String LAST_NAME_PROPERTY = "lastName";
 6     public static final String PASSWORD_PROPERTY = "password";
 7 
 8     public static final String ID_PK_COLUMN = "ID";
 9 
10     public void setEmailAddress(String emailAddress) {
11         writeProperty("emailAddress", emailAddress);
12     }
13     public String getEmailAddress() {
14         return (String)readProperty("emailAddress");
15     }
16 
17     public void setFirstName(String firstName) {
18         writeProperty("firstName", firstName);
19     }
20     public String getFirstName() {
21         return (String)readProperty("firstName");
22     }
23 
24     public void setLastName(String lastName) {
25         writeProperty("lastName", lastName);
26     }
27     public String getLastName() {
28         return (String)readProperty("lastName");
29     }
30 
31     public void setPassword(String password) {
32         writeProperty("password", password);
33     }
34     public String getPassword() {
35         return (String)readProperty("password");
36     }
37 
38 }

The first section (lines 3-6) contain constants representing the attribute names. Line 8 contains the key for the primary key. Cayenne typically does not include getters/setters for the primary key, but you can override this behavior in Cayenne Modeler. Lines 10-36 contain the setters/getters for the attributes. NOTE: These attribute represent the Java names, not the database column names.

The concrete class is much simpler:

1 public class Person extends _Person {
2 }

Person inherits the attributes from the _Person superclass and provides a blank class for your custom behavior. For example, you may decide you need a derived getFullName method which combines the firstName and lastName attributes. This technique of adding custom behavior will be demonstrated in Basic Inserts 3 by overriding the setPassword method to automatically hash the value. (If you look at the code in the repository you will already see it there.)

Basic Inserts 1: A Single Insert

 1 public class BasicInserts1
 2 {
 3     ObjectContext dataContext = null;
 4     ServerRuntime runtime     = new ServerRuntime("cayenne-cbe-inserting.xml");
 5     
 6     public BasicInserts1()
 7     {
 8         // Create a new DataContext. This will also initialize the Cayenne
 9         // Framework.
10         dataContext = runtime.getContext();
11 
12         // Create a new Person object tracked by the DataContext.
13         Person person = dataContext.newObject(Person.class);
14 
15         // Set values for the new person.
16         person.setFirstName("System");
17         person.setLastName("Administrator");
18         person.setEmailAddress("admin@example.com");
19 
20         // Commit the changes to the database.
21         dataContext.commitChanges();
22     }
23 
24     public static void main(String[] arguments)
25     {
26         new BasicInserts1();
27     }
28 }

This example is about as simple as you can get. It inserts a single record into the PEOPLE table in the database.

As you can see, the DataContext is used not only to create new objects (the Person), but also to track changes made to the Person and commit those changes (inside a transaction) to the database. Some ORMs are object-centric and require you to call a save/insert/update/delete type method on each object (and call them in the correct order), but Cayenne manages all objects registered in a DataContext and knows if they need to be inserted, updated, or deleted when you call commitChanges and the order of operations for committing those changes to the database.

When the program is run, it produces output similar to the following (timestamps and INFO prefixes have been stripped):

 1 Jul 28, 2013 12:29:47 PM org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader load
 2 INFO: Loading XML configuration resource from file:/Users/mrg/Projects/cbe/InsertingObjects/BasicInserts/target/classes/cayenne-cbe-inserting.xml
 3 Jul 28, 2013 12:29:47 PM org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader$DataSourceChildrenHandler createChildTagHandler
 4 INFO: loading user name and password.
 5 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger logPoolCreated
 6 INFO: Created connection pool: jdbc:h2:mem:cbe
 7  Driver class: org.h2.Driver
 8  Min. connections in the pool: 1
 9  Max. connections in the pool: 10
10 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger logConnect
11 INFO: Opening connection: jdbc:h2:mem:cbe
12  Login: null
13  Password: *******
14 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger logConnectSuccess
15 INFO: +++ Connecting: SUCCESS.
16 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger log
17 INFO: Detected and installed adapter: org.apache.cayenne.dba.h2.H2Adapter
18 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger logBeginTransaction
19 INFO: --- transaction started.
20 Jul 28, 2013 12:29:47 PM org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy processSchemaUpdate
21 INFO: No schema detected, will create mapped tables
22 Jul 28, 2013 12:29:47 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
23 INFO: CREATE TABLE PEOPLE (EMAIL_ADDRESS VARCHAR(50) NULL, FIRST_NAME VARCHAR(25) NULL, ID BIGINT NOT NULL, LAST_NAME VARCHAR(25) NULL, PASSWORD VARCHAR(40) NULL, PRIMARY KEY (ID))
24 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
25 INFO: CREATE TABLE AUTO_PK_SUPPORT (  TABLE_NAME CHAR(100) NOT NULL,  NEXT_ID BIGINT NOT NULL,  PRIMARY KEY(TABLE_NAME))
26 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
27 INFO: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('PEOPLE')
28 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
29 INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PEOPLE', 200)
30 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
31 INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = 'PEOPLE'
32 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logSelectCount
33 INFO: === returned 1 row. - took 4 ms.
34 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
35 INFO: UPDATE AUTO_PK_SUPPORT SET NEXT_ID = NEXT_ID + 20 WHERE TABLE_NAME = 'PEOPLE'
36 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logUpdateCount
37 INFO: === updated 1 row.
38 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQuery
39 INFO: INSERT INTO PEOPLE (EMAIL_ADDRESS, FIRST_NAME, ID, LAST_NAME, PASSWORD) VALUES (?, ?, ?, ?, ?)
40 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logQueryParameters
41 INFO: [bind: 1->EMAIL_ADDRESS:'admin@example.com', 2->FIRST_NAME:'System', 3->ID:200, 4->LAST_NAME:'Administrator', 5->PASSWORD:NULL]
42 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logUpdateCount
43 INFO: === updated 1 row.
44 Jul 28, 2013 12:29:48 PM org.apache.cayenne.log.CommonsJdbcEventLogger logCommitTransaction
45 INFO: +++ transaction committed.

Further examples will skip the initialization of Cayenne to focus on the main example, but for this first example, each step will be explained.

Basic Inserts 2: Multiple Inserts

 1 public class BasicInserts2
 2 {
 3     ObjectContext dataContext = null;
 4     ServerRuntime runtime     = new ServerRuntime("cayenne-cbe-inserting.xml");
 5 
 6     public BasicInserts2()
 7     {
 8          // Create a new DataContext. This will also initialize the Cayenne
 9          // Framework.
10          dataContext = runtime.getContext();
11 
12          // Create Persons (in the DataContext).
13          createPerson("Aaron", "Caldwell", "acaldwell@example.com");
14          createPerson("Heidi", "Freeman", "hfreeman@example.com");
15          createPerson("Marcus", "Kerr", "mkerr@example.com");
16          createPerson("Rose", "Newton", "rnewton@example.com");
17          createPerson("Ulric", "Reeves", "ureeves@example.com");
18          createPerson("Victoria", "Waters", "vwaters@example.com");
19 
20         // Commit the changes to the database.
21         dataContext.commitChanges();
22     }
23 
24     /**
25      * Helper method to create and initialize a Person in a DataContext.
26      */
27     private void createPerson(String firstName, String lastName, String emailAddress)
28     {
29         // Create a new Person object tracked by the DataContext.
30         Person person = dataContext.newObject(Person.class);
31 
32         // Set values for the new person.
33         person.setFirstName(firstName);
34         person.setLastName(lastName);
35         person.setEmailAddress(emailAddress);
36     }
37 
38 
39     public static void main(String[] arguments)
40     {
41         new BasicInserts2();
42     }
43 }

Running this example produces (greatly trimmed output):

 1 --- transaction started.
 2 CREATE TABLE PEOPLE (EMAIL_ADDRESS VARCHAR(50) NULL, FIRST_NAME VARCHAR(25) NULL, ID BIGINT NOT NULL, LAST_NAME VARCHAR(25) NULL, PASSWORD VARCHAR(40) NULL, PRIMARY KEY (ID))
 3 CREATE TABLE AUTO_PK_SUPPORT (  TABLE_NAME CHAR(100) NOT NULL,  NEXT_ID BIGINT NOT NULL,  PRIMARY KEY(TABLE_NAME))
 4 DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('PEOPLE')
 5 INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PEOPLE', 200)
 6 SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = 'PEOPLE'
 7 === returned 1 row. - took 5 ms.
 8 UPDATE AUTO_PK_SUPPORT SET NEXT_ID = NEXT_ID + 20 WHERE TABLE_NAME = 'PEOPLE'
 9 === updated 1 row.
10 INSERT INTO PEOPLE (EMAIL_ADDRESS, FIRST_NAME, ID, LAST_NAME, PASSWORD) VALUES (?, ?, ?, ?, ?)
11 [bind: 1->EMAIL_ADDRESS:'vwaters@example.com', 2->FIRST_NAME:'Victoria', 3->ID:200, 4->LAST_NAME:'Waters', 5->PASSWORD:NULL]
12 === updated 1 row.
13 [bind: 1->EMAIL_ADDRESS:'ureeves@example.com', 2->FIRST_NAME:'Ulric', 3->ID:201, 4->LAST_NAME:'Reeves', 5->PASSWORD:NULL]
14 === updated 1 row.
15 [bind: 1->EMAIL_ADDRESS:'mkerr@example.com', 2->FIRST_NAME:'Marcus', 3->ID:202, 4->LAST_NAME:'Kerr', 5->PASSWORD:NULL]
16 === updated 1 row.
17 [bind: 1->EMAIL_ADDRESS:'hfreeman@example.com', 2->FIRST_NAME:'Heidi', 3->ID:203, 4->LAST_NAME:'Freeman', 5->PASSWORD:NULL]
18 === updated 1 row.
19 [bind: 1->EMAIL_ADDRESS:'rnewton@example.com', 2->FIRST_NAME:'Rose', 3->ID:204, 4->LAST_NAME:'Newton', 5->PASSWORD:NULL]
20 === updated 1 row.
21 [bind: 1->EMAIL_ADDRESS:'acaldwell@example.com', 2->FIRST_NAME:'Aaron', 3->ID:205, 4->LAST_NAME:'Caldwell', 5->PASSWORD:NULL]
22 === updated 1 row.
23 +++ transaction committed.

This example is very similar to Basic Inserts 1, but this time it inserts six records within a single transaction. Six Person objects were created in the dataContext (Java lines 12-17) before being committed to the database on Java line 21. One thing to note here is that the order the Person objects were created in the dataContext does NOT match the order they were inserted into the database. Cayenne does not guarantee or require the ordering to match. At runtime, Cayenne evaluates every object in the dataContext and determines the order of operations to satisfy persisting the data to the database.

Basic Inserts 3: Multiple Inserts with Overridden Method

This example is identical to Basic Inserts 2, except the createPerson method makes one additional call to to setPassword on line 15:

 1     /**
 2      * Helper method to create and initialize a Person in a DataContext.
 3      */
 4     private void createPerson(String firstName, String lastName, String emailAddress)
 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(firstName);
11         person.setLastName(lastName);
12         person.setEmailAddress(emailAddress);
13 
14         // Default the password to the e-mail address with "123" appended.
15         person.setPassword(emailAddress + "123");
16     }

Normal methods, such as setFirstName and setLastName call the Cayenne-generated setter (or getter) in the base class (_Person). However, Cayenne allows you to override methods or provide additional methods in the subclass (Person). In the case of a password, we don't want to store a plain-text password, but instead want to store a hashed value. Therefore, the setPassword method is overridden to automatically hash the plain-text value:

 1 public class Person extends _Person
 2 {
 3     /**
 4      * Override the default setPassword() method generated by Cayenne to
 5      * automatically hash the plain-text password before storing it in the
 6      * database.
 7      *
 8      * @see cbe.inserting.model.auto._User#setPassword(java.lang.String)
 9      */
10     @Override
11     public void setPassword(String plainTextPassword)
12     {
13         // A real password handler would do more than this.  Read:
14         // http://www.owasp.org/index.php/Hashing_Java
15         super.setPassword(DigestUtils.shaHex(plainTextPassword));
16     }
17 }

Unless the developer goes out of their way to circumvent the automatic hashing, then they'll never have to worry about a plain-text password accidentally being saved to the database.

Running this version produces:

 1 --- transaction started.
 2 --- transaction started.
 3 No schema detected, will create mapped tables
 4 CREATE TABLE PEOPLE (EMAIL_ADDRESS VARCHAR(50) NULL, FIRST_NAME VARCHAR(25) NULL, ID BIGINT NOT NULL, LAST_NAME VARCHAR(25) NULL, PASSWORD VARCHAR(40) NULL, PRIMARY KEY (ID))
 5 CREATE TABLE AUTO_PK_SUPPORT (  TABLE_NAME CHAR(100) NOT NULL,  NEXT_ID BIGINT NOT NULL,  PRIMARY KEY(TABLE_NAME))
 6 DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('PEOPLE')
 7 INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PEOPLE', 200)
 8 SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = 'PEOPLE'
 9 === returned 1 row. - took 5 ms.
10 UPDATE AUTO_PK_SUPPORT SET NEXT_ID = NEXT_ID + 20 WHERE TABLE_NAME = 'PEOPLE'
11 === updated 1 row.
12 INSERT INTO PEOPLE (EMAIL_ADDRESS, FIRST_NAME, ID, LAST_NAME, PASSWORD) VALUES (?, ?, ?, ?, ?)
13 [bind: 1->EMAIL_ADDRESS:'hfreeman@example.com', 2->FIRST_NAME:'Heidi', 3->ID:200, 4->LAST_NAME:'Freeman', 5->PASSWORD:'cc7020db3fec12fe28d0a8380dad52...']
14 === updated 1 row.
15 [bind: 1->EMAIL_ADDRESS:'vwaters@example.com', 2->FIRST_NAME:'Victoria', 3->ID:201, 4->LAST_NAME:'Waters', 5->PASSWORD:'c39fc265673f1e79c791136efb6b8e...']
16 === updated 1 row.
17 [bind: 1->EMAIL_ADDRESS:'ureeves@example.com', 2->FIRST_NAME:'Ulric', 3->ID:202, 4->LAST_NAME:'Reeves', 5->PASSWORD:'2820da6db5ae050bb97daf86a51e31...']
18 === updated 1 row.
19 [bind: 1->EMAIL_ADDRESS:'acaldwell@example.com', 2->FIRST_NAME:'Aaron', 3->ID:203, 4->LAST_NAME:'Caldwell', 5->PASSWORD:'4a34161c0ce98fe4ed52ff2e2baa81...']
20 === updated 1 row.
21 [bind: 1->EMAIL_ADDRESS:'mkerr@example.com', 2->FIRST_NAME:'Marcus', 3->ID:204, 4->LAST_NAME:'Kerr', 5->PASSWORD:'1b659a077a3e4c2cad0999a85760c3...']
22 === updated 1 row.
23 [bind: 1->EMAIL_ADDRESS:'rnewton@example.com', 2->FIRST_NAME:'Rose', 3->ID:205, 4->LAST_NAME:'Newton', 5->PASSWORD:'83a08b1bb1a7ca0157bd40493f86b0...']
24 === updated 1 row.
25 +++ transaction committed.

Since this version is identical to Basic Inserts 2, except for setting the password, the SQL is nearly identical. As you can see in the output, the password is no longer bound to NULL, but has been passed through our overridden setPassword method to automatically hash the plain-text value prior to saving. (Although Cayenne has truncated the logged value showing a "..." for a long string value.) Another item to note in the SQL output is the insertion order is NOT identical to the order in Basic Inserts 2, even though it is the same set of records. This is normal.