001package com.avaje.ebean.config.dbplatform; 002 003import java.sql.Connection; 004import java.sql.PreparedStatement; 005import java.sql.ResultSet; 006import java.sql.SQLException; 007import java.util.ArrayList; 008 009import javax.persistence.PersistenceException; 010import javax.sql.DataSource; 011 012import com.avaje.ebean.BackgroundExecutor; 013import com.avaje.ebean.Transaction; 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016 017/** 018 * Database sequence based IdGenerator. 019 */ 020public abstract class SequenceIdGenerator implements PlatformIdGenerator { 021 022 private static final Logger logger = LoggerFactory.getLogger(SequenceIdGenerator.class); 023 024 /** 025 * Used to synchronise the idList access. 026 */ 027 protected final Object monitor = new Object(); 028 029 /** 030 * Used to synchronise background loading (loadBatchInBackground). 031 */ 032 protected final Object backgroundLoadMonitor = new Object(); 033 034 /** 035 * The actual sequence name. 036 */ 037 protected final String seqName; 038 039 protected final DataSource dataSource; 040 041 protected final BackgroundExecutor backgroundExecutor; 042 043 protected final ArrayList<Long> idList = new ArrayList<Long>(50); 044 045 protected final int batchSize; 046 047 protected int currentlyBackgroundLoading; 048 049 /** 050 * Construct given a dataSource and sql to return the next sequence value. 051 */ 052 public SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) { 053 this.backgroundExecutor = be; 054 this.dataSource = ds; 055 this.seqName = seqName; 056 this.batchSize = batchSize; 057 } 058 059 public abstract String getSql(int batchSize); 060 061 /** 062 * Returns the sequence name. 063 */ 064 public String getName() { 065 return seqName; 066 } 067 068 /** 069 * Returns true. 070 */ 071 public boolean isDbSequence() { 072 return true; 073 } 074 075 /** 076 * If allocateSize is large load some sequences in a background thread. 077 * <p> 078 * For example, when inserting a bean with a cascade on a OneToMany with many 079 * beans Ebean can call this to ensure . 080 * </p> 081 */ 082 public void preAllocateIds(int allocateSize) { 083 if (batchSize > 1 && allocateSize > batchSize) { 084 // only bother if allocateSize is bigger than 085 // the normal loading batchSize 086 if (allocateSize > 100) { 087 // max out at 100 for now 088 allocateSize = 100; 089 } 090 loadLargeAllocation(allocateSize); 091 } 092 } 093 094 /** 095 * Called by preAllocateIds when we know that a large number of Id's is going 096 * to be needed shortly. 097 */ 098 protected void loadLargeAllocation(final int allocateSize) { 099 // preAllocateIds was called with a relatively large batchSize 100 // so we will just go ahead and load those anyway in background 101 backgroundExecutor.execute(new Runnable() { 102 public void run() { 103 loadMoreIds(allocateSize, null); 104 } 105 }); 106 } 107 108 /** 109 * Return the next Id. 110 * <p> 111 * If a Transaction has been passed in use the Connection from it. 112 * </p> 113 */ 114 public Object nextId(Transaction t) { 115 synchronized (monitor) { 116 117 if (idList.isEmpty()) { 118 loadMoreIds(batchSize, t); 119 } 120 Long nextId = idList.remove(0); 121 122 if (batchSize > 1) { 123 if (idList.size() <= batchSize / 2) { 124 loadBatchInBackground(); 125 } 126 } 127 128 return nextId; 129 } 130 } 131 132 /** 133 * Load another batch of Id's using a background thread. 134 */ 135 protected void loadBatchInBackground() { 136 137 // single threaded processing... 138 synchronized (backgroundLoadMonitor) { 139 140 if (currentlyBackgroundLoading > 0) { 141 // skip as already background loading 142 logger.debug("... skip background sequence load (another load in progress)"); 143 return; 144 } 145 146 currentlyBackgroundLoading = batchSize; 147 148 backgroundExecutor.execute(new Runnable() { 149 public void run() { 150 loadMoreIds(batchSize, null); 151 synchronized (backgroundLoadMonitor) { 152 currentlyBackgroundLoading = 0; 153 } 154 } 155 }); 156 } 157 } 158 159 protected void loadMoreIds(final int numberToLoad, Transaction t) { 160 161 ArrayList<Long> newIds = getMoreIds(numberToLoad, t); 162 163 if (logger.isDebugEnabled()) { 164 logger.debug("... seq:" + seqName + " loaded:" + numberToLoad + " ids:" + newIds); 165 } 166 167 synchronized (monitor) { 168 for (int i = 0; i < newIds.size(); i++) { 169 idList.add(newIds.get(i)); 170 } 171 } 172 } 173 174 /** 175 * Get more Id's by executing a query and reading the Id's returned. 176 */ 177 protected ArrayList<Long> getMoreIds(int loadSize, Transaction t) { 178 179 String sql = getSql(loadSize); 180 181 ArrayList<Long> newIds = new ArrayList<Long>(loadSize); 182 183 boolean useTxnConnection = t != null; 184 185 Connection c = null; 186 PreparedStatement pstmt = null; 187 ResultSet rset = null; 188 try { 189 c = useTxnConnection ? t.getConnection() : dataSource.getConnection(); 190 191 pstmt = c.prepareStatement(sql); 192 rset = pstmt.executeQuery(); 193 while (rset.next()) { 194 newIds.add(rset.getLong(1)); 195 } 196 if (newIds.isEmpty()) { 197 throw new PersistenceException("Always expecting more than 1 row from " + sql); 198 } 199 200 return newIds; 201 202 } catch (SQLException e) { 203 if (e.getMessage().contains("Database is already closed")) { 204 String msg = "Error getting SEQ when DB shutting down " + e.getMessage(); 205 logger.info(msg); 206 System.out.println(msg); 207 return newIds; 208 } else { 209 throw new PersistenceException("Error getting sequence nextval", e); 210 } 211 } finally { 212 if (useTxnConnection) { 213 closeResources(null, pstmt, rset); 214 } else { 215 closeResources(c, pstmt, rset); 216 } 217 } 218 } 219 220 /** 221 * Close the JDBC resources. 222 */ 223 protected void closeResources(Connection c, PreparedStatement pstmt, ResultSet rset) { 224 try { 225 if (rset != null) { 226 rset.close(); 227 } 228 } catch (SQLException e) { 229 logger.error( "Error closing ResultSet", e); 230 } 231 try { 232 if (pstmt != null) { 233 pstmt.close(); 234 } 235 } catch (SQLException e) { 236 logger.error("Error closing PreparedStatement", e); 237 } 238 try { 239 if (c != null) { 240 c.close(); 241 } 242 } catch (SQLException e) { 243 logger.error("Error closing Connection", e); 244 } 245 } 246 247}