From e424097b564273b807f0637056ea244c40a125c3 Mon Sep 17 00:00:00 2001 From: "Richard G. Curtis" Date: Thu, 27 Sep 2012 19:36:44 +0000 Subject: [PATCH] OPENJPA-2269: Fix duplicate key exception when inserting into sequence table on multithreaded init. git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1391185 13f79535-47bb-0310-9956-ffa450edef68 --- .../openjpa/jdbc/kernel/TableJDBCSeq.java | 21 +++- .../persistence/generationtype/Dog.java | 41 +++++++ ...eGeneratorMultithreadedInitialization.java | 116 ++++++++++++++++++ 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreadedInitialization.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java index de4c27876..18fc7c74d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java @@ -474,6 +474,7 @@ public class TableJDBCSeq extends AbstractJDBCSeq implements Configurable { */ private void insertSequence(ClassMapping mapping, Connection conn) throws SQLException { + if (_log.isTraceEnabled()) _log.trace(_loc.get("insert-seq")); @@ -895,12 +896,20 @@ public class TableJDBCSeq extends AbstractJDBCSeq implements Configurable { closeConnection(conn); if (!sequenceSet) { - // insert a new sequence column. - // Prefer connection2 / non-jta-data-source when inserting - // a sequence column regardless of Seq.type. - conn = _conf.getDataSource2(store.getContext()) - .getConnection(); - insertSequence(mapping, conn); + // insert a new sequence column. Prefer connection2 / non-jta-data-source when inserting a + // sequence column regardless of Seq.type. + conn = _conf.getDataSource2(store.getContext()).getConnection(); + try { + insertSequence(mapping, conn); + } catch (SQLException e) { + // it is possible another thread already got in and inserted this sequence. Try to keep going + if (_log.isTraceEnabled()) { + _log.trace( + "Caught an exception while trying to insert sequence. Will try to reselect the " + + "seqence. ", e); + } + } + conn.close(); // now we should be able to update using the connection per diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/Dog.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/Dog.java index ab8db2466..7ca64b774 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/Dog.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/Dog.java @@ -50,4 +50,45 @@ public class Dog { public void setName(String name) { this.name = name; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Dog other = (Dog) obj; + if (id != other.id) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Dog [id=" + id + ", name=" + name + "]"; + } + + } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreadedInitialization.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreadedInitialization.java new file mode 100644 index 000000000..8e3f0b439 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreadedInitialization.java @@ -0,0 +1,116 @@ +/* + * 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.generationtype; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase; + +public class TestTableGeneratorMultithreadedInitialization extends AbstractPersistenceTestCase { + Object[] props = new Object[] { Dog.class + // , "openjpa.Log", "SQL=trace" + }; + + public void setUp() throws Exception { + EntityManagerFactory emf = createNamedEMF(getPersistenceUnitName(), props); + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + em.createNativeQuery("DROP TABLE ID_Gen").executeUpdate(); + em.createNativeQuery("DROP TABLE dog").executeUpdate(); + em.getTransaction().commit(); + emf.close(); + } + + public void test() throws Exception { + + EntityManagerFactory emf1 = createNamedEMF(getPersistenceUnitName(), props); + EntityManagerFactory emf2 = createNamedEMF(getPersistenceUnitName(), props); + EntityManagerFactory emf3 = createNamedEMF(getPersistenceUnitName(), props); + + assertNotEquals(emf1, emf2); + + emf1.createEntityManager().close(); + emf2.createEntityManager().close(); + + final EntityManager em1 = emf1.createEntityManager(); + final EntityManager em2 = emf2.createEntityManager(); + final EntityManager em3 = emf3.createEntityManager(); + + Worker w1 = new Worker(em1); + Worker w2 = new Worker(em2); + + w1.start(); + w2.start(); + + w1.join(); + w2.join(); + + assertNull("Caught an exception in worker 1" + w1.getException(), w1.getException()); + assertNull("Caught an exception in worker 2" + w2.getException(), w2.getException()); + + Dog d1 = w1.getDog(); + Dog d2 = w2.getDog(); + assertNotNull(d1); + assertNotNull(d2); + assertNotEquals(d1, d2); + + Dog d1_found = em3.find(Dog.class, d1.getId()); + Dog d2_found = em3.find(Dog.class, d2.getId()); + + assertEquals(d1_found, d1); + assertEquals(d2_found, d2); + + emf1.close(); + emf2.close(); + emf3.close(); + } + + class Worker extends Thread { + final EntityManager em; + Dog dog = new Dog(); + Exception exception; + + Worker(EntityManager e) { + em = e; + } + + public Dog getDog() { + return dog; + } + + public Exception getException() { + return exception; + } + + @Override + public void run() { + try { + em.getTransaction().begin(); + em.persist(dog); + em.getTransaction().commit(); + em.close(); + } catch (Exception e) { + exception = e; + e.printStackTrace(); + // TODO: handle exception + } + } + } +}