Skip to content

Commit

Permalink
[feature]Add Postgresql support
Browse files Browse the repository at this point in the history
  • Loading branch information
reza1057 authored and mosidev committed Oct 22, 2024
1 parent c941bab commit 017899d
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.internal.SessionFactoryImpl;

/**
Expand All @@ -20,6 +21,8 @@ public static DbmsLockDao getDbmsLockDao(EntityManager entityManager) {
return new OracleDbmsLockDaoImpl(entityManager);
} else if (dialect instanceof DB2Dialect) {
return new Db2DbmsLockDaoImpl(entityManager);
} else if (dialect instanceof PostgreSQLDialect) {
return new PostgresqlDbmsLockDaoImpl(entityManager);
}
throw new LockManagerRunTimeException("the database dialect is not supported: " + dialect.getClass());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.tosan.tools.lockmanager.impl.dbms.dao;

import com.tosan.tools.lockmanager.exception.LockManagerRunTimeException;
import com.tosan.tools.lockmanager.impl.dbms.dao.exception.DaoRuntimeException;
import com.tosan.tools.lockmanager.impl.dbms.dao.service.PostgresqlDbmsLockServiceUtil;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author mortezaei
* @since 11/19/2024
*/
public class PostgresqlDbmsLockDaoImpl implements DbmsLockDao {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlDbmsLockDaoImpl.class);
private String schemaName = null;
private final PostgresqlDbmsLockServiceUtil postgresqlDbmsLockServiceUtil;

public PostgresqlDbmsLockServiceUtil getPostgresqlDbmsLockServiceUtil() {
return postgresqlDbmsLockServiceUtil;
}

@PersistenceContext
private EntityManager entityManager;

public EntityManager getEntityManager() {
return entityManager;
}

public PostgresqlDbmsLockDaoImpl(EntityManager entityManager) {
this.entityManager = entityManager;
postgresqlDbmsLockServiceUtil = new PostgresqlDbmsLockServiceUtil();
}

public void setLockIdentifiersCache(boolean lockIdentifiersCache) {
postgresqlDbmsLockServiceUtil.setLockIdentifiersCache(lockIdentifiersCache);
}

public void setAllocatedLockTimeToLiveInSecond(int allocatedLockTimeToLiveInSecond) {
postgresqlDbmsLockServiceUtil.setAllocatedLockTimeToLiveInSecond(allocatedLockTimeToLiveInSecond);
}

@Override
public void requestReadLock(String lockNameType, String lockName, Integer timeout, boolean releaseOnCommit) {
postgresqlDbmsLockServiceUtil.requestLock(entityManager, postgresqlDbmsLockServiceUtil.getLockHandle(entityManager, getSchemaName(),
lockNameType, lockName),
PostgresqlDbmsLockServiceUtil.SHARED_MODE, timeout, releaseOnCommit);
}

@Override
public void requestWriteLock(String lockNameType, String lockName, Integer timeout, boolean releaseOnCommit) {
postgresqlDbmsLockServiceUtil.requestLock(entityManager, postgresqlDbmsLockServiceUtil.getLockHandle(entityManager, getSchemaName(),
lockNameType, lockName),
PostgresqlDbmsLockServiceUtil.EXCLUSIVE_MODE, timeout, releaseOnCommit);
}

@Override
public void convertToReadLock(String lockNameType, String lockName, Integer timeout) {
}

@Override
public void convertToWriteLock(String lockNameType, String lockName, Integer timeout) {
}

@Override
public void unLock(String lockNameType, String lockName) {
releaseLock(postgresqlDbmsLockServiceUtil.getLockHandle(entityManager, getSchemaName(), lockNameType, lockName));
}

@Override
public String getSchemaName() {
try {
if (schemaName == null) {
synchronized (PostgresqlDbmsLockDaoImpl.class) {
if (schemaName == null) {
Query query = getEntityManager().createNativeQuery(PostgresqlDbmsLockServiceUtil.GET_SCHEMA_NAME_QUERY);
schemaName = (String) query.getSingleResult();
}
}
}
return schemaName;
} catch (Throwable e) {
throw new DaoRuntimeException(e.getMessage());
}
}

private void releaseLock(final String lockHandle) {
LOGGER.debug("Requesting release lock with handle {}", lockHandle);
Query query = getEntityManager().createNativeQuery(PostgresqlDbmsLockServiceUtil.RELEASE_LOCK_QUERY);
query.setParameter("lockHandle", lockHandle);
int result = ((Number) query.getSingleResult()).intValue();

switch (result) {
case 0:
case 4:
LOGGER.debug("Released lock with handle {}", lockHandle);
return;
case 3:
throw new LockManagerRunTimeException("Parameter error occurred in 'DBMS_LOCK' release.");
case 5:
throw new LockManagerRunTimeException("Illegal lock handle error occurred in 'DBMS_LOCK' release.");
default:
throw new LockManagerRunTimeException("Error occurred in 'DBMS_LOCK' release.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.tosan.tools.lockmanager.impl.dbms.dao.service;

import com.tosan.tools.lockmanager.exception.LockManagerRunTimeException;
import com.tosan.tools.lockmanager.exception.LockManagerTimeoutException;
import com.tosan.tools.lockmanager.impl.dbms.dao.exception.DaoRuntimeException;
import com.tosan.tools.lockmanager.impl.dbms.dao.util.DbmsLockInfo;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.StoredProcedureQuery;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.procedure.ProcedureOutputs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.CallableStatement;
import java.sql.Types;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author mortezaei
* @since 11/19/2024
*
* This util uses pg_dbms_lock package to handle locks.
*/
public class PostgresqlDbmsLockServiceUtil {
public static final Integer SHARED_MODE = 4;
public static final Integer EXCLUSIVE_MODE = 6;
public static final String GET_SCHEMA_NAME_QUERY = "SELECT current_schema()";
public static final String RELEASE_LOCK_QUERY = "SELECT dbms_lock.release(:lockHandle)";
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlDbmsLockServiceUtil.class);
private static final int DEFAULT_READ_LOCK_TIMEOUT = 60;
private static final int DEFAULT_WRIT_LOCK_TIMEOUT = 7200;
private static final Map<String, DbmsLockInfo> dbmsLockHandle = new ConcurrentHashMap<>();
private boolean lockIdentifiersCache = false;
private int allocatedLockTimeToLiveInSecond = 864000;

public void setLockIdentifiersCache(boolean lockIdentifiersCache) {
this.lockIdentifiersCache = lockIdentifiersCache;
}

public void setAllocatedLockTimeToLiveInSecond(Integer allocatedLockTimeToLiveInSecond) {
this.allocatedLockTimeToLiveInSecond = allocatedLockTimeToLiveInSecond;
}

public void requestLock(final EntityManager entityManager, final String lockHandle, final Integer lockMode,
final Integer timeout, final boolean releaseOnCommit) {
LOGGER.debug("Requesting lock with handle {}", lockHandle);
final int[] callStatus = new int[1];
//As DBMS_LOCK.REQUEST has boolean input param, cant use entityManager
// .createNativeQuery because select does not support boolean value
Session session = entityManager.unwrap(Session.class);
try {
session.doWork(connection -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{ ? = call dbms_lock.request(?,?,?");
if (releaseOnCommit) {
stringBuilder.append(",true");
}
stringBuilder.append(") }");
try (CallableStatement call = connection.prepareCall(stringBuilder.toString())) {
call.registerOutParameter(1, Types.INTEGER);
call.setString(2, lockHandle);
call.setInt(3, lockMode);
call.setInt(4, timeout == null ? lockMode.equals(SHARED_MODE) ?
DEFAULT_READ_LOCK_TIMEOUT : DEFAULT_WRIT_LOCK_TIMEOUT : timeout);
call.execute();
callStatus[0] = call.getInt(1);
}
});
} catch (Throwable e) {
throw new DaoRuntimeException(e.getMessage());
}
switch (callStatus[0]) {
case 0:
LOGGER.debug("Acquired lock with handle {}.", lockHandle);
return;
case 4:
throw new LockManagerRunTimeException("convert not supported.");
case 1:
throw new LockManagerTimeoutException("Timeout error occurred in 'DBMS_LOCK' request.");
case 2:
throw new LockManagerRunTimeException("deadlock error occurred in 'DBMS_LOCK' request.");
case 3:
throw new LockManagerRunTimeException("Parameter error occurred in 'DBMS_LOCK' request.");
case 5:
throw new LockManagerRunTimeException("Illegal lock handle error occurred in 'DBMS_LOCK' request.");
default:
throw new LockManagerRunTimeException("Error occurred in 'DBMS_LOCK' request.");
}
}

public String getLockHandle(EntityManager entityManager, String schemaName, String lockNameType, String lockName) {
LOGGER.debug("Requesting lock handle for lock with name '{}'.", lockName);
String uniqueLockName =
schemaName + "-" + lockNameType + (StringUtils.isNotEmpty(lockName) ? "-" + lockName : "");
if (!lockIdentifiersCache) {
return getLockHandle(entityManager, uniqueLockName, allocatedLockTimeToLiveInSecond);
}
DbmsLockInfo dbmsLockDto = dbmsLockHandle.get(uniqueLockName);
if (dbmsLockDto == null || dbmsLockDto.getLockExpireTime().compareTo(new Date()) <= 0) {
short DBMS_LOCK_NAME_MAX_LENGTH = 128;
if (uniqueLockName.length() > DBMS_LOCK_NAME_MAX_LENGTH) {
LOGGER.error("Dbms lock name '{}' cannot be more than {} characters.",
uniqueLockName, DBMS_LOCK_NAME_MAX_LENGTH);
throw new LockManagerRunTimeException("Dbms lock name cannot be more than " +
DBMS_LOCK_NAME_MAX_LENGTH + " characters.");
}
Calendar expireDate = Calendar.getInstance();
expireDate.set(Calendar.SECOND, expireDate.get(Calendar.SECOND) + allocatedLockTimeToLiveInSecond);
dbmsLockDto = new DbmsLockInfo(
uniqueLockName, getLockHandle(entityManager, uniqueLockName, allocatedLockTimeToLiveInSecond),
expireDate.getTime());
dbmsLockHandle.put(uniqueLockName, dbmsLockDto);
}
return dbmsLockDto.getLockHandel();
}

/**
* 'getLockHandle' using ALLOCATE_UNIQUE procedure in DBMS_LOCK package that allocates a unique lock identifier
* (in the range of 1073741824 to 1999999999) given a lock name.
* Lock identifiers are used to enable applications to coordinate their use of locks.
* A lock name is associated with the returned lock ID for at least expiration_secs (defaults to 10 days) past the
* last call to 'getLockHandle' with the given lock name.
* After this time, the row in the dbms_lock_allocated table for this lock name may be deleted in order to recover space.
*
* @param entityManager entityManager
* @param lockName Name of the lock for which you want to generate a unique ID.
* @param expirationSecond Length of time to leave lock allocated
* @return The handle to the lock ID generated by ALLOCATE_UNIQUE.
*/
public String getLockHandle(final EntityManager entityManager, final String lockName, final int expirationSecond) {
StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("dbms_lock.allocate_unique")
.registerStoredProcedureParameter(1, String.class, ParameterMode.IN)
.registerStoredProcedureParameter(2, String.class, ParameterMode.OUT)
.registerStoredProcedureParameter(3, Integer.class, ParameterMode.IN)
.setParameter(1, lockName)
.setParameter(3, expirationSecond);
try {
query.execute();
String lockHandle = (String) query.getOutputParameterValue(2);
LOGGER.debug("Acquired lock handle {} for lock with name '{}'.", lockHandle, lockName);
return lockHandle;
} finally {
query.unwrap(ProcedureOutputs.class)
.release();
}
}
}

0 comments on commit 017899d

Please sign in to comment.