io.ebean.config.dbplatform.SequenceIdGenerator Maven / Gradle / Ivy
package io.ebean.config.dbplatform;
import io.avaje.applog.AppLog;
import io.ebean.BackgroundExecutor;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.ERROR;
/**
* Database sequence based IdGenerator.
*/
public abstract class SequenceIdGenerator implements PlatformIdGenerator {
protected static final System.Logger log = AppLog.getLogger("io.ebean.SEQ");
private final ReentrantLock lock = new ReentrantLock();
protected final String seqName;
protected final DataSource dataSource;
protected final BackgroundExecutor backgroundExecutor;
protected final NavigableSet idList = new TreeSet<>();
protected final int allocationSize;
protected AtomicBoolean currentlyBackgroundLoading = new AtomicBoolean(false);
/**
* Construct given a dataSource and sql to return the next sequence value.
*/
protected SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int allocationSize) {
this.backgroundExecutor = be;
this.dataSource = ds;
this.seqName = seqName;
this.allocationSize = allocationSize;
}
public abstract String getSql(int batchSize);
/**
* Returns the sequence name.
*/
@Override
public String getName() {
return seqName;
}
/**
* Returns true.
*/
@Override
public boolean isDbSequence() {
return true;
}
/**
* If allocateSize is large load some sequences in a background thread.
*
* For example, when inserting a bean with a cascade on a OneToMany with many
* beans Ebean can call this to ensure .
*
*/
@Override
public void preAllocateIds(int requestSize) {
// do nothing by default
}
/**
* Return the next Id.
*
* If a Transaction has been passed in use the Connection from it.
*
*/
@Override
public Object nextId(Transaction t) {
lock.lock();
try {
int size = idList.size();
if (size > 0) {
maybeLoadMoreInBackground(size);
} else {
loadMore(allocationSize);
}
return idList.pollFirst();
} finally {
lock.unlock();
}
}
private void maybeLoadMoreInBackground(int currentSize) {
if (allocationSize > 1) {
if (currentSize <= allocationSize / 2) {
loadInBackground(allocationSize);
}
}
}
private void loadMore(int requestSize) {
List newIds = getMoreIds(requestSize);
lock.lock();
try {
idList.addAll(newIds);
} finally {
lock.unlock();
}
}
/**
* Load another batch of Id's using a background thread.
*/
protected void loadInBackground(final int requestSize) {
if (currentlyBackgroundLoading.get()) {
// skip as already background loading
log.log(DEBUG, "... skip background sequence load (another load in progress)");
return;
}
currentlyBackgroundLoading.set(true);
backgroundExecutor.execute(() -> {
loadMore(requestSize);
currentlyBackgroundLoading.set(false);
});
}
/**
* Read the resultSet returning the list of Id values.
*/
protected abstract List readIds(ResultSet resultSet, int loadSize) throws SQLException;
/**
* Get more Id's by executing a query and reading the Id's returned.
*/
protected List getMoreIds(int requestSize) {
String sql = getSql(requestSize);
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
List newIds = readIds(resultSet, requestSize);
if (newIds.isEmpty()) {
throw new PersistenceException("Always expecting more than 1 row from " + sql);
}
return newIds;
} catch (SQLException e) {
if (e.getMessage().contains("Database is already closed")) {
String msg = "Error getting SEQ when DB shutting down " + e.getMessage();
log.log(ERROR, msg);
System.out.println(msg);
return Collections.emptyList();
} else {
throw new PersistenceException("Error getting sequence nextval", e);
}
} finally {
closeResources(connection, statement, resultSet);
}
}
/**
* Close the JDBC resources.
*/
private void closeResources(Connection connection, PreparedStatement statement, ResultSet resultSet) {
JdbcClose.close(resultSet);
JdbcClose.close(statement);
JdbcClose.close(connection);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy