How-To Use EJB 3.0 O-R Mapping Annotations

Date: 2/28/05
Author: Guy Pelletier

Introduction

This example application demonstrates Oracle's support for the EJB 3.0 annotation specification; specifically, the Metadata for Object/Relational (O/R) Mappings.

The Metadata for O/R Mapping annotations allow users to decorate their EJB 3.0 entity bean classes with O/R mapping metadata. This metadata is then used to define the persistence and retrieval of their entity beans.

This document will outline the many details of each O/R mapping annotation supported in this preview release. This also describes how each of these metadata relates to the mapping API used with Oracle TopLink.

@Table

The @Table specifies the primary table for the annotated entity.

Annotation specification

@Target({TYPE}) @Retention(RUNTIME)
public @interface Table {
  String name() default "";
  String catalog() default "";
  String schema() default "";
  UniqueConstraint[] uniqueConstraints() default {};
  boolean specified() default true; // For internal use only
} }

Example

From: Employee.java

@Entity
@Table(name="EJB_EMPLOYEE")
@SecondaryTable(name="EJB_SALARY")
@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID")
@GeneratedIdTable(name="EMPLOYEE_GENERATOR_TABLE", table=@Table(name="EJB_EMPLOYEE_SEQ"), pkColumnName="SEQ_NAME", valueColumnName="SEQ_COUNT")
@NamedQuery (
  name="findAllEmployeesByFirstName",
  queryString="SELECT OBJECT(employee) FROM Employee employee WHERE employee.firstName = :firstname"
)
public class Employee implements Serializable {
   ...
}

TopLink defaults and equivalent code

descriptor.addTableName( tableName);

Where:

tableName is built by appending the following annotation members: schema().catalog().name(). If the name() annotation member is not specified, then it defaults to the descriptor alias (which defaults to the class name of the annotated entity). In the example above, the tableName == EJB_EMPLOYEE .


@SecondaryTable

The @SecondaryTable is used to specify a secondary table for an entity bean class. Specifying one or more secondary tables indicates that the entity data is stored across multiple tables. A @Table must already have been specified for the annotated entity.

Annotation specification

@Target({TYPE}) @Retention(RUNTIME)
public @interface SecondaryTable {
  String name();
  String catalog() default "";
  String schema() default "";
  JoinColumn[] join() default {};
  UniqueConstraint[] uniqueConstraints() default {};
} }

Example

From: Employee.java

@Entity
@Table(name="EJB_EMPLOYEE")
@SecondaryTable(name="EJB_SALARY")
@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID")
@GeneratedIdTable(name="EMPLOYEE_GENERATOR_TABLE", table=@Table(name="EJB_EMPLOYEE_SEQ"), pkColumnName="SEQ_NAME", valueColumnName="SEQ_COUNT")
@NamedQuery (
  name="findAllEmployeesByFirstName",
  queryString="SELECT OBJECT(employee) FROM Employee employee WHERE employee.firstName = :firstname"
)
public class Employee implements Serializable {
   ...
}

TopLink defaults and equivalent code

descriptor.addTableName( secondaryTableName);
descriptor.addMultipleTablePrimaryKeyFieldName( fkColumn, pkColumn);

Where:

secondaryTableName is built by appending the following annotation members: schema().catalog().name(). If the name() annotation member is not specified, then it defaults to the descriptor alias (which defaults to the class name of the annotated entity). In the example above, the secondaryTableName == EJB_SALARY .

pkColumn and fkColumn are extracted from the join() annotation member which contains an array of @JoinColumns. addMultipleTablePrimaryKeyFieldName( fkColumn, pkColumn) is called for each @JoinColumn specified.

pkColumn is equal to JoinColumn.referencedColumnName() annotation member. If it is not specified it is defaulted to the primary key of the primary table. In the example above, the pkColumn == EJB_EMPLOYEE.EMP_ID .

fkColumn is equal to JoinColumn.name() annotation member. If it is not specified it is defaulted to the primary key of the primary table. In the example above, the fkColumn == EJB_SALARY.EMP_ID .


@Column

The @Column is used to specify a mapped column for a persistent property or field.


Annotation specification

@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface Column {
  String name() default "";
  boolean primaryKey() default false;
  boolean unique() default false;
  boolean nullable() default true;
  boolean insertable() default true;
  boolean updatable() default true;
  String columnDefinition() default "";
  String secondaryTable() default "";
  int length() default 255;
  int precision() default 0;
  int scale() default 0;
  boolean specified() default true; // For internal use only
} }

Example

From: Employee.java

@Column(name="F_NAME")
public String getFirstName() {
  return firstName;
}

TopLink defaults and equivalent code

mapping.setFieldName( columnName);
mapping.setIsReadOnly( isReadOnly)

Where:

columnName is equals to the name() annotation member. In the example above, columnName == F_NAME . If no name() annotation member is specified, then, if @Entity PROPERTY access, the column name is extracted from a getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then the column name equals the field name. In the example above, the columnName == F_NAME .

isReadOnly is determined from the insertable() and updatable() annotation members. If both are true, then the mapping is set to read only. In the example above, the isReadOnly == false .


@Id

The @Id selects the identifier property of an entity root class.

Annotation specification

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Id {
   GeneratorType generate() default NONE;
   String generator() default "";
}

Example

From: Address.java

@Id(generate=TABLE, generator="ADDRESS_TABLE_GENERATOR")
@TableGenerator(name="ADDRESS_TABLE_GENERATOR", tableName="EMPLOYEE_GENERATOR_TABLE", pkColumnValue="ADDRESS_SEQ")
@Column(name="ADDRESS_ID", primaryKey=true)
public Integer getId() {
  return id;
}

Note

GeneratorType specified by Id.generate() imposes the following restrictions on Id.generator():

  • NONE - no generator should be specified, no generator will be used.
  • AUTO - no generator should be specified, container will use generator named "SEQ_GEN", which uses container defaults (unless specified by user).
  • ENTITY, SEQUENCE - generator should be a SequenceGenerator.
  • TABLE - generator should be a TableGenerator.

TopLink defaults and equivalent code

descriptor.addPrimaryKeyFieldName(columnName);



columnName is extracted from a @Column. In the case of an id field, if a @Column name() annotation member is not specified, columnName will default to ID. In the example above, columnName == ADDRESS_ID .


@Version

The @Version is a marker annotation that keeps track of the version property (optimistic lock value, TopLink OptimisticLockingPolicy) of an entity class. This is used to ensure integrity when reattaching and for overall optimistic concurrency control. It can be used in conjunction with a @Column and/or @Id but may appear on its own. See @Column and @Id TopLink defaults and equivalent code for more information.


Annotation specification

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Version {}

Example

From: Employee.java

@Version
@Column(name="VERSION")
public int getVersion() {
  return version;
}

TopLink defaults and equivalent code

VersionLockingPolicy vlp = new VersionLockingPolicy(); vlp.setWriteLockFieldName( columnName); descriptor.setOptimisticLockingPolicy(vlp);

Where:

columnName is extracted from a @Column or its defaults. In the example above, columnName == VERSION .


@Basic

The @Basic maps directly to a TopLink DirectToFieldMapping. It can be used in conjunction with a @Column but it is not necessary. See @Column TopLink defaults and equivalent code for more information.


Annotation specification

public enum FetchType { LAZY, EAGER };

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Basic {
  FetchType fetch() default EAGER;
}

Example

From: Employee.java

@Basic(fetch=EAGER)
@Column(name="F_NAME")
public String getFirstName() {
  return firstName;
}

TopLink defaults and equivalent code

DirectToFieldMapping mapping = new DirectToFieldMapping();
mapping.setFieldName( columnName);
mapping.setIsReadOnly( isReadOnly);
mapping.setAttributeName( attributeName);

Where:

columnName is extracted from a @Column or its defaults. In the example above, columnName == F_NAME .

isReadOnly is extracted from a @Column or its defaults. In the example above, isReadOnly == false .

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == firstName .

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getFirstName .

setMethodName is equal to the setXyz method (built from the get method). In the example above, setMethodName == setFirstName .


@Lob

The @Lob specifies that a persistent property or field should be persisted as a large object to a database-supported large object type. A Lob may be either a binary or character type. It maps directly to a TopLink TypeConversionMapping. It can be used in conjunction with a @Column but it is not necessary. See @Column TopLink defaults and equivalent code for more information.


Annotation specification

public enum FetchType { LAZY, EAGER };
public enum LobType { BLOB, CLOB };

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Lob {
  FetchType fetch() default FetchType.LAZY;
  LobType type() default BLOB;
}

Example

@Lob(fetch=EAGER, type=BLOB)
@Column(name="IMAGE")
public Byte[] getImage() {
  return image;
}

TopLink defaults and equivalent code

TypeConversionMapping mapping = new TypeConversionMappingMapping();
mapping.setFieldName( columnName);
mapping.setIsReadOnly( isReadOnly);
mapping.setAttributeName( attributeName);
mapping.setFieldClassification( fieldClassification);

Where:

columnName is extracted from a @Column or its defaults. In the example above, columnName == IMAGE .

isReadOnly is extracted from a @Column or its defaults. In the example above, isReadOnly == false .

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == image .

fieldClassification , if the type() annotation member is equal to LobType.BLOB, then it is java.sql.Blob.class. If type() annotation member is equal to LobType.CLOB, it is java.sql.Clob.class.

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getImage .

setMethodName is equal to the setXyz method (built from the get method). In the example above, setMethodName == setImage .

@Serialized

The @Serialized specifies that a persistent property should be persisted as a serialized bytestream. It maps directly to a TopLink DirectToFieldMapping with a SerializedObjectConverter. It can be used in conjunction with a @Column but it is not necessary. See @Column TopLink defaults and equivalent code for more information.


Annotation specification

public enum FetchType { LAZY, EAGER };

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Serialized {
  FetchType fetch() default FetchType.EAGER;
}

Example

@Serialized(fetch=EAGER)
@Column(name="PICTURE")
public byte[] getPicture() {
  return picture;
}

TopLink defaults and equivalent code

DirectToFieldMapping mapping = new DirectToFieldMapping();
mapping.setFieldName( columnName);
mapping.setIsReadOnly( isReadOnly);
mapping.setAttributeName( attributeName); mapping.setConverter(new SerializedObjectConverter(mapping));

Where:

columnName is extracted from a @Column or its defaults. In the example above, columnName == PICTURE .

isReadOnly is extracted from a @Column or its defaults. In the example above, isReadOnly == false .

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == picture .

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getPicture .

setMethodName is equal to the setXyz method (built from the get method). In the example above, setMethodName == setPicture .


@JoinColumn

The @JoinColumn is used to specify a mapped column for joining an entity association or a secondary table. The name() annotation member defines the name of the foreign key column. The other annotation members refer to this column and have the same semantics as for the @Column .

Annotation specification

@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface JoinColumn {
  String name() default "";
  String referencedColumnName() default "";
  boolean primaryKey() default false;
  boolean unique() default false;
  boolean nullable() default true;
  boolean insertable() default true;
  boolean updatable() default true;
  String columnDefinition() default "";
  String secondaryTable() default "";
}

Example

Used within the following annotation contexts. See their TopLink defaults and equivalent code for more information. @SecondaryTable , @OneToOne , @ManyToOne , @OneToMany .

 


@OneToOne

The @OneToOne maps directly to a TopLink OneToOneMapping. It can be used in conjunction with a @JoinColumn but it is not necessary. See @JoinColumn TopLink defaults and equivalent code for more information.

Annotation specification

public enum FetchType { LAZY, EAGER };
public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH };

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToOne {
  String targetEntity() default "";
  CascadeType[] cascade() default {};
  FetchType fetch() default EAGER;
  boolean optional() default true;
  String mappedBy() default "";
  boolean usePKasFK() default false;
}

Example

From: Employee.java

@OneToOne(cascade=ALL, fetch=LAZY)
@JoinColumn(name="ADDR_ID")
public Address getAddress() {
  return address;
}

TopLink defaults and equivalent code

OneToOneMapping mapping = new OneToOneMapping(); mapping.setIsReadOnly(false);
mapping.setIsPrivateOwned(false);
mapping.setAttributeName( attributeName);
mapping.setReferenceClass( referenceClass);
mapping.setUsesIndirection( usesIndirection);

Where:

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == address .

referenceClass is equal to targetEntity(). If it is not defined then referenceClass is extracted from the generic return type if @Entity PROPERTY access. If @Entity FIELD access, it is extracted from the generic field type. In the example above, referenceClass == Address .

usesIndirection is true when the fetch() annotation member is equal to FetchType.LAZY and false when the fetch() annotation member is equal to FetchType.EAGER.

For the cascade() annotation member

mapping.setCascadeAll(true); // when CascadeType.All
mapping.setCascadeMerge(true); // when CascadeType.MERGE
mapping.setCascadeCreate(true); // when CascadeType.CREATE
mapping.setCascadeRefresh(true); // when CascadeType.REFRESH
mapping.setCascadeRemove(true); // when CascadeType.REMOVE

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getAddress .

setMethodName is equal to the setXyz method (built from the method being processed). In the example above, setMethodName == setAddress .

A @JoinColumn is processed for a @OneToOne for the foreign and primary keys.

mapping.addForeignKeyFieldName( fkColumn, pkColumn);

Where:

pkColumn is equal to the @JoinColumn referencedColumnName() annotation member. If it is not specified, the default is the primary key column name of the referenced class. In the example above, pkColumn == EJB_ADDRESS.ADDRESS_ID .

fkColumn is equal to the @JoinColumn name() annotation member. If it is not specified, the default is the primary key column name of the referenced class. In the example above, fkColumn == EJB_EMPLOYEE.ADDR_ID .


@ManyToOne

The @ManyToOne maps directly to a TopLink OneToOneMapping. It can be used in conjunction with a @JoinColumn but it is not necessary. See @JoinColumn TopLink defaults and equivalent code for more information.

Annotation specification

public enum FetchType { LAZY, EAGER };
public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH};

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToOne {
  String targetEntity() default "";
  CascadeType[] cascade() default {};
  FetchType fetch() default EAGER;
  boolean optional() default true;
}

Example

From: Employee.java

@ManyToOne(cascade=PERSIST, fetch=LAZY)
@JoinColumn(name="MANAGER_ID", referencedColumnName="EMP_ID")
public Employee getManager() {
  return manager;
}

TopLink defaults and equivalent code

OneToOneMapping mapping = new OneToOneMapping();
mapping.setIsReadOnly(false);
mapping.setIsPrivateOwned(false);
mapping.setAttributeName( attributeName);
mapping.setReferenceClass( referenceClass);
mapping.setUsesIndirection( usesIndirection);

Where:

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == manager .

referenceClass is equal to targetEntity(). If it is not defined then referenceClass is extracted from the generic return type if @Entity PROPERTY access. If @Entity FIELD access, it is extracted from the generic field type. In the example above, referenceClass == Employee .

usesIndirection is true when the fetch() annotation member is equal to FetchType.LAZY and false when the fetch() annotation member is equal to FetchType.EAGER. For the cascade() annotation member

mapping.setCascadeAll(true); // when CascadeType.All
mapping.setCascadeMerge(true); // when CascadeType.MERGE
mapping.setCascadeCreate(true); // when CascadeType.CREATE
mapping.setCascadeRefresh(true); // when CascadeType.REFRESH
mapping.setCascadeRemove(true); // when CascadeType.REMOVE

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getManager .

setMethodName is equal to the setXyz method (built from the method being processed). In the example above, setMethodName == setManager .

A @JoinColumn is processed for a @ManyToOne for the foreign and primary keys.

mapping.addForeignKeyFieldName( fkColumn, pkColumn);

Where:

pkColumn is equal to the @JoinColumn referencedColumnName() annotation member. If it is not specified, the default is the primary key column name of the referenced class. In the example above, pkColumn == EJB_EMPLOYEE.EMP_ID .

fkColumn is equal to the @JoinColumn name() annotation member. If it is not specified, the default is the primary key column name of the referenced class. In the example above, fkColumn == EJB_EMPLOYEE.MANAGER_ID .


@OneToMany

The @OneToMany maps directly to a TopLink OneToManyMapping. It can be used in conjunction with a @JoinColumn but it is not necessary. See @JoinColumn TopLink defaults and equivalent code for more information.

Annotation specification

public enum FetchType { LAZY, EAGER };
public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH};

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToMany {
  String targetEntity() default "";
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}

Example

From: Employee.java

@OneToMany(cascade=PERSIST)
@JoinColumn(name="MANAGER_ID", referencedColumnName="EMP_ID")
public Collection getManagedEmployees() {
  return managedEmployees;
}

TopLink defaults and equivalent code

OneToManyMapping mapping = new OneToManyMapping();
mapping.setIsReadOnly(false);
mapping.setIsPrivateOwned(false);
mapping.setAttributeName( attributeName);
mapping.setReferenceClass( referenceClass);

Where:

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == address .

referenceClass is equal to targetEntity(). If it is not defined then referenceClass is extracted from the generic return type if @Entity PROPERTY access. If @Entity FIELD access, it is extracted from the generic field type. In the example above, referenceClass == Address . For the cascade() annotation member

mapping.setCascadeAll(true); // when CascadeType.All
mapping.setCascadeMerge(true); // when CascadeType.MERGE
mapping.setCascadeCreate(true); // when CascadeType.CREATE
mapping.setCascadeRefresh(true); // when CascadeType.REFRESH
mapping.setCascadeRemove(true); // when CascadeType.REMOVE

For the fetch() annotation member

mapping.dontUseIndirection(); // when Fetch.EAGER mapping.useTransparentCollection(); // when FetchType.LAZY

If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getAddress .

setMethodName is equal to the setXyz method (built from the method being processed). In the example above, setMethodName == setAddress .

A @JoinColumn is processed for a @ToOneMany for the foreign and primary keys.

mapping.addTargetForeignKeyFieldName( fkColumn, pkColumn);

Where:

pkColumn is equal to the @JoinColumn referencedColumnName() annotation member. If it is not specified, the default is the primary key column name of the source class. In the example above, pkColumn == EJB_EMPLOYEE.EMP_ID .

fkColumn is equal to the @JoinColumn name() annotation member. If it is not specified, the default is the primary key column name of the source class. In the example above, fkColumn == EJB_EMPLOYEE.MANAGER_ID .


@ManyToMany

The @ManyToMany maps directly to a TopLink ManyToManyMapping. It can be used in conjunction with an @AssociationTable but it is not necessary (see TopLink defaults and equivalent code below).

Annotation specification

public enum FetchType { LAZY, EAGER };
public enum CascadeType { ALL, PERSIST, MERGE, REMOVE, REFRESH};

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToMany {
  String targetEntity() default "";
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}

Example

From: Employee.java

@ManyToMany(cascade=PERSIST)
@AssociationTable(
  table=@Table(name="EJB_PROJ_EMp),
  joinColumns=@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID"),
  inverseJoinColumns=@JoinColumn(name="PROJ_ID", referencedColumnName="PROJ_ID")
)

public Collection getProjects() {
  return projects;
}

TopLink defaults and equivalent code

ManyToManyMapping mapping = new ManyToManyMapping();
mapping.setIsPrivateOwned(false);
mapping.setAttributeName( attributeName);
mapping.setReferenceClass( referenceClass);

Where:

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == projects .

referenceClass is equal to targetEntity(). If it is not defined then referenceClass is extracted from the generic return type if @Entity PROPERTY access. If @Entity FIELD access, it is extracted from the generic field type. In the example above, referenceClass == Project . For the cascade() annotation member

mapping.setCascadeAll(true); // when CascadeType.All
mapping.setCascadeMerge(true); // when CascadeType.MERGE
mapping.setCascadeCreate(true); // when CascadeType.CREATE
mapping.setCascadeRefresh(true); // when CascadeType.REFRESH
mapping.setCascadeRemove(true); // when CascadeType.REMOVE

For the fetch() annotation member

mapping.dontUseIndirection(); // when Fetch.EAGER
mapping.useTransparentCollection(); // when FetchType.LAZY

For the mappedBy() annotation member. If it is set, it used to indicate that this is the non-owning side of the relationship. The source and target foreign keys are then processed from the owning side and no @AssociationTable is needed.

mapping.setIsReadOnly(true);

If this is the owning side of the relationship, then an AssociationTable is processed. See @AssociationTable for more information.


@AssociationTable

The @AssociationTable is normally specified by one side of a many-to-many association.

Annotation specification

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface AssociationTable {
  Table table() default @Table(specified=false);
  JoinColumn[] joinColumns() default {};
  JoinColumn[] inverseJoinColumns() default {};
}

Example

From: Employee.java

@ManyToMany(cascade=PERSIST)
@AssociationTable(
  table=@Table(name="EJB_PROJ_EMp),
  joinColumns=@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID"),
  inverseJoinColumns=@JoinColumn(name="PROJ_ID", referencedColumnName="PROJ_ID")
)

public Collection getProjects() {
  return projects;
}

TopLink defaults and equivalent code

mapping.addSourceRelationKeyFieldName( sourceFKColumn, sourcePKColumn); mapping.addTargetRelationKeyFieldName( targetFKColumn, targetPKColumn);

Where:

sourcePKColumn is extracted from the joinColumns annotation member, which is an array of @JoinColumn .
sourcePKColumn == @JoinColumn referenceColumnName() annotation member. If it is not specified, the default is the primary key column name of the source class. In the example above, sourcePKColumn == EJB_EMPLOYEE.EMP_ID .

sourceFKColumn is extracted from the inverseJoinColumns() annotation member, which is an array of @JoinColumn .
sourceFKColumn == @JoinColumn name() annotation member. If it is not specified, the default is the primary key column name of the source class. In the example above, sourcePKColumn == EJB_PROJ_EMP.EMP_ID .

targetPKColumn is extracted from the inverseJoinColumns annotation member, which is an array of @JoinColumn .
targetPKColumn == @JoinColumn referenceColumnName() annotation member. If it is not specified, the default is the primary key column name of the target class. In the example above, targetPKColumn == EJB_PROJECT.PROJ_ID .

targetFKColumn is extracted from the inverseJoinColumns() annotation member, which is an array of @JoinColumn .
targetFKColumn == @JoinColumn name() annotation member. If it is not specified, the default is the primary key column name of the target class. In the example above, targetPKColumn == EJB_PROJ_EMP.PROJ_ID .

If the @AssociationTable is missing, the default values of the annotation members apply. The name of the association table is assumed to be the table names of the associated primary tables concatenated together using an underscore.


@Embedded

The @Embedded maps directly to a TopLink AggregateObjectMapping. It may be used in an entity bean class when it is using a shared embeddable class. The entity may override the column mappings declared within the embeddable class to apply to its own entity table.

Annotation specification

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Embedded {
  AttributeOverride[] value() default {};
}

Example

From: Employee.java

@Embedded
public EmploymentPeriod getPeriod() {
  return period;
}

TopLink defaults and equivalent code

AggregateObjectMapping mapping = new AggregateObjectMapping();
mapping.setIsReadOnly(false);
mapping.setIsNullAllowed(true);
mapping.setAttributeName( attributeName);
mapping.setReferenceClass( referenceClass);

Where:

attributeName , if @Entity PROPERTY access, is extracted from the getXyz method name into an xyz attribute name. If @Entity FIELD accesss, then it equals the field name. In the example above, attributeName == firstName .

referenceClass is extracted from a the return type if @Entity PROPERTY access. If @Entity FIELD access, then it is extracted from the field type. In the example above, the referenceClass == EmploymentPeriod. If @Entity PROPERTY access then the getter and setter access methods are set on the mapping.

mapping.setGetMethodName( getMethodName);
mapping.setSetMethodName( setMethodName);

Where:

getMethodName is equal to the getXyz method being processed. In the example above, getMethodName == getPeriod .

setMethodName is equal to the setXyz method (built from the get method). In the example above, setMethodName == setPeriod .

The @AttributeOverride value() annotation member is processed into TopLink field name translations. See @AttributeOverride for more information.


@AttributeOverride

The @AttributeOverride is used to specify TopLink field name translations for a TopLink AggregateObjectMapping. See @Embedded for more information.

Annotation specification

@Target({}) @Retention(RUNTIME)
public @interface AttributeOverride {
  String name();
  Column[] column() default {};
}

Example

@Embedded({
  @AttributeOverride(name="startDate", column=@Column("EMP_START")),
  @AttributeOverride(name="endDate", column=@Column("EMP_END"))
})
public EmploymentPeriod getPeriod() {
  return period;
}

TopLink defaults and equivalent code

mapping.addFieldNameTranslation( columnName, name);

The above example would generate the following code:

mapping.addFieldNameTranslation("startDate", "EMP_START"); mapping.addFieldNameTranslation("endDate", "EMP_END");

If an @AttributeOverride is defined on an @Embedded , it must define columns otherwise an exception is thrown.

If no @AttributeOverride is defined, then TopLink will default a field name translation for every column (direct to field mapping) in the @Embeddable class.

@Embeddable mapping

The @Embeddable specifies that this class is an intrinsic part of an owning entity and shares the identity of that entity. The embeddable class is then processed for @Basic , @Table , @Column , and @Serialized mapping annotations.

Annotation specification

@Target({TYPE}) @Retention(RUNTIME)
public @interface Embeddable {
  AccessType access() default PROPERTY;
}

Example

From: EmploymentPeriod.java

@Embeddable
@Table(name="EJB_EMPLOYEE")
public class EmploymentPeriod implements Serializable {
  private Date startDate;
  private Date endDate;

  public EmploymentPeriod() {
  }

  ...
}

TopLink defaults and equivalent code

descriptor.descriptorIsAggregate();


Sequencing annotations

@GeneratedIdTable

The @GeneratedIdTable defines a table that may be used by the container to store generated id values for entities. It's usually shared by multiple entity types that use table-based id generation. Each entity type will typically use its own row in the table to generate the id values for that entity class. The id values are positive integers.

Annotation specification

@Target({PACKAGE, TYPE}) @Retention(RUNTIME)
public @interface GeneratedIdTable {
  String name() default "";
  Table table() default @Table(specified=false);
  String pkColumnName() default "";
  String valueColumnName() default "";
}

Example

From: Employee.java

@Entity
@Table(name="EJB_EMPLOYEE")
@SecondaryTable(name="EJB_SALARY")
@JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID")
@GeneratedIdTable(name="EMPLOYEE_GENERATOR_TABLE", table=@Table(name="EJB_EMPLOYEE_SEQ"), pkColumnName="SEQ_NAME", valueColumnName="SEQ_COUNT")
@NamedQuery (
  name="findAllEmployeesByFirstName",
  queryString="SELECT OBJECT(employee) FROM Employee employee WHERE employee.firstName = :firstname"
)
public class Employee implements Serializable {
   ...
}

TopLink defaults and equivalent code

No TopLink code is generated. @GeneratedIdTable are stored and used within @TableGenerator


@TableGenerator

The @TableGenerator defines a primary key or id generator which may be referenced by name when annotating the id attribute (see @Id ). A generator may be defined at either the package, class, method, or field level. The level at which it is defined will depend upon the desired visibility and sharing of the generator. No scoping or visibility rules are actually enforced. However, it is good practice to define the generator at the level for which it will be used. It maps to a TopLink TableSequence.


Annotation specification

@Target({PACKAGE, TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface TableGenerator {
  String name();
  String tableName() default "";
  String pkColumnValue() default "";
  int allocationSize() default 50;
}

Example

From: Address.java

@Id(generate=TABLE, generator="ADDRESS_TABLE_GENERATOR")
@TableGenerator(name="ADDRESS_TABLE_GENERATOR", tableName="EMPLOYEE_GENERATOR_TABLE", pkColumnValue="ADDRESS_SEQ")
@Column(name="ADDRESS_ID", primaryKey=true)
public Integer getId() {
  return id;
}

TopLink defaults and equivalent code

TableSequence sequence = new TableSequence( sequenceName, allocationSize);
sequence.setTableName( tableName);
sequence.setNameFieldName( pkColumnName);
sequence.setCounterFieldName( valueColumnName);

Where:

sequenceName is equal to the pkColumnValue() annotation member. If the pkColumnValue() annotation member is not specified then it is equal to the TableGenerator name() annotation member.

allocationSize is equal to the TableGenerator allocationSize() annotation member.

tableName is equal to the @GeneratedIdTable table() annotation member if it is specified as follows, table.schema().table.catalog().table.name(). If the table() annotation member is not specified then it is equal to @TableGenerator name() annotation member.

pkColumnName is equal to the @GeneratedIdTable pkColumnName() annotation member. The @GeneratedIdTable is looked up from the TableGenerator tableName() annotation member.

valueColumnName is equal to the @GeneratedIdTable valuleColumnName() annotation member. The @GeneratedIdTable is looked up from the TableGenerator tableName() annotation member.


@SequenceGenerator

The @SequenceGenerator defines a primary key or id generator which may be referenced by name when annotating the id attribute (see @Id ). A generator may be defined at either the package, class, method, or field level. The level at which it is defined will depend upon the desired visibility and sharing of the generator. No scoping or visibility rules are actually enforced. However, it is good practice to define the generator at the level for which it will be used. It maps to a TopLink NativeSequence.

Annotation specification

@Target({PACKAGE, TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface SequenceGenerator {
  String name();
  String sequenceName() default "";
  int initialValue() default 0;
  int allocationSize() default 50;
}

Example

@Id(generate=TABLE, generator="ADDRESS_TABLE_GENERATOR")
@SequenceGenerator(name="ADDRESS_TABLE_GENERATOR", sequenceName="ADDRESS_SEQ")
@Column(name="ADDRESS_ID", primaryKey=true)
public Integer getId() {
  return id;
}

TopLink defaults and equivalent code

NativeSequence sequence = new NativeSequence( sequenceName, allocationSize);

Where:

sequenceName is equal to the sequenceName annotation member. If the sequenceName() annotation member is not specified then it is equal to the SequenceGenerator name() annotation member.

allocationSize is equal to the SequenceGenerator allocationSize() annotation member.


Callback events

@PrePersist

The @PrePersist is used to register a method to be called on an object when that object has the create operation applied to it.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME)
public @interface PrePersist {}

Example

@PrePersist
public int initialize() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setPrePersistSelector("initialize");


@PostPersist

The @PostPersist is used to register a method to be called on an object that has just been inserted into the database. This event can be used to notify any dependent on the object, or to update information not accessible until the object has been inserted.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME)
public @interface PostPersist {}

Example

@PostPersist
public int cleanUp() {
  ...
}

TopLink defaults and equivalent code

descriptor.getEventManager().setPostInsertSelector("cleanUp);


@PreRemove

The @PreRemove is used to register a method to be called on an object when that object has the remove operation applied to it.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME)
public @interface PreRemove {}

Example

@PreRemove
public int finalizeOperation() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setPreRemoveSelector("finalizeOperation");


@PostRemove

The @PostRemove is used to register a method to be called on an object that has just been deleted from the database. This event can notify/remove any dependents on the object.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME)
public @interface PostRemove {}

Example

@PostRemove
public int shutdownMaintenanceServer() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setPostDeleteSelector("shutdownMaintenanceServer");


@PreUpdate

The @PreUpdate is used to register a method to be called when an object's row it about to be updated. TopLink uses the optional event argument of the DatabaseRow. This is different from pre/postUpdate because it occurs after the row has already been built, and it is ONLY called if the update is required (changed within a unit of work), as the other occur ALWAYS. This event can be used to modify the row before insert, such as adding a user inserted by.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME) public @interface PreUpdate {}

Example

@PreUpdate
public int removeCalculations() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setAboutToUpdateSelector("removeCalculations");


@PostUpdate

The @PostUpdate is used to register a method to be called on an object that has just been updated into the database. This event is raised on any registered object in a unit of work, even if it has not changed, refer to the @PreUpdate if it is required for the event to be raised only when the object has been changed. This event can be used to notify any dependent on the object.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME)
public @interface PostUpdate {}

Example

@PostUpdate
public int updateEmployeeList() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setPostUpdateSelector("updateEmployeeList");


@PostLoad

The @PostLoad is used to register a method to be called on a object that has just been built from the database. TopLink uses the optional event argument of the DatabaseRow. This event can be used to correctly initialize an object's non-persistent attributes or to perform complex optimizations or mappings. This event is called whenever an object is built.

Annotation specification

@Target({METHOD}) @Retention(RUNTIME) public @interface PostLoad {}

Example

@PostLoad
public int refreshOrderCount() {
  ...
}

TopLink equivalent code

descriptor.getEventManager().setPostBuildSelector("refreshOrderCount");


@EntityListener

The @EntityListener is used to register a TopLink Listener object with the event manager to be notified when an event occurs on any instance of the descriptor's class.

Annotation specification

@Target({TYPE}) @Retention(RUNTIME)
public @interface EntityListener {
  String value(); // fully qualified name of the callback listener class
}

Example

@EntityListener("com.acme.Listener")
public class Customer implements Serializable {
  ...
}

TopLink equivalent code

descriptor.getEventManager().addListener( listner);

Where listener is an instance of com.acme.Listener which must implement oracle.toplink.descriptors.DescriptorEventListener. The listener class is processed for each callback event ( @PostLoad , @PostUpdate etc.) outlined in this document.


Prerequisites

What you need to know

In order to complete the example application, you should be familiar with the following:

  • EJB 3.0

For further information on EJB 3.0, see the following documents on OTN:

Software Requirements

This demonstration requires that the following software components are installed and configured correctly:

  • Oracle Application Server EJB 3.0 Preview
  • Sun JDK version 1.5 or above
  • Apache Ant version 1.6.2 or above, available here
  • Any HTML browser like Mozilla, Microsoft Internet Explorer, Netscape, etc.
  • A relational database such as Oracle.

Notations

  • %ORACLE_HOME% - The directory where you installed Oracle Application Server EJB 3.0 Preview.
  • %JAVA_HOME% - The directory where your JDK is installed
  • %HOWTO_HOME% - The directory where this demo is unzipped

Building the Application

The configuration files are located in the %HOWTO_HOME%/etc directory, including deployment descriptor files such as application.xml.

Running the Application

To run the sample application on a standalone instance of Oracle Application Server EJB 3.0 Preview, follow these steps:

1. Examine the Sample File Directories

  • build - temporary directory created during the build
  • log - temporary directory holding build/deploy logs
  • etc - xml files required to package the application, and an optional file ejb3-toplink-sessions.xml.
  • lib - holds the application archives that could be deployed
  • script - contains SQL script to create a table
  • doc - the How-to document and Javadoc's
    • javadoc - the javadoc of the different source files
    • how-to-ejb30-mapping-annotations.html - this How-to page
  • src - the source of the demo
    • ejb - contains the sample EJB code
    • web - contains application client code

2. Configure the Environment

Ensure the following environment variables are defined:

  • %ORACLE_HOME% - The directory where you installed Oracle Application Server EJB 3.0 Preview.
  • %JAVA_HOME% - The directory where you installed the J2SE 5.0

Configure Database

This example requires the creation of the database to be done using initdb.sql in %HOWTO_HOME%/scripts directory. This script will create the tables and set up the sample data used in this how to.NOTE: if you are not using an Oracle database, you will need to edit this initdb.sql script to conform to your chosen database.

Configure DataSource

This example requires the DataSource configured to connect to the database that contains the tables defined above. Hence you need to configure your %ORACLE_HOME%/j2ee/home/config/data-sources.xml to the schema that owns the tables as follows:

<connection-pool name="Example Connection Pool">
<!-- This is an example of a connection factory that emulates XA behavior. -->
<connection-factory factory-class="oracle.jdbc.pool.OracleDataSource"
user="scott"
password="tiger"
url="jdbc:oracle:thin:@//localhost:1521/ORCL">
</connection-factory>
</connection-pool>

<managed-data-source name="OracleManagedDS"
connection-pool-name="Example Connection Pool"
jndi-name="jdbc/OracleManagedDS"/>
 

Additional steps if not connected to an Oracle database

If you are not connecting to an Oracle database, you must also do the following additional steps:

  • configure your datasource to use the proper configuration for the chosen database.rename %HOW_TO_HOME%/etc/ejb3-toplink-sessions.xml.removeforuse to %HOW_TO_HOME%/etc/ejb3-toplink-sessions.xml. This file will now be picked up and put into your jar when you build. Configure %HOW_TO_HOME%/etc/ejb3-toplink-sessions.xml by opening the file and change the platform tag to point to your chosen database.
  • Ensure that your JDBC driver is in the classpath of the Oracle Application Server. Copy your JDBC driver to %ORACLE_HOME%/j2ee/home/applib

3. Start Oracle Application Server EJB 3.0 Preview

An Oracle Application Server EJB 3.0 Preview must be running. Start the preview container using the following command:

>%ORACLE_HOME%/bin/ejb30 -start

4. Generate, Compile, and Deploy the Application

Ensure Ant 1.6.2 or above is installed on your machine and configured correctly. On some operating systems, Ant does not currently support the use of environment variables. If this is the case for your operating system, please modify the ant-oracle.properties file located in the %HOWTO_HOME% directory. Edit ant-oracle.properties (in the demo directory) and ensure the following properties are set to the correct values, as indicated below:

  • oc4j.host: host where OC4J is running (default localhost)
  • oc4j.admin.port: RMI port number (default 23791)
  • oc4j.admin.user: admin user name (default oc4jadmin)
  • oc4j.admin.password: admin user password (default welcome)
  • oc4j.binding.module: website name where deployed web modules are bound (default http-web-site)

To build the application, type the following command from the %HOWTO_HOME% directory:

>ant

You should now have the newly created ear in your %HOWTO_HOME%/lib directory.

This command would also attempt to deploy the application if the build is successful. It will first test whether Oracle Application Server is running.

Note that you can also deploy the application separately . Ensure the %ORACLE_HOME% environment variable is defined, and from the %HOWTO_HOME% directory, type the command:

>ant deploy

5. Run the Application

Run the application by browsing to http://localhost:8888/cmp30-advanced/mainPage.jsp.

From there use the applicaton to add/remove employees, projects etc.

Summary

In this document, you should have learned

  • How to use Object/Relational mapping annotations used to define the persistence of your EJB 3.0 entities.
  • Deploy a sample application that uses EJB 3.0 O-R Annotations.
Left Curve
Popular Downloads
Right Curve
Untitled Document