mirror of https://github.com/apache/openjpa.git
OPENJPA-1762: lock join table when extended pessimistic scope is used
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1032116 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6d423c5b6b
commit
4b19d0fa4a
|
@ -26,6 +26,10 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||||
|
import org.apache.openjpa.jdbc.meta.FieldMapping;
|
||||||
|
import org.apache.openjpa.jdbc.meta.Strategy;
|
||||||
|
import org.apache.openjpa.jdbc.meta.strats.ContainerFieldStrategy;
|
||||||
|
import org.apache.openjpa.jdbc.schema.ForeignKey;
|
||||||
import org.apache.openjpa.jdbc.sql.DBDictionary;
|
import org.apache.openjpa.jdbc.sql.DBDictionary;
|
||||||
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
||||||
import org.apache.openjpa.jdbc.sql.SQLFactory;
|
import org.apache.openjpa.jdbc.sql.SQLFactory;
|
||||||
|
@ -124,6 +128,7 @@ public class PessimisticLockManager
|
||||||
ClassMapping mapping = (ClassMapping) sm.getMetaData();
|
ClassMapping mapping = (ClassMapping) sm.getMetaData();
|
||||||
|
|
||||||
List<SQLBuffer> sqls = getLockRows(dict, id, mapping, fetch, _store.getSQLFactory());
|
List<SQLBuffer> sqls = getLockRows(dict, id, mapping, fetch, _store.getSQLFactory());
|
||||||
|
lockJoinTables(sqls, dict, id, mapping, fetch, _store.getSQLFactory());
|
||||||
|
|
||||||
ensureStoreManagerTransaction();
|
ensureStoreManagerTransaction();
|
||||||
Connection conn = _store.getConnection();
|
Connection conn = _store.getConnection();
|
||||||
|
@ -164,6 +169,21 @@ public class PessimisticLockManager
|
||||||
return sqls;
|
return sqls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void lockJoinTables(List<SQLBuffer> sqls, DBDictionary dict, Object id, ClassMapping mapping,
|
||||||
|
JDBCFetchConfiguration fetch, SQLFactory factory) {
|
||||||
|
FieldMapping[] fms = mapping.getFieldMappings();
|
||||||
|
for (int i = 0; i < fms.length; i++) {
|
||||||
|
Strategy strat = fms[i].getStrategy();
|
||||||
|
if (strat instanceof ContainerFieldStrategy) {
|
||||||
|
ForeignKey fk = ((ContainerFieldStrategy)strat).getJoinForeignKey();
|
||||||
|
Select select = factory.newSelect();
|
||||||
|
select.select(fk.getColumns());
|
||||||
|
select.whereForeignKey(fk, id, fms[i].getDefiningMapping(), _store);
|
||||||
|
sqls.add(select.toSelect(true, fetch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforce that we have an actual transaction in progress so that we can
|
* Enforce that we have an actual transaction in progress so that we can
|
||||||
* start locking. The transaction should already be begun when using a
|
* start locking. The transaction should already be begun when using a
|
||||||
|
|
|
@ -77,7 +77,7 @@ public abstract class ContainerFieldStrategy
|
||||||
appendSize(sql, sel, joins);
|
appendSize(sql, sel, joins);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ForeignKey getJoinForeignKey();
|
public abstract ForeignKey getJoinForeignKey();
|
||||||
|
|
||||||
public void appendSize(SQLBuffer sql, Select sel, Joins joins) {
|
public void appendSize(SQLBuffer sql, Select sel, Joins joins) {
|
||||||
DBDictionary dict = field.getMappingRepository().getDBDictionary();
|
DBDictionary dict = field.getMappingRepository().getDBDictionary();
|
||||||
|
|
|
@ -182,7 +182,7 @@ public abstract class MapTableFieldStrategy
|
||||||
return field.join(joins, forceOuter, true);
|
return field.join(joins, forceOuter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ForeignKey getJoinForeignKey() {
|
public ForeignKey getJoinForeignKey() {
|
||||||
return field.getJoinForeignKey();
|
return field.getJoinForeignKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -597,7 +597,7 @@ public abstract class StoreCollectionFieldStrategy
|
||||||
return loadElement(null, store, fetch, res, joins);
|
return loadElement(null, store, fetch, res, joins);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ForeignKey getJoinForeignKey() {
|
public ForeignKey getJoinForeignKey() {
|
||||||
return getJoinForeignKey(getDefaultElementMapping(false));
|
return getJoinForeignKey(getDefaultElementMapping(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* 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.persistence.lockmgr;
|
||||||
|
|
||||||
|
import java.io.Externalizable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInput;
|
||||||
|
import java.io.ObjectOutput;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToMany;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Person implements Externalizable {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private List<PhoneNumber> phoneNumbers;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToMany(mappedBy = "owners")
|
||||||
|
public List<PhoneNumber> getPhoneNumbers(){
|
||||||
|
return phoneNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoneNumbers(List<PhoneNumber> numbers){
|
||||||
|
phoneNumbers = numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getName() + '@'
|
||||||
|
+ Integer.toHexString(System.identityHashCode(this)) + "[id="
|
||||||
|
+ getId() + "] first=" + getFirstName()
|
||||||
|
+ ", last=" + getLastName() + " phone numbers="+phoneNumbers.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readExternal(ObjectInput in) throws IOException,
|
||||||
|
ClassNotFoundException {
|
||||||
|
id = in.readInt();
|
||||||
|
firstName = (String) in.readObject();
|
||||||
|
lastName = (String) in.readObject();
|
||||||
|
phoneNumbers = (List<PhoneNumber>) in.readObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeExternal(ObjectOutput out) throws IOException {
|
||||||
|
out.writeInt(id);
|
||||||
|
out.writeObject(firstName);
|
||||||
|
out.writeObject(lastName);
|
||||||
|
out.writeObject(phoneNumbers);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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.persistence.lockmgr;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToMany;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class PhoneNumber implements Serializable {
|
||||||
|
private static final long serialVersionUID = 6918497432632022604L;
|
||||||
|
|
||||||
|
PhoneNumber() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhoneNumber(String p) {
|
||||||
|
number = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
String number;
|
||||||
|
|
||||||
|
@ManyToMany
|
||||||
|
List<Person> owners;
|
||||||
|
|
||||||
|
public void setOwners(List<Person> o) {
|
||||||
|
owners = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* 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.persistence.lockmgr;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.LockModeType;
|
||||||
|
import javax.persistence.PessimisticLockScope;
|
||||||
|
|
||||||
|
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
|
||||||
|
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
|
||||||
|
|
||||||
|
public class TestLocking extends SQLListenerTestCase {
|
||||||
|
String _phone = "5075555555";
|
||||||
|
|
||||||
|
public void setUp() {
|
||||||
|
super.setUp(CLEAR_TABLES, Person.class, PhoneNumber.class
|
||||||
|
// ,"openjpa.Log", "SQL=trace"
|
||||||
|
);
|
||||||
|
populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtendedLockScope() throws Exception {
|
||||||
|
Map<String, Object> props = new HashMap<String, Object>();
|
||||||
|
props.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);
|
||||||
|
|
||||||
|
OpenJPAEntityManagerSPI em1 = emf.createEntityManager();
|
||||||
|
OpenJPAEntityManagerSPI em2 = emf.createEntityManager();
|
||||||
|
CommitterThread committer = new CommitterThread(em2);
|
||||||
|
|
||||||
|
em1.getTransaction().begin();
|
||||||
|
Person e1 = em1.find(Person.class, 1);
|
||||||
|
assertEquals(1, e1.getPhoneNumbers().size());
|
||||||
|
|
||||||
|
// This SHOULD lock Employee with id=1 AND the join table.
|
||||||
|
//
|
||||||
|
// pg 86
|
||||||
|
// Element collections and relationships owned by the entity that are contained in join tables will be
|
||||||
|
// locked if the javax.persistence.lock.scope property is specified with a value of
|
||||||
|
// PessimisticLockScope.EXTENDED. The state of entities referenced by such relationships will
|
||||||
|
// not be locked (unless those entities are explicitly locked). This property may be passed as an argument
|
||||||
|
// to the methods of the EntityManager, Query, and TypedQuery interfaces that allow lock modes
|
||||||
|
// to be specified or used with the NamedQuery annotation.
|
||||||
|
|
||||||
|
em1.refresh(e1, LockModeType.PESSIMISTIC_FORCE_INCREMENT, props);
|
||||||
|
|
||||||
|
// Kick off the committer thread
|
||||||
|
committer.start();
|
||||||
|
|
||||||
|
// Make sure to sleep at least for 5 seconds AFTER the committer calls commit
|
||||||
|
while (System.currentTimeMillis() - committer.sleepStartTime < 5000) {
|
||||||
|
Thread.sleep(5000);
|
||||||
|
}
|
||||||
|
// The committer should still be waiting because the em1.refresh(...) call should have locked the join table and
|
||||||
|
// the remove can't complete
|
||||||
|
assertFalse(committer.commitComplete);
|
||||||
|
em1.getTransaction().commit();
|
||||||
|
em1.close();
|
||||||
|
// wait for child thread to finish
|
||||||
|
committer.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CommitterThread extends Thread {
|
||||||
|
OpenJPAEntityManagerSPI _em2;
|
||||||
|
boolean inCommit = false;
|
||||||
|
boolean commitComplete = false;
|
||||||
|
long sleepStartTime = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
public CommitterThread(OpenJPAEntityManagerSPI e) {
|
||||||
|
_em2 = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
_em2.getTransaction().begin();
|
||||||
|
PhoneNumber phoneNumber = _em2.find(PhoneNumber.class, _phone);
|
||||||
|
_em2.remove(phoneNumber);
|
||||||
|
inCommit = true;
|
||||||
|
sleepStartTime = System.currentTimeMillis();
|
||||||
|
_em2.getTransaction().commit();
|
||||||
|
commitComplete = true;
|
||||||
|
_em2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populate() {
|
||||||
|
OpenJPAEntityManagerSPI em = emf.createEntityManager();
|
||||||
|
em.getTransaction().begin();
|
||||||
|
|
||||||
|
PhoneNumber p = new PhoneNumber(_phone);
|
||||||
|
List<PhoneNumber> numbers = Arrays.asList(new PhoneNumber[] { p });
|
||||||
|
|
||||||
|
Person e1 = new Person();
|
||||||
|
e1.setId(1);
|
||||||
|
e1.setPhoneNumbers(numbers);
|
||||||
|
Person e2 = new Person();
|
||||||
|
e2.setId(2);
|
||||||
|
e2.setPhoneNumbers(numbers);
|
||||||
|
|
||||||
|
p.setOwners(Arrays.asList(new Person[] { e1, e2 }));
|
||||||
|
em.persist(e1);
|
||||||
|
em.persist(e2);
|
||||||
|
em.persist(p);
|
||||||
|
|
||||||
|
em.getTransaction().commit();
|
||||||
|
em.close();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue