Chapter 6. Getting started with persistent objects

Table of Contents

Inspecting and Customizing Persistent Objects
Create New Objects

In this chapter we'll learn about persistent objects, how to customize them and how to create and save them in DB.

Inspecting and Customizing Persistent Objects

Persistent classes in Cayenne implement a DataObject interface. If you inspect any of the classes generated earlier in this tutorial (e.g. org.example.cayenne.persistent.Artist), you'll see that it extends a class with the name that starts with underscore (org.example.cayenne.persistent.auto._Artist), which in turn extends from org.apache.cayenne.CayenneDataObject. Splitting each persistent class into user-customizable subclass (Xyz) and a generated superclass (_Xyz) is a useful technique to avoid overwriting the custom code when refreshing classes from the mapping model.

Let's for instance add a utility method to the Artist class that sets Artist date of birth, taking a string argument for the date. It will be preserved even if the model changes later:

package org.example.cayenne.persistent;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.example.cayenne.persistent.auto._Artist;

public class Artist extends _Artist {

    static final String DEFAULT_DATE_FORMAT = "yyyyMMdd";

    /**
     * Sets date of birth using a string in format yyyyMMdd.
     */
    public void setDateOfBirthString(String yearMonthDay) {
        if (yearMonthDay == null) {
            setDateOfBirth(null);
        } else {

            Date date;
            try {
                date = new SimpleDateFormat(DEFAULT_DATE_FORMAT)
                        .parse(yearMonthDay);
            } catch (ParseException e) {
                throw new IllegalArgumentException(
                        "A date argument must be in format '"
                        + DEFAULT_DATE_FORMAT + "': " + yearMonthDay);
            }

            setDateOfBirth(date);
        }
    }
}

Create New Objects

Now we'll create a bunch of objects and save them to the database. An object is created and registered with ObjectContext using "newObject" method. Objects must be registered with DataContext to be persisted and to allow setting relationships with other objects. Add this code to the "main" method of the Main class:

Artist picasso = context.newObject(Artist.class);
picasso.setName("Pablo Picasso");
picasso.setDateOfBirthString("18811025");

Note that at this point "picasso" object is only stored in memory and is not saved in the database. Let's continue by adding a Metropolitan Museum "Gallery" object and a few Picasso "Paintings":

Gallery metropolitan = context.newObject(Gallery.class);
metropolitan.setName("Metropolitan Museum of Art"); 

Painting girl = context.newObject(Painting.class);
girl.setName("Girl Reading at a Table");
        
Painting stein = context.newObject(Painting.class);
stein.setName("Gertrude Stein");

Now we can link the objects together, establishing relationships. Note that in each case below relationships are automatically estabslished in both directions (e.g. picasso.addToPaintings(girl) has exactly the same effect as girl.setToArtist(picasso)).

picasso.addToPaintings(girl);
picasso.addToPaintings(stein);
        
girl.setGallery(metropolitan);
stein.setGallery(metropolitan);

Now lets save all five new objects, in a single method call:

context.commitChanges();

Now you can run the application again as described in the previous chapter. The new output will show a few actual DB operations:

org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader load
INFO: Loading XML configuration resource from file:cayenne-project.xml
... 
INFO: Opening connection: jdbc:derby:memory:testdb;create=true
    Login: null
    Password: *******
INFO: +++ Connecting: SUCCESS.
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: --- transaction started.
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE, ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER, GALLERY_ID INTEGER, ID INTEGER NOT NULL, 
      NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST (ID)
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (GALLERY_ID) REFERENCES GALLERY (ID)
INFO: CREATE TABLE AUTO_PK_SUPPORT (  
      TABLE_NAME CHAR(100) NOT NULL,  NEXT_ID BIGINT NOT NULL,  PRIMARY KEY(TABLE_NAME))
INFO: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('ARTIST', 'GALLERY', 'PAINTING')
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('ARTIST', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('GALLERY', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PAINTING', 200)
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'ARTIST']
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'GALLERY']
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'PAINTING']
INFO: INSERT INTO GALLERY (ID, NAME) VALUES (?, ?)
INFO: [batch bind: 1->ID:200, 2->NAME:'Metropolitan Museum of Art']
INFO: === updated 1 row.
INFO: INSERT INTO ARTIST (DATE_OF_BIRTH, ID, NAME) VALUES (?, ?, ?)
INFO: [batch bind: 1->DATE_OF_BIRTH:'1881-10-25 00:00:00.0', 2->ID:200, 3->NAME:'Pablo Picasso']
INFO: === updated 1 row.
INFO: INSERT INTO PAINTING (ARTIST_ID, GALLERY_ID, ID, NAME) VALUES (?, ?, ?, ?)
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:200, 4->NAME:'Gertrude Stein']
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:201, 4->NAME:'Girl Reading at a Table']
INFO: === updated 2 rows.
INFO: +++ transaction committed.

So first Cayenne creates the needed tables (remember, we used "CreateIfNoSchemaStrategy"). Then it runs a number of inserts, generating primary keys on the fly. Not bad for just a few lines of code.