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.
- Line 4: The Cayenne runtime is initialized with the name of the Cayenne Model file.
- Line 10: A
DataContext
(which implementsObjectContext
) is created from the runtime. - Line 13: A new Java
Person
object is created and registered in theDataContext
. - Lines 16-18: Set values in the
Person
(lines 15-17). - Line 21: Commit the
Person
to 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.
- Lines 1-9: Cayenne starts to initialize itself when the first
DataContext
is created (whenruntime.getContext()
is called). This won't happen for additionalDataContext
creations. This loads thecayenne-cbe-inserting.xml
file (in the resources directory). Cayenne searches the JavaCLASSPATH
to locate this file. - Lines 10-15: An actual connection to the database is established (by the
commitChanges()
call). - lines 16-17: The database adapter is installed.
- Lines 18-45: The
commitChanges()
call wraps all work in a database transaction. - Lines 20-29: Cayenne creates the database schema if missing because
CreateIfNoSchemaStrategy
was specified. This is normally NOT the case in a real application, but is convenient for these examples. You can see Cayenne creating thePEOPLE
table (line 23) and theAUTO_PK_SUPPORT
support tables (lines 25-29), which is used by Cayenne's built-in primary key generator (although other primary key generation options are available, such as MySQL's auto increment). - Lines 30-37: Cayenne needs a primary key for the
Person
it is about to insert, so it fetches the current primary key value fromAUTO_PK_SUPPORT
and then increments it by 20. Cayenne by default caches 20 primary keys to improve insert performance. - Lines 38-45: Cayenne is finally ready to insert our single record into the database. Line 39 issues the
INSERT
to the database, line 41 binds the values to insert (notice theid
of 200, which came from theAUTO_PK_SUPPORT
table). Line 43 confirms one row was updated. Line 45 confirms the transaction was committed, which began way back on line 18.
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.