mirror of https://github.com/apache/openjpa.git
OPENJPA-1394 - DB2 supports Order By clause with recording locking using "WITH R*" construct. By enabling this feature in the DB2 dictionary, row locking can be perform with the fetch and eliminate the time window other thread snike in to fetch the same row.
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@882358 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
0291158f58
commit
f6a6ee15f2
|
@ -122,6 +122,7 @@ public class DB2Dictionary
|
|||
supportsDeferredConstraints = false;
|
||||
supportsDefaultDeleteAction = false;
|
||||
supportsAlterTableWithDropColumn = false;
|
||||
supportsLockingWithOrderClause = true;
|
||||
|
||||
supportsNullTableForGetColumns = false;
|
||||
requiresCastForMathFunctions = true;
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.Collection;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
@Entity
|
||||
public class Department implements Externalizable {
|
||||
|
||||
private int id;
|
||||
|
||||
private String name;
|
||||
|
||||
private Collection<Employee> employees;
|
||||
|
||||
@OneToMany(cascade=CascadeType.ALL, mappedBy="department")
|
||||
public Collection<Employee> getEmployees() {
|
||||
return employees;
|
||||
}
|
||||
|
||||
@Id
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getName() + '@'
|
||||
+ Integer.toHexString(System.identityHashCode(this)) + "[id="
|
||||
+ getId() + "] first=" + getName();
|
||||
}
|
||||
|
||||
public void readExternal(ObjectInput in) throws IOException,
|
||||
ClassNotFoundException {
|
||||
id = in.readInt();
|
||||
name = (String) in.readObject();
|
||||
}
|
||||
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeInt(id);
|
||||
out.writeObject(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class Employee implements Externalizable {
|
||||
|
||||
private int id;
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private Department department;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name="FK_DEPT")
|
||||
public Department getDepartment() {
|
||||
return department;
|
||||
}
|
||||
|
||||
public void setDepartment(Department department) {
|
||||
this.department = department;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getClass().getName() + '@'
|
||||
+ Integer.toHexString(System.identityHashCode(this)) + "[id="
|
||||
+ getId() + "] first=" + getFirstName()
|
||||
+ ", last=" + getLastName();
|
||||
}
|
||||
|
||||
public void readExternal(ObjectInput in) throws IOException,
|
||||
ClassNotFoundException {
|
||||
id = in.readInt();
|
||||
firstName = (String) in.readObject();
|
||||
lastName = (String) in.readObject();
|
||||
}
|
||||
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeInt(id);
|
||||
out.writeObject(firstName);
|
||||
out.writeObject(lastName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.PessimisticLockException;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.QueryTimeoutException;
|
||||
|
||||
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
|
||||
|
||||
/**
|
||||
* Test Pessimistic Lock and exception behavior against EntityManager and Query interface methods.
|
||||
*/
|
||||
public class TestPessimisticLocks extends SQLListenerTestCase {
|
||||
|
||||
public void setUp() {
|
||||
setSupportedDatabases(
|
||||
// org.apache.openjpa.jdbc.sql.DerbyDictionary.class,
|
||||
// org.apache.openjpa.jdbc.sql.OracleDictionary.class,
|
||||
org.apache.openjpa.jdbc.sql.DB2Dictionary.class);
|
||||
if (isTestsDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUp(Employee.class, Department.class, "openjpa.LockManager", "mixed");
|
||||
String empTable = getMapping(Employee.class).getTable().getFullName();
|
||||
String deptTable = getMapping(Department.class).getTable().getFullName();
|
||||
|
||||
EntityManager em = null;
|
||||
try {
|
||||
em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
|
||||
em.createQuery("delete from " + empTable).executeUpdate();
|
||||
em.createQuery("delete from " + deptTable).executeUpdate();
|
||||
|
||||
em.getTransaction().commit();
|
||||
|
||||
Employee e1, e2;
|
||||
Department d1, d2;
|
||||
d1 = new Department();
|
||||
d1.setId(10);
|
||||
d1.setName("D10");
|
||||
|
||||
e1 = new Employee();
|
||||
e1.setId(1);
|
||||
e1.setDepartment(d1);
|
||||
e1.setFirstName("first.1");
|
||||
e1.setLastName("last.1");
|
||||
|
||||
d2 = new Department();
|
||||
d2.setId(20);
|
||||
d2.setName("D20");
|
||||
|
||||
e2 = new Employee();
|
||||
e2.setId(2);
|
||||
e2.setDepartment(d2);
|
||||
e2.setFirstName("first.2");
|
||||
e2.setLastName("last.2");
|
||||
|
||||
em.getTransaction().begin();
|
||||
em.persist(d1);
|
||||
em.persist(d2);
|
||||
em.persist(e1);
|
||||
em.persist(e2);
|
||||
em.getTransaction().commit();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (em != null && em.isOpen()) {
|
||||
em.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test a find with pessimistic lock after a query with pessimistic lock and expect PessimisticLockException.
|
||||
*/
|
||||
public void testFindWithLockTimeoutAfterQueryWithPessimisticLocks() {
|
||||
EntityManager em1 = emf.createEntityManager();
|
||||
EntityManager em2 = emf.createEntityManager();
|
||||
try {
|
||||
em1.getTransaction().begin();
|
||||
|
||||
Query query = em1.createQuery(
|
||||
"select e from Employee e where e.id < 10 order by e.id").setFirstResult(1);
|
||||
query.setLockMode(LockModeType.PESSIMISTIC_READ);
|
||||
List<Employee> q = query.getResultList();
|
||||
assertEquals("Expected 1 element with emplyee id=2", q.size(), 1);
|
||||
assertEquals("Test Employee first name = 'first.2'", q.get(0).getFirstName(), "first.2");
|
||||
|
||||
em2.getTransaction().begin();
|
||||
|
||||
Map<String,Object> map = new HashMap<String,Object>();
|
||||
map.put("javax.persistence.lock.timeout", 2000);
|
||||
em2.find(Employee.class, 2, LockModeType.PESSIMISTIC_READ, map);
|
||||
fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
|
||||
} catch (QueryTimeoutException e) {
|
||||
// TODO: DB2: This is the current unexpected exception due to OPENJPA-991.
|
||||
// Remove this when the problem is fixed
|
||||
// System.out.println("Caught " + e.getClass().getName() + ":" + e.getMessage());
|
||||
} catch (PessimisticLockException e) {
|
||||
// TODO: This is the expected exception but will be fixed under OPENJPA-991
|
||||
// System.out.println("Caught " + e.getClass().getName() + ":" + e.getMessage());
|
||||
} catch (Exception ex) {
|
||||
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
|
||||
} finally {
|
||||
if( em1.getTransaction().isActive())
|
||||
em1.getTransaction().rollback();
|
||||
if( em2.getTransaction().isActive())
|
||||
em2.getTransaction().rollback();
|
||||
}
|
||||
|
||||
try {
|
||||
em1.getTransaction().begin();
|
||||
|
||||
Query query = em1.createQuery(
|
||||
"select e.department from Employee e where e.id < 10 order by e.department.id").setFirstResult(1);
|
||||
query.setLockMode(LockModeType.PESSIMISTIC_READ);
|
||||
List<Department> q = query.getResultList();
|
||||
assertEquals("Expected 1 element with department id=20", q.size(), 1);
|
||||
assertEquals("Test department name = 'D20'", q.get(0).getName(), "D20");
|
||||
|
||||
em2.getTransaction().begin();
|
||||
|
||||
Map<String,Object> map = new HashMap<String,Object>();
|
||||
map.put("javax.persistence.lock.timeout", 2000);
|
||||
Employee emp = em2.find(Employee.class, 1, LockModeType.PESSIMISTIC_READ, map);
|
||||
assertNotNull("Query locks department but find locks Employee.", emp);
|
||||
fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
|
||||
} catch (QueryTimeoutException e) {
|
||||
// TODO: This is the current unexpected exception due to OPENJPA-991. Remove this when the problem is fixed
|
||||
// System.out.println("Caught " + e.getClass().getName() + ":" + e.getMessage());
|
||||
} catch (PessimisticLockException e) {
|
||||
// TODO: This is the expected exception but will be fixed under OPENJPA-991
|
||||
// System.out.println("Caught " + e.getClass().getName() + ":" + e.getMessage());
|
||||
} catch (Exception ex) {
|
||||
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
|
||||
} finally {
|
||||
if( em1.getTransaction().isActive())
|
||||
em1.getTransaction().rollback();
|
||||
if( em2.getTransaction().isActive())
|
||||
em2.getTransaction().rollback();
|
||||
}
|
||||
em1.close();
|
||||
em2.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test a query with pessimistic lock after a find with pessimistic lock and expect PessimisticLockException.
|
||||
*/
|
||||
public void testQueryAfterFindWithPessimisticLocks() {
|
||||
EntityManager em1 = emf.createEntityManager();
|
||||
EntityManager em2 = emf.createEntityManager();
|
||||
try {
|
||||
em2.getTransaction().begin();
|
||||
|
||||
Map<String,Object> map = new HashMap<String,Object>();
|
||||
map.put("javax.persistence.lock.timeout", 2000);
|
||||
em2.find(Employee.class, 1, LockModeType.PESSIMISTIC_READ, map);
|
||||
|
||||
em1.getTransaction().begin();
|
||||
|
||||
Query query = em1.createQuery(
|
||||
"select e.department from Employee e where e.id < 10 order by e.department.id").setFirstResult(1);
|
||||
query.setLockMode(LockModeType.PESSIMISTIC_READ);
|
||||
List<Department> q = query.getResultList();
|
||||
|
||||
fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
|
||||
} catch (PessimisticLockException e) {
|
||||
// This is the expected exception.
|
||||
} catch (Exception ex) {
|
||||
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
|
||||
} finally {
|
||||
if( em1.getTransaction().isActive())
|
||||
em1.getTransaction().rollback();
|
||||
if (em2.getTransaction().isActive())
|
||||
em2.getTransaction().rollback();
|
||||
}
|
||||
em1.close();
|
||||
em2.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test a query with pessimistic lock with query timeout set after a find
|
||||
* with pessimistic lock and expect QueryTimeoutException.
|
||||
*/
|
||||
public void testQueryWithQueryTimeoutAfterFindWithPessimisticLocks() {
|
||||
EntityManager em1 = emf.createEntityManager();
|
||||
EntityManager em2 = emf.createEntityManager();
|
||||
try {
|
||||
em2.getTransaction().begin();
|
||||
|
||||
Map<String,Object> map = new HashMap<String,Object>();
|
||||
map.put("javax.persistence.lock.timeout", 2000);
|
||||
em2.find(Employee.class, 1, LockModeType.PESSIMISTIC_READ, map);
|
||||
|
||||
em1.getTransaction().begin();
|
||||
|
||||
Query query = em1.createQuery(
|
||||
"select e.department from Employee e where e.id < 10 order by e.department.id").setFirstResult(1);
|
||||
query.setLockMode(LockModeType.PESSIMISTIC_READ);
|
||||
query.setHint("javax.persistence.query.timeout", 2000);
|
||||
List<Department> q = query.getResultList();
|
||||
|
||||
fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
|
||||
} catch (QueryTimeoutException e) {
|
||||
// This is the expected exception.
|
||||
} catch (Exception ex) {
|
||||
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
|
||||
} finally {
|
||||
if( em1.getTransaction().isActive())
|
||||
em1.getTransaction().rollback();
|
||||
if( em2.getTransaction().isActive())
|
||||
em2.getTransaction().rollback();
|
||||
}
|
||||
em1.close();
|
||||
em2.close();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue