I am having a terrible time implementing EclipseLink JPA History for OneToMany attributes. The heart of the matter is that when I do an 'as of' query on my entity, the JPA object comes back with the correct snapshot for the 'one' level data, but the list (or 'many') reflects the current records, not the historical. When I look at the raw data in the database, everything appears to be stored as I would expect, so I hope it's just a missed step preventing me from pulling it out as I would like. Here are the details:
The Event entity is built with a OneToMany attribute called 'details'.
Event
public class Event implements Serializable {
protected Long id;
private List<Detail> details;
private EventType eventType;
}
Orm.xml snippet
<entity class="program_x.entities.Event" name="Event">
<customizer class=" program_x.history.HistoryCustomizer"/>
<attributes>
<id name="id">
<generated-value generator="EVNTSEQ" strategy="SEQUENCE"/>
<sequence-generator name="EVNTSEQ" sequence-name="EVNT_SEQ" allocation-size="50"/>
</id>
<one-to-many name="details" fetch="EAGER" orphan-removal="true" mapped-by="event">
<order-column/>
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
<basic name="eventType">
<enumerated>STRING</enumerated>
</basic>
</attributes>
</entity>
Detail
public class Detail implements Serializable {
protected Long id;
private String detailName;
protected Event event;
}
Orm.xml snippet
<entity class=" program_x.entities.Detail" name="Detail">
<customizer class=" program_x.history.HistoryCustomizer"/>
<attributes>
<id name="id">
<generated-value generator="DETSEQ" strategy="SEQUENCE"/>
<sequence-generator name="DETSEQ" sequence-name="DET_SEQ" allocation-size="50"/>
</id>
<basic name=" detailName "/>
<many-to-one name="event"/>
</attributes>
</entity>
Currently all the entities use the same generic History Customizer.
History Customizer
HistoryPolicy policy = new HistoryPolicy();
policy.addStartFieldName("STARTDATE");
policy.addEndFieldName("ENDDATE");
policy.addHistoryTableName(descriptor.getTableName(), descriptor.getTableName() + "_HISTORY");
policy.setShouldHandleWrites(true);
descriptor.setHistoryPolicy(policy);
In order to allow EclipseLink to drop-and-create all of our required tables at whim, we also use the Entity notation to generate the history tables. Please note that 1) the Event_History table does not require the 'details' attribute to be defined because these entities are ONLY being used for table generation purposes and the One-To-Many magic means that there is not actually a details field in the Event database table... it just created a JPA relationship; and
2) the Detail_History table saves the event field as Long (the ID) (instead of an Event as the Detail entity does).
Event_History
@Entity
@Cacheable(false)
public class Event_History implements Serializable {
@Id
@SequenceGenerator(name = "EVNTHISTSEQ", sequenceName = "EVNT_HIST_SEQ", allocationSize = 50)
@GeneratedValue(generator = "EVNTHISTSEQ")
private Long hid;
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date startDate;
@Temporal(TemporalType.TIMESTAMP)
private Date endDate;
//Not Needed private List<Detail> details;
@Enumerated(EnumType.STRING)
private EventType eventType;
}
Detail_History
@Entity
@Cacheable(false)
public class Detail_History implements Serializable {
@Id
@SequenceGenerator(name = "DETHISTSEQ", sequenceName = "DET_HIST_SEQ", allocationSize = 50)
@GeneratedValue(generator = "DETHISTSEQ")
private Long hid;
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date startDate;
@Temporal(TemporalType.TIMESTAMP)
private Date endDate;
private String detailName;
@Column(name = "EVENT_ID")
private Long event;
}
Test
@Test
public void testSpecificEventAsOfDate() throws InterruptedException {
//setup to create the test Event/targetRecord
<blah blah>...
Date targetDate = new Date();
Long eventId = targetRecord.getId();
Detail det = targetRecord.getDetails().get(0);
Long detId = det.getId();
//find the current record
Event expectedEvent = eController.get(eventId);
debugOutput(expectedEvent, detId, "Current"); //pretty print
Thread.sleep(10000);
//edit a record so that it has some history
det.setDetailName("MODIFIED DESC"); //change the event
targetRecord.setEventType(EventType.B); //change one of it's details
try {
aeController.edit(targetRecord); //cascaded save
} catch (Exception ex) {
fail(ex.getMessage());
}
Event eventBEFORE = eController.findEntityAsOfDate(eventId, targetDate);
debugOutput(eventBEFORE, detId, "Before");
assertEquals(expectedEvent, eventBEFORE); //FAILS!!!!!!!!!!
//now get the current record one more time
Event eventAFTER = eController.findEntityAsOfDate(eventId, new Date());
debugOutput(eventAFTER, detId, "After");
assertNotNull(eventAFTER);
assertNotSame(eventBEFORE, eventAFTER);
}
Results
Quote://---------------------------------------
Current Type: A
Current Det: Detail 1
//---------------------------------------
Before Type: A
Before Det: MODIFIED DESC <--THIS SHOULD SAY 'Detail 1'
//---------------------------------------
After Type: B
After Det: MODIFIED DESC
I saw a couple postings that mentioned adding an additional 'addHistoryTableName' to the History Customizer so I tried breaking out a customizer for each of my Entities and then trying to list more than one history table in each. Then I get an error and no tables at all are created...
History Customizer
HistoryPolicy policy = new HistoryPolicy();
policy.addStartFieldName("STARTDATE");
policy.addEndFieldName("ENDDATE");
policy.addHistoryTableName("EVENT", "EVENT_HISTORY");
policy.addHistoryTableName("DETAIL", "DETAIL_HISTORY");
policy.setShouldHandleWrites(true);
Results
Quote:java.lang.ArrayIndexOutOfBoundsException: -1
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:696)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:632)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:568)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:799)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.login(DatabaseSessionImpl.java:756)
This is really a show-stopper to us utilizing this technology on our program so any help you can provide would be greatly appreciated.
[Updated on: Mon, 06 January 2014 13:59]
Report message to a moderator