It has always been a goal of the Castor JDO project to eventually fully support the JPA specification and become a first class JPA provider that can e.g. be easily integrated with Spring ORM. Whilst full compliance is still work in progress, there are several small areas where sufficient progress has been made, and where partial support will be made available to the user community.
One such area is (partial) support for JPA annotations. This chapter highlights how JPA-annotated Java classes can be used with Castor JDO to persist such classes through the existing persistence framework part of Castor, without little additional requirements.
The following sections describe ...
The prerequisites.
The current limitations.
The supported JPA annotations.
How to use Castor JDO to persist JPA-annotated classes.
How to use Castor JDO as Spring ORM provider to persist JPA-annotated classes.
The following sections assume that you have a (set of) JPA-annotated domain classes which you would like to persist using Castor JDO.
As such, we explain how to enlist those classes with
Castor JDO (through the JDOClassDescriptorResolver
interface, so that Castor JDO will be able to find and work with your
JPA-annotated classes. In addition, we explain how to achieve the same
with Spring ORM and the Spring ORM provider for Castor JDO.
By the end of this chapter is should become obvious that Castor JDO is well-prepared to integrate with the annotation part of the JPA specification, although support for JPA annotations is currently limited.
In Castor JPA there is no use or support for a JPA
persistence.xml
configuration file for
now. All required configuration needs to be supplied by one
of the following means:
Castor JDO configuration file.
JDOClassDescriptorResolver
configuration.
Spring configuration file for the Spring ORM provider for Castor JDO.
Because Castor does not support direct field access, this feature is not supported by Castor JPA. Thus all annotations have to be defined on the getter methods of the fields. If JPA related annotations are found on fields, Castor will throw an exception.
Primary keys made of single fields are supported by Castor as defined
in the JPA specification (through the use of the @Id
annotation). If
you need to define composite primary keys, please note that that
Castor does not support relations
with composite primary keys.
If you still want to persist single classes with the use of
composite primary keys, none of the available JPA annotations
(@EmbeddedId
or @IdClass
) is supported as such. Instead Castor
uses a kind of ad-hoc IdClass
mechanism. Simply
define multiple @Id
annotations on the fields that make up your
composite primary key, and Castor JDO will internally create the
relevant constructs.
These JPA annotations are currently not supported by Castor JDO. For now, you can only define entities.
S ... Supported |
PS ... Partially Supported |
NS ... Not Supported |
Table 6.1. JPA-Annotations
Annotation | Supported | Comment |
---|---|---|
AssociationOverride | NS | |
AssociationOverrides | NS | |
AttributeOverride | NS | |
AttributeOverrides | NS | |
Basic | S | See information on Castor fetch types! |
Column | PS | Supported: column name, nullable |
ColumnResult | NS | |
DiscriminatorColumn | NS | Castor does not support Joined Table Class Hierachy. |
DiscriminatorValue | NS | Castor does not support Joined Table Class Hierachy. |
Embeddable | NS | |
Embedded | NS | |
EmbeddedId | NS | Castor does not support composed primary keys embedded in classes of their own. |
Entity | S | This annotation is needed to tell Castor that this Class is an entity. |
EntityListeners | S | |
EntityResult | NS | |
Enumerated | S | |
ExcludeDefaultListeners | S | |
ExcludeSuperclassListeners | S | |
FieldResult | NS | |
GeneratedValue | NS | |
Id | S | Use this annotation to make a field a primary key (or part of it). |
IdClass | NS | Castor creates IdClass-like behaviour implicity when you define multiple Id fields. Castor does not support composed primary keys in relations! |
Inheritance | NS | |
JoinColumn | PS | Supported: name |
JoinColumns | NS | This is not supported because Castor does not support composed keys in relations. |
JoinTable | PS | Supported: name, joincolumns, inverseJoincolumns |
Lob | S | |
ManyToMany | PS | this is not tested properly yet. |
ManyToOne | PS | Supported: targetEntity, fetch, optional, cascade - Relations MUST BE optional! Required relations are not supported. |
MapKey | NS | |
MappedSuperclass | NS | |
NamedQuery | S | This annotation is used to specify a named query in OQL. |
NamedQueries | S | This annotation specifies an array of named queries |
NamedNativeQuery | S | This annotation is used to specify a native SQL named query. |
NamedNativeQueries | S | This annotation specifies an array of named native queries. |
OneToMany | PS | Supported: targetEntity, fetch, mappedBy, cascade |
OneToOne | PS | Supported: targetEntity, fetch, optional, cascade - Relations MUST BE optional! Required relations are not supported. |
OrderBy | NS | |
PersistenceContext | NS | |
PersistenceContexts | NS | |
PersistenceProperty | NS | |
PersistenceUnit | NS | |
PersistenceUnits | NS | |
PostLoad | S | |
PostPersist | S | |
PostRemove | S | |
PostUpdate | S | |
PrePersist | S | |
PreRemove | S | |
PreUpdate | S | |
PrimaryKeyJoinColumn | NS | |
PrimaryKeyJoinColumns | NS | |
QueryHint | NS | |
SecondaryTable | NS | |
SecondaryTables | NS | |
SequenceGenerator | NS | |
SqlResultSetMapping | NS | |
SqlResultSetMappings | NS | |
Table | PS | Supported: name |
TableGenerator | NS | |
Temporal | S | |
Transient | S | |
UniqueConstraint | NS | |
Version | NS |
This selection of HOW-TOs will show you how to persist JPA-annotated classes with Castor JDO, and will outline the required steps for each of the following cases:
Singular (stand-alone) entities
1:1 relations
1:M relations
M:N relations
The goal is to take an existing JPA-annotated class
Single
and persist it with
Castor JDO. Let's first have a look at the domain class
itself, first without JPA annotattions.
public class Single { private int id; private String name; public int getId() { ... } public void setId(int id ) { ... } public String getName() { ... } public void setName(String name) { ... } }
Here's the same class again, this time with JPA annotations.
@Entity @Table(name="mySingleTable") public class Single { private int id; private String name; @Id @Column(name="id") public int getId() { ... } public void setId(int id) { ... } public String getName() { ... } public void setName(String name) { ... } }
As shown, the class Single
is
mapped against the table mySingleTable
,
and its fields id
and name
are mapped to the columns id
and
name
, where the column name for the
id
property is supplied explicitly and
where the column name for the name
property is derived from the property itself.
Next point is to create an DAO interface and
its implementation where we will be using
CastorDaoSupport
from Castor's
support for Spring ORM to implement the required methods.
public interface SingleDao { void save(Single single); Single get(int id); void delete(Single single); } public class SingleCastorDao extends CastorDaoSupport implements SingleDao { public void delete(Single single) { this.getCastorTemplate().remove(single); } public Single get(int id) { return (Single) this.getCastorTemplate().load(Single.class, new Integer(id)); } public void save(Single single) { this.getCastorTemplate().create(single); } }
There's one small final code change needed: For Castor to be able to work
with JPA-annotated classes, you have to configure an instance of
JDOClassDescriptorResolver
and pass
it to your JDOManager
, else Castor
won't be able to see those class files. Simply add
the individual classes one by one or the package(s)
as shown below:
JDOClassDescriptorResolver resolver = new JDOClassDescriptorResolverImpl(); resolver.addClass(org.castor.jpa.Single.class); // or alternatively you can add the package: resolver.addPackage("org.castor.jpa"); InputSource jdoConfiguration = ...; JDOManager.loadConfiguration(jdoConfiguration, null, null, resolver); JDOManager jdoManager = JDOManager.createInstance("jpa-extensions"); ...
The goal is to take the existing JPA-annotated classes
OneToOne_A
and OneToOne_B
and persist them with Castor JDO. Let's first have a look
at the domain classes themselves, this time with JPA
annotations already in place.
@Entity public class OneToOne_A { private int id; private String title; @Id @Column(name = "id") public int getId() { ... } public void setId(int id) { ... } @Column(name = "name") public String getTitle() { ... } public void setTitle(String title) { ... } } @Entity @Table(name="OneToOne_B") public class B { private int id; private String name; private OneToOne_A objA; @Id @Column(name = "id") public int getId() { ... } public void setId(int id) { ... } @Column(name = "name") public String getName() { ... } public void setName(String name) { ... } @OneToOne(optional=false) public OneToOne_A getOneToOne_A() { ... } public void setOneToOne_a(OneToOne_A objA) { ... } }
As shown, the class OneToOne_A
is
mapped against the table OneToOne_A
(implicit mapping), and the B
against the table OneToOne-B
(explicit
mapping). Please note the @OneToOne
annotation
that specifies the 1:1 relation from class
B
to class
OneToOne_A
.
As with the example shown further above, do not forget to
register all classes involved with the
JDOClassDescriptorResolver
as shown below:
JDOClassDescriptorResolver fragment:
resolver.addClass(org.castor.jpa.OneToOne_A.class); resolver.addClass(org.castor.jpa.B.class);
or with the addPackage
method:
resolver.addPackage("org.castor.jpa");
First we have to annotate our java classes.
@Entity @Table(name="OneToMany_actor") public class Actor { private int svnr; private String lastname; private String firstname; @Id public int getSvnr() { ... } public void setSvnr(int svnr) { ... } @Column(name="surname") public String getLastname() { ... } public void setLastname(String lastname) { ... } @Column(name="firstname") public String getFirstname() { ... } public void setFirstname(String firstname) { ... } } @Entity @Table(name="OneToMany_broadcast") public class Broadcast { private int id; private String name; private Collection<Actor> actors; @Id public int getId() { ... } public void setId(int id) { ... } public String getName() { ... } public void setName(String name) { ... } @OneToMany(targetEntity=Actor.class, mappedBy="actor_id") public Collection<Actor> getActors() { ... } public void setActors(Collection<Actor> actors) { ... } }
What you see is that with the small modification you can persist one to many relations easily.
Last don't forget to change your JDOClassDescriptorResolver accordingly.
The @NamedQuery
annotation is used to specify a
named query in castor's own query language (OQL) and it is expressed
in metadata. The annotation takes the name
and
an OQL query
as parameters.
To define a named query, we first need a persistence entity where we
can attach the @NamedQuery
annotation.
package your.package; @Entity @NamedQuery(name = "findPersonByName", query = "SELECT p FROM your.package.Person p WHERE p.name = $1") public class Person { private long id; private String name; @Id public long getId() {...} public void setId(final long id) {...} public String getName() {...} public void setName(final String name) {...} }
As you can see, we defined a query using the name
findPersonByName
. The query itself uses
$1
as a placeholder in its WHERE
-clause,
which must be bound when executing the query.
The following code sample illustrates how to execute the named query defined above:
Database db = jdoManager.getDatabase(); db.begin(); final OQLQuery query = db.getNamedQuery("findPersonByName"); query.bind("Max Mustermann"); final QueryResults queryResults = query.execute(); final Person queriedPerson = (Person) queryResults.next(); queryResults.close(); db.commit();
Let's have a closer look on some of the lines from this example.
... creates an OQL query using the above defined named query. | |
.. binds the placeholder | |
... executes the query and handle the results. |
The @NamedQueries
annotation is used to specifiy
multiple named queries.
package your.package; @Entity @NamedQueries({ @NamedQuery(name = "findPersonByName", query = "SELECT p FROM your.package.Person p WHERE p.name = $1"), @NamedQuery(name = "findPersonById", query = "SELECT p FROM you.package.Person p WHERE p.id = $1") }) public class Person { private long id; private String name; @Id public long getId() {...} public void setId(final long id) {...} public String getName() {...} public void setName(final String name) {...} }
In the obove example we defined two named queries, namly
findPersonByName
and findPersonById
.
The usage of each query is identical to the usasage of a single named query.
Database db = jdoManager.getDatabase(); db.begin(); final OQLQuery query = db.getNamedQuery("findPersonById"); query.bind(1000L); final QueryResults queryResults = query.execute(); final Person queriedPerson = (Person) queryResults.next(); queryResults.close(); db.commit();
A named native query is a named query using native SQL syntax instead of castor's own query language. The handling of the annotation is similar to named queries.
First we need a entity to attach a query.
@Entity @Table(name = personTable) @NamedNativeQuery(name = "selectAllPersons", query = "SELECT * FROM personTable") public class Person { private long id; private String name; @Id public long getId() {...} public void setId(final long id) {...} public String getName() {...} public void setName(final String name) {...} }
Although the query
itself is written
in native SQL syntax, we - again - use a OQLQuery
object
to execute the query.
Database db = jdoManager.getDatabase(); db.begin(); final OQLQuery query = db.getNamedQuery("selectAllPersons"); final QueryResults queryResults = query.execute(); ... //process the results queryResults.close(); db.commit();
The @NamedNativeQueries
annotation is used to specifiy
multiple named native SQL queries.
package your.package; @Entity @Table(name = personTable) @NamedNativeQueries({ @NamedNativeQuery(name = "selectAllPersons", query = "SELECT * FROM personTable"), @NamedNativeQuery(name = "findMustermann", query = "SELECT * FROM personTable WHERE name='Max Mustermann' and id=1000") }) public class Person { private long id; private String name; @Id public long getId() {...} public void setId(final long id) {...} public String getName() {...} public void setName(final String name) {...} }
As we have already seen, the usage of the two above defined queries is equivalent to the usage of a single named native query.
Database db = jdoManager.getDatabase(); db.begin(); final OQLQuery query = db.getNamedQuery("findMustermann"); final QueryResults queryResults = query.execute(); final Person maxMustermann = (Person) queryResults.next(); queryResults.close(); db.commit();
The following annotations can be used for handling persistence callbacks via JPA:
Additionally, there are the following listener-related annotations:
So, here's a basic usage example:
@Entity public class Person { private final Log log = LogFactory.getLog(this.getClass()); private long id; private String name; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } public String getName() { return name; } public void setName(final String name) { this.name = name; } @PostLoad protected void testPostLoadCallbackHooking() { log.debug(String.format("Hello from `PostLoad`. My name is %s.", this.name)); } @PrePersist protected void validateCreation() { if (this.name.equals("Max Mustermann")) { throw new PersistenceException(String.format( "Person mustn't be called %s.", this.name)); } } @PostPersist protected void validatePersistence() { if (this.name.equals("Manfred Mustermann")) { throw new PersistenceException(String.format( "Person shouldn't be called %s either.", this.name)); } } @PreRemove protected void validateRemoval() { if (this.name.equals("Max Musterfrau")) { throw new PersistenceException(this.name + " mustn't be removed."); } } @PostRemove protected void validateDeletion() { if (this.name.equals("Manfred Musterfrau")) { throw new PersistenceException(this.name + " shouldn't be removed either."); } } @PreUpdate protected void validateModification() { if (this.name.equals("Max Musterfrau")) { throw new PersistenceException(String.format( "Person mustn't be renamed to %s.", this.name)); } } @PostUpdate protected void validateUpdating() { if (this.name.equals("Hans Wurst")) { throw new PersistenceException(String.format( "Person shouldn't be renamed to %s either.", this.name)); } } }
As one can see from this example, such callbacks can e.g. be used for handling validation based on CRUD (create, retrieve, update, delete) operation events.
Furthermore, there are possibilites to define listeners which allow for decoupling callback handling from entities.
Here's an example for that:
@Entity @EntityListeners(DogListener.class) public class Dog extends Animal { private long id; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } } // Corresponding listener. public class DogListener { @PostPersist protected void postPersistDogListener() { // Do something. } }
Apart from that, ExcludeDefaultListeners
and
ExcludeSuperclassListeners
enable specifying
exclusion of listeners within an inheritance chain of entities.
Enumerated
can be used to persist
Enum
types.
Here's an example:
@Entity public class EnumEntity { private long id; private StringEnum stringEnum; private OrdinalEnum ordinalEnum; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } @Enumerated(STRING) public StringEnum getStringEnum() { return stringEnum; } public void setStringEnum(final StringEnum stringEnum) { this.stringEnum = stringEnum; } public OrdinalEnum getOrdinalEnum() { return ordinalEnum; } public void setOrdinalEnum(final OrdinalEnum ordinalEnum) { this.ordinalEnum = ordinalEnum; } }
So, by default enums are serialized to their corresponding ordinal
value representations for persistence. In this case, it's also
sufficient to skip explicitly defining so via
Enumerated
. If serialization to respective string
name representations is preferred annotating accordingly is required.
This annotation can be used to specify properties mapped to temporal data structures.
Example:
@Entity public class Person { private long id; private Date birthDate; private Date anotherDate; private Date yetAnotherDate; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } @Temporal(TIMESTAMP) public Date getBirthDate() { return birthDate; } public void setBirthDate(final Date birthDate) { this.birthDate = birthDate; } @Temporal(TIME) public Date getAnotherDate() { return anotherDate; } public void setAnotherDate(final Date anotherDate) { this.anotherDate = anotherDate; } @Temporal(DATE) public Date getYetAnotherDate() { return yetAnotherDate; } public void setYetAnotherDate(final Date yetAnotherDate) { this.yetAnotherDate = yetAnotherDate; } }
So, it's possible to say which underlying DB-based field data structure to use (datetime, date or time).
Here's an example for that:
@Entity public class LobEntity { private long id; private String clob; private byte[] blob; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } @Lob public String getClob() { return clob; } public void setClob(final String clob) { this.clob = clob; } @Lob public byte[] getBlob() { return blob; } public void setBlob(final byte[] blob) { this.blob = blob; } }
Consequently, default behavior here is to serialize to
CLOB
for character-based data and to
BLOB
for data based on byte arrays (i.e., files).
This guide will show you how to enable the use of JPA annotations with Castor JDO in the context of Spring, Spring ORM and the existing Spring ORM support for Castor JDO.
Let's look at a typical Spring configuration file that shows how to use Castor JDO with Spring as a Spring ORM provider.
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- Enable transaction support using Annotations --> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="transactionManager" class="org.castor.spring.orm.CastorTransactionManager"> <property name="JDOManager" ref="jdoManager" /> </bean> <bean id="jdoManager" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="dbName" /> <property name="configLocation" value="jdo-conf.xml" /> </bean> <bean id="singleDao" class="SingleCastorDao"> <property name="JDOManager"> <ref bean="jdoManager"/> </property> </bean> </beans>
Above Spring application context configures the following Spring beans:
A factory bean for JDOManager instantiation
A Castor-specific transaction manager.
The DAO implementation as shown above.
As shown above, the bean definition for the
JDOManager
factory bean points to
a Castor JDO configuration file (jdo-conf.xml
),
whose content is shown below:
jdo-conf.xml
<!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN""http://castor.org/jdo-conf.dtd"> <jdo-conf> <database name="dbName" engine="mysql"> <driver url="jdbc:mysql://localhost:3306/single" class-name="com.mysql.jdbc.Driver"> <param name="user" value="user" /> <param name="password" value="password" /> </driver> <mapping href="mapping-empty.xml" /> </database> <transaction-demarcation mode="local" /> </jdo-conf>
More on how to configure the Spring ORM provider for Castor JDO can be found at TBD.
In order to use JPA-annotated classes with the Spring ORM provider
for Castor JDO, you will have to use and configure a
JDOClassDescriptorResolver
through an
additional bean definition and link it to your
JDOManager
bean factory definition.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> ... <bean id="classDescriptorResolver" class="org.castor.spring.orm.ClassDescriptorResolverFactoryBean"> <property name="classes"> <list> <value>org.castor.jpa.test.Single</value> </list> </property> </bean> ... <bean id="jdoManager" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="dbName" /> <property name="configLocation" value="jdo-conf.xml" /> <property name="classDescriptorResolver" ref="classDescriptorResolver" /> </bean> ... </beans>
where ....
Defines a | |
links the |
If your domain classes share a set of packages, it is also
possible to enlist those packages with the
JDOClassDescriptorResolver
bean, replacing
the bean definition shown above as follows:
<bean id="classDescriptorResolver" class="org.castor.spring.orm.ClassDescriptorResolverFactoryBean"> <property name="packages"> <list> <value>org.castor.jpa.test</value> </list> </property> </bean>
In order to enable JPA callbacks handling via Spring ORM following exemplary config snippet is required:
<bean id="jdoManager" class="org.castor.spring.orm.LocalCastorFactoryBean"> <property name="databaseName" value="testSimple" /> <property name="configLocation" value="classpath:org/castor/jpa/scenario/callbacks/derby-jdo-conf.xml" /> <property name="classDescriptorResolver" ref="classDescriptorResolver" /> <property name="callbackInterceptor" ref="jpaCallbackHandler" /> </bean> <bean id="jpaCallbackHandler" class="org.castor.jdo.jpa.info.JPACallbackHandler" />
This section describes all JPA-extensions provided by Castor.
In order to get the maximum out of the chosen built-in or
external cache engine Castor provides a generic way to
specify properties in a vendor-independent way.
Castor allows for cache-tuning on a per-entity basis by
simply providing key-value pairs with the @CacheProperty
annotation in the @Cache container annnotation.
@Entity @Cache({ @CacheProperty(key="type", value="ehcache"), @CacheProperty(key="capacity", value="50") }) @Table(name="Cache_limited") public class LimitedCachingEntity implements CacheTestEntity { private long id; private String name; @Id public long getId() { return id; } public void setId(final long id) { this.id = id; } public String getName() { return name; } public void setName(final String name) { this.name = name; } }
@Cache is based on Castor JDO and uses its default settings: 'count-limited' as cache type with a capacity of 30 entries.