org.smallmind.persistence.database.mysql.SimulatedSequence Maven / Gradle / Ivy
/*
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 David Berkman
*
* This file is part of the SmallMind Code Project.
*
* The SmallMind Code Project is free software, you can redistribute
* it and/or modify it under either, at your discretion...
*
* 1) The terms of GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* ...or...
*
* 2) The terms of the Apache License, Version 2.0.
*
* The SmallMind Code Project is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License or Apache License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and the Apache License along with the SmallMind Code Project. If not, see
* or .
*
* Additional permission under the GNU Affero GPL version 3 section 7
* ------------------------------------------------------------------
* If you modify this Program, or any covered work, by linking or
* combining it with other code, such other code is not for that reason
* alone subject to any of the requirements of the GNU Affero GPL
* version 3.
*/
package org.smallmind.persistence.database.mysql;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import org.smallmind.persistence.database.Sequence;
import org.smallmind.persistence.database.SequenceManager;
import org.smallmind.scribe.pen.LoggerManager;
public class SimulatedSequence extends Sequence {
private final ConcurrentHashMap DATA_MAP = new ConcurrentHashMap<>();
private final DataSource dataSource;
private final String tableName;
private final int incrementBy;
public SimulatedSequence (DataSource dataSource, String tableName, int incrementBy) {
this.dataSource = dataSource;
this.tableName = tableName;
this.incrementBy = incrementBy;
}
public void register () {
SequenceManager.register(this);
}
@Override
public long nextLong (String name) {
return getSequenceData(name).nextLong();
}
private SequenceData getSequenceData (String name) {
SequenceData sequenceData;
if ((sequenceData = DATA_MAP.get(name)) == null) {
synchronized (DATA_MAP) {
if ((sequenceData = DATA_MAP.get(name)) == null) {
DATA_MAP.put(name, sequenceData = new SequenceData(name));
}
}
}
return sequenceData;
}
private class SequenceData {
private final AtomicLong atomicBoundary;
private final AtomicLong atomicOffset = new AtomicLong(0);
private final String name;
private final String insertSql;
private final String updateSql;
public SequenceData (String name) {
this.name = name;
insertSql = "INSERT IGNORE INTO " + tableName + " (name, next_val) VALUES('" + name + "', 0)";
updateSql = "UPDATE " + tableName + " SET next_val=LAST_INSERT_ID(next_val + " + incrementBy + ") where name='" + name + "'";
insertName();
atomicBoundary = new AtomicLong(getLastInsertId());
}
public long nextLong () {
long nextValue = 0;
do {
if (incrementBy == 1) {
nextValue = getLastInsertId();
} else {
long currentOffset;
do {
if ((currentOffset = atomicOffset.incrementAndGet()) < incrementBy) {
nextValue = atomicBoundary.get() + currentOffset;
} else if (currentOffset == incrementBy) {
try {
atomicBoundary.set(nextValue = getLastInsertId());
atomicOffset.set(0);
} catch (SimulatedSequenceDisasterException simulatedSequenceDisasterException) {
LoggerManager.getLogger(SimulatedSequence.class).error(simulatedSequenceDisasterException);
currentOffset = incrementBy + 1;
atomicOffset.set(incrementBy - 1);
}
}
} while (currentOffset > incrementBy);
}
} while (nextValue == 0);
return nextValue;
}
private void insertName () {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
connection.setAutoCommit(true);
statement.executeUpdate(insertSql);
} catch (SQLException sqlException) {
throw new SimulatedSequenceDisasterException(sqlException, "Unable to create sequence(%s)", name);
}
}
private long getLastInsertId () {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
connection.setAutoCommit(true);
statement.executeUpdate(updateSql, Statement.RETURN_GENERATED_KEYS);
try (ResultSet resultSet = statement.getGeneratedKeys()) {
if (resultSet.next()) {
return resultSet.getLong(1);
} else {
throw new SimulatedSequenceDisasterException("No sequence(%s) has been generated", name);
}
}
} catch (SimulatedSequenceDisasterException simulatedSequenceDisasterException) {
throw simulatedSequenceDisasterException;
} catch (SQLException sqlException) {
throw new SimulatedSequenceDisasterException(sqlException, "Unable to update sequence(%s)", name);
} catch (Throwable throwable) {
throw new SimulatedSequenceDisasterException(throwable, "Unknown exception encountered in sequence(%s) update", name);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy