Table of Contents

Chapter 3: Model
Business Component
Entity
Properties
Stereotype
IMAGES_GALLERY stereotype
Concurrency and version property
Enums
Calculated properties
Default value calculator
Default values on create
Property validator
Default validator (new in v2.0.3)
References
Default value calculator in references
Using references as key
Collections
Methods
Finders
Entity validator
Remove validator
JPA callback methods
Embeddable classes
Embedded reference
Embedded collections
Inheritance
Composite key
Id class
Embedded id

Chapter 3: Model

The model layer in an object oriented application contains the business logic, that is the structure of the data and all calculations, validations and processes associated to this data.
OpenXava is a model oriented framework where the model is the most important, and the rest (e.g. user interface) depends on it.
The way to define the model in OpenXava is using plain Java classes (although a XML version is also available). OpenXava provides generates a full featured application from your model definition.

Business Component

The basic unit to create an OpenXava application is the business component. A business component is defined using a Java class called Entity. This class is a regular EJB3 entity, or in other words, a POJO class with annotations that follows the Java Persistence API (JPA) standard.
JPA is the Java standard for persistence, that is, for objects that store its state in a database. If you know how to develop using POJOs with JPA, you already know how to develop OpenXava applications.
Using a simple Java class you can define a Business Component with:
This chapter explains how to define the model part, that is, all about structure, validations, calculations, etc.

Entity

In order to define the model part you have to define a Java class with annotations. In addition to its own annotations, OpenXava supports annotations from JPA and Hibernate Validator. This Java class is an entity, that is, a persistent class that represents a business concept.
In this chapter JPA is used to indicate that it's a standard Java Persistent API annotations, HV for indicating it's a Hibernate Validator annotation, and OX for indicating that is an annotation of OpenXava.
This is the syntax for a entity:
@Entity                     //  1
@EntityValidator            //  2
@RemoveValidator            //  3
public class EntityName {   //  4
  // Properties             //  5
  // References             //  6
  // Collections            //  7
  // Methods                //  8
  // Finders                //  9
  // Callback methods       // 10
}
  1. @Entity (JPA, one, required): Indicates that this class is a JPA entity, in other words, its instances will be persistent objects.
  2. @EntityValidator (OX, several, optional): Executes a validation at model level. This validator can receive the value of various model properties. To validate a single property it is better to use a property level validator.
  3. @RemoveValidator (OX, several, optional): It's executed before removal, and can deny the object removing.
  4. Class declaration: As a regular Java class. You can use extends and implements.
  5. Properties: Regular Java properties. They represent the main state of the object.
  6. References: References to other entities.
  7. Collections: Collections of references to other entities.
  8. Methods: Java methods with the business logic.
  9. Finders: Finder methods are static method that do searches using JPA query facilities.
  10. Callback methods: JPA callbacks methods for insert logic on creating, modifying, loading, removing, etc.

Properties

A property represents the state of an object that can be read and in some cases updated. The object does not have the obligation to store physically the property data, it only must return it when required.
The syntax to define a property is:
@Stereotype                                                  //  1
@Column(length=) @Max @Length(max=) @Digits(integerDigits=)  //  2
@Digits(fractionalDigits=)                                   //  3
@Required @Min @Range(min=) @Length(min=)                    //  4
@Id                                                          //  5
@Hidden                                                      //  6
@SearchKey                                                   //  7
@Version                                                     //  8
@DefaultValueCalculator                                      //  9
@PropertyValidator                                           // 10
private type propertyName;                                   // 11
public type getPropertyName() { ... }                        // 11
public void setPropertyName(type newValue) { ... }           // 11
  1. @Stereotype (OX, optional): Allows to specify a special behavior for some properties.
  2. @Column(length=) (JPA), @Max (HV), @Length(max=) (HV), @Digits(integerDigits=) (HV, optional, usually you only use one of them): Length in characters of property, except in @Max case that is the max value. Useful to generate user interfaces. If you do not specify the size, then a default value is assumed. This default value is associated to the stereotype or type and is obtained from default-size.xml.
  3. @Digits(fractionalDigits=) (HV, optional): Scale (size of decimal part) of property. Only applies to numeric properties. If you do not specify the scale, then a default value is assumed. This default value is associated to the stereotype or type and is obtained from default-size.xml.
  4. @Required (OX), @Min (HV), @Range(min=) (HV), @Length(min=) (HV) (optional, usually you only use one of them): Indicates if this property is required. In the case of @Min, @Range and @Length you have to put a value greather than zero for min in order to assume that the property is required. By default a property is required for key properties hidden (new in v2.1.3) and false in all other cases. On saving OpenXava verifies if the required properties are present. If this is not the case, then saving is not done and a validation error list is returned. The logic to determine if a property is present or not can be configured by creating a file called validators.xml in your project. You can see the syntax in OpenXava/xava/validators.xml.
  5. @Id (JPA, optional): Indicates that this property is part of the key. At least one property (or reference) must be key. The combination of key properties (and key references) must be mapped to a group of database columns that do not have duplicate values, typically the primary key.
  6. @Hidden (OX, optional): A hidden property has a meaning for the developer but not for the user. The hidden properties are excluded when the automatic user interface is generated. However at Java code level they are present and fully functional. Even if you put it explicitly into a view the property will be shown in the user interface.
  7. @SearchKey (OX, optional): The search key properties are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the @Id properties for searching by default, and if the id properties are hidden then it uses the first property in the view. With @SearchKey you can choose explicitly the properties for searching.
  8. @Version (JPA, optional): A version property is used for optimistic concurrency control. If you want control concurrency you only need to have a property marked as @Version in your entity. Only a single version property should be used per entity. The following types are supported for version properties: int, Integer, short, Short, long, Long, Timestamp. The version properties are considered hidden.
  9. @DefaultValueCalculator (OX, one, optional): Implements the logic to calculate the default (initial) value for this property. A property with @DefaultValueCalculator has setter and it is persistent.
  10. @PropertyValidator (OX, several, optional): Implements the validation logic to execute on this property before modifying or creating the object that contains it.
  11. Property declaration: A regular Java property declaration with its getters and setters. You can create a calculated property using only a getter with no field nor setter. Any type legal for JPA is available, you only need to provide a Hibernate Type to allow saving in database and an OpenXava editor to render as HTML.

Stereotype

A stereotype (@Stereotype) is the way to determine a specific behavior of a type. For example, a name, a comment, a description, etc. all correspond to the Java type java.lang.String but you surely wish validators, default sizes, visual editors, etc. different in each case and you need to tune finer; you can do this assigning a stereotype to each case. That is, you can have the next sterotypes NAME, MEMO or DESCRIPTION and assign them to your properties.
OpenXava comes with these generic stereotypes:
Now you will learn how to define your own stereotype. You will create one called PERSON_NAME to represent names of persons.
Edit (or create) the file editors.xml in your folder xava. And add:
<editor url="personNameEditor.jsp">
    <for-stereotype stereotype="PERSON_NAME"/>
</editor>
This way you define the editor to render for editing and displaying properties of stereotype PERSON_NAME.
Furthermore it is useful to indicate the default size; you can do this by editing default-size.xml of your project:
<for-stereotype name="PERSON_NAME" size="40"/>
Thus, if you do not put the size in a property of type PERSON_NAME a value of 40 is assumed.
Not so common is changing the validator for required, but if you wish to change it you can do it adding to validators.xml of your project the next definition:
<required-validator>
    <validator-class class="org.openxava.validators.NotBlankCharacterValidator"/>
    <for-stereotype stereotype="PERSON_NAME"/>
</required-validator>
Now everything is ready to define properties of stereotype PERSON_NAME:
@Stereotype("PERSON_NAME")
private String name;
In this case a value of 40 is assumed as size, String as type and the NotBlankCharacterValidator validator is executed to verify if it is required.

IMAGES_GALLERY stereotype

If you want that a property of your component hold a gallery of images. You only have to declare your property with the IMAGES_GALLERY stereotype, in this way:
@Stereotype("IMAGES_GALLERY")
private String photos;
Furthermore, in the mapping part you have to map your property to a table column suitable to store a String with a length of 32 characters (VARCHAR(32)).
And everything is done.
In order to support this stereotype you need to setup the system appropriately for your application.
First, create a table in your database to store the images:
CREATE TABLE IMAGES (
    ID VARCHAR(32) NOT NULL PRIMARY KEY,
    GALLERY VARCHAR(32) NOT NULL,
    IMAGE BLOB);
CREATE INDEX IMAGES01
    ON IMAGES (GALLERY);
 
The type of IMAGE column can be a more suitable one for your database to store byte [] (for example LONGVARBINARY) .
And finally you need to define the mapping in your persistence/hibernate.cfg.xml file, thus:
<hibernate-configuration>
    <session-factory>
        ...
        <mapping resource="GalleryImage.hbm.xml"/>
        ...
    </session-factory>
</hibernate-configuration>
After this you can use the IMAGES_GALLERY stereotype in all components of your application.

Concurrency and version property

Concurrency is the ability of the application to allow several users to save data at same time without losing data. OpenXava uses the optimistic concurrency of JPA. Using optimistic concurrency the records are not locked allowing high concurrency without losing data integrity.
For example, if a user A read a record and then a user B read the same record, modify it and save the changes, when the user A try to save the record he receives an error, then he need to refresh the data and retry his modification.
For activating concurrency support for an entity you only need to declare a property using @Version, in this way:
@Version
private int version;
This property is for use of persistence engine, your application or your user must not use this property directly.

Enums

OpenXava supports Java 5 enums. An enum allows you to define a property that can hold one of the indicated values only .
It's easy to use, let's see this example:
private Distance distance;
public enum Distance { LOCAL, NATIONAL, INTERNATIONAL };
 
The distance property only can take the following values: LOCAL, NATIONAL or INTERNATIONAL, and as you have not put @Required no value (null) is allowed too.
At user interface level the current implementation uses a combo. The label for each value is obtained from the i18n files.
At database level the value is by default saved as an integer (0 for LOCAL, 1 for NATIONAL, 2 for INTERNATIONAL and null for no value), but you can configure easily to use another type and work with no problem with legate databases. See more about this in mapping chapter.

Calculated properties

The calculated properties are read only (only have getter) and not persistent (they do not match with any column of database table).
A calculated property is defined in this way:
@Depends("unitPrice")  // 1
@Max(9999999999L)      // 2
public BigDecimal getUnitPriceInPesetas() {
    if (unitPrice == null) return null;
    return unitPrice.multiply(new BigDecimal("166.386")).setScale(0, BigDecimal.ROUND_HALF_UP);
}
 
According to the above definition now you can use the code in this way:
Product product = ...
product.setUnitPrice(2);
BigDecimal result = product.getUnitPriceInPesetas();
 
And result will hold 332.772.
When the property unitPriceInPesetas is displayed to the user it's not editable, and its editor has a length of 10, indicated using @Max(9999999999L) (2). Also, because of you use @Depends("unitPrice") (1) when the user will change the value of the unitPrice property in the user interface the unitPriceInPesetas property will be recalculated and its value will be refreshed to the user.
From a calculated property you have direct access to JDBC connections, here is an example:
@Max(999)
public int getDetailsCount() {
    // An example of using JDBC
    Connection con = null;
    try {
        con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection();  // 1
        String table = MetaModel.get("InvoiceDetail").getMapping().getTable();
        PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
            " where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
        ps.setInt(1, getYear());
        ps.setInt(2, getNumber());
        ResultSet rs = ps.executeQuery();
        rs.next();
        Integer result = new Integer(rs.getInt(1));
        ps.close();
        return result;
    }
    catch (Exception ex) {
        log.error("Problem calculating details count of an Invoice", ex);
        // You can throw any runtime exception here
        throw new SystemException(ex);
    }
    finally {
        try {
            con.close();
        }
        catch (Exception ex) {
        }
    }
}
 
Yes, the JDBC code is ugly and awkward, but sometimes it can help to solve performance problems. The DataSourceConnectionProvider class allows you to obtain a connection associated to the same data source that the indicated entity (Invoice in this case). This class is for your convenience, but you can access to a JDBC connection using JNDI or any other way you want. In fact, in a calculated property you can write any code that Java allows you.

Default value calculator

With @DefaultValueCalculator you can associate logic to a property, in this case the property is readable and writable. This calculator is for calculating its initial value. For example:
@DefaultValueCalculator(CurrentYearCalculator.class)
private int year;
 
In this case when the user tries to create a new Invoice (for example) he will find that the year field already has a value, that he can change it if he wants to do. The logic for generating this value is in the CurrentYearCalculator class, that it's:
package org.openxava.calculators;
 
import java.util.*;
 
/**
 * @author Javier Paniza
 */
public class CurrentYearCalculator implements ICalculator {
 
    public Object calculate() throws Exception {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new java.util.Date());
        return new Integer(cal.get(Calendar.YEAR));
    }
 
}
 
It's possible customize the behaviour of a calculator setting the value of its properties, as following:
@DefaultValueCalculator(
    value=org.openxava.calculators.StringCalculator.class,
    properties={ @PropertyValue(name="string", value="GOOD") }
)
private String relationWithSeller;
In this case for calculating the default value OpenXava instances StringCalculator and then injects the value "GOOD" in the property string of StringCalculator, and finally it calls to the calculate() method in order to obtain the default value for relationWithSeller. As you see, the use of @PropertyValue annotation allows you create reusable calculators.
@PropertyValue allows to inject the value from other displayed properties, in this way:
@DefaultValueCalculator(
    value=org.openxava.test.calculators.CarrierRemarksCalculator.class,
    properties={
        @PropertyValue(name="drivingLicenceType", from="drivingLicence.type")
    }
)
private String remarks;
In this case before to execute the calculator OpenXava fills the drivingLicenceType property of CarrierRemarksCalculator with the value of the displayed property type from the reference drivingLicence. As you see the from attribute supports qualified properties (reference.property).
Also you can use @PropertyValue without from nor value:
@DefaultValueCalculator(value=DefaultProductPriceCalculator.class, properties=
    @PropertyValue(name="familyNumber")
)
In this case OpenXava takes the value of the displayed property familyNumber and inject it in the property familyNumber of the calculator; that is @PropertyValue(name="familiyNumber") is equivalent to @PropertyValue(name="familiyNumber", from="familyNumber").
From a calculator you have direct access to JDBC connections, here is an example:
@DefaultValueCalculator(value=DetailsCountCalculator.class,
    properties= {
        @PropertyValue(name="year"),
        @PropertyValue(name="number"),
    }
)
private int detailsCount;
 
And the calculator class:
package org.openxava.test.calculators;
 
import java.sql.*;
 
import org.openxava.calculators.*;
import org.openxava.util.*;
 
/**
 * @author Javier Paniza
 */
public class DetailsCountCalculator implements IJDBCCalculator {      // 1
 
    private IConnectionProvider provider;
    private int year;
    private int number;
 
    public void setConnectionProvider(IConnectionProvider provider) { // 2
        this.provider = provider;
    }
 
    public Object calculate() throws Exception {
        Connection con = provider.getConnection();
        try {
            PreparedStatement ps = con.prepareStatement(
                "select count(*) from XAVATEST.INVOICEDETAIL “ +
                “where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
            ps.setInt(1, getYear());
            ps.setInt(2, getNumber());
            ResultSet rs = ps.executeQuery();
            rs.next();
            Integer result = new Integer(rs.getInt(1));
            ps.close();
            return result;
        }
        finally {
            con.close();
        }
    }
 
    public int getYear() {
        return year;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setYear(int year) {
        this.year = year;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
 
}
To use JDBC your calculator must implement IJDBCCalculator (1) and then it will receive an IConnectionProvider (2) that you can use within calculate().
OpenXava comes with a set of predefined calculators, you can find them in org.openxava.calculators.

Default values on create

You can indicate that the value will be calculated just before creating (inserting into database) an object for the first time.
Usually for the key case you use the JPA standard. For example, if you want to use an identity (auto increment) column as key:
@Id @Hidden
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
 
You can use other generation techniques, for example, a database sequence can be defined in this JPA standard way:
@SequenceGenerator(name="SIZE_SEQ", sequenceName="SIZE_ID_SEQ", allocationSize=1 )
@Hidden @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SIZE_SEQ")
private Integer id;
 
If you want to generate a unique identifier of type String and 32 characters, you can use a Hibernate extesion of JPA:
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String oid;
 
Look at section 9.1.9 of JPA 1.0 specification (part of JSR-220) for learning more about @GeneratedValues.
If you want to use your own logic for generating the value on create, or you want a generated new value for a non-key property then you cannot use the JPA @GeneratedValue, although it's easy to solve these cases using JPA. You only need to add this code to your class:
@PrePersist
private void calculateCounter() {
    counter = new Long(System.currentTimeMillis()).intValue();
}
 
The JPA @PrePersist annotation does that this method will be executed before inserting the data the first time in database, in this method you can calculate the value for your key or non-key properties with your own logic.

Property validator

A @PropertyValidator executes validation logic on the value assigned to the property just before storing. A property may have several validators:
@PropertyValidators ({
    @PropertyValidator(value=ExcludeStringValidator.class, properties=
        @PropertyValue(name="string", value="MOTO")
    ),
    @PropertyValidator(value=ExcludeStringValidator.class, properties=
        @PropertyValue(name="string", value="COCHE"),
        onlyOnCreate=true
    )
})
private String description;
 
The technique to configure the validator (with @PropertyValue) is exactly the same than in calculators. With the attribute onlyOnCreate=”true” you can define that the validation will be executed only when the object is created, and not when it is modified.
The validator code is:
package org.openxava.test.validators;
 
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
 
public class ExcludeStringValidator implements IPropertyValidator { // 1
 
    private String string;
 
    public void validate(
        Messages errors,                                           // 2
        Object value,                                              // 3
        String objectName,                                         // 4
        String propertyName)                                       // 5
        throws Exception {
        if (value==null) return;
        if (value.toString().indexOf(getString()) >= 0) {
            errors.add("exclude_string", propertyName, objectName, getString());
        }
    }
 
    public String getString() {
        return string==null?"":string;
    }
 
    public void setString(String string) {
        this.string = string;
    }
 
}
A validator has to implement IPropertyValidator (1), this obliges to the calculator to have a validate() method where the validation of property is executed. The arguments of validate() method are:
  1. Messages errors: A object of type Messages that represents a set of messages (like a smart collection) and where you can add the validation errors that you find.
  2. Object value: The value to validate.
  3. String objectName: Object name of the container of the property to validate. Useful to use in error messages.
  4. String propertyName: Name of the property to validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with errors.add()) by sending a message identifier and the arguments. If you want to obtain a significant message you need to add to your i18n file the next entry:
exclude_string={0} cannot contain {2} in {1}
If the identifier sent is not found in the resource file, this identifier is shown as is; but the recommended way is always to use identifiers of resource files.
The validation is successful if no messages are added and fails if messages are added. OpenXava collects all messages of all validators before saving and if there are messages, then it display them and does not save the object.
The package org.openxava.validators contains some common validators.

Default validator (new in v2.0.3)

You can define a default validator for properties depending of its type or stereotype. In order to do it you have to use the file xava/validators.xml of your project to define in it the default validators.
For example, you can define in your xava/validators.xml the following:
<validators>
    <default-validator>
        <validator-class
            class="org.openxava.test.validators.PersonNameValidator"/>
        <for-stereotype stereotype="PERSON_NAME"/>
    </default-validator>
</validators>
In this case you are associating the validator PersonNameValidator to the stereotype PERSON_NAME. Now if you define a property as the next one:
@Required @Stereotype("PERSON_NAME")
private String name;
This property will be validated using PersonNameValidator although the property itself does not define any validator. PersonNameValidator is applied to all properties with PERSON_NAME stereotype.
You can also assign a default validator to a type.
In validators.xml files you can also define the validators for determine if a required value is present (executed when you use @Required). Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining OpenXava/xava/validators.xml and OpenXavaTest/xava/validators.xml.

References

A reference allows access from an entity to another entity. A reference is translated to Java code as a property (with its getter and its setter) whose type is the referenced model Java type. For example a Customer can have a reference to his Seller, and that allows you to write code like this:
Customer customer = ...
customer.getSeller().getName();
to access to the name of the seller of that customer.
The syntax of reference is:
@Required                                            // 1
@Id                                                  // 2
@DefaultValueCalculator                              // 3
@ManyToOne(                                          // 4
    optional=false                                   // 1
)
private type referenceName;                          // 4
public type getReferenceName() { ... }               // 4
public void setReferenceName(type newValue) { ... }  // 4
  1. @ManyToOne(optional=false) (JPA), @Required (OX) (optional, the JPA is the preferred one): Indicates if the reference is required. When saving OpenXava verifies if the required references are present, if not the saving is aborted and a list of validation errors is returned.
  2. @Id (JPA, optional): Indicates if the reference is part of the key. The combination of key properties and reference properties should map to a group of database columns with unique values, typically the primary key.
  3. @DefaultValueCalculator (OX, one, optional): Implements the logic for calculating the initial value of the reference. This calculator must return the key value, that can be a simple value (only if the key of referenced object is simple) or key object (a special object that wraps the key).
  4. Reference declaration: A regular Java reference declaration with its getters and setters. The reference is marked with @ManyToOne (JPA) and the type must be another entity.
A little example of references:
@ManyToOne
private Seller seller;                  // 1
public Seller getSeller() {
    return seller;
}
public void setSeller(Seller seller) {
    this.seller = seller;
}
 
@ManyToOne(fetch=FetchType.LAZY)
private Seller alternateSeller;         // 2
public Seller getAlternateSeller() {
    return alternateSeller;
}
public void setAlternateSeller(Seller alternateSeller) {
    this.alternateSeller = alternateSeller;
}
 
 
  1. A reference called seller to the entity of Seller entity.
  2. A reference called alternateSeller to the entity Seller. In this case we use fetch=FetchType.LAZY, in this way the data is readed from database on demand. This is the more efficient approach, but it's not the JPA default, therefore it's advisable to use always fetch=FetchType.LAZY when declaring the references.
If you assume that this is in an entity named Customer, you could write:
Customer customer = ...
Seller seller = customer.getSeller();
Seller alternateSeller = customer.getAlternateSeller();
 

Default value calculator in references

In a reference @DefaultValueCalculator works like in a property, only that it has to return the value of the reference key.
For example, in the case of a reference with simple key, you can write:
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
    @PropertyValue(name="value", value="2")
)
private Family family;
 
The calculate() method is:
public Object calculate() throws Exception {
    return new Integer(value);
}
 
As you can see an integer is returned, that is, the default value for family is 2.
In the case of composed key:
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
    @JoinColumn(name="ZONE", referencedColumnName="ZONE"),
    @JoinColumn(name="WAREHOUSE", referencedColumnName="NUMBER")
})
@DefaultValueCalculator(DefaultWarehouseCalculator.class)
private Warehouse warehouse;
 
And the calculator code:
package org.openxava.test.calculators;
 
import org.openxava.calculators.*;
 
/**
 * @author Javier Paniza
 */
public class DefaultWarehouseCalculator implements ICalculator {
 
    public Object calculate() throws Exception {
        WarehouseKey key = new WarehouseKey();
        key.setNumber(4);
        key.setZoneNumber(4);
        return key;
    }
 
}
 
Returns an object of type WarehouseKey.

Using references as key

You can use references as key, or as part of the key. You have to declare the reference as @Id, and use an id class, as following:
@Entity
@IdClass(AdditionalDetailKey.class)
public class AdditionalDetail {
 
    // JoinColumn is also specified in AditionalDetailKey because
    // a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
    @Id @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="SERVICE")
    private Service service;
 
    @Id @Hidden
    private int counter;
 
    ...
 
}
Also, you need to write your key class:
public class AdditionalDetailKey implements java.io.Serializable {
 
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="SERVICE")
    private Service service;
 
    @Hidden
    private int counter;
 
    // equals, hashCode, toString, getters and setters
    ...
 
 }
You need to write the key class although the key would be only a reference with only a join column.
It's better to use this feature only when you are working against legate databases, if you have control over the schema use an autogenerated id instead.

Collections

You can define a collection of references to entities. A collection is a Java property of type java.util.Collection.
Here syntax for collection:
@Size                                                       // 1
@Condition                                                  // 2
@OrderBy                                                    // 3
@XOrderBy                                                   // 4
@OneToMany/@ManyToMany                                      // 5
private Collection collectionName;                          // 5
public Collection getCollectionName() { ... }               // 5
public void setCollectionName(Collection newValue) { ... }  // 5
 
  1. @Size (HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
  2. @Condition (OX, optional): Restricts the elements that appear in the collection.
  3. @OrderBy (JPA, optional): The elements in collections will be in the indicated order.
  4. @XOrderBy (OX, optional): The @OrderBy of JPA does not allow to use qualified properties (properties of references). @XOrderBy does allow it.
  5. Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @OneToMany (JPA) or @ManyToMany (JPA) and the type must be another entity.
Let's have a look at some examples. First a simple one:
@OneToMany (mappedBy="invoice")
private Collection deliveries;
public Collection getDeliveries() {
    return deliveries;
}
public void setDeliveries(Collection deliveries) {
    this.deliveries = deliveries;
}
If you have this within an Invoice, then you are defining a deliveries collection associated to that Invoice. The details to make the relationship are defined in the object/relational mapping.You use mappedBy="invoice" to indicate that the reference invoice of Delivery is used to mapping this collection.
Now you can write a code like this:
Invoice invoice = ...
for (Delivery delivery: invoice.getDeliveries()) {
    delivery.doSomething();
}
To do something with all deliveries associated to an invoice.
Let's look at another example a little more complex, but still in Invoice:
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)  // 1
@OrderBy("serviceType desc")                                 // 2
@org.hibernate.validator.Size(min=1)                         // 3
private Collection details;
  1. Using REMOVE as cascade type produces that when the user removes an invoice its details are also removed.
  2. With @OrderBy you force that the details will be returned ordered by serviceType.
  3. The restriction @Size(min=1) requires at least one detail for the invoice to be valid.
You have full freedom to define how the collection data is obtained, with @Condition you can overwrite the default condition:
@Condition(
    "${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
    "${warehouse.number} = ${this.warehouse.number} AND " +
    "NOT (${number} = ${this.number})"
)
public Collection getFellowCarriers() {
    return null;
}
 
If you have this collection within Carrier, you can obtain with this collection all carriers of the same warehouse but not himself, that is the list of his fellow workers. As you see you can use this in the condition in order to reference the value of a property of current object. @Condition only applied to the user interface generated by OpenXava, if you call directly to getFellowCarriers() it will be returns null.
If with this you have not enough, you can write the logic that returns the collection. The previous example can be written in the following way too:
public Collection getFellowCarriers() {
    Query query = XPersistence.getManager().createQuery("from Carrier c where " +
        "c.warehouse.zoneNumber = :zone AND " +
        "c.warehouse.number = :warehouseNumber AND " +
        "NOT (c.number = :number) ");
    query.setParameter("zone", getWarehouse().getZoneNumber());
    query.setParameter("warehouseNumber", getWarehouse().getNumber());
    query.setParameter("number",  getNumber());
    return query.getResultList();
}
As you see this is a conventional getter method. Obviously it must return a java.util.Collection whose elements are of type Carrier.
The references in collections are bidirectional, this means that if in a Seller you have a customers collection, then in Customer you must have a reference to Seller. But it's possible that in Customer you have more than one reference to Seller (for example, seller and alternateSeller) JPA does not know which to choose, becase of this you have the attribute mappedBy of @OneToMany. You can use it in this way:
@OneToMany(mappedBy="seller")
private Collection customers;
To indicate that the reference seller and not alternateSeller will be used in this collection.
The @ManyToMany (JPA) anotation allows to define a collection with many-to-many multiplicity. As following:
@Entity
public class Customer {
    ...
    @ManyToMany
    private Collection<State> states;
    ...
}
 
In this case a customer have a collection of states, but a state can be present in several customers.

Methods

Methods are defined in an OpenXava entity (really a JPA entity) as in a regular Java class. For example:
public void increasePrice() {
    setUnitPrice(getUnitPrice().multiply(new BigDecimal("1.02")).setScale(2));
}
Methods are the sauce of the objects, without them the object only would be a silly wrapper of data. When possible it is better to put the business logic in methods (model layer) instead of in actions (controller layer).

Finders

A finder is a special static method that allows you to find an object or a collection of objects that follow some criteria.
Some examples:
public static Customer findByNumber(int number) throws NoResultException {
    Query query = XPersistence.getManager().createQuery(
    "from Customer as o where o.number = :number");
    query.setParameter("number", number);
    return (Customer) query.getSingleResult();
}
 
public static Collection findAll()  {
    Query query = XPersistence.getManager().createQuery("from Customer as o");
    return query.getResultList();
}
 
public static Collection findByNameLike(String name)  {
    Query query = XPersistence.getManager().createQuery(
    "from Customer as o where o.name like :name order by o.name desc");
    query.setParameter("name", name);
    return query.getResultList();
}
This methods can be used this way:
Customer customer = Customer.findByNumber(8);
Collection javieres = Customer.findByNameLike(“%JAVI%”);
As you see, using finder methods creates a more readable code than using the verbose query API of JPA. But this is only a style recomendation, you can choose do not write finder methods and to use directly JPA queries.

Entity validator

An @EntityValidator allows to define a validation at model level. When you need to make a validation on several properties at a time, and that validation does not correspond logically with any of them, then you can use this type of validation.
Its syntax is:
@EntityValidator(
    value=class,                        // 1
    onlyOnCreate=(true|false),          // 2
    properties={ @PropertyValue ... }   // 3
)
 
  1. value (required): Class that implements the validation logic. It has to be of type IValidator.
  2. onlyOnCreate (optional): If true the validator is executed only when creating a new object, not when an existing object is modified. The default value is false.
  3. properties (several @PropertyValue, optional): To set a value of the validator properties before executing it.
An example:
@EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
    @PropertyValue(name="limit", value="100"),
    @PropertyValue(name="description"),
    @PropertyValue(name="unitPrice")
})
public class Product {
And the validator code:
package org.openxava.test.validators;
 
import java.math.*;
 
/**
 * @author Javier Paniza
 */
 
public class CheapProductValidator implements IValidator {       // 1
 
    private int limit;
    private BigDecimal unitPrice;
    private String description;
 
    public void validate(Messages errors) {                      // 2
        if (getDescription().indexOf("CHEAP") >= 0 ||
            getDescription().indexOf("BARATO") >= 0 ||
            getDescription().indexOf("BARATA") >= 0) {
            if (getLimiteBd().compareTo(getUnitPrice()) < 0) {
                errors.add("cheap_product", getLimitBd());       // 3
            }
        }
    }
 
    public BigDecimal getUnitPrice() {
        return unitPrice;
    }
 
    public void setUnitPrice(BigDecimal decimal) {
        unitPrice = decimal;
    }
 
    public String getDescription() {
        return description==null?"":description;
    }
 
    public void setDescription(String string) {
        description = string;
    }
 
    public int getLimit() {
        return limit;
    }
 
    public void setLimit(int i) {
        limit = i;
    }
 
    private BigDecimal getLimitBd() {
        return new BigDecimal(Integer.toString(limit));
    }
 
}
 
This validator must implement IValidator (1), this forces you to write a validate(Messages messages) (2). In this method you add the error message ids (3) (whose texts are in the i18n files). And if the validation process (that is the execution of all validators) produces some error, then OpenXava does not save the object and displays the errors to the user.
In this case you see how description and unitPrice properties are used to validate, for that reason the validation is at model level and not at individual property level, because the scope of validation is more than one property.
You can define more than one validator for entity using @EntityValidators, as following:
@EntityValidators({
    @EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
        @PropertyValue(name="limit", value="100"),
        @PropertyValue(name="description"),
        @PropertyValue(name="unitPrice")
    }),
    @EntityValidator(value=org.openxava.test.validators.ExpensiveProductValidator.class, properties= {
        @PropertyValue(name="limit", value="1000"),
        @PropertyValue(name="description"),
        @PropertyValue(name="unitPrice")
    }),
    @EntityValidator(value=org.openxava.test.validators.ForbiddenPriceValidator.class,
        properties= {
            @PropertyValue(name="forbiddenPrice", value="555"),
            @PropertyValue(name="unitPrice")
        },
        onlyOnCreate=true
    )
})
public class Product {
 

Remove validator

The @RemoveValidator is a level model validator too, but in this case it is executed just before removing an object, and it has the possibility to deny the deletion.
Its syntax is:
@RemoveValidator(
    value=class,                        // 1
    properties={ @PropertyValue ... }   // 2
)
 
  1. class (required): Class that implements the validation logic. Must implement IRemoveValidator.
  2. properties (several @PropertyValue, optional): To set the value of the validator properties before executing it.
An example can be:
@RemoveValidator(value=DeliveryTypeRemoveValidator.class,
    properties=@PropertyValue(name="number")
)
public class DeliveryType {
 
And the validator:
package org.openxava.test.validators;
 
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
 
/**
 * @author Javier Paniza
 */
public class DeliveryTypeRemoveValidator implements IRemoveValidator {                           // 1
 
    private DeliveryType deliveryType;
    private int number; // We use this (instaed of obtaining it from deliveryType)
                // for testing @PropertyValue for simple properties
 
    public void setEntity(Object entity) throws Exception {                                      // 2
        this.deliveryType = (DeliveryType) entity;
    }
 
    public void validate(Messages errors) throws Exception {
        if (!deliveryType.getDeliveries().isEmpty()) {
            errors.add("not_remove_delivery_type_if_in_deliveries", new Integer(getNumber()));  // 3
        }
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
 
}
 
As you see this validator must implement IRemoveValidator (1) this forces you to write a setEntity() (2) method that receives the object to remove. If validation error is added to the Messages object sent to validate() (3) the validation fails. If after executing all validations there are validation errors, then OpenXava does not remove the object and displays a list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery type before deleting it.
As in the case of @EntityValidator you can use several @RemoveValidator for entity using @RemoveValidators annotation.

JPA callback methods

With @PrePersist you can plug in your own logic to execute just before creating the object as persistent object.
As following:
@PrePersist
private void prePersist() {
    setDescription(getDescription() + " CREATED");
}
In this case each time that a DeliveryType is created a suffix to description is added.
As you see, this is exactly the same as in other methods but is automatically executed just before creation.
With @PreUpdate you can plug in some logic to execute after the state of the object is changed and just before it is stored in the database, that is, just before executing UPDATE against database.
As following:
@PreUpdate
private void preUpdate() {
    setDescription(getDescription() + " MODIFIED");
}
In this case whenever that a DeliveryType is modified a suffix is added to its description.
As you see, this is exactly the same as in other methods, but it is executed just before modifying.
You can use all the JPA callback annotations: @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate and @PostLoad.

Embeddable classes

As stated in JPA specification:
"An entity may use other fine-grained classes to represent entity state. Instances of these classes, unlike
entity instances themselves, do not have persistent identity. Instead, they exist only as embedded objects
of the entity to which they belong. Such embedded objects belong strictly to their owning entity, and are
not sharable across persistent entities."

The embeddable class syntax is:
@Embeddable                    // 1
public class EmbeddableName {  // 2
  // Properties                // 3
  // References                // 4
  // Methods                   // 5
}
 
  1. @Embeddable (JPA, one, required): Indicates that this class is a embeddable class of JPA, in other words, its instances will be part of persistent objects.
  2. Class declaration: As a regular Java class. You can use extends and implements.
  3. Properties: Regular Java properties.
  4. References: References to entities. This is not supported by JPA 1.0 (EJB 3.0), but the Hibernate implementation support it.
  5. Methods: Java methods with the business logic.

Embedded reference

This example is an embedded (annotated with @Embedded) Address that is referenced from the main entity.
In the main entity you can write:
@Embedded
private Address address;
 
And you have to define the Address class as embeddable:
package org.openxava.test.model;
 
import javax.persistence.*;
import org.openxava.annotations.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Embeddable
public class Address implements IWithCity {  // 1
 
    @Required @Column(length=30)
    private String street;
 
    @Required @Column(length=5)
    private int zipCode;
 
    @Required @Column(length=20)
    private String city;
 
    // ManyToOne inside an Embeddable is not supported by JPA 1.0 (see at 9.1.34),
    // but Hibernate implementation supports it.
    @ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")
    private State state;                    // 2
 
    public String getCity() {
        return city;
    }
 
    public void setCity(String city) {
        this.city = city;
    }
 
    public String getStreet() {
        return street;
    }
 
    public void setStreet(String street) {
        this.street = street;
    }
 
    public int getZipCode() {
        return zipCode;
    }
 
    public void setZipCode(int zipCode) {
        this.zipCode = zipCode;
    }
 
    public State getState() {
        return state;
    }
 
    public void setState(State state) {
        this.state = state;
    }
 
}
As you see an embeddable class can implement an interface (1) and contain references (2), among other things, but it cannot has persistent collections nor to use JPA callbacks methods.
This code can be used this way, for reading:
Customer customer = ...
Address address = customer.getAddress();
address.getStreet(); // to obtain the value
Or in this other way to set a new address:
// to set a new address
Address address = new Address();
address.setStreet(“My street”);
address.setZipCode(46001);
address.setCity(“Valencia”);
address.setState(state);
customer.setAddress(address);
In this case you have a simple reference (not collection), and the generated code is a simple JavaBean, whose life cycle is associated to its container object, that is, the Address is removed and created through the Customer. An Address never will have its own life and cannot be shared by other Customer.

Embedded collections

Embedded collections are not supported by JPA 1.0. But you can simulate them using collections to entities with cascade type REMOVE or ALL. OpenXava manages these collections in a special way, as they were embedded collections.
Now an example of an embedded collection. In the main entity (for example Invoice) you can write:
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection details;
 
Note that you use CascadeType.REMOVE, and InvoiceDetail is an entity, not an embeddable class:
package org.openxava.test.model;
 
import java.math.*;
 
import javax.persistence.*;
 
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.*;
import org.openxava.calculators.*;
import org.openxava.test.validators.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@EntityValidator(value=InvoiceDetailValidator.class,
    properties= {
        @PropertyValue(name="invoice"),
        @PropertyValue(name="oid"),
        @PropertyValue(name="product"),
        @PropertyValue(name="unitPrice")
    }
)
public class InvoiceDetail {
 
    @ManyToOne // Lazy fetching produces a fails on removing a detail from invoice
    private Invoice invoice;
 
    @Id @GeneratedValue(generator="system-uuid") @Hidden
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String oid;
 
    private ServiceType serviceType;
    public enum ServiceType { SPECIAL, URGENT }
 
    @Column(length=4) @Required
    private int quantity;
 
    @Stereotype("MONEY") @Required
    private BigDecimal unitPrice;
 
    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    private Product product;
 
    @DefaultValueCalculator(CurrentDateCalculator.class)
    private java.util.Date deliveryDate;
 
    @ManyToOne(fetch=FetchType.LAZY)
    private Seller soldBy;
 
    @Stereotype("MEMO")
    private String remarks;
 
    @Stereotype("MONEY") @Depends("unitPrice, quantity")
    public BigDecimal getAmount() {
        return getUnitPrice().multiply(new BigDecimal(getQuantity()));
    }
 
    public boolean isFree() {
        return getAmount().compareTo(new BigDecimal("0")) <= 0;
    }
 
     @PostRemove
    private void postRemove() {
        invoice.setComment(invoice.getComment() + "DETAIL DELETED");
    }
 
    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
    public ServiceType getServiceType() {
        return serviceType;
    }
    public void setServiceType(ServiceType serviceType) {
        this.serviceType = serviceType;
    }
    public int getQuantity() {
        return quantity;
    }
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
    public BigDecimal getUnitPrice() {
        return unitPrice==null?BigDecimal.ZERO:unitPrice;
    }
    public void setUnitPrice(BigDecimal unitPrice) {
        this.unitPrice = unitPrice;
    }
 
    public Product getProduct() {
        return product;
    }
 
    public void setProduct(Product product) {
        this.product = product;
    }
 
    public java.util.Date getDeliveryDate() {
        return deliveryDate;
    }
 
    public void setDeliveryDate(java.util.Date deliveryDate) {
        this.deliveryDate = deliveryDate;
    }
 
    public Seller getSoldBy() {
        return soldBy;
    }
 
    public void setSoldBy(Seller soldBy) {
        this.soldBy = soldBy;
    }
 
    public String getRemarks() {
        return remarks;
    }
 
    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }
 
    public Invoice getInvoice() {
        return invoice;
    }
 
    public void setInvoice(Invoice invoice) {
        this.invoice = invoice;
    }
 
}
As you see this is a complex entity, with calculators, validators, references and so on. Also you have to define a reference to the container class (invoice). In this case when an Invoice is removed all its details are removed too. Moreover there are differences at user interface level (you can learn more on view chapter).

Inheritance

OpenXava supports Java and JPA inheritance.
For example you can define a @MappedSuperclass in this way:
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.hibernate.annotations.*;
import org.openxava.annotations.*;
 
/**
 * Base class for defining entities with a UUID oid. <p>
 *
 * @author Javier Paniza
 */
 
@MappedSuperclass
public class Identifiable {
 
    @Id @GeneratedValue(generator="system-uuid") @Hidden
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String oid;
 
    public String getOid() {
        return oid;
    }
 
    public void setOid(String oid) {
        this.oid = oid;
    }
 
}
 
You can define another @MappedSuperclass that extends from this one, for example:
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
 
/**
 * Base class for entities with a 'name' property. <p>
 *
 * @author Javier Paniza
 */
@MappedSuperclass
public class Nameable extends Identifiable {
 
    @Column(length=50) @Required
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
 
Now you can use Identifiable or Nameable for defining your entities, as following:
package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSON")
@AttributeOverrides(
    @AttributeOverride(name="name", column=@Column(name="PNAME"))
)
public class Human extends Nameable {
 
    @Enumerated(EnumType.STRING)
    private Sex sex;
    public enum Sex { MALE, FEMALE };
 
    public Sex getSex() {
        return sex;
    }
    public void setSex(Sex sex) {
        this.sex = sex;
    }
 
}
 
And now, the real entity inheritance, an entity that extends other entity:
package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@DiscriminatorValue("PRO")
public class Programmer extends Human {
 
    @Column(length=20)
    private String mainLanguage;
 
    public String getMainLanguage() {
        return mainLanguage;
    }
 
    public void setMainLanguage(String mainLanguage) {
        this.mainLanguage = mainLanguage;
    }
 
}
 
You can create an OpenXava module for Human and Programmer (not for Identifiable or Nameble directly). In the Programmer module the user can only access to programmers, in the other hand using Human module the user can access to Human and Programmer objects. Moreover when the user tries to view the detail of a Programmer from the Human module the Programmer view will be show. True polymorphism.
About mapping, @AttributeOverrides is supported, but, at the moment, only a single table per class hierarchy mapping strategy works.

Composite key

The preferred way for defining the key of an entity is a single autogenerated key (annotated with @Id and @GeneratedValue), but sometimes, for example when you go against legate database, you need to have an entity mapped to a table that uses several column as key. This case can be solved with JPA (therefore with OpenXava) in two ways, using @IdClass or using @EmbeddedId

Id class

In this case you use @IdClass in your entity to indicate a key class, and you mark the key properties as @Id in your entity:
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
import org.openxava.jpa.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
@IdClass(WarehouseKey.class)
public class Warehouse {
 
    @Id
    // Column is also specified in WarehouseKey because a bug in Hibernate, see
    // http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
    @Column(length=3, name="ZONE")
    private int zoneNumber;
 
    @Id @Column(length=3)
    private int number;
 
    @Column(length=40) @Required
    private String name;
 
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
 
    public int getZoneNumber() {
        return zoneNumber;
    }
 
    public void setZoneNumber(int zoneNumber) {
        this.zoneNumber = zoneNumber;
    }
 
}
 
You also need to declare your id class, a serializable regular class with all key properties from the entity:
package org.openxava.test.model;
 
import java.io.*;
 
import javax.persistence.*;
 
 
/**
 *
 * @author Javier Paniza
 */
 
public class WarehouseKey implements Serializable {
 
    @Column(name="ZONE")
    private int zoneNumber;
    private int number;
 
    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        return obj.toString().equals(this.toString());
    }
 
    @Override
    public int hashCode() {
        return toString().hashCode();
    }
 
    @Override
    public String toString() {
        return "WarehouseKey::" + zoneNumber+ ":" + number;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
 
    public int getZoneNumber() {
        return zoneNumber;
    }
 
    public void setZoneNumber(int zoneNumber) {
        this.zoneNumber = zoneNumber;
    }
 
}

Embedded id

In this case you have a reference to a @Embeddable object marked as @EmbeddedId:
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Entity
public class Warehouse {
 
    @EmbeddedId
    private WarehouseKey key;
 
    @Column(length=40) @Required
    private String name;
 
    public WarehouseKey getKey() {
        return key;
    }
 
    public void setKey(WarehouseKey key) {
        this.key = key;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
 
And you key is an embeddable class that holds the key properties:
package org.openxava.test.model;
 
import javax.persistence.*;
 
/**
 *
 * @author Javier Paniza
 */
 
@Embeddable
public class WarehouseKey implements java.io.Serializable {
 
 
    @Column(length=3, name="ZONE")
    private int zoneNumber;
 
    @Column(length=3)
    private int number;
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
 
    public int getZoneNumber() {
        return zoneNumber;
    }
 
    public void setZoneNumber(int zoneNumber) {
        this.zoneNumber = zoneNumber;
    }
 
}