diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
index cdc503dd145d..e4bc9bdfc83c 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java
@@ -23,21 +23,25 @@
*/
package org.hibernate.dialect;
-import java.sql.SQLException;
-import java.sql.Types;
-
-import org.jboss.logging.Logger;
-
+import org.hibernate.JDBCException;
+import org.hibernate.PessimisticLockException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.function.AvgWithArgumentCastFunction;
import org.hibernate.dialect.function.NoArgSQLFunction;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
+import org.hibernate.exception.LockAcquisitionException;
+import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.internal.CoreMessageLogger;
+import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.StandardBasicTypes;
+import org.jboss.logging.Logger;
+
+import java.sql.SQLException;
+import java.sql.Types;
/**
* A dialect compatible with the H2 database.
@@ -292,6 +296,33 @@ public String extractConstraintName(SQLException sqle) {
return constraintName;
}
};
+
+ @Override
+ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
+ SQLExceptionConversionDelegate delegate = super.buildSQLExceptionConversionDelegate();
+ if (delegate == null) {
+ delegate = new SQLExceptionConversionDelegate() {
+
+ @Override
+ public JDBCException convert(SQLException sqlException, String message, String sql) {
+ JDBCException exception = null;
+
+ int errorCode = JdbcExceptionHelper.extractErrorCode(sqlException);
+
+ if (40001 == errorCode) { // DEADLOCK DETECTED
+ exception = new LockAcquisitionException(message, sqlException, sql);
+ }
+
+ if (50200 == errorCode) { // LOCK NOT AVAILABLE
+ exception = new PessimisticLockException(message, sqlException, sql);
+ }
+
+ return exception;
+ }
+ };
+ }
+ return delegate;
+ }
@Override
public boolean supportsTemporaryTables() {
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java
index 0d1102a9c852..38a15c7ebacd 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java
@@ -28,13 +28,17 @@
import java.sql.SQLException;
import java.sql.Types;
+import org.hibernate.JDBCException;
import org.hibernate.LockOptions;
+import org.hibernate.PessimisticLockException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.NoArgSQLFunction;
import org.hibernate.dialect.function.PositionSubstringFunction;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
+import org.hibernate.exception.LockAcquisitionException;
+import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.id.SequenceGenerator;
@@ -367,6 +371,34 @@ public String extractConstraintName(SQLException sqle) {
}
};
+ @Override
+ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
+ SQLExceptionConversionDelegate delegate = super.buildSQLExceptionConversionDelegate();
+ if (delegate == null) {
+ delegate = new SQLExceptionConversionDelegate() {
+ @Override
+ public JDBCException convert(SQLException sqlException, String message, String sql) {
+ JDBCException exception = null;
+
+ if (exception == null) {
+ String sqlState = JdbcExceptionHelper.extractSqlState(sqlException);
+
+ if ("40P01".equals(sqlState)) { // DEADLOCK DETECTED
+ exception = new LockAcquisitionException(message, sqlException, sql);
+ }
+
+ if ("55P03".equals(sqlState)) { // LOCK NOT AVAILABLE
+ exception = new PessimisticLockException(message, sqlException, sql);
+ }
+ }
+
+ return exception;
+ }
+ };
+ }
+ return delegate;
+ }
+
public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException {
// Register the type of the out param - PostgreSQL uses Types.OTHER
statement.registerOutParameter(col++, Types.OTHER);
diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java
new file mode 100644
index 000000000000..a2640c07b0af
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java
@@ -0,0 +1,68 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by
+ * third-party contributors as indicated by either @author tags or express
+ * copyright attribution statements applied by the authors. All
+ * third-party contributions are distributed under license by Red Hat Inc.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301 USA
+ */
+package org.hibernate.dialect;
+
+import org.junit.Test;
+
+import java.sql.SQLException;
+import org.hibernate.JDBCException;
+import org.hibernate.PessimisticLockException;
+import org.hibernate.exception.LockAcquisitionException;
+import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
+
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseUnitTestCase;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
+
+/**
+ * Testing of patched support for PostgreSQL Lock error detection. HHH-7251
+ *
+ * @author Bryan Varner
+ */
+@TestForIssue( jiraKey = "HHH-7251" )
+public class PostgreSQL81DialectTestCase extends BaseUnitTestCase {
+
+ @Test
+ public void testDeadlockException() {
+ PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
+ SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate();
+ assertNotNull(delegate);
+
+ JDBCException exception = delegate.convert(new SQLException("Deadlock Detected", "40P01"), "", "");
+ assertTrue(exception instanceof LockAcquisitionException);
+ }
+
+ @Test
+ public void testTimeoutException() {
+ PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
+ SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate();
+ assertNotNull(delegate);
+
+ JDBCException exception = delegate.convert(new SQLException("Lock Not Available", "55P03"), "", "");
+ assertTrue(exception instanceof PessimisticLockException);
+ }
+}
diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java
index 8c7e84f965a1..e436096bd9ca 100755
--- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java
+++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java
@@ -792,9 +792,10 @@ public A find(Class entityClass, Object primaryKey, LockModeType lockMode
try {
getSession().setCacheMode( cacheMode );
if ( lockModeType != null ) {
+ lockOptions = getLockRequest( lockModeType, properties );
return ( A ) getSession().get(
- entityClass, ( Serializable ) primaryKey,
- getLockRequest( lockModeType, properties )
+ entityClass, ( Serializable ) primaryKey,
+ lockOptions
);
}
else {
@@ -919,7 +920,8 @@ public void refresh(Object entity, LockModeType lockModeType, Map p
if ( !contains( entity ) ) {
throw new IllegalArgumentException( "entity not in the persistence context" );
}
- getSession().buildLockRequest( ( lockOptions = getLockRequest( lockModeType, properties ) ) )
- .lock( entity );
+ lockOptions = getLockRequest( lockModeType, properties );
+ getSession().buildLockRequest( lockOptions ).lock( entity );
}
catch ( HibernateException he ) {
throw convert( he, lockOptions );
diff --git a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/lock/LockTest.java b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/lock/LockTest.java
index eaff6c99c762..147483c28db5 100644
--- a/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/lock/LockTest.java
+++ b/hibernate-entitymanager/src/matrix/java/org/hibernate/ejb/test/lock/LockTest.java
@@ -23,6 +23,17 @@
*/
package org.hibernate.ejb.test.lock;
+import org.hibernate.dialect.HSQLDialect;
+import org.hibernate.dialect.Oracle10gDialect;
+import org.hibernate.dialect.PostgreSQL81Dialect;
+import org.hibernate.dialect.SybaseASE15Dialect;
+import org.hibernate.ejb.AvailableSettings;
+import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
+import org.hibernate.testing.*;
+import org.jboss.logging.Logger;
+import org.junit.Test;
+
+import javax.persistence.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,27 +41,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
-import javax.persistence.EntityManager;
-import javax.persistence.LockModeType;
-import javax.persistence.LockTimeoutException;
-import javax.persistence.OptimisticLockException;
-import javax.persistence.Query;
-import javax.persistence.QueryTimeoutException;
-
-import org.jboss.logging.Logger;
-import org.junit.Test;
-import org.hibernate.dialect.HSQLDialect;
-import org.hibernate.dialect.Oracle10gDialect;
-import org.hibernate.dialect.SybaseASE15Dialect;
-import org.hibernate.ejb.AvailableSettings;
-import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
-import org.hibernate.testing.SkipForDialect;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
/**
* @author Emmanuel Bernard
@@ -83,7 +75,58 @@ public void testFindWithTimeoutHint() {
em.getTransaction().commit();
em.close();
}
+
+ @Test
+ @TestForIssue( jiraKey = "HHH-7252" )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class,
+ comment = "Test verifies proper exception throwing when a lock timeout is specified.",
+ jiraKey = "HHH-7252" )
+ public void testFindWithPessimisticWriteLockTimeoutException() {
+ Lock lock = new Lock();
+ lock.setName( "name" );
+ EntityManager em = getOrCreateEntityManager();
+ em.getTransaction().begin();
+ em.persist( lock );
+ em.getTransaction().commit();
+ em.close();
+ EntityManager em2 = createIsolatedEntityManager();
+ em2.getTransaction().begin();
+ Map properties = new HashMap();
+ properties.put( AvailableSettings.LOCK_TIMEOUT, 0L );
+ Lock lock2 = em2.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties );
+ assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, em2.getLockMode( lock2 ) );
+
+ EntityManager em3 = createIsolatedEntityManager();
+ em3.getTransaction().begin();
+ try {
+ em3.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE, properties );
+ assertFalse("Exception should be thrown", true);
+ } catch (LockTimeoutException lte) {
+ assertTrue("Proper exception thrown for dialect supporting lock timeouts when an immediate timeout is set.", true);
+ } catch (PessimisticLockException pe) {
+ assertTrue("Find with immediate timeout should have thrown LockTimeoutException.", false);
+ } catch (PersistenceException pe) {
+ log.info("EntityManager.find() for PESSIMISTIC_WRITE with timeout of 0 threw a PersistenceException.\n" +
+ "This is likely a consequence of " + getDialect().getClass().getName() + " not properly mapping SQL errors into the correct HibernateException subtypes.\n" +
+ "See HHH-7251 for an example of one such situation.", pe);
+ assertTrue("EntityManager should be throwing LockTimeoutException.", false);
+ } finally {
+ if (em3.getTransaction().getRollbackOnly()) {
+ em3.getTransaction().rollback();
+ } else {
+ em3.getTransaction().commit();
+ }
+ em3.close();
+ }
+
+ em2.getTransaction().commit();
+ em2.getTransaction().begin();
+ em2.remove( lock2 );
+ em2.getTransaction().commit();
+ em2.close();
+ }
+
@Test
public void testLockRead() throws Exception {
Lock lock = new Lock();
@@ -394,14 +437,11 @@ private Lock createAndPersistLockInstance(EntityManager em) {
}
@Test
+ @RequiresDialect( value = Oracle10gDialect.class )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testContendedPessimisticReadLockTimeout() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
- // TODO: replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testContendedPessimisticReadLockTimeout" );
- return;
- }
Lock lock = new Lock();
Thread t = null;
FutureTask bgTask = null;
@@ -490,15 +530,12 @@ public Boolean call() {
}
@Test
+ @RequiresDialect( value = Oracle10gDialect.class )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testContendedPessimisticWriteLockTimeout() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
- // TODO: replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testContendedPessimisticWriteLockTimeout" );
- return;
- }
Lock lock = new Lock();
Thread t = null;
FutureTask bgTask;
@@ -582,15 +619,12 @@ public Boolean call() {
}
@Test
+ @RequiresDialect( value = { Oracle10gDialect.class, PostgreSQL81Dialect.class })
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testContendedPessimisticWriteLockNoWait() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
- // TODO: replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testContendedPessimisticWriteLockNoWait" );
- return;
- }
Lock lock = new Lock();
Thread t = null;
FutureTask bgTask;
@@ -674,15 +708,12 @@ public Boolean call() {
}
@Test
+ @RequiresDialect( value = Oracle10gDialect.class )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testQueryTimeout() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
- // TODO: replace dialect instanceof test with a Dialect.hasCapability
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testQueryTimeout" );
- return;
- }
Lock lock = new Lock();
Thread t = null;
FutureTask bgTask;
@@ -770,12 +801,9 @@ public Boolean call() {
}
@Test
+ @RequiresDialect( value = Oracle10gDialect.class )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testQueryTimeoutEMProps() throws Exception {
- // TODO: replace dialect instanceof test with a Dialect.hasCapability
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testQueryTimeout" );
- return;
- }
EntityManager em = getOrCreateEntityManager();
Map queryTimeoutProps = new HashMap();
queryTimeoutProps.put( "javax.persistence.query.timeout", 500 ); // 1 sec timeout (should round up)
@@ -866,17 +894,14 @@ public Boolean call() {
}
@Test
+ @RequiresDialect( value = Oracle10gDialect.class )
+ @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class )
public void testLockTimeoutEMProps() throws Exception {
EntityManager em = getOrCreateEntityManager();
Map TimeoutProps = new HashMap();
TimeoutProps.put( "javax.persistence.lock.timeout", 1000 ); // 1 second timeout
final EntityManager em2 = createIsolatedEntityManager( TimeoutProps );
- // TODO: replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
- if ( !( getDialect() instanceof Oracle10gDialect ) ) {
- log.info( "skipping testLockTimeoutEMProps" );
- return;
- }
Lock lock = new Lock();
Thread t = null;
FutureTask bgTask;
diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
index 46ab68c59a47..a2e56fdda219 100644
--- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
+++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
@@ -163,4 +163,10 @@ public boolean isMatch(Dialect dialect) {
return dialect.supportsLobValueChangePropogation();
}
}
+
+ public static class SupportsLockTimeouts implements DialectCheck {
+ public boolean isMatch(Dialect dialect) {
+ return dialect.supportsLockTimeouts();
+ }
+ }
}