OPENJPA-817 fixed in trunk

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@724765 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael Dick 2008-12-09 16:04:37 +00:00
parent 411382d4d9
commit 82e702867e
8 changed files with 449 additions and 53 deletions

View File

@ -58,7 +58,7 @@ public class ConstraintUpdateManager
(ConstraintUpdateManager.class);
public boolean orderDirty() {
return false;
return true;
}
protected PreparedStatementManager newPreparedStatementManager
@ -84,9 +84,10 @@ public class ConstraintUpdateManager
flush(rmimpl.getAllRowUpdates(), psMgr);
// analyze foreign keys
Collection inserts = rmimpl.getInserts();
Collection updates = rmimpl.getUpdates();
Collection deletes = rmimpl.getDeletes();
Collection<PrimaryRow> inserts = rmimpl.getInserts();
Collection<PrimaryRow> updates = rmimpl.getUpdates();
Collection<PrimaryRow> deletes = rmimpl.getDeletes();
Graph[] graphs = new Graph[2]; // insert graph, delete graph
analyzeForeignKeys(inserts, updates, deletes, rmimpl, graphs);

View File

@ -22,7 +22,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -39,17 +39,17 @@ import org.apache.openjpa.util.InternalException;
public class RowManagerImpl
implements RowManager {
private Map _inserts = null;
private Map _updates = null;
private Map _deletes = null;
private Collection _secondaryUpdates = null;
private Collection _secondaryDeletes = null;
private Collection _allRowUpdates = null;
private Collection _allRowDeletes = null;
private Map<Key, PrimaryRow> _inserts = null;
private Map<Key, PrimaryRow> _updates = null;
private Map<Key, PrimaryRow> _deletes = null;
private Collection<SecondaryRow> _secondaryUpdates = null;
private Collection<SecondaryRow> _secondaryDeletes = null;
private Collection<Row> _allRowUpdates = null;
private Collection<Row> _allRowDeletes = null;
// we maintain a list of the order of all primary rows if the user
// wants to be able to fetch them in order
private final List _primaryOrder;
private final List<PrimaryRow> _primaryOrder;
// track whether we're dealing with any auto-inc columns
private boolean _auto = false;
@ -66,7 +66,7 @@ public class RowManagerImpl
* @param order whether to keep track of the order in which rows are added
*/
public RowManagerImpl(boolean order) {
_primaryOrder = (order) ? new ArrayList() : null;
_primaryOrder = (order) ? new ArrayList<PrimaryRow>() : null;
}
/**
@ -80,61 +80,98 @@ public class RowManagerImpl
* Return the ordered primary rows. Only available if ordering requested
* on construction.
*/
public List getOrdered() {
return (_primaryOrder == null) ? Collections.EMPTY_LIST : _primaryOrder;
public List<PrimaryRow> getOrdered() {
if(_primaryOrder == null ) {
return Collections.emptyList();
}
else {
return _primaryOrder;
}
}
/**
* Return all inserted primary rows.
*/
public Collection getInserts() {
return (_inserts == null) ? Collections.EMPTY_LIST : _inserts.values();
public Collection<PrimaryRow> getInserts() {
if(_inserts == null ) {
return Collections.emptyList();
}
else {
return _inserts.values();
}
}
/**
* Return all updated primary rows.
*/
public Collection getUpdates() {
return (_updates == null) ? Collections.EMPTY_LIST : _updates.values();
public Collection<PrimaryRow> getUpdates() {
if(_updates == null ){
return Collections.emptyList();
}
else {
return _updates.values();
}
}
/**
* Return all deleted primary rows.
*/
public Collection getDeletes() {
return (_deletes == null) ? Collections.EMPTY_LIST : _deletes.values();
public Collection<PrimaryRow> getDeletes() {
if(_deletes == null) {
return Collections.emptyList();
}
else {
return _deletes.values();
}
}
/**
* Return all inserted and updated secondary rows.
*/
public Collection getSecondaryUpdates() {
return (_secondaryUpdates == null) ? Collections.EMPTY_LIST
: _secondaryUpdates;
public Collection<SecondaryRow> getSecondaryUpdates() {
if(_secondaryUpdates == null) {
return Collections.emptyList();
}
else {
return _secondaryUpdates;
}
}
/**
* Return all deleted secondary rows.
*/
public Collection getSecondaryDeletes() {
return (_secondaryDeletes == null) ? Collections.EMPTY_LIST
: _secondaryDeletes;
public Collection<SecondaryRow> getSecondaryDeletes() {
if(_secondaryDeletes == null) {
return Collections.emptyList();
}
else {
return _secondaryDeletes;
}
}
/**
* Return any 'all row' updates.
*/
public Collection getAllRowUpdates() {
return (_allRowUpdates == null) ? Collections.EMPTY_LIST
: _allRowUpdates;
public Collection<Row> getAllRowUpdates() {
if(_allRowUpdates == null) {
return Collections.emptyList();
}
else {
return _allRowUpdates;
}
}
/**
* Return any 'all row' deletes.
*/
public Collection getAllRowDeletes() {
return (_allRowDeletes == null) ? Collections.EMPTY_LIST
: _allRowDeletes;
public Collection<Row> getAllRowDeletes() {
if(_allRowDeletes == null) {
return Collections.emptyList();
}
else {
return _allRowDeletes;
}
}
public Row getSecondaryRow(Table table, int action) {
@ -149,12 +186,12 @@ public class RowManagerImpl
SecondaryRow srow = (SecondaryRow) row;
if (srow.getAction() == Row.ACTION_DELETE) {
if (_secondaryDeletes == null)
_secondaryDeletes = new ArrayList();
_secondaryDeletes.add(srow.clone());
_secondaryDeletes = new ArrayList<SecondaryRow>();
_secondaryDeletes.add((SecondaryRow) srow.clone());
} else {
if (_secondaryUpdates == null)
_secondaryUpdates = new ArrayList();
_secondaryUpdates.add(srow.clone());
_secondaryUpdates = new ArrayList<SecondaryRow>();
_secondaryUpdates.add((SecondaryRow) srow.clone());
}
}
@ -169,12 +206,12 @@ public class RowManagerImpl
switch (row.getAction()) {
case Row.ACTION_UPDATE:
if (_allRowUpdates == null)
_allRowUpdates = new ArrayList();
_allRowUpdates = new ArrayList<Row>();
_allRowUpdates.add(row);
break;
case Row.ACTION_DELETE:
if (_allRowDeletes == null)
_allRowDeletes = new ArrayList();
_allRowDeletes = new ArrayList<Row>();
_allRowDeletes.add(row);
break;
default:
@ -192,25 +229,25 @@ public class RowManagerImpl
&& _row != null && _row.getAction() == action)
return _row;
Map map;
Map<Key, PrimaryRow> map;
if (action == Row.ACTION_DELETE) {
if (_deletes == null && create)
_deletes = new HashMap();
_deletes = new LinkedHashMap<Key, PrimaryRow>();
map = _deletes;
} else if (action == Row.ACTION_INSERT) {
if (_inserts == null && create)
_inserts = new HashMap();
_inserts = new LinkedHashMap<Key, PrimaryRow>();
map = _inserts;
} else {
if (_updates == null && create)
_updates = new HashMap();
_updates = new LinkedHashMap<Key, PrimaryRow>();
map = _updates;
}
if (map == null)
return null;
_key = new Key(table, sm);
_row = (PrimaryRow) map.get(_key);
_row = map.get(_key);
if (_row == null && create) {
_row = new PrimaryRow(table, action, sm);

View File

@ -32,10 +32,13 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.transaction.Status;
import javax.transaction.Synchronization;
@ -61,7 +64,6 @@ import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.ReferenceHashMap;
import org.apache.openjpa.lib.util.ReferenceHashSet;
import org.apache.openjpa.lib.util.ReferenceMap;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
@ -3310,7 +3312,7 @@ public class BrokerImpl
Object obj;
StateManagerImpl sm;
ClassMetaData meta;
Collection sms = new HashSet(objs.size());
Collection sms = new LinkedHashSet(objs.size());
List exceps = null;
for (Iterator itr = objs.iterator(); itr.hasNext();) {
obj = itr.next();
@ -3637,7 +3639,7 @@ public class BrokerImpl
try {
assertActiveTransaction();
Collection sms = new HashSet(objs.size());
Collection sms = new LinkedHashSet(objs.size());
Object obj;
StateManagerImpl sm;
for (Iterator itr = objs.iterator(); itr.hasNext();) {
@ -3815,7 +3817,7 @@ public class BrokerImpl
protected Collection getPendingTransactionalStates() {
if (_pending == null)
return Collections.EMPTY_SET;
return new HashSet(_pending);
return new LinkedHashSet(_pending);
}
/**
@ -4508,7 +4510,7 @@ public class BrokerImpl
// size may not be entirely accurate due to refs expiring, so
// manually copy each object; doesn't matter this way if size too
// big by some
Set copy = new HashSet(size());
Set copy = new LinkedHashSet(size());
if (_dirty != null)
for (Iterator itr = _dirty.iterator(); itr.hasNext();)
copy.add(itr.next());
@ -4524,7 +4526,7 @@ public class BrokerImpl
public Collection copyDirty() {
if (_dirty == null || _dirty.isEmpty())
return Collections.EMPTY_SET;
return new HashSet(_dirty);
return new LinkedHashSet(_dirty);
}
/**

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.kernel;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
// Try not to collide with other Employee entities
@Table(name = "PER_JDBC_KERN_EMP")
public class Employee {
@Id
private int id;
@Version
private int version;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "employee", cascade = { CascadeType.MERGE,
CascadeType.PERSIST })
private Collection<Task> tasks;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Collection<Task> getTasks() {
return tasks;
}
public void setTasks(Collection<Task> tasks) {
this.tasks = tasks;
}
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.kernel;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name = "PER_JDBC_KERN_STORY") // try not to collide
public class Story {
@Id
private int id;
@Version
private int version;
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
private Task task;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Task getTask() {
return task;
}
public void setTask(Task task) {
this.task = task;
}
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.kernel;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name = "PER_JDBC_KERN_TASK")
// try not to collide
public class Task {
@Id
private int id;
@Version
private int version;
@OneToMany(mappedBy = "task", cascade = { CascadeType.MERGE,
CascadeType.PERSIST })
private Collection<Story> stories;
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
private Employee employee;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Collection<Story> getStories() {
return stories;
}
public void setStories(Collection<Story> stories) {
this.stories = stories;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.kernel;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.EntityManager;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
/**
* Test that insert order is preserved when using the ConstraintUpdateManager
* for entities which are not annotated with ForeignKey constraints.
*/
public class TestInsertOrder extends SQLListenerTestCase {
private String empTableName;
private String taskTableName;
private String storyTableName;
public void setUp() {
setUp(Employee.class, Task.class, Story.class);
empTableName = getMapping(Employee.class).getTable().getFullName();
taskTableName = getMapping(Task.class).getTable().getFullName();
storyTableName = getMapping(Story.class).getTable().getFullName();
}
/**
* <P>Persist an Employee entity and allow the cascade to insert the children.
* The inserts should be executed in this order, Employee, Task, Story.
* </P>
*
* <P>
* Originally this test would pass in some scenarios. I believe the order
* relied on the hashcode of the underlying entities.
* </P>
*/
public void testCascadePersist() {
Employee e = newTree(10);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(e);
em.getTransaction().commit();
em.close();
assertSQLOrder("INSERT INTO " + empTableName + ".*", "INSERT INTO "
+ taskTableName + ".*", "INSERT INTO " + storyTableName + ".*");
}
/**
* Merge an Employee entity and allow the cascade to insert the children.
* The inserts should be executed in this order, Employee, Task, Story.
*/
public void testCascadeMerge() {
Employee e = newTree(11);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.merge(e);
em.getTransaction().commit();
em.close();
assertSQLOrder("INSERT INTO " + empTableName + ".*", "INSERT INTO "
+ taskTableName + ".*", "INSERT INTO " + storyTableName + ".*");
}
/**
* Helper to create a tree of entities
*
* @param id
* ID for the entities.
* @return an unmanaged Employee instance with the appropriate relationships
* set.
*/
private Employee newTree(int id) {
Employee e = new Employee();
e.setId(id);
Task t = new Task();
t.setId(id);
Story s = new Story();
s.setId(id);
Collection<Task> tasks = new ArrayList<Task>();
tasks.add(t);
Collection<Story> stories = new ArrayList<Story>();
stories.add(s);
e.setTasks(tasks);
t.setEmployee(e);
t.setStories(stories);
s.setTask(t);
return e;
}
}

View File

@ -21,7 +21,6 @@ package org.apache.openjpa.persistence.test;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
import org.apache.openjpa.lib.jdbc.JDBCEvent;
@ -34,7 +33,7 @@ import org.apache.openjpa.lib.jdbc.JDBCListener;
*/
public abstract class SQLListenerTestCase
extends SingleEMFTestCase {
private static String _nl = System.getProperty("line.separator");
protected List<String> sql = new ArrayList<String>();
protected int sqlCount;
@ -130,4 +129,28 @@ public abstract class SQLListenerTestCase
}
}
}
public void assertSQLOrder(String... expected) {
int hits = 0;
for (String executedSQL : sql) {
if (executedSQL.matches(expected[hits])) {
hits++;
}
}
if (hits != expected.length) {
StringBuilder sb = new StringBuilder();
sb.append("Did not find SQL in expected order : ").append(_nl);
for (String s : expected) {
sb.append(s).append(_nl);
}
sb.append("SQL Statements issued : ");
for (String s : sql) {
sb.append(s).append(_nl);
}
fail(sb.toString());
}
}
}