package jakiro.sequence.dao; import jakiro.sequence.SequenceException; import jakiro.sequence.seq.SequenceRange; import jakiro.util.StringUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractSequenceDao implements SequenceDao { protected static final Logger log = LoggerFactory.getLogger(AbstractSequenceDao.class); private static final int DEFAULT_RETRY_TIMES = 150; private static final int MIN_STEP = 1; private static final int MAX_STEP = 100000; private static final long DELTA = 100000000L; private static final String DEFAULT_TABLE_NAME = "sequence"; private static final String DEFAULT_NAME_COLUMN_NAME = "name"; private static final String DEFAULT_VALUE_COLUMN_NAME = "value"; private static final String DEFAULT_GMT_MODIFIED_COLUMN_NAME = "gmt_modified"; private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } private String tableName = DEFAULT_TABLE_NAME; private String nameColumnName = DEFAULT_NAME_COLUMN_NAME; private String valueColumnName = DEFAULT_VALUE_COLUMN_NAME; private String gmtModifiedColumnName = DEFAULT_GMT_MODIFIED_COLUMN_NAME; private int retryTimes = DEFAULT_RETRY_TIMES; protected int step = 1000; private volatile String selectSql; private volatile String updateSql; public void setStep(int step) { if (step < MIN_STEP || step > MAX_STEP) { StringBuilder message = new StringBuilder(); message.append("Property step out of range [").append(MIN_STEP); message.append(",").append(MAX_STEP).append("], step = ").append(step); throw new IllegalArgumentException(message.toString()); } this.step = step; } public void setRetryTimes(int retryTimes) { if (retryTimes < 0) { throw new IllegalArgumentException("Property retryTimes cannot be less than zero, retryTimes = " + retryTimes); } this.retryTimes = retryTimes; } protected String getSelectSql() { if (selectSql == null) { synchronized (this) { if (selectSql == null) { StringBuilder buffer = new StringBuilder(); buffer.append("select ").append(valueColumnName); buffer.append(" from ").append(tableName); buffer.append(" where ").append(nameColumnName).append(" = ?"); selectSql = buffer.toString(); } } } return selectSql; } protected String getUpdateSql() { if (updateSql == null) { synchronized (this) { if (updateSql == null) { StringBuilder buffer = new StringBuilder(); buffer.append("update ").append(tableName); buffer.append(" set ").append(valueColumnName).append(" = ?, "); buffer.append(gmtModifiedColumnName).append(" = ? where "); buffer.append(nameColumnName).append(" = ? and "); buffer.append(valueColumnName).append(" = ?"); updateSql = buffer.toString(); } } } return updateSql; } protected static void closeResultSet(ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { log.debug("Could not close JDBC ResultSet", e); } catch (Throwable e) { log.debug("Unexpected exception on closing JDBC ResultSet", e); } } } protected static void closeStatement(Statement stmt) { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { log.debug("Could not close JDBC Statement", e); } catch (Throwable e) { log.debug("Unexpected exception on closing JDBC Statement", e); } } } protected static void closeConnection(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { log.debug("Could not close JDBC Connection", e); } catch (Throwable e) { log.debug("Unexpected exception on closing JDBC Connection", e); } } } @Override public SequenceRange nextRange(String name, int index, int total) throws SequenceException { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("Name is Null"); } long oldValue; long newValue; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; for (int i = 0; i < retryTimes + 1; ++i) { try { conn = dataSource.getConnection(); stmt = conn.prepareStatement(getSelectSql()); stmt.setString(1, name); rs = stmt.executeQuery(); rs.next(); oldValue = rs.getLong(1); if (oldValue < 0) { StringBuilder message = new StringBuilder(); message.append("Sequence value cannot be less than zero, value = ").append(oldValue); message.append(", please check table ").append(tableName); throw new SequenceException(message.toString()); } if (oldValue > Long.MAX_VALUE - DELTA) { StringBuilder message = new StringBuilder(); message.append("Sequence value overflow, value = ").append(oldValue); message.append(", please check table ").append(tableName); throw new SequenceException(message.toString()); } newValue = buildNewValue(oldValue, index, total); if (!check(oldValue, newValue, index, total)) { throw new SequenceException("Check Failed" + oldValue + " " + newValue + " " + index + " " + total); } } catch (SQLException e) { throw new SequenceException(e); } finally { closeResultSet(rs); rs = null; closeStatement(stmt); stmt = null; closeConnection(conn); conn = null; } try { conn = dataSource.getConnection(); stmt = conn.prepareStatement(getUpdateSql()); stmt.setLong(1, newValue); stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis())); stmt.setString(3, name); stmt.setLong(4, oldValue); int affectedRows = stmt.executeUpdate(); if (affectedRows == 0) { continue; } return buildReturnRange(newValue, oldValue); } catch (SQLException e) { throw new SequenceException(e); } finally { closeStatement(stmt); stmt = null; closeConnection(conn); conn = null; } } throw new SequenceException("Retried too many times, retryTimes = " + retryTimes); } protected abstract long buildNewValue(long old, int index, int total); protected abstract SequenceRange buildReturnRange(long newV, long oldV); protected abstract boolean check(long oldV, long newV, int index, int total); }