From 4b19d0fa4ad20b385125b5502f886f64016d5665 Mon Sep 17 00:00:00 2001 From: Fay Wang Date: Sat, 6 Nov 2010 17:24:17 +0000 Subject: [PATCH] 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 --- .../jdbc/kernel/PessimisticLockManager.java | 20 +++ .../meta/strats/ContainerFieldStrategy.java | 2 +- .../meta/strats/MapTableFieldStrategy.java | 2 +- .../strats/StoreCollectionFieldStrategy.java | 2 +- .../openjpa/persistence/lockmgr/Person.java | 98 ++++++++++++++ .../persistence/lockmgr/PhoneNumber.java | 53 ++++++++ .../persistence/lockmgr/TestLocking.java | 127 ++++++++++++++++++ 7 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/Person.java create mode 100644 openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/PhoneNumber.java create mode 100644 openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestLocking.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java index b348644ee..1d6b30a69 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java @@ -26,6 +26,10 @@ import java.util.ArrayList; import java.util.List; 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.SQLBuffer; import org.apache.openjpa.jdbc.sql.SQLFactory; @@ -124,6 +128,7 @@ public class PessimisticLockManager ClassMapping mapping = (ClassMapping) sm.getMetaData(); List sqls = getLockRows(dict, id, mapping, fetch, _store.getSQLFactory()); + lockJoinTables(sqls, dict, id, mapping, fetch, _store.getSQLFactory()); ensureStoreManagerTransaction(); Connection conn = _store.getConnection(); @@ -163,6 +168,21 @@ public class PessimisticLockManager sqls.add(select.toSelect(true, fetch)); return sqls; } + + protected void lockJoinTables(List 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 diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java index 07bbee57d..d2fe2d677 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ContainerFieldStrategy.java @@ -77,7 +77,7 @@ public abstract class ContainerFieldStrategy appendSize(sql, sel, joins); } - protected abstract ForeignKey getJoinForeignKey(); + public abstract ForeignKey getJoinForeignKey(); public void appendSize(SQLBuffer sql, Select sel, Joins joins) { DBDictionary dict = field.getMappingRepository().getDBDictionary(); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/MapTableFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/MapTableFieldStrategy.java index df8edcc9c..a9af073ae 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/MapTableFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/MapTableFieldStrategy.java @@ -182,7 +182,7 @@ public abstract class MapTableFieldStrategy return field.join(joins, forceOuter, true); } - protected ForeignKey getJoinForeignKey() { + public ForeignKey getJoinForeignKey() { return field.getJoinForeignKey(); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java index d2dfc2fd7..a6a3f604a 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java @@ -597,7 +597,7 @@ public abstract class StoreCollectionFieldStrategy return loadElement(null, store, fetch, res, joins); } - protected ForeignKey getJoinForeignKey() { + public ForeignKey getJoinForeignKey() { return getJoinForeignKey(getDefaultElementMapping(false)); } diff --git a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/Person.java b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/Person.java new file mode 100644 index 000000000..7058ee4cd --- /dev/null +++ b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/Person.java @@ -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 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 getPhoneNumbers(){ + return phoneNumbers; + } + + public void setPhoneNumbers(List 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) in.readObject(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeInt(id); + out.writeObject(firstName); + out.writeObject(lastName); + out.writeObject(phoneNumbers); + } +} diff --git a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/PhoneNumber.java b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/PhoneNumber.java new file mode 100644 index 000000000..36c3366ef --- /dev/null +++ b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/PhoneNumber.java @@ -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 owners; + + public void setOwners(List o) { + owners = o; + } + + @Override + public String toString() { + return number; + } +} diff --git a/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestLocking.java b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestLocking.java new file mode 100644 index 000000000..47812e9c1 --- /dev/null +++ b/openjpa-persistence-locking/src/test/java/org/apache/openjpa/persistence/lockmgr/TestLocking.java @@ -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 props = new HashMap(); + 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 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(); + } +}