Jump to: navigation, search

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

(Collection Order Preservation (JPA 1.0))
 
(14 intermediate revisions by one other user not shown)
Line 1: Line 1:
 
__NOTOC__
 
__NOTOC__
  
== Maintain Collection Ordering ==
+
== Collection Order Preservation (JPA 1.0) ==
  
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.  See, [[EclipseLink/Examples/JPA/2.0/OrderColumns|OrderColumns]].
 +
 
 +
=== 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:
 +
 
 +
# Ensure that a ''index'' attribute is mapped (@Basic) in the target entity of the collection where order maintenance is required.
 +
# Ensure that the collection is mapped and has OrderBy("index")  specified
 +
# 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.
  
 
<source lang="java">
 
<source lang="java">
public class MaintainOrderLineItemsIndex extends DescriptorEventAdapter implements DescriptorCustomizer {
+
@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;
  
private void setLineNumbers(Order order) {
+
@ManyToOne
for (int index = 0; index < order.getLineItems().size(); index++) {
+
@JoinColumn(name = "ORDER_NUM")
LineItem item = order.getLineItems().get(index);
+
private Order order;
item.setLineNumber(index);
+
}
+
}
+
  
@Override
+
@Column(name = "LINE_NUM")
public void preInsert(DescriptorEvent event) {
+
private int lineNumber = -1;
setLineNumbers((Order) event.getSource());
+
}
+
  
@Override
+
@Id
public void preUpdate(DescriptorEvent event) {
+
@Column(name = "PROD_ID", insertable = false, updatable = false)
setLineNumbers((Order) event.getSource());
+
private int productId;
}
+
  
@Override
+
@ManyToOne(fetch = FetchType.LAZY)
public void postMerge(DescriptorEvent event) {
+
@JoinColumn(name = "PROD_ID")
setLineNumbers((Order) event.getOriginalObject());
+
private Product product;
setLineNumbers((Order) event.getSource());
+
}
+
  
@Override
+
private int quantity;
public void prePersist(DescriptorEvent event) {
+
 
setLineNumbers((Order) event.getSource());
+
private double price;
 +
 
 +
// ...
 +
 
 +
public int getLineNumber() {
 +
return this.lineNumber;
 
}
 
}
  
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", "lineNumber");
 +
 
 +
session.getEventManager().addListener(listener);
 
}
 
}
  
Line 47: Line 87:
 
</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:
Line 58: Line 111:
 
* To customize the database you must edit src/META-INF/persistence.xml
 
* To customize the database you must edit src/META-INF/persistence.xml
  
In order to verify the database schema creation and population run <pre>testing.CreateDatabase</pre>
+
In order to verify the database schema creation and population run '''testing.CreateDatabase'''. To perform all of the tests run '''testing.AllTests'''.

Latest revision as of 10:48, 30 November 2009


Collection Order Preservation (JPA 1.0)

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. See, OrderColumns.

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;
	}
 
	// ...

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>();

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", "lineNumber");
 
		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.