Difference between revisions of "EclipseLink/Examples/JPA/Collectionordering"

From Eclipsepedia

Jump to: navigation, search
Line 11: Line 11:
 
== Maintain Collection Ordering ==
 
== Maintain Collection Ordering ==
  
In some applications it is valuable to maintain collection ordering within the Java application. EclipseLink JPA provides support for ordering the target of a collection mapping (OneToMany, ManyToMany) but the order is not preserved in-memory. This example illustrates how an index column can be mapped in the target of a OneToMany mapping and used during the lifecycle of the entities to esnure the order set in the application is maintained when the collection is persisted and re-read.
+
In some applications it is valuable to maintain collection ordering within the Java application. This requires that the order of a collection is maintained between writes and reads.  
  
In order to have a collection ordered when it is read the @OrderBy configuration must be specified on the collection mapping. EclipseLink JPA will then add ORDER BY into the SQL when the collection is read in and return the initial result with this ordering. After this EclipseLink will only ensure that the collection's order as maintained by the application in a transaction is preserved when merging into the shared cache. This means that any application code modifying a collection must ensure that order is the way they want it prior to committing the changes in a transaction.
+
EclipseLink does provide support for ordering the target of a collection mapping (OneToMany, ManyToMany) but the order is not preserved in-memory and is not transparently saved. This example illustrates how an index column can be mapped in the target of a OneToMany mapping and used during the lifecycle of the entities to ensure the order set in the application is maintained when the collection is persisted and re-read.
  
=== Re-Ordering using Events ===
+
'''Note:''' Transparent support for order preservation will be provided in EclipseLink 2.0 as part of its JPA 2.0 implementation. This example is intended to help those using JPA 1.0 who need this functionality in EclipseLink 1.0 or 1.1.
  
<source lang="java">
+
=== Configuration ===
public class MaintainOrderLineItemsIndex extends DescriptorEventAdapter implements DescriptorCustomizer {
+
  
private void setLineNumbers(Order order) {
+
In this example a simple Order domain model is used when an '''Order''' has a collection of '''LineItem''' and each of these has a '''Product'''. To configure things a developer must do the following:
for (int index = 0; index < order.getLineItems().size(); index++) {
+
LineItem item = order.getLineItems().get(index);
+
item.setLineNumber(index);
+
}
+
}
+
  
@Override
+
# Ensure that a ''index'' attribute is mapped (@Basic) in the target entity of the collection where order maintenance is required.
public void preInsert(DescriptorEvent event) {
+
# Ensure that the collection is mapped and has OrderBy("index") specified
setLineNumbers((Order) event.getSource());
+
# Configure the use of the provided '''CollectionIndexSessionListener'''
}
+
  
@Override
+
==== Step 1: Map an Index Attribute ====
public void preUpdate(DescriptorEvent event) {
+
setLineNumbers((Order) event.getSource());
+
}
+
  
@Override
+
Within the '''LineItem''' an additional index attribute is added. Since this attribute is intended to be transparent to the application its set method is protected. This method will be reflective used by the session-event-listener.
public void postMerge(DescriptorEvent event) {
+
 
setLineNumbers((Order) event.getOriginalObject());
+
<source lang="java"/>
setLineNumbers((Order) event.getSource());
+
@Entity
 +
@IdClass(LineItem.ID.class)
 +
@Table(name = "PO_LINE_ITEM")
 +
public class LineItem implements Serializable {
 +
@Id
 +
@Column(name = "ORDER_NUM", insertable = false, updatable = false)
 +
private String orderNumber;
 +
 
 +
@ManyToOne
 +
@JoinColumn(name = "ORDER_NUM")
 +
private Order order;
 +
 
 +
@Column(name = "LINE_NUM")
 +
private int lineNumber = -1;
 +
 
 +
@Id
 +
@Column(name = "PROD_ID", insertable = false, updatable = false)
 +
private int productId;
 +
 
 +
@ManyToOne(fetch = FetchType.LAZY)
 +
@JoinColumn(name = "PROD_ID")
 +
private Product product;
 +
 
 +
private int quantity;
 +
 
 +
private double price;
 +
 
 +
// ...
 +
 
 +
public int getLineNumber() {
 +
return this.lineNumber;
 
}
 
}
  
@Override
+
protected void setLineNumber(int lineNum) {
public void prePersist(DescriptorEvent event) {
+
this.lineNumber = lineNum;
setLineNumbers((Order) event.getSource());
+
 
}
 
}
  
public void customize(ClassDescriptor descriptor) throws Exception {
+
// ...
descriptor.getEventManager().addListener(this);
+
</source>
 +
 
 +
==== Step 2: Map the Collection with Ordering ====
 +
 
 +
Within the '''Order.lineItems''' mapping the ordering is configured using the mapped index attribute (lienNumber):
 +
 
 +
<source lang="java"/>
 +
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
 +
@OrderBy("lineNumber")
 +
@PrivateOwned
 +
private List<LineItem> lineItems = new ArrayList<LineItem>();
 +
</source>
 +
 
 +
==== Step 3: Configure the Event Listener ====
 +
 
 +
The '''CollectionIndexSessionListener''' is configured using a [http://www.eclipse.org/eclipselink/api/latest/org/eclipse/persistence/config/SessionCustomizer.html SessionCustomizer].
 +
 
 +
<source lang="java">
 +
public class ConfigureCollectionIndexing implements SessionCustomizer {
 +
 
 +
public void customize(Session session) throws Exception {
 +
CollectionIndexSessionListener listener = new CollectionIndexSessionListener();
 +
 
 +
listener.addCollection(session, Order.class, "lineItems", "setLineNumber");
 +
 
 +
session.getEventManager().addListener(listener);
 
}
 
}
  
Line 55: Line 99:
 
</source>
 
</source>
  
=== Ordering by Index ===
+
The use of the SessionCustomizer is configured in the persistence unit properties:
 +
 
 +
<source lang="xml">
 +
<persistence-unit name="persist-order" transaction-type="RESOURCE_LOCAL">
 +
<class> model.Product</class>
 +
<class> model.Order</class>
 +
<class> model.LineItem</class>
 +
<properties>
 +
...
 +
<property name="eclipselink.session.customizer" value="model.ConfigureCollectionIndexing"/>
 +
</properties>
 +
</persistence-unit>
 +
</source>
  
 
== Downloading and running the Example ==
 
== Downloading and running the Example ==
  
The example is available for download here:
+
The example is available for download here: [http://dev.eclipse.org/svnroot/rt/org.eclipse.persistence/trunk/examples/org.eclipse.persistence.example.jpa.persist-order/example-collection-order.zip example-collection-order.zip].
* TRUNK: [http://dev.eclipse.org/svnroot/rt/org.eclipse.persistence/trunk/examples/org.eclipse.persistence.example.jpa.persist-order/example-collection-order.zip example-collection-order.zip]
+
 
 +
''Note: While it was developed and tested against EclipseLink 1.1 it should work with earlier versions of EclipseLink and can be easily transalted to work with TopLink releases using native ORM.''
  
 
In order to run this example you must have [http://www.eclipse.org/downloads EclipseLink installed] on the machine and must have a relational database with its JDBC driver available. In order to customize the example you must:
 
In order to run this example you must have [http://www.eclipse.org/downloads EclipseLink installed] on the machine and must have a relational database with its JDBC driver available. In order to customize the example you must:

Revision as of 16:23, 15 January 2009


Catnicon.gif This page is under construction. See bug bug 225026 for more information and to provide feedback on this work-in-progress. Catnicon.gif
STATUS: Jan 15/09: Not all test cases are passing. Please use with caution.


Maintain Collection Ordering

In some applications it is valuable to maintain collection ordering within the Java application. This requires that the order of a collection is maintained between writes and reads.

EclipseLink does provide support for ordering the target of a collection mapping (OneToMany, ManyToMany) but the order is not preserved in-memory and is not transparently saved. This example illustrates how an index column can be mapped in the target of a OneToMany mapping and used during the lifecycle of the entities to ensure the order set in the application is maintained when the collection is persisted and re-read.

Note: Transparent support for order preservation will be provided in EclipseLink 2.0 as part of its JPA 2.0 implementation. This example is intended to help those using JPA 1.0 who need this functionality in EclipseLink 1.0 or 1.1.

Configuration

In this example a simple Order domain model is used when an Order has a collection of LineItem and each of these has a Product. To configure things a developer must do the following:

  1. Ensure that a index attribute is mapped (@Basic) in the target entity of the collection where order maintenance is required.
  2. Ensure that the collection is mapped and has OrderBy("index") specified
  3. Configure the use of the provided CollectionIndexSessionListener

Step 1: Map an Index Attribute

Within the LineItem an additional index attribute is added. Since this attribute is intended to be transparent to the application its set method is protected. This method will be reflective used by the session-event-listener.

 

@Entity @IdClass(LineItem.ID.class) @Table(name = "PO_LINE_ITEM") public class LineItem implements Serializable { @Id @Column(name = "ORDER_NUM", insertable = false, updatable = false) private String orderNumber;

@ManyToOne @JoinColumn(name = "ORDER_NUM") private Order order;

@Column(name = "LINE_NUM") private int lineNumber = -1;

@Id @Column(name = "PROD_ID", insertable = false, updatable = false) private int productId;

@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "PROD_ID") private Product product;

private int quantity;

private double price;

// ...

public int getLineNumber() { return this.lineNumber; }

protected void setLineNumber(int lineNum) { this.lineNumber = lineNum; }

// ... </source>

Step 2: Map the Collection with Ordering

Within the Order.lineItems mapping the ordering is configured using the mapped index attribute (lienNumber):

 

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) @OrderBy("lineNumber") @PrivateOwned private List<LineItem> lineItems = new ArrayList<LineItem>(); </source>

Step 3: Configure the Event Listener

The CollectionIndexSessionListener is configured using a SessionCustomizer.

public class ConfigureCollectionIndexing implements SessionCustomizer {
 
	public void customize(Session session) throws Exception {
		CollectionIndexSessionListener listener = new CollectionIndexSessionListener();
 
		listener.addCollection(session, Order.class, "lineItems", "setLineNumber");
 
		session.getEventManager().addListener(listener);
	}
 
}

The use of the SessionCustomizer is configured in the persistence unit properties:

<persistence-unit name="persist-order" transaction-type="RESOURCE_LOCAL">
	<class> model.Product</class>
	<class> model.Order</class>
	<class> model.LineItem</class>
	<properties>
		...
		<property name="eclipselink.session.customizer" value="model.ConfigureCollectionIndexing"/>
	</properties>
</persistence-unit>

Downloading and running the Example

The example is available for download here: example-collection-order.zip.

Note: While it was developed and tested against EclipseLink 1.1 it should work with earlier versions of EclipseLink and can be easily transalted to work with TopLink releases using native ORM.

In order to run this example you must have EclipseLink installed on the machine and must have a relational database with its JDBC driver available. In order to customize the example you must:

  • To run using ANT you must configure the build.properties file
  • To customize the database you must edit src/META-INF/persistence.xml

In order to verify the database schema creation and population run testing.CreateDatabase. To perform all of the tests run testing.AllTests.