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

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

Go to download

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

There is a newer version: 1.6.9
Show 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 java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import liquibase.database.Database;
import liquibase.database.core.PostgresDatabase;
import liquibase.exception.DatabaseException;
import liquibase.exception.LockException;
import liquibase.lockservice.DatabaseChangeLogLock;

/**
 * Employs PostgreSQL advisory locks.
 *
 * 
* *

While a flag stored in a table could be used for the same purpose, advisory locks are faster, * avoid table bloat, and are automatically cleaned up by the server at the end of the session. * *

There are two ways to acquire an advisory lock in PostgreSQL: at session level or at * transaction level. Once acquired at session level, an advisory lock is held until explicitly * released or the session ends. Unlike standard lock requests, session-level advisory lock requests * do not honor transaction semantics: a lock acquired during a transaction that is later rolled * back will still be held following the rollback, and likewise an unlock is effective even if the * calling transaction fails later. * *

* * @see "Advisory * Locks (PostgreSQL Documentation)" */ public class PGLockService extends SessionLockService { static final String SQL_TRY_LOCK = "SELECT pg_try_advisory_lock(?,?)"; static final String SQL_UNLOCK = "SELECT pg_advisory_unlock(?,?)"; static final String SQL_LOCK_INFO = "SELECT l.pid," + " a.client_hostname," + " a.backend_start," + " a.state" + " FROM pg_locks l" + " LEFT JOIN pg_stat_activity a" + " ON a.pid = l.pid" + " WHERE l.locktype = 'advisory'" + " AND l.classid = ?" + " AND l.objid = ?" + " AND l.objsubid = 2" + " AND l.granted"; @Override public boolean supports(Database database) { return (database instanceof PostgresDatabase) && isAtLeastPostgres91(database); } private static boolean isAtLeastPostgres91(Database database) { try { return (database.getDatabaseMajorVersion() > 9) || (database.getDatabaseMajorVersion() == 9 && database.getDatabaseMinorVersion() >= 1); } catch (DatabaseException e) { getLog(PGLockService.class).warning("Problem querying database version", e); return false; } } private int[] getChangeLogLockId() throws LockException { String defaultSchemaName = database.getDefaultSchemaName(); if (defaultSchemaName == null) { throw new LockException("Default schema name is not set for current DB user/connection"); } // Unlike the general Object.hashCode() contract, // String.hashCode() should be stable across VM instances and Java versions. return new int[] { database.getDatabaseChangeLogLockTableName().hashCode(), defaultSchemaName.hashCode() }; } private static Boolean getBooleanResult(PreparedStatement stmt) throws SQLException { try (ResultSet rs = stmt.executeQuery()) { rs.next(); return (Boolean) rs.getObject(1); } } /** * @see " * pg_try_advisory_lock (Advisory Lock Functions)" */ @Override protected boolean acquireLock(Connection con) throws SQLException, LockException { try (PreparedStatement stmt = con.prepareStatement(SQL_TRY_LOCK)) { int[] lockId = getChangeLogLockId(); stmt.setInt(1, lockId[0]); stmt.setInt(2, lockId[1]); return Boolean.TRUE.equals(getBooleanResult(stmt)); } } /** * @see " * pg_advisory_unlock (Advisory Lock Functions)" */ @Override protected void releaseLock(Connection con) throws SQLException, LockException { try (PreparedStatement stmt = con.prepareStatement(SQL_UNLOCK)) { int[] lockId = getChangeLogLockId(); stmt.setInt(1, lockId[0]); stmt.setInt(2, lockId[1]); Boolean unlocked = getBooleanResult(stmt); if (!Boolean.TRUE.equals(unlocked)) { throw new LockException("pg_advisory_unlock() returned " + unlocked); } } } /** * Obtains information about the database changelog lock. * *
* * Like all locks in PostgreSQL, a complete list of advisory locks currently held by any session * can be found in the pg_locks system view. * *
* * @see "pg_locks * (PostgreSQL Documentation)" * @see " * pg_stat_activity View (PostgreSQL Documentation)" */ @Override protected DatabaseChangeLogLock usedLock(Connection con) throws SQLException, LockException { try (PreparedStatement stmt = con.prepareStatement(SQL_LOCK_INFO)) { int[] lockId = getChangeLogLockId(); stmt.setInt(1, lockId[0]); stmt.setInt(2, lockId[1]); try (ResultSet rs = stmt.executeQuery()) { if (!rs.next()) { return null; } // This is not really the time the lock has been obtained... Timestamp lockGranted = rs.getTimestamp("backend_start"); return new DatabaseChangeLogLock(1, lockGranted, lockedBy(rs)); } } } private static String lockedBy(ResultSet rs) throws SQLException { String host = rs.getString("client_hostname"); if (host == null) { return "pid#" + rs.getInt("pid"); } return host + " (" + rs.getString("state") + ")"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy