blob: e08fbdea961159cc2ac9b6fbf09f82f00c762f87 [file] [log] [blame]
/*
* 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.jdbc.kernel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.kernel.VersionLockManager;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.LockException;
/**
* Lock manager that uses exclusive database locks.
*
* @author Marc Prud'hommeaux
*/
public class PessimisticLockManager
extends VersionLockManager
implements JDBCLockManager {
public static final int LOCK_DATASTORE_ONLY = 1;
private static final Localizer _loc = Localizer.forPackage
(PessimisticLockManager.class);
private JDBCStore _store;
public PessimisticLockManager() {
setVersionCheckOnReadLock(false);
setVersionUpdateOnWriteLock(false);
}
public void setContext(StoreContext ctx) {
super.setContext(ctx);
_store = (JDBCStore) ctx.getStoreManager().getInnermostDelegate();
}
public boolean selectForUpdate(Select sel, int lockLevel) {
if (lockLevel == LOCK_NONE)
return false;
DBDictionary dict = _store.getDBDictionary();
if (dict.simulateLocking)
return false;
dict.assertSupport(dict.supportsSelectForUpdate,
"SupportsSelectForUpdate");
if (!sel.supportsLocking()) {
if (log.isInfoEnabled())
log.info(_loc.get("cant-lock-on-load",
sel.toSelect(false, null).getSQL()));
return false;
}
ensureStoreManagerTransaction();
return true;
}
public void loadedForUpdate(OpenJPAStateManager sm) {
// we set a low lock level to indicate that we don't need datastore
// locking, but we don't necessarily have a read or write lock
// according to our superclass
if (getLockLevel(sm) == LOCK_NONE)
setLockLevel(sm, LOCK_DATASTORE_ONLY);
}
protected void lockInternal(OpenJPAStateManager sm, int level, int timeout,
Object sdata) {
// we can skip any already-locked instance regardless of level because
// we treat all locks the same (though super doesn't)
if (getLockLevel(sm) == LOCK_NONE) {
// only need to lock if not loaded from locking result
ConnectionInfo info = (ConnectionInfo) sdata;
if (info == null || info.result == null || !info.result.isLocking())
lockRow(sm, timeout);
}
super.lockInternal(sm, level, timeout, sdata);
}
/**
* Lock the specified instance row by issuing a "SELECT ... FOR UPDATE"
* statement.
*/
private void lockRow(OpenJPAStateManager sm, int timeout) {
// assert that the dictionary supports the "SELECT ... FOR UPDATE"
// construct; if not, and we the assertion does not throw an
// exception, then just return without locking
DBDictionary dict = _store.getDBDictionary();
if (dict.simulateLocking)
return;
dict.assertSupport(dict.supportsSelectForUpdate,
"SupportsSelectForUpdate");
Object id = sm.getObjectId();
ClassMapping mapping = (ClassMapping) sm.getMetaData();
while (mapping.getJoinablePCSuperclassMapping() != null)
mapping = mapping.getJoinablePCSuperclassMapping();
// select only the PK columns, since we just want to lock
Select select = _store.getSQLFactory().newSelect();
select.select(mapping.getPrimaryKeyColumns());
select.wherePrimaryKey(id, mapping, _store);
SQLBuffer sql = select.toSelect(true, _store.getFetchConfiguration());
ensureStoreManagerTransaction();
Connection conn = _store.getConnection();
PreparedStatement stmnt = null;
ResultSet rs = null;
try {
stmnt = prepareStatement(conn, sql);
setTimeout(stmnt, timeout);
rs = executeQuery(conn, stmnt, sql);
checkLock(rs, sm);
} catch (SQLException se) {
throw SQLExceptions.getStore(se, dict);
} finally {
if (stmnt != null)
try { stmnt.close(); } catch (SQLException se) {}
if (rs != null)
try { rs.close(); } catch (SQLException se) {}
try { conn.close(); } catch (SQLException se) {}
}
}
/**
* Enforce that we have an actual transaction in progress so that we can
* start locking. The transaction should already be begun when using a
* datastore transaction; this will just be used if we are locking in
* optimistic mode.
*/
private void ensureStoreManagerTransaction() {
if (!_store.getContext().isStoreActive()) {
_store.getContext().beginStore();
if (log.isInfoEnabled())
log.info(_loc.get("start-trans-for-lock"));
}
}
public JDBCStore getStore() {
return _store;
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of preparing statement.
*/
protected PreparedStatement prepareStatement(Connection conn, SQLBuffer sql)
throws SQLException {
return sql.prepareStatement(conn);
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of setting query timeout.
*/
protected void setTimeout(PreparedStatement stmnt, int timeout)
throws SQLException {
DBDictionary dict = _store.getDBDictionary();
if (timeout >= 0 && dict.supportsQueryTimeout) {
if (timeout < 1000) {
timeout = 1000;
if (log.isWarnEnabled())
log.warn(_loc.get("millis-query-timeout"));
}
stmnt.setQueryTimeout(timeout / 1000);
}
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of executing query.
*/
protected ResultSet executeQuery(Connection conn, PreparedStatement stmnt,
SQLBuffer sql) throws SQLException {
return stmnt.executeQuery();
}
/**
* This method is to provide override for non-JDBC or JDBC-like
* implementation of checking lock from the result set.
*/
protected void checkLock(ResultSet rs, OpenJPAStateManager sm)
throws SQLException {
if (!rs.next())
throw new LockException(sm.getManagedInstance());
return;
}
}