001package io.ebean.config.dbplatform; 002 003import io.ebean.BackgroundExecutor; 004import io.ebean.Transaction; 005import io.ebean.util.JdbcClose; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import javax.persistence.PersistenceException; 010import javax.sql.DataSource; 011import java.sql.Connection; 012import java.sql.PreparedStatement; 013import java.sql.ResultSet; 014import java.sql.SQLException; 015import java.util.ArrayList; 016import java.util.Collections; 017import java.util.List; 018 019/** 020 * Database sequence based IdGenerator. 021 */ 022public abstract class SequenceIdGenerator implements PlatformIdGenerator { 023 024 protected static final Logger logger = LoggerFactory.getLogger("io.ebean.SEQ"); 025 026 /** 027 * Used to synchronise the idList access. 028 */ 029 protected final Object monitor = new Object(); 030 031 /** 032 * Used to synchronise background loading (loadBatchInBackground). 033 */ 034 protected final Object backgroundLoadMonitor = new Object(); 035 036 /** 037 * The actual sequence name. 038 */ 039 protected final String seqName; 040 041 protected final DataSource dataSource; 042 043 protected final BackgroundExecutor backgroundExecutor; 044 045 protected final ArrayList<Long> idList = new ArrayList<>(50); 046 047 protected final int allocationSize; 048 049 protected boolean currentlyBackgroundLoading; 050 051 /** 052 * Construct given a dataSource and sql to return the next sequence value. 053 */ 054 protected SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int allocationSize) { 055 this.backgroundExecutor = be; 056 this.dataSource = ds; 057 this.seqName = seqName; 058 this.allocationSize = allocationSize; 059 } 060 061 public abstract String getSql(int batchSize); 062 063 /** 064 * Returns the sequence name. 065 */ 066 @Override 067 public String getName() { 068 return seqName; 069 } 070 071 /** 072 * Returns true. 073 */ 074 @Override 075 public boolean isDbSequence() { 076 return true; 077 } 078 079 /** 080 * If allocateSize is large load some sequences in a background thread. 081 * <p> 082 * For example, when inserting a bean with a cascade on a OneToMany with many 083 * beans Ebean can call this to ensure . 084 * </p> 085 */ 086 @Override 087 public void preAllocateIds(int requestSize) { 088 // do nothing by default 089 } 090 091 /** 092 * Return the next Id. 093 * <p> 094 * If a Transaction has been passed in use the Connection from it. 095 * </p> 096 */ 097 @Override 098 public Object nextId(Transaction t) { 099 synchronized (monitor) { 100 101 int size = idList.size(); 102 if (size > 0) { 103 maybeLoadMoreInBackground(size); 104 } else { 105 loadMore(allocationSize); 106 } 107 108 return idList.remove(0); 109 } 110 } 111 112 private void maybeLoadMoreInBackground(int currentSize) { 113 if (allocationSize > 1) { 114 if (currentSize <= allocationSize / 2) { 115 loadInBackground(allocationSize); 116 } 117 } 118 } 119 120 private void loadMore(int requestSize) { 121 List<Long> newIds = getMoreIds(requestSize); 122 synchronized (monitor) { 123 idList.addAll(newIds); 124 } 125 } 126 127 /** 128 * Load another batch of Id's using a background thread. 129 */ 130 protected void loadInBackground(final int requestSize) { 131 132 // single threaded processing... 133 synchronized (backgroundLoadMonitor) { 134 if (currentlyBackgroundLoading) { 135 // skip as already background loading 136 logger.debug("... skip background sequence load (another load in progress)"); 137 return; 138 } 139 140 currentlyBackgroundLoading = true; 141 142 backgroundExecutor.execute(() -> { 143 loadMore(requestSize); 144 synchronized (backgroundLoadMonitor) { 145 currentlyBackgroundLoading = false; 146 } 147 }); 148 } 149 } 150 151 /** 152 * Read the resultSet returning the list of Id values. 153 */ 154 protected abstract List<Long> readIds(ResultSet resultSet, int loadSize) throws SQLException; 155 156 /** 157 * Get more Id's by executing a query and reading the Id's returned. 158 */ 159 protected List<Long> getMoreIds(int requestSize) { 160 161 String sql = getSql(requestSize); 162 163 Connection connection = null; 164 PreparedStatement statement = null; 165 ResultSet resultSet = null; 166 try { 167 connection = dataSource.getConnection(); 168 169 statement = connection.prepareStatement(sql); 170 resultSet = statement.executeQuery(); 171 172 List<Long> newIds = readIds(resultSet, requestSize); 173 if (logger.isTraceEnabled()) { 174 logger.trace("seq:{} loaded:{} sql:{}", seqName, newIds.size(), sql); 175 } 176 if (newIds.isEmpty()) { 177 throw new PersistenceException("Always expecting more than 1 row from " + sql); 178 } 179 180 return newIds; 181 182 } catch (SQLException e) { 183 if (e.getMessage().contains("Database is already closed")) { 184 String msg = "Error getting SEQ when DB shutting down " + e.getMessage(); 185 logger.error(msg); 186 System.out.println(msg); 187 return Collections.emptyList(); 188 } else { 189 throw new PersistenceException("Error getting sequence nextval", e); 190 } 191 } finally { 192 closeResources(connection, statement, resultSet); 193 } 194 } 195 196 /** 197 * Close the JDBC resources. 198 */ 199 private void closeResources(Connection connection, PreparedStatement statement, ResultSet resultSet) { 200 JdbcClose.close(resultSet); 201 JdbcClose.close(statement); 202 JdbcClose.close(connection); 203 } 204 205}