package io.ebeaninternal.server.persist; import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanDescriptor; import java.util.ArrayList; import java.util.IdentityHashMap; /** * Holds lists of persist requests for beans of a given type. * <p> * This is used to delay the actual binding of the bean to PreparedStatements. * The reason is that we don't have all the bind values yet in the case of inserts * with getGeneratedKeys. * </p> * <p> * Has a depth which is used to determine the order in which it should be * executed. The lowest depth is executed first. * </p> */ public class BatchedBeanHolder { private static final Object DUMMY = new Object(); /** * The owning queue. */ private final BatchControl control; private final String shortDesc; /** * The 'depth' which is used to determine the execution order. */ private final int order; /** * The list of bean insert requests. */ private ArrayList<PersistRequest> inserts; /** * The list of bean update requests. */ private ArrayList<PersistRequest> updates; /** * The list of bean delete requests. */ private ArrayList<PersistRequest> deletes; /** * Set of beans in this batch. This is used to ensure that a single bean instance is not included * in the batch twice (two separate insert requests etc). */ private final IdentityHashMap<Object, Object> persistedBeans = new IdentityHashMap<>(); /** * Create a new entry with a given type and depth. */ public BatchedBeanHolder(BatchControl control, BeanDescriptor<?> beanDescriptor, int order) { this.control = control; this.shortDesc = beanDescriptor.getName() + ":" + order; this.order = order; } /** * Return the depth. */ public int getOrder() { return order; } /** * Execute all the persist requests in this entry. * <p> * This will Batch all the similar requests into one or more BatchStatements * and then execute them. * </p> */ public void executeNow() throws BatchedSqlException { // process the requests. Creates one or more PreparedStatements // with binding addBatch() for each request. // Note updates and deletes can result in many PreparedStatements // if their where clauses differ via use of IS NOT NULL. if (inserts != null && !inserts.isEmpty()) { control.executeNow(inserts); inserts.clear(); } if (updates != null && !updates.isEmpty()) { control.executeNow(updates); updates.clear(); } if (deletes != null && !deletes.isEmpty()) { control.executeNow(deletes); deletes.clear(); } persistedBeans.clear(); } @Override public String toString() { StringBuilder sb = new StringBuilder(shortDesc.length() + 18); sb.append(shortDesc); if (inserts != null) { sb.append(" i:").append(inserts.size()); } if (updates != null) { sb.append(" u:").append(updates.size()); } if (deletes != null) { sb.append(" d:").append(deletes.size()); } return sb.toString(); } /** * Add the request to the appropriate persist list. */ public int append(PersistRequestBean<?> request) { Object alreadyInBatch = persistedBeans.put(request.getEntityBean(), DUMMY); if (alreadyInBatch != null) { // special case where the same bean instance has already been // added to the batch (doesn't really occur with non-batching // as the bean gets changed from dirty to loaded earlier) return 0; } request.setBatched(); switch (request.getType()) { case INSERT: if (inserts == null) { inserts = new ArrayList<>(); } inserts.add(request); return inserts.size(); case UPDATE: case SOFT_DELETE: if (updates == null) { updates = new ArrayList<>(); } updates.add(request); return updates.size(); case DELETE: if (deletes == null) { deletes = new ArrayList<>(); } deletes.add(request); return deletes.size(); default: throw new RuntimeException("Invalid type code " + request.getType()); } } /** * Return true if this is empty containing no batched beans. */ public boolean isEmpty() { return persistedBeans.isEmpty(); } }