code.onehippo.org is currently readonly. We are migrating to code.bloomreach.com, please continue working there on Monday 14/12. See: https://docs.bloomreach.com/display/engineering/GitLab

Commit bb1f233b authored by Ard Schrijvers's avatar Ard Schrijvers

REPO-1811 Add a cleanup background job that removes rows from database of old locks

parent 6938d49c
......@@ -28,4 +28,6 @@ public interface InternalLockManager extends LockManager {
*/
void clear();
void addJob(final Runnable runnable, final long initialDelaySeconds, final long periodSeconds);
}
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.repository.lock.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.onehippo.repository.lock.db.DbHelper.close;
import static org.onehippo.repository.lock.db.DbLockManager.REFRESH_LOCK_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.REMOVE_OUTDATED_LOCKS;
/**
* Removes all locks that are free for longer than a day
*/
public class DbLockCleanupJanitor implements Runnable {
private static final Logger log = LoggerFactory.getLogger(DbLockCleanupJanitor.class);
private final DataSource dataSource;
public DbLockCleanupJanitor(final DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void run() {
Connection connection = null;
boolean originalAutoCommit = false;
try {
connection = dataSource.getConnection();
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement removeStatement = connection.prepareStatement(REMOVE_OUTDATED_LOCKS);
long dayAgoTime = System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
removeStatement.setLong(1, dayAgoTime);
int updated = removeStatement.executeUpdate();
log.info("Removed {} outdated locks", updated);
removeStatement.close();
} catch (SQLException e) {
log.error("Error while trying remove outdated locks", e);
} finally {
close(connection, originalAutoCommit);
}
}
}
......@@ -48,12 +48,13 @@ public class DbLockManager extends AbstractLockManager {
"lockThread VARCHAR(256)," +
"status VARCHAR(256) NOT NULL," +
"lockTime BIGINT," +
"expirationTime BIGINT" +
"expirationTime BIGINT," +
"lastModified BIGINT" +
")";
public static final String CREATE_STATEMENT = "INSERT INTO " + TABLE_NAME_LOCK + " VALUES(?,?,?,'RUNNING',?,?)";
public static final String CREATE_STATEMENT = "INSERT INTO " + TABLE_NAME_LOCK + " VALUES(?,?,?,'RUNNING',?,?,?)";
public static final String SELECT_STATEMENT = "SELECT * FROM " + TABLE_NAME_LOCK + " WHERE lockKey=?";
public static final String LOCK_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET status='RUNNING', lockTime=?, expirationTime=?, lockOwner=?, lockThread=? WHERE lockKey=? AND status='FREE'";
public static final String LOCK_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET status='RUNNING', lockTime=?, lastModified=?, expirationTime=?, lockOwner=?, lockThread=? WHERE lockKey=? AND status='FREE'";
public static final String ALL_LOCKED_STATEMENT = "SELECT * FROM " + TABLE_NAME_LOCK + " WHERE status='RUNNING' OR status='ABORT'";
......@@ -62,22 +63,26 @@ public class DbLockManager extends AbstractLockManager {
"lockThread=NULL, " +
"status='FREE', " +
"lockTime=0, " +
"expirationTime=0 " +
"expirationTime=0, " +
"lastModified=? " +
"WHERE lockKey=? AND lockOwner=? AND lockThread=?";
public static final String RESET_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET " +
public static final String RESET_EXPIRED_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET " +
"lockOwner=NULL, " +
"lockThread=NULL, " +
"status='FREE', " +
"lockTime=0, " +
"expirationTime=0 " +
"expirationTime=0, " +
"lastModified=? " +
"WHERE expirationTime<? AND (status='RUNNING' OR status='ABORT')";
public static final String ABORT_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET status='ABORT' WHERE lockKey=? AND status='RUNNING'";
public static final String REMOVE_OUTDATED_LOCKS = "DELETE FROM " + TABLE_NAME_LOCK + " WHERE lastModified<?";
public static final String ABORT_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET status='ABORT', lastModified=? WHERE lockKey=? AND status='RUNNING'";
// only refreshes its own cluster locks
public static final String REFRESH_LOCK_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET expirationTime=expirationTime+"+ REFRESH_RATE_SECONDS * 1000 +
public static final String REFRESH_LOCK_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET lastModified=?, expirationTime=expirationTime+"+ REFRESH_RATE_SECONDS * 1000 +
" WHERE lockOwner=? AND expirationTime<? AND (status='RUNNING' OR status='ABORT')";
public static final String SELECT_ABORT_STATEMENT = "SELECT * FROM " + TABLE_NAME_LOCK + " WHERE status='ABORT' AND lockOwner=?";
......@@ -92,6 +97,8 @@ public class DbLockManager extends AbstractLockManager {
addJob(new UnlockStoppedThreadJanitor());
addJob(new DbResetExpiredLocksJanitor(dataSource));
final int oneDaySeconds = 24 * 60 * 60;
addJob(new DbLockCleanupJanitor(dataSource), 60, oneDaySeconds);
addJob(new DbLockRefresher(dataSource, clusterNodeId));
addJob(new LockThreadInterrupter(dataSource, clusterNodeId, this));
}
......@@ -114,10 +121,11 @@ public class DbLockManager extends AbstractLockManager {
final long expirationTime = lockTime + REFRESH_RATE_SECONDS * 1000;
final PreparedStatement lockStatement = connection.prepareStatement(LOCK_STATEMENT);
lockStatement.setLong(1, lockTime);
lockStatement.setLong(2, expirationTime);
lockStatement.setString(3, clusterNodeId);
lockStatement.setString(4, threadName);
lockStatement.setString(5, key);
lockStatement.setLong(2, lockTime);
lockStatement.setLong(3, expirationTime);
lockStatement.setString(4, clusterNodeId);
lockStatement.setString(5, threadName);
lockStatement.setString(6, key);
int changed = lockStatement.executeUpdate();
lockStatement.close();
......@@ -130,6 +138,7 @@ public class DbLockManager extends AbstractLockManager {
createStatement.setString(3, threadName);
createStatement.setLong(4, lockTime);
createStatement.setLong(5, expirationTime);
createStatement.setLong(6, lockTime);
try {
createStatement.execute();
createStatement.close();
......@@ -161,9 +170,10 @@ public class DbLockManager extends AbstractLockManager {
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement resetLockStatement = connection.prepareStatement(RESET_LOCK_STATEMENT);
resetLockStatement.setString(1, key);
resetLockStatement.setString(2, clusterNodeId);
resetLockStatement.setString(3, threadName);
resetLockStatement.setLong(1, System.currentTimeMillis());
resetLockStatement.setString(2, key);
resetLockStatement.setString(3, clusterNodeId);
resetLockStatement.setString(4, threadName);
int changed = resetLockStatement.executeUpdate();
resetLockStatement.close();
if (changed == 0) {
......@@ -203,7 +213,8 @@ public class DbLockManager extends AbstractLockManager {
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement abortStatement = connection.prepareStatement(ABORT_STATEMENT);
abortStatement.setString(1, key);
abortStatement.setLong(1, System.currentTimeMillis());
abortStatement.setString(2, key);
int changed = abortStatement.executeUpdate();
abortStatement.close();
......
......@@ -53,18 +53,16 @@ public class DbLockRefresher implements Runnable {
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement refreshStatement = connection.prepareStatement(REFRESH_LOCK_STATEMENT);
refreshStatement.setString(1, clusterNodeId);
long currentTime = System.currentTimeMillis();
refreshStatement.setLong(1, currentTime);
refreshStatement.setString(2, clusterNodeId);
// select all rows that have less than 20 seconds to live
refreshStatement.setLong(2, System.currentTimeMillis() + 20000);
refreshStatement.setLong(3, currentTime + 20000);
int updated = refreshStatement.executeUpdate();
log.info("Refreshed {} locks", updated);
refreshStatement.close();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows:", this.getClass().getName(), e);
} else {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows: {}", this.getClass().getName(), e.toString());
}
log.error("Error while trying to refresh locks", e);
} finally {
close(connection, originalAutoCommit);
}
......
......@@ -25,7 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.onehippo.repository.lock.db.DbHelper.close;
import static org.onehippo.repository.lock.db.DbLockManager.RESET_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.RESET_EXPIRED_STATEMENT;
/**
* Resets expired locks to 'FREE' if they are in state 'RUNNING' or 'ABORT'
......@@ -48,17 +48,15 @@ public class DbResetExpiredLocksJanitor implements Runnable {
connection = dataSource.getConnection();
originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(true);
final PreparedStatement resetStatement = connection.prepareStatement(RESET_STATEMENT);
resetStatement.setLong(1, System.currentTimeMillis());
final PreparedStatement resetStatement = connection.prepareStatement(RESET_EXPIRED_STATEMENT);
long currentTime = System.currentTimeMillis();
resetStatement.setLong(1, currentTime);
resetStatement.setLong(2, currentTime);
int updated = resetStatement.executeUpdate();
log.info("Expired {} locks", updated);
resetStatement.close();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows:", this.getClass().getName(), e);
} else {
log.info("Exception in {} happened. Possibly another cluster node did already reset some lock rows: {}", this.getClass().getName(), e.toString());
}
log.error("Error while trying to reset locks", e);
} finally {
close(connection, originalAutoCommit);
}
......
......@@ -16,6 +16,7 @@
package org.onehippo.repository.lock;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
......@@ -40,6 +41,7 @@ public class MemoryLockManagerTest {
@Test
public void same_thread_can_lock_same_key_multiple_times() throws Exception {
memoryLockManager.lock("123");
memoryLockManager.lock("123");
assertEquals(1, memoryLockManager.getLocks().size());
......
......@@ -21,6 +21,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import javax.jcr.Repository;
import javax.jcr.Session;
import javax.sql.DataSource;
import org.apache.jackrabbit.core.util.db.ConnectionHelperDataSourceAccessor;
......@@ -114,8 +115,29 @@ public abstract class AbstractLockManagerTest extends RepositoryTestCase {
}
}
protected void assertKeyMissing(final String key) throws SQLException {
if (dataSource == null) {
// not a clustered db test
return;
}
try (Connection connection = dataSource.getConnection()) {
final PreparedStatement selectStatement = connection.prepareStatement(SELECT_STATEMENT);
selectStatement.setString(1, key);
ResultSet resultSet = selectStatement.executeQuery();
if (resultSet.next()) {
fail(String.format("Key '%s' not expected to be present in database", key));
}
}
}
protected void addManualLockToDatabase(final String key, final String clusterNodeId,
final String threadName) throws LockException {
addManualLockToDatabase(key, clusterNodeId, threadName, 0L, 0L, 0L);
}
protected void addManualLockToDatabase(final String key, final String clusterNodeId,
final String threadName, long lockTime, long expirationTime, long lastModified) throws LockException {
if (dataSource != null) {
try (Connection connection = dataSource.getConnection()) {
......@@ -123,9 +145,12 @@ public abstract class AbstractLockManagerTest extends RepositoryTestCase {
createStatement.setString(1, key);
createStatement.setString(2, clusterNodeId);
createStatement.setString(3, threadName);
long lockTime = System.currentTimeMillis();
lockTime = (lockTime == 0L) ? System.currentTimeMillis() : lockTime;
createStatement.setLong(4, lockTime);
createStatement.setLong(5, lockTime + REFRESH_RATE_SECONDS * 1000);
expirationTime = (expirationTime ==0) ? lockTime + REFRESH_RATE_SECONDS * 1000 : expirationTime;
createStatement.setLong(5, expirationTime);
lastModified = (lastModified == 0L) ? lockTime : lastModified;
createStatement.setLong(6, lastModified);
try {
createStatement.execute();
} catch (SQLException e) {
......@@ -148,9 +173,16 @@ public abstract class AbstractLockManagerTest extends RepositoryTestCase {
createStatement.setString(3, threadName);
createStatement.setLong(4, lockTime);
createStatement.setLong(5, expirationTime);
createStatement.setLong(6, lockTime);
createStatement.execute();
}
}
protected String getClusterNodeId(final Session session) {
String clusterNodeId = session.getRepository().getDescriptor("jackrabbit.cluster.id");
if (clusterNodeId == null) {
clusterNodeId = "default";
}
return clusterNodeId;
}
}
......@@ -87,7 +87,8 @@ public class LockManagerAbortTest extends AbstractLockManagerTest {
private void abortDataRowLock(final String key) throws SQLException {
try (Connection connection = dataSource.getConnection()){
final PreparedStatement abortStatement = connection.prepareStatement(ABORT_STATEMENT);
abortStatement.setString(1, key);
abortStatement.setLong(1, System.currentTimeMillis());
abortStatement.setString(2, key);
int changed = abortStatement.executeUpdate();
assertEquals("Abort should had modified 1 row", 1, changed);
}
......
......@@ -22,36 +22,20 @@ import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.jcr.Repository;
import javax.sql.DataSource;
import org.apache.jackrabbit.core.util.db.ConnectionHelperDataSourceAccessor;
import org.hippoecm.repository.impl.RepositoryDecorator;
import org.hippoecm.repository.jackrabbit.RepositoryImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onehippo.cms7.services.HippoServiceRegistry;
import org.onehippo.cms7.services.lock.Lock;
import org.onehippo.cms7.services.lock.LockException;
import org.onehippo.cms7.services.lock.LockManager;
import org.onehippo.cms7.services.lock.LockManagerException;
import org.onehippo.cms7.services.lock.LockResource;
import org.onehippo.repository.journal.JournalConnectionHelperAccessor;
import org.onehippo.repository.lock.db.DbLockManager;
import org.onehippo.repository.lock.memory.MemoryLockManager;
import org.onehippo.repository.testutils.RepositoryTestCase;
import org.onehippo.repository.lock.InternalLockManager;
import org.onehippo.repository.lock.MutableLock;
import org.onehippo.testutils.log4j.Log4jInterceptor;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.onehippo.repository.lock.db.DbLockManager.CREATE_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.SELECT_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.TABLE_NAME_LOCK;
......@@ -350,7 +334,7 @@ public class LockManagerBasicTest extends AbstractLockManagerTest {
try (Log4jInterceptor interceptor = Log4jInterceptor.onWarn().trap(MemoryLockManager.class, DbLockManager.class).build()) {
lockManager.clear();
assertTrue(interceptor.messages().anyMatch(m -> m.contains("Lock 'a' owned by 'default' was never")));
assertTrue(interceptor.messages().anyMatch(m -> m.contains("Lock 'a' owned by '"+getClusterNodeId(session)+"' was never")));
}
dbRowAssertion("a", "FREE");
......
/*
* Copyright 2017 Hippo B.V. (http://www.onehippo.com)
*
* Licensed 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.onehippo.repository.lock;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onehippo.cms7.services.HippoServiceRegistry;
import org.onehippo.cms7.services.lock.LockManager;
import org.onehippo.repository.lock.db.DbLockCleanupJanitor;
public class LockManagerCleanupTest extends AbstractLockManagerTest {
@Override
@Before
public void setUp() throws Exception {
super.setUp();
final InternalLockManager internalLockManager = (InternalLockManager)HippoServiceRegistry.getService(LockManager.class);
// Add a DbLockCleanupJanitor that runs evcery 5 secs instead of one per day to trigger it more frequently in the
// integration tests below
internalLockManager.addJob(new DbLockCleanupJanitor(dataSource), 0, 5);
}
@Override
@After
public void tearDown() throws Exception {
// since we add a LockManager job we need to tear down the repository : Otherwise, other integration tests
// have this extra job also running
super.tearDown(true);
}
@Test
public void locks_with_lastmodified_older_than_a_day_get_cleaned_even_when_status_running() throws Exception {
if (dataSource == null) {
return;
}
// reason why status running still gets cleaned is because even a running lock should get its lastmodified updated
long now = System.currentTimeMillis();
long dayAgoTime = now - TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
long halfDayAgoTime = now - TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
addManualLockToDatabase("LockManagerCleanupTest1", "otherClusterNode", "threadName", dayAgoTime, now + 60_000, dayAgoTime);
addManualLockToDatabase("LockManagerCleanupTest2", getClusterNodeId(session), "threadName", dayAgoTime, now + 60_000,dayAgoTime);
addManualLockToDatabase("LockManagerCleanupTest3", "otherClusterNode", "threadName", dayAgoTime, now + 60_000,halfDayAgoTime);
addManualLockToDatabase("LockManagerCleanupTest4", getClusterNodeId(session), "threadName", dayAgoTime, now + 60_000,halfDayAgoTime);
addManualLockToDatabase("LockManagerCleanupTest5", "otherClusterNode", "threadName", dayAgoTime, now + 60_000,now);
addManualLockToDatabase("LockManagerCleanupTest6", getClusterNodeId(session), "threadName", dayAgoTime, now + 60_000,now);
Thread.sleep(10_000);
// d,e,f,g should still be there because last modified not a day ago
dbRowAssertion("LockManagerCleanupTest3", "RUNNING");
dbRowAssertion("LockManagerCleanupTest4", "RUNNING");
dbRowAssertion("LockManagerCleanupTest5", "RUNNING");
dbRowAssertion("LockManagerCleanupTest6", "RUNNING");
// a, b, have lastmodified older than a day and should had been removed
assertKeyMissing("LockManagerCleanupTest1");
assertKeyMissing("LockManagerCleanupTest2");
}
}
......@@ -85,14 +85,6 @@ public class LockManagerDestroyTest extends AbstractLockManagerTest {
unstoppableLockThread.join();
}
private String getClusterNodeId(final Session session) {
String clusterNodeId = session.getRepository().getDescriptor("jackrabbit.cluster.id");
if (clusterNodeId == null) {
clusterNodeId = "default";
}
return clusterNodeId;
}
protected class LockRunnable implements Runnable {
private String key;
......
......@@ -15,18 +15,7 @@
*/
package org.onehippo.repository.lock;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import org.onehippo.cms7.services.lock.LockException;
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.onehippo.repository.lock.db.DbLockManager.ABORT_STATEMENT;
public class LockManagerExpiresTest extends AbstractLockManagerTest {
......
......@@ -27,6 +27,7 @@ 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.onehippo.repository.lock.db.DbLockManager.SELECT_STATEMENT;
import static org.onehippo.repository.lock.db.DbLockManager.TABLE_NAME_LOCK;
public class LockManagerRefreshTest extends AbstractLockManagerTest {
......@@ -50,6 +51,7 @@ public class LockManagerRefreshTest extends AbstractLockManagerTest {
setExpireTime(key, 10);
long expires = getExpireTime(key);
long lastModified = getLastModifiedTime(key);
assertTrue("Expires time expected to be in the future", expires > System.currentTimeMillis());
// within 5 seconds the DbLockRefresher must have refreshed the expires time
......@@ -65,6 +67,9 @@ public class LockManagerRefreshTest extends AbstractLockManagerTest {
// at 'SET expirationTime=expirationTime+"+ REFRESH_RATE_SECONDS * 1000'
assertEquals(expires+60_000, getExpireTime(key));
// assert the lastModified is also bumped:
assertTrue(getLastModifiedTime(key) > lastModified);
runnable.keepAlive = false;
// after the thread is finished, the lock manager should have no locks any more
lockThread.join();
......@@ -131,14 +136,11 @@ public class LockManagerRefreshTest extends AbstractLockManagerTest {
lockThread2.join();
}
public static final String GET_EXPIRE_STATEMENT = "SELECT * FROM " + TABLE_NAME_LOCK + " WHERE lockKey=?";
public static final String SET_EXPIRE_STATEMENT = "UPDATE " + TABLE_NAME_LOCK + " SET expirationTime=? WHERE lockKey=?";
private long getExpireTime(final String key) throws SQLException {
try (Connection connection = dataSource.getConnection()){
final PreparedStatement getExpireStatement = connection.prepareStatement(GET_EXPIRE_STATEMENT);
final PreparedStatement getExpireStatement = connection.prepareStatement(SELECT_STATEMENT);
getExpireStatement.setString(1, key);
ResultSet resultSet = getExpireStatement.executeQuery();
assertTrue("Should had one db result",resultSet.next());
......@@ -146,6 +148,16 @@ public class LockManagerRefreshTest extends AbstractLockManagerTest {
}
}
private long getLastModifiedTime(final String key) throws SQLException {
try (Connection connection = dataSource.getConnection()){
final PreparedStatement getExpireStatement = connection.prepareStatement(SELECT_STATEMENT);
getExpireStatement.setString(1, key);
ResultSet resultSet = getExpireStatement.executeQuery();
assertTrue("Should had one db result",resultSet.next());
return resultSet.getLong("lastModified");
}
}
private void setExpireTime(final String key, final int expireInSeconds) throws SQLException {
try (Connection connection = dataSource.getConnection()){
final PreparedStatement setExpireStatement = connection.prepareStatement(SET_EXPIRE_STATEMENT);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment