All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.blagerweij.sessionlock.MSSQLLockService Maven / Gradle / Ivy

Go to download

Replaces the default DATABASECHANGELOGLOCK with a RDBMS lock, which is automatically released when the container is stopped unexpectedly

The newest version!
/*
 * This module, both source code and documentation,
 * is in the Public Domain, and comes with NO WARRANTY.
 */
package com.github.blagerweij.sessionlock;

import liquibase.database.Database;
import liquibase.database.core.MSSQLDatabase;
import liquibase.exception.LockException;
import liquibase.lockservice.DatabaseChangeLogLock;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.Locale;

import static com.github.blagerweij.sessionlock.util.StringUtils.toUpperCase;

/**
 * Employs MSSQL application resource locks.
 *
 * 
* *

A lock obtained with sp_getapplock() is released explicitly by executing * sp_releaseapplock() or implicitly when your session terminates (either normally or abnormally). * Locks obtained with sp_getapplock(@LockOwner = 'Session') are not released when transactions * commit or roll back. * *

* * @see Application resource locking function (Microsoft SQL documentation) * @see Release applicaiton resource lock function (Microsoft SQL documentation) */ public class MSSQLLockService extends SessionLockService { static final String SQL_GET_LOCK = "DECLARE @lockResult int;" + " EXEC @lockResult = sp_getapplock" + " @Resource = ?," + " @LockMode = 'Exclusive'," + " @LockOwner = 'Session'," + " @LockTimeout = ?;" + " SELECT @lockResult;"; static final String SQL_RELEASE_LOCK = "DECLARE @releaseLockResult int;" + "EXEC @releaseLockResult = sp_releaseapplock" + " @Resource = ?," + " @LockOwner = 'Session';" + "select @releaseLockResult;"; static final String SQL_LOCK_INFO = "SELECT SP.hostname, SP.login_time, SP.status" + " FROM sys.dm_tran_locks DTL INNER JOIN sys.sysprocesses SP" + " ON DTL.request_session_id = SP.spid" + " WHERE DTL.resource_description like ?"; @Override public boolean supports(Database database) { return (database instanceof MSSQLDatabase); } private String getChangeLogLockName() { return toUpperCase(database.getDefaultSchemaName() + "." + database.getDatabaseChangeLogLockTableName()); } private static Integer getIntegerResult(PreparedStatement stmt) throws SQLException { try (ResultSet rs = stmt.executeQuery()) { rs.next(); Number locked = (Number) rs.getObject(1); return (locked == null) ? null : locked.intValue(); } } /** * Return code values for sp_getapplock() * 0 - The lock was successfully granted synchronously. * 1 - The lock was granted successfully after waiting for other incompatible locks to be released. * -1 - The lock request timed out. * -2 - The lock request was canceled. * -3 - The lock request was chosen as a deadlock victim. * -999 - Indicates a parameter validation or other call error. * In current implementation exception will be thrown only with null and -999 code values. * @see Application resource locking function (Microsoft SQL documentation) */ @Override protected boolean acquireLock(final Connection con) throws SQLException, LockException { try (final PreparedStatement stmt = con.prepareStatement(SQL_GET_LOCK)) { stmt.setString(1, getChangeLogLockName()); final int timeoutMillis = 5000; stmt.setInt(2, timeoutMillis); Integer locked = getIntegerResult(stmt); if (locked == null) { throw new LockException("GET_LOCK() returned NULL"); } else if (locked == -999) { throw new LockException("GET_LOCK() returned " + locked + ". Indicates a parameter validation or other call error."); } else if (locked == -1 || locked == -2 || locked == -3) { return false; } return true; } } /** * Return code values for sp_getapplock() * 0 - Lock was successfully released. * -999 - Indicates parameter validation or other call error. * @see Release applicaiton resource lock function (Microsoft SQL documentation) */ @Override protected void releaseLock(final Connection con) throws SQLException, LockException { try (final PreparedStatement stmt = con.prepareStatement(SQL_RELEASE_LOCK)) { stmt.setString(1, getChangeLogLockName()); Integer unlocked = getIntegerResult(stmt); if (!Integer.valueOf(0).equals(unlocked)) { throw new LockException( "RELEASE_LOCK() returned " + toUpperCase(String.valueOf(unlocked))); } } } /** * Obtains information about the database changelog lock. * * @see The sys.dm_tran_locks table (Microsoft SQL documentation) * @see The sys.sysprocesses table (Microsoft SQL documentation) */ @Override protected DatabaseChangeLogLock usedLock(final Connection con) throws SQLException, LockException { try (final PreparedStatement stmt = con.prepareStatement(SQL_LOCK_INFO)) { stmt.setString(1, "%" + getChangeLogLockName() + "%"); try (final ResultSet rs = stmt.executeQuery()) { if (!rs.next()) { return null; } // This is not really the time the lock has been obtained but gives // insight on session login time. final Date sessionLoginTime = rs.getDate("login_time"); return new DatabaseChangeLogLock(1, sessionLoginTime, lockedBy(rs)); } } } private static String lockedBy(ResultSet rs) throws SQLException { final String host = rs.getString("hostname"); if (host == null) { return "system_process_id#" + rs.getString("spid"); } return host + " (" + rs.getString("status") + ")"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy