Using Java with Caché Jalapeño
Using Annotations
[Back] [Next]
   
Server:docs2
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

An object database schema has a richer structure than can be expressed by a programming language, such as Java. For example, databases define concepts such as constraints, relationships (with referential integrity), and indices, which have no equivalent within a Java class definition. In addition, it may be useful to override some aspects of the default object database schema. For example, you may want to use names or types different from those that the Jalapeño SchemaBuilder would generate automatically.

In order to make it possible to specify this additional information within an original POJO class definition, Jalapeño supports the use of Java annotations, which are tags (similar to javadoc tags) that provide a standard mechanism for specifying additional metadata within a Java class definition. Annotations do not directly affect the runtime behavior of POJO classes; they merely provide additional information that the Jalapeño SchemaBuilder uses when generating an object database schema.
The following subjects are discussed in this chapter:
See the Jalapeño Annotation Reference for a complete listing and description of all Jalapeño annotations. The online Jalapeño Tutorial provides a step-by-step description of how to create a simple Jalapeño application using the most common annotations.
How Annotations Work
The Jalapeño SchemaBuilder utility (see Generating Caché Schema with SchemaBuilder) reads a Java class and generates a corresponding Caché class. The Caché class is a schema — a model of the Java class data mapped to a Caché database structure. For example, here is a very simple Java class:
  package tinyproject;
  public class Employee {
    public String name = null;
    public String ssn = null;
    public Department department = null;
  }
The SchemaBuilder would generate the following Cache class for it (slightly modified for clarity. The full listing would include a JavaBlock, which isn't shown here, and will be discussed elsewhere):
  Class tinyproject.Employee Extends %Library.Persistent 
  [ SqlTableName = Employee ]
  {
  Property name As %Library.String(JAVATYPE = "java.lang.String");
  Property ssn As %Library.String(JAVATYPE = "java.lang.String");
  }
This is a valid Caché class, but it would be more useful if we enhanced it with database structures that aren't present in the original Java class. For example, we might want to create an index for it. To do this, we would import com.jalapeno.annotations.Index, and add an @Index annotation to the Java file just before the class declaration:
  package tinyproject;
  import com.jalapeno.annotations.Index;
  @Index (name="EmpIdx", propertyNames={"ssn"}, isPrimaryKey=true)
  public class Employee 
  {...}
This annotation creates an index named "EmpIdx" on the ssn property, and declares it the primary key for the class. When we regenerate the Caché class with SchemaBuilder, an index declaration will be added:
  Class tinyproject.Employee Extends %Library.Persistent 
  [ SqlTableName = Employee ]
  {
  Property name As %Library.String(JAVATYPE = "java.lang.String");
  Property ssn As %Library.String(JAVATYPE = "java.lang.String");
  Index EmpIdx On ssn [ PrimaryKey, Unique ];
  }
This is effectively how all annotations work. They do not alter the functionality of the original Java class in any way, but they provide instructions that the Caché SchemaBuilder uses to enhance the generated Caché class.
Adding Indices
In the previous section, we used the @Index annotation to define the following simple index:
  @Index (name="EmpIdx", propertyNames={"ssn"}, isPrimaryKey=true)
The first two parameters are required for any index: name defines the index name, and propertyNames specifies a list of one or more properties on which the index with be based.
isPrimaryKey is one of three parameters that can be used to define a key:
Using @Indices
@Indices defines multiple indices for the class. For example:
  @Indices({
    @Index(name="IndexOnName",propertyNames={"name"}),
    @Index(name="IndexOnSSN",propertyNames={"ssn"})
  })
Adding Relationships
One of the more important database structures is the Relationship. For example, consider two classes, Person and Dog. A Person can own any number of Dogs, but a Dog will have only one Person as its owner. The following example uses the @ManyToOne and @OneToMany annotations to define this relationship:
  public class Person {
  @OneToMany(inverseClass=Dog.class, inverseProperty="getOwner"
  dependent=true)
  public ArrayList<Dog> getDogs();

  public class Dog {
  @ManyToOne(inverseClass=Person.class, inverseProperty="getDogs")
  public Person getOwner();
The inverseClass parameters specify that Person and Dog are the members of the relationship. The inverseProperty parameters define Person.getDogs() and Dog.getOwner() as the specific properties to be related. The dependent parameter specifies that the Caché relationship is a parent-child relationship.
The resulting Caché classes would contain the following entries:
  Class tinyproject.Person Extends %Library.Persistent
  [ SqlTableName = Person ]
  {
  Relationship getDog As Dog(JAVATYPE = "java.util.ArrayList") 
  [ Cardinality = children, Inverse = getOwner ];
  ...
  }

  Class tinyproject.Dog Extends (%Library.Persistent)
  [ SqlTableName = Dog ]
  {
  Relationship getOwner As Person(JAVATYPE = "tinyproject.Person") 
  [ Cardinality = parent, Inverse = getDog ];
  ...
  }
Note:
The inverse property names are taken from the generated Caché proxy class, not the original Java class. Due to differences between Java and Caché naming conventions, property names generated for the proxy class are not always identical to the original Java property names. You can use the @cacheproperty annotation to explicitly specify property names that will be used when generating the proxy class.
Adding SuperClasses
One very important feature of a Caché class definition is the "Extends" keyword (see Defining a Class: The Basics in Using Caché Objects), which declares the superclasses inherited by the class. For example, all persistent Caché classes must declare %Library.Persistent as a superclass. The following sections discuss annotations that allow you to add superclasses to the generated Caché class:
Determining the Default Superclass
The superclass of a generated Caché class is usually determined by the following rule:
Using @Extends
The @Extends annotation explicitly specifies the superclass that will be used by the generated Caché class. In the following example, Java class Test implicitly extends java.lang.Object. The generated Caché class would normally have %Library.Persistent as its superclass, but the @Extends annotation causes it to use %Config.Connectivity instead.
@Extends (Config.Connectivity)
public class Test
{...}
In the Extends clause of the generated Caché class, the default %Library.Persistent declaration has been replaced by %Config.Connectivity:
Class testPkg.Test Extends (%Config.Connectivity) 
[ SqlTableName = Employee ]
Using @Embeddable
The @Embeddable annotation is used to define the generated Caché class as a serial class. The optional access parameter specifies that only property accessors (not fields such as myField) will be mapped as properties in the generated Caché class. The following example defines the Address class as serial:
@Embeddable (access = AccessType.PROPERTY)
public class Address{
  public String myField;
  private String mCity;
  public String getCity() {return mCity;}
  public void setCity(String value) { mCity = value;}
}
The resulting Caché class definition would be as follows:
Class Address Extends %Library.SerialObject 
{
Property City As %Library.String(JAVATYPE = "java.lang.String");
}
Superclass %Library.SerialObject has been added to the Extends declaration. The City property has been added, but the myField property has been ignored because it doesn't use get and set methods.
Using @CacheClass populatable
The @CacheClass(populatable) annotation is used in conjunction with populate() method (see Creating Test Data). It adds superclass %Library.Populate to a generated class, which is required before test data can be generated for properties of the class.
The following code fragment shows how the TinyPojo project's Employee class would look with the required annotations. The @CacheClass(populatable) annotation is used to add %Populate as a superclass. Within the class, each property uses the @PropertyParameter annotation (see Adding Property Parameters) to add a POPSPEC parameter:
  ...
  @CacheClass(populatable = true)
  public class Employee {

    @PropertyParameter(name = "POPSPEC", value = "Name()")
    public String name = null;

    @PropertyParameter(name = "POPSPEC", value = "SSN()")
    public String ssn = null;

    @PropertyParameter(name = "POPSPEC", 
    value = "Integer(25000,120000)")
    public int salary = 0;
  ...
In the generated Caché class, these annotations add the %Library.Populate declaration to the Extends clause, and a POPSPEC parameter to each property:
  Class tinyproject.Employee Extends (%Library.Persistent, %Library.Populate)
  [ SqlTableName = Employee ]
  {
  ...
  Property name As %Library.String(JAVATYPE = "java.lang.String", 
  MAXLEN = 4096, POPSPEC = "Name()");
  Property salary As %Library.Integer(JAVATYPE = "int", 
  POPSPEC = "Integer(25000,120000)");
  Property ssn As %Library.String(JAVATYPE = "java.lang.String", 
  MAXLEN = 128, POPSPEC = "SSN()");
  ...
  }
See the %Library.PopulateUtils class documentation for a list of all valid POPSPEC values. For more information on the POPSPEC parameter, see The Caché Data Population Utility in Using Caché Objects.
Using @CacheClass xmlSerializable
In the previous chapter, the section on XML Serialization with ObjectManager.utilities describes an interface used to serialize objects (store objects as XML text) and deserialize them (convert the XML back to objects). Serialized objects must belong to a class that declares %XML.Adaptor as a superclass. The following example uses the @CacheClass(xmlSerializable) annotation to make the Employee class serializable:
  @CacheClass(xmlSerializable=true)
  public class Employee {
    ...
  }
In the generated Caché class, the %XML.Adaptor declaration is added to the Extends clause:
  Class tinyproject.Employee Extends (%Library.Persistent, %XML.Adaptor) 
  [ SqlTableName = Employee ]
  { ... }
Using @Implements
The @Implements annotation defines a list of superclasses to be added to the generated Caché class for added functionality. Unlike the Java implements clause, additional superclasses can include actual implementations of the additional functionality. The following example adds %Library.Populate and %Library.Utility to the Employee class:
  @Implements({"%Library.Populate", "%Library.Utility"})
  public class Employee 
  {...}
In the resulting Caché class, these superclasses have been added to the Extends clause:
  Class tinyproject.Employee Extends (%Library.Persistent, %Library.Populate,
  %Library.Utility) [ SqlTableName = Employee ]
  { ... }
Adding Class and Property Parameters
Custom or pre-declared parameters can be added to generated properties and classes.
Adding Class Parameters
The @ClassParameter annotation defines class-specific custom parameters (see Class Parameters in Using Caché Objects) that are declared in the generated Caché class. The following example declares a parameter myParam, which will be used as a constant in the generated class:
  @ClassParameter(name="myParam" value="Hello World")
  public class Employee 
  {...}
The following declaration is generated in the Caché class:
  Parameter myParam As STRING = "Hello World";
A Cache ObjectScript method might use the parameter as shown in the following example:
  Method writeParam() {
      write ..#myParam
   }
@ClassParameters
@ClassParameters allows you to specify several parameters for the same class. For example:
  @ClassParameters({
    @ClassParameter(name="myParm1",value="snark"),
    @ClassParameter(name="myParm2",value="boojum")
  })
Adding Property Parameters
@PropertyParameter defines property parameters to be added to properties of the generated Caché class. For example:
  @PropertyParameter (name = "PATTERN", 
  value = "3N1\"-\"2N1\"-\"4N")
  public String ssn;

  @PropertyParameter (name = "MINVAL", value = "0")
  public float balance;

  @PropertyParameter (name = "POPSPEC", 
  value = "ValueList(\",checking,checking,saving,cd,money market\")")
  public String type;
See Parameters in Using Caché Objects for a list of predefined parameters used by system Data Type classes (such as MAXVAL and MINVAL for numeric data types).
Also see Using @CacheClass populatable for an extended example using the POPSPEC parameter.
@PropertyParameters
The @PropertyParameters annotation allows you to specify several parameters for the same property. For example:
  @PropertyParameters ({
    @PropertyParameter(name = "PATTERN", value = "3N1\"-\"2N1\"-\"4N"),
    @PropertyParameter(name = "POPSPEC", value = "SSN()")
  })
  public String ssn;
Overriding Property Types
@CacheProperty(type) redefines the type of a generated property. Normally, the TreeMap property in the following example would be generated as an array of %Library.Persistent, but in this case, we want it to be specifically an array of Employee objects:
  @CacheProperty (type="tinyproject.Employee")
  public TreeMap EmployeeList = new TreeMap ();
In the generated class, the property type has been redefined:
  Property EmployeeList As array Of tinyproject.Employee
  (JAVATYPE = "java.util.TreeMap");
Property parameters are frequently associated with a redefined property. The following example defines a property as %Library.Text and sets its associated parameters:
  @CacheProperty(type="%Text")
  @PropertyParameters ({
    @PropertyParameter(name = "LANGUAGECLASS", value = "%Text.Japanese") 
    @PropertyParameter(name = "MAXLEN", value = "32000")
  })
  public String notes;
Controlling Schema Generation
Annotations can be used to control which classes or properties will be persisted. The @Transient annotation prevents a class or a property from being persisted. The @Access annotation controls which properties can be persisted, depending on how they are accessed.
Preventing a Class or Property from being Persisted
The @Transient annotation can be used as either a class-level or property-level annotation to prevent a class or a property from being persisted. The following example uses @Transient to prevent class myClass from being persisted:
  @Transient ()
  public class myClass
  {...}
The following example uses @Transient to prevent property myProp from being persisted:
  public class myClass{
    @Transient ()
    String myProp;
    public String getMyProp () {
      getMyProp = myProp;
    }
  }
Limiting Persisted Properties by Access Level
The @Access(level) annotation allows you to persist only those properties that have the specified minimum level of access restriction. Valid values for the level parameter are:
AccessLevel.UNDEFINED is the default.
In the following example, the public method PublicFunc will be persisted, but private method PrivateFunc will not:
@Access(level = AccessLevel.PROTECTED)
  public class myClass{
    public void PublicFunc ()
    {...}
    private void PrivateFunc () 
    {...}
  }
Limiting Persisted Properties by Access Type
The @Access(type) annotation allows you to persist only those properties that have one or more accessors (Get or Set methods). Valid values for the type parameter are:
AccessType.PROPERTY is the default unless the SchemaBuilder defaultaccesstype property specifies a different value.
In the following example, property myProp will be persisted because it has a Get accessor, but the unencapsulated property myField will not:
  @Access (type = AccessType.PROPERTY)
  public class myClass{
    String myField;
    String myProp;
    public String getMyProp () {
      getMyProp = myProp;
    }
  }