diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java index 4b54bc78b..f3ab9fea5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java @@ -1,105 +1,105 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import com.avaje.ebean.FetchConfig; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; - -/** - * Base class for Bean and BeanCollection loading (lazy loading and query join loading). - */ -public abstract class DLoadBaseContext { - - protected final DLoadContext parent; - - protected final BeanDescriptor desc; - - protected final String path; - - protected final String fullPath; - - protected final OrmQueryProperties queryProps; - - protected final boolean hitCache; - - protected final String serverName; - - protected final int firstBatchSize; - - protected final int secondaryBatchSize; - - protected final ObjectGraphNode objectGraphNode; - - protected final boolean queryFetch; - - - public DLoadBaseContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { - - this.parent = parent; - this.serverName = parent.getEbeanServer().getName(); - this.desc = desc; - this.queryProps = queryProps; - this.path = path; - this.fullPath = parent.getFullPath(path); - - this.hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching(); - - this.objectGraphNode = parent.getObjectGraphNode(path); - - this.queryFetch = queryProps != null && queryProps.isQueryFetch(); - this.firstBatchSize = initFirstBatchSize(defaultBatchSize, queryProps); - this.secondaryBatchSize = initSecondaryBatchSize(defaultBatchSize, firstBatchSize, queryProps); - } - - private int initFirstBatchSize(int batchSize, OrmQueryProperties queryProps) { - if (queryProps == null) { - return batchSize; - } - - int queryFetchBatch = queryProps.getQueryFetchBatch(); - if (queryFetchBatch > 0) { - // property join was automatically set to a 'query join' - return queryFetchBatch; - } - - FetchConfig fetchConfig = queryProps.getFetchConfig(); - if (fetchConfig == null) { - return batchSize; - } - - int queryBatchSize = fetchConfig.getQueryBatchSize(); - if (queryBatchSize == -1) { - // not eager query fetch, just lazy loading - return batchSize; - - } else if (queryBatchSize == 0) { - // default query fetch batch size is 100 - return 100; - - } else { - return queryBatchSize; - } - } - - private int initSecondaryBatchSize(int defaultBatchSize, int firstBatchSize, OrmQueryProperties queryProps) { - if (queryProps == null) { - return defaultBatchSize; - } - FetchConfig fetchConfig = queryProps.getFetchConfig(); - if (fetchConfig == null) { - return defaultBatchSize; - } - if (fetchConfig.isQueryAll()) { - return firstBatchSize; - } - - int lazyBatchSize = fetchConfig.getLazyBatchSize(); - return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize; - } - - protected PersistenceContext getPersistenceContext() { - return parent.getPersistenceContext(); - } - -} +package com.avaje.ebeaninternal.server.loadcontext; + +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Base class for Bean and BeanCollection loading (lazy loading and query join loading). + */ +public abstract class DLoadBaseContext { + + protected final DLoadContext parent; + + protected final BeanDescriptor desc; + + protected final String path; + + protected final String fullPath; + + protected final OrmQueryProperties queryProps; + + protected final boolean hitCache; + + protected final String serverName; + + protected final int firstBatchSize; + + protected final int secondaryBatchSize; + + protected final ObjectGraphNode objectGraphNode; + + protected final boolean queryFetch; + + + public DLoadBaseContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { + + this.parent = parent; + this.serverName = parent.getEbeanServer().getName(); + this.desc = desc; + this.queryProps = queryProps; + this.path = path; + this.fullPath = parent.getFullPath(path); + + this.hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching(); + + this.objectGraphNode = parent.getObjectGraphNode(path); + + this.queryFetch = queryProps != null && queryProps.isQueryFetch(); + this.firstBatchSize = initFirstBatchSize(defaultBatchSize, queryProps); + this.secondaryBatchSize = initSecondaryBatchSize(defaultBatchSize, firstBatchSize, queryProps); + } + + private int initFirstBatchSize(int batchSize, OrmQueryProperties queryProps) { + if (queryProps == null) { + return batchSize; + } + + int queryFetchBatch = queryProps.getQueryFetchBatch(); + if (queryFetchBatch > 0) { + // property join was automatically set to a 'query join' + return queryFetchBatch; + } + + FetchConfig fetchConfig = queryProps.getFetchConfig(); + if (fetchConfig == null) { + return batchSize; + } + + int queryBatchSize = fetchConfig.getQueryBatchSize(); + if (queryBatchSize == -1) { + // not eager query fetch, just lazy loading + return batchSize; + + } else if (queryBatchSize == 0) { + // default query fetch batch size is 100 + return 100; + + } else { + return queryBatchSize; + } + } + + private int initSecondaryBatchSize(int defaultBatchSize, int firstBatchSize, OrmQueryProperties queryProps) { + if (queryProps == null) { + return defaultBatchSize; + } + FetchConfig fetchConfig = queryProps.getFetchConfig(); + if (fetchConfig == null) { + return defaultBatchSize; + } + if (fetchConfig.isQueryAll()) { + return firstBatchSize; + } + + int lazyBatchSize = fetchConfig.getLazyBatchSize(); + return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize; + } + + protected PersistenceContext getPersistenceContext() { + return parent.getPersistenceContext(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index fab92203c..dcb611f54 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -1,208 +1,208 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import com.avaje.ebean.bean.BeanLoader; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.api.LoadBeanBuffer; -import com.avaje.ebeaninternal.api.LoadBeanContext; -import com.avaje.ebeaninternal.api.LoadBeanRequest; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; - -/** - * Default implementation of LoadBeanContext. - */ -public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext{ - - private List bufferList; - - private LoadBuffer currentBuffer; - - public DLoadBeanContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { - - super(parent, desc, path, defaultBatchSize, queryProps); - - // bufferList only required when using query joins (queryFetch) - this.bufferList = (!queryFetch) ? null : new ArrayList(); - this.currentBuffer = createBuffer(firstBatchSize); - } - - /** - * Reset the buffers after a query iterator reset. - */ - public void clear() { - if (bufferList != null) { - bufferList.clear(); - } - currentBuffer = createBuffer(secondaryBatchSize); - } - - protected void configureQuery(SpiQuery query, String lazyLoadProperty) { - - // propagate the readOnly state - if (parent.isReadOnly() != null) { - query.setReadOnly(parent.isReadOnly()); - } - query.setParentNode(objectGraphNode); - query.setLazyLoadProperty(lazyLoadProperty); - - if (queryProps != null) { - queryProps.configureBeanQuery(query); - } - if (parent.isUseAutofetchManager()) { - query.setAutofetch(true); - } - } - - protected void register(EntityBeanIntercept ebi){ - - if (currentBuffer.isFull()) { - currentBuffer = createBuffer(secondaryBatchSize); - } - // set the persistenceContext on the bean first - ebi.setBeanLoader(currentBuffer, getPersistenceContext()); - currentBuffer.add(ebi); - } - - private LoadBuffer createBuffer(int size) { - LoadBuffer buffer = new LoadBuffer(this, size); - if (bufferList != null) { - bufferList.add(buffer); - } - return buffer; - } - - public void loadSecondaryQuery(OrmQueryRequest parentRequest) { - - if (!queryFetch) { - throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); - } - synchronized (this) { - - if (bufferList != null) { - for (LoadBuffer loadBuffer : bufferList) { - if (!loadBuffer.list.isEmpty()) { - boolean loadCache = false; - LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest, false, null, loadCache); - - parent.getEbeanServer().loadBean(req); - if (!queryProps.isQueryFetchAll()) { - // Stop - only fetch the first batch ... the rest will be lazy loaded - break; - } - } - // this is only run once - secondary query is a one shot deal - this.bufferList = null; - } - } - } - } - - - /** - * A buffer for batch loading beans on a given path. - */ - public static class LoadBuffer implements BeanLoader, LoadBeanBuffer { - - private final DLoadBeanContext context; - private final int batchSize; - private final List list; - private PersistenceContext persistenceContext; - - public LoadBuffer(DLoadBeanContext context, int batchSize) { - this.context = context; - this.batchSize = batchSize; - this.list = new ArrayList(batchSize); - } - - public int getBatchSize() { - return batchSize; - } - - /** - * Return true if the buffer is full. - */ - public boolean isFull() { - return batchSize == list.size(); - } - - /** - * Return true if the buffer is full. - */ - public void add(EntityBeanIntercept ebi) { - if (persistenceContext == null) { - // get persistenceContext from first loaded bean into the buffer - persistenceContext = ebi.getPersistenceContext(); - } - list.add(ebi); - } - - @Override - public List getBatch() { - return list; - } - - @Override - public String getName() { - return context.serverName; - } - - @Override - public String getFullPath() { - return context.fullPath; - } - - @Override - public BeanDescriptor getBeanDescriptor() { - return context.desc; - } - - @Override - public PersistenceContext getPersistenceContext() { - return persistenceContext; - } - - @Override - public void configureQuery(SpiQuery query, String lazyLoadProperty) { - context.configureQuery(query, lazyLoadProperty); - } - - @Override - public void loadBean(EntityBeanIntercept ebi) { - // A synchronized (this) is effectively held by EntityBeanIntercept.loadBean() - - if (context.desc.lazyLoadMany(ebi)) { - // lazy load property was a Many - return; - } - - if (context.hitCache && context.desc.cacheBeanLoad(ebi)) { - // successfully hit the L2 cache so don't invoke DB lazy loading - list.remove(ebi); - return; - } - - if (context.hitCache) { - // Check each of the beans in the batch to see if they are in the L2 cache. - Iterator iterator = list.iterator(); - while (iterator.hasNext()) { - EntityBeanIntercept bean = iterator.next(); - if (context.desc.cacheBeanLoad(bean)) { - iterator.remove(); - } - } - } - - LoadBeanRequest req = new LoadBeanRequest(this, true, ebi.getLazyLoadProperty(), context.hitCache); - context.desc.getEbeanServer().loadBean(req); - } - - } - -} +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.avaje.ebean.bean.BeanLoader; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadBeanBuffer; +import com.avaje.ebeaninternal.api.LoadBeanContext; +import com.avaje.ebeaninternal.api.LoadBeanRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Default implementation of LoadBeanContext. + */ +public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext{ + + private List bufferList; + + private LoadBuffer currentBuffer; + + public DLoadBeanContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { + + super(parent, desc, path, defaultBatchSize, queryProps); + + // bufferList only required when using query joins (queryFetch) + this.bufferList = (!queryFetch) ? null : new ArrayList(); + this.currentBuffer = createBuffer(firstBatchSize); + } + + /** + * Reset the buffers after a query iterator reset. + */ + public void clear() { + if (bufferList != null) { + bufferList.clear(); + } + currentBuffer = createBuffer(secondaryBatchSize); + } + + protected void configureQuery(SpiQuery query, String lazyLoadProperty) { + + // propagate the readOnly state + if (parent.isReadOnly() != null) { + query.setReadOnly(parent.isReadOnly()); + } + query.setParentNode(objectGraphNode); + query.setLazyLoadProperty(lazyLoadProperty); + + if (queryProps != null) { + queryProps.configureBeanQuery(query); + } + if (parent.isUseAutofetchManager()) { + query.setAutofetch(true); + } + } + + protected void register(EntityBeanIntercept ebi){ + + if (currentBuffer.isFull()) { + currentBuffer = createBuffer(secondaryBatchSize); + } + // set the persistenceContext on the bean first + ebi.setBeanLoader(currentBuffer, getPersistenceContext()); + currentBuffer.add(ebi); + } + + private LoadBuffer createBuffer(int size) { + LoadBuffer buffer = new LoadBuffer(this, size); + if (bufferList != null) { + bufferList.add(buffer); + } + return buffer; + } + + public void loadSecondaryQuery(OrmQueryRequest parentRequest) { + + if (!queryFetch) { + throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); + } + synchronized (this) { + + if (bufferList != null) { + for (LoadBuffer loadBuffer : bufferList) { + if (!loadBuffer.list.isEmpty()) { + boolean loadCache = false; + LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest, false, null, loadCache); + + parent.getEbeanServer().loadBean(req); + if (!queryProps.isQueryFetchAll()) { + // Stop - only fetch the first batch ... the rest will be lazy loaded + break; + } + } + // this is only run once - secondary query is a one shot deal + this.bufferList = null; + } + } + } + } + + + /** + * A buffer for batch loading beans on a given path. + */ + public static class LoadBuffer implements BeanLoader, LoadBeanBuffer { + + private final DLoadBeanContext context; + private final int batchSize; + private final List list; + private PersistenceContext persistenceContext; + + public LoadBuffer(DLoadBeanContext context, int batchSize) { + this.context = context; + this.batchSize = batchSize; + this.list = new ArrayList(batchSize); + } + + public int getBatchSize() { + return batchSize; + } + + /** + * Return true if the buffer is full. + */ + public boolean isFull() { + return batchSize == list.size(); + } + + /** + * Return true if the buffer is full. + */ + public void add(EntityBeanIntercept ebi) { + if (persistenceContext == null) { + // get persistenceContext from first loaded bean into the buffer + persistenceContext = ebi.getPersistenceContext(); + } + list.add(ebi); + } + + @Override + public List getBatch() { + return list; + } + + @Override + public String getName() { + return context.serverName; + } + + @Override + public String getFullPath() { + return context.fullPath; + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return context.desc; + } + + @Override + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + @Override + public void configureQuery(SpiQuery query, String lazyLoadProperty) { + context.configureQuery(query, lazyLoadProperty); + } + + @Override + public void loadBean(EntityBeanIntercept ebi) { + // A synchronized (this) is effectively held by EntityBeanIntercept.loadBean() + + if (context.desc.lazyLoadMany(ebi)) { + // lazy load property was a Many + return; + } + + if (context.hitCache && context.desc.cacheBeanLoad(ebi)) { + // successfully hit the L2 cache so don't invoke DB lazy loading + list.remove(ebi); + return; + } + + if (context.hitCache) { + // Check each of the beans in the batch to see if they are in the L2 cache. + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + EntityBeanIntercept bean = iterator.next(); + if (context.desc.cacheBeanLoad(bean)) { + iterator.remove(); + } + } + } + + LoadBeanRequest req = new LoadBeanRequest(this, true, ebi.getLazyLoadProperty(), context.hitCache); + context.desc.getEbeanServer().loadBean(req); + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index 5cc7b23c7..a24ccca51 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -1,297 +1,297 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import com.avaje.ebean.bean.*; -import com.avaje.ebeaninternal.api.LoadContext; -import com.avaje.ebeaninternal.api.LoadSecondaryQuery; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.el.ElPropertyValue; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Default implementation of LoadContext. - */ -public class DLoadContext implements LoadContext { - - private final SpiEbeanServer ebeanServer; - - private final BeanDescriptor rootDescriptor; - - private final Map beanMap = new HashMap(); - private final Map manyMap = new HashMap(); - - private final DLoadBeanContext rootBeanContext; - - private final Boolean readOnly; - private final boolean excludeBeanCache; - private final int defaultBatchSize; - - /** - * The path relative to the root of the object graph. - */ - private final String relativePath; - private final ObjectGraphOrigin origin; - private final boolean useAutofetchManager; - - private final Map nodePathMap = new HashMap(); - - private PersistenceContext persistenceContext; - - private List secQuery; - - public DLoadContext(OrmQueryRequest request) { - - this.persistenceContext = request.getPersistenceContext(); - this.ebeanServer = request.getServer(); - this.defaultBatchSize = request.getLazyLoadBatchSize(); - this.rootDescriptor = request.getBeanDescriptor(); - - SpiQuery query = request.getQuery(); - this.readOnly = query.isReadOnly(); - this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache()); - this.useAutofetchManager = query.getAutoFetchManager() != null; - - ObjectGraphNode parentNode = query.getParentNode(); - if (parentNode != null){ - this.origin = parentNode.getOriginQueryPoint(); - this.relativePath = parentNode.getPath(); - } else { - this.origin = null; - this.relativePath = null; - } - - // initialise rootBeanContext after origin and relativePath have been set - this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null); - } - - protected boolean isExcludeBeanCache() { - return excludeBeanCache; - } - - /** - * Return the minimum batch size when using QueryIterator with query joins. - */ - public int getSecondaryQueriesMinBatchSize(OrmQueryRequest parentRequest, int defaultQueryBatch) { - - if (secQuery == null) { - return -1; - } - - int maxBatch = 0; - for (int i = 0; i < secQuery.size(); i++) { - int batchSize = secQuery.get(i).getQueryFetchBatch(); - if (batchSize == 0) { - batchSize = defaultQueryBatch; - } - maxBatch = Math.max(maxBatch, batchSize); - } - return maxBatch; - } - - /** - * Execute all the secondary queries. - */ - public void executeSecondaryQueries(OrmQueryRequest parentRequest) { - - if (secQuery != null){ - for (int i = 0; i < secQuery.size(); i++) { - OrmQueryProperties properties = secQuery.get(i); - LoadSecondaryQuery load = getLoadSecondaryQuery(properties.getPath()); - load.loadSecondaryQuery(parentRequest); - } - } - } - - /** - * Return the LoadBeanContext or LoadManyContext for the given path. - */ - private LoadSecondaryQuery getLoadSecondaryQuery(String path){ - LoadSecondaryQuery beanLoad = beanMap.get(path); - if (beanLoad == null){ - beanLoad = manyMap.get(path); - } - return beanLoad; - } - - /** - * Remove the +query and +lazy secondary queries and - * register them with their appropriate LoadBeanContext - * or LoadManyContext. - *

- * The parts of the secondary queries are removed and used - * by LoadBeanContext/LoadManyContext to build the appropriate - * queries. - *

- */ - public void registerSecondaryQueries(SpiQuery query) { - - secQuery = query.removeQueryJoins(); - if (secQuery != null){ - for (int i = 0; i < secQuery.size(); i++) { - OrmQueryProperties props = secQuery.get(i); - registerSecondaryQuery(props); - } - } - - List lazyQueries = query.removeLazyJoins(); - if (lazyQueries != null){ - for (int i = 0; i < lazyQueries.size(); i++) { - OrmQueryProperties lazyProps = lazyQueries.get(i); - registerSecondaryQuery(lazyProps); - } - } - } - - /** - * Setup the load context at this path with OrmQueryProperties which is - * used to build the appropriate query for +query or +lazy loading. - */ - private void registerSecondaryQuery(OrmQueryProperties props) { - - String propName = props.getPath(); - ElPropertyValue elGetValue = rootDescriptor.getElGetValue(propName); - - boolean many = elGetValue.getBeanProperty().containsMany(); - registerSecondaryNode(many, props); - } - - - public ObjectGraphNode getObjectGraphNode(String path) { - - ObjectGraphNode node = nodePathMap.get(path); - if (node == null){ - node = createObjectGraphNode(path); - nodePathMap.put(path, node); - } - - return node; - } - - private ObjectGraphNode createObjectGraphNode(String path) { - - if (relativePath != null){ - if (path == null){ - path = relativePath; - } else { - path = relativePath+"."+path; - } - } - return new ObjectGraphNode(origin, path); - } - - public boolean isUseAutofetchManager() { - return useAutofetchManager; - } - - protected String getFullPath(String path) { - if (relativePath == null) { - return path; - } else { - return relativePath + "." + path; - } - } - - protected SpiEbeanServer getEbeanServer() { - return ebeanServer; - } - - /** - * Return the parent state which defines the sharedInstance and readOnly status - * which needs to be propagated to other beans and collections. - */ - protected Boolean isReadOnly() { - return readOnly; - } - - public PersistenceContext getPersistenceContext() { - return persistenceContext; - } - - public void resetPersistenceContext(PersistenceContext persistenceContext) { - this.persistenceContext = persistenceContext; - // clear the load contexts for beans and beanCollections - for (DLoadBeanContext beanContext : beanMap.values()) { - beanContext.clear(); - } - for (DLoadManyContext manyContext : manyMap.values()) { - manyContext.clear(); - } - this.rootBeanContext.clear(); - } - - public void register(String path, EntityBeanIntercept ebi){ - getBeanContext(path).register(ebi); - } - - public void register(String path, BeanCollection bc){ - getManyContext(path).register(bc); - } - - private DLoadBeanContext getBeanContext(String path) { - if (path == null){ - return rootBeanContext; - } - DLoadBeanContext beanContext = beanMap.get(path); - if (beanContext == null){ - beanContext = createBeanContext(path, defaultBatchSize, null); - beanMap.put(path, beanContext); - } - return beanContext; - } - - private void registerSecondaryNode(boolean many, OrmQueryProperties props) { - - String path = props.getPath(); - int lazyJoinBatch = props.getLazyFetchBatch(); - int batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize; - - if (many){ - DLoadManyContext manyContext = createManyContext(path, batchSize, props); - manyMap.put(path, manyContext); - } else { - DLoadBeanContext beanContext = createBeanContext(path, batchSize, props); - beanMap.put(path, beanContext); - } - } - - private DLoadManyContext getManyContext(String path) { - if (path == null){ - throw new RuntimeException("path is null?"); - } - DLoadManyContext ctx = manyMap.get(path); - if (ctx == null){ - ctx = createManyContext(path, defaultBatchSize, null); - manyMap.put(path, ctx); - } - return ctx; - } - - private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) { - - BeanPropertyAssocMany p = (BeanPropertyAssocMany)getBeanProperty(rootDescriptor, path); - - return new DLoadManyContext(this, p, path, batchSize, queryProps); - } - - private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) { - - BeanPropertyAssoc p = (BeanPropertyAssoc)getBeanProperty(rootDescriptor, path); - BeanDescriptor targetDescriptor = p.getTargetDescriptor(); - - return new DLoadBeanContext(this, targetDescriptor, path, batchSize, queryProps); - } - - private BeanProperty getBeanProperty(BeanDescriptor desc, String path){ - return desc.getBeanPropertyFromPath(path); - } - -} +package com.avaje.ebeaninternal.server.loadcontext; + +import com.avaje.ebean.bean.*; +import com.avaje.ebeaninternal.api.LoadContext; +import com.avaje.ebeaninternal.api.LoadSecondaryQuery; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.el.ElPropertyValue; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Default implementation of LoadContext. + */ +public class DLoadContext implements LoadContext { + + private final SpiEbeanServer ebeanServer; + + private final BeanDescriptor rootDescriptor; + + private final Map beanMap = new HashMap(); + private final Map manyMap = new HashMap(); + + private final DLoadBeanContext rootBeanContext; + + private final Boolean readOnly; + private final boolean excludeBeanCache; + private final int defaultBatchSize; + + /** + * The path relative to the root of the object graph. + */ + private final String relativePath; + private final ObjectGraphOrigin origin; + private final boolean useAutofetchManager; + + private final Map nodePathMap = new HashMap(); + + private PersistenceContext persistenceContext; + + private List secQuery; + + public DLoadContext(OrmQueryRequest request) { + + this.persistenceContext = request.getPersistenceContext(); + this.ebeanServer = request.getServer(); + this.defaultBatchSize = request.getLazyLoadBatchSize(); + this.rootDescriptor = request.getBeanDescriptor(); + + SpiQuery query = request.getQuery(); + this.readOnly = query.isReadOnly(); + this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache()); + this.useAutofetchManager = query.getAutoFetchManager() != null; + + ObjectGraphNode parentNode = query.getParentNode(); + if (parentNode != null){ + this.origin = parentNode.getOriginQueryPoint(); + this.relativePath = parentNode.getPath(); + } else { + this.origin = null; + this.relativePath = null; + } + + // initialise rootBeanContext after origin and relativePath have been set + this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null); + } + + protected boolean isExcludeBeanCache() { + return excludeBeanCache; + } + + /** + * Return the minimum batch size when using QueryIterator with query joins. + */ + public int getSecondaryQueriesMinBatchSize(OrmQueryRequest parentRequest, int defaultQueryBatch) { + + if (secQuery == null) { + return -1; + } + + int maxBatch = 0; + for (int i = 0; i < secQuery.size(); i++) { + int batchSize = secQuery.get(i).getQueryFetchBatch(); + if (batchSize == 0) { + batchSize = defaultQueryBatch; + } + maxBatch = Math.max(maxBatch, batchSize); + } + return maxBatch; + } + + /** + * Execute all the secondary queries. + */ + public void executeSecondaryQueries(OrmQueryRequest parentRequest) { + + if (secQuery != null){ + for (int i = 0; i < secQuery.size(); i++) { + OrmQueryProperties properties = secQuery.get(i); + LoadSecondaryQuery load = getLoadSecondaryQuery(properties.getPath()); + load.loadSecondaryQuery(parentRequest); + } + } + } + + /** + * Return the LoadBeanContext or LoadManyContext for the given path. + */ + private LoadSecondaryQuery getLoadSecondaryQuery(String path){ + LoadSecondaryQuery beanLoad = beanMap.get(path); + if (beanLoad == null){ + beanLoad = manyMap.get(path); + } + return beanLoad; + } + + /** + * Remove the +query and +lazy secondary queries and + * register them with their appropriate LoadBeanContext + * or LoadManyContext. + *

+ * The parts of the secondary queries are removed and used + * by LoadBeanContext/LoadManyContext to build the appropriate + * queries. + *

+ */ + public void registerSecondaryQueries(SpiQuery query) { + + secQuery = query.removeQueryJoins(); + if (secQuery != null){ + for (int i = 0; i < secQuery.size(); i++) { + OrmQueryProperties props = secQuery.get(i); + registerSecondaryQuery(props); + } + } + + List lazyQueries = query.removeLazyJoins(); + if (lazyQueries != null){ + for (int i = 0; i < lazyQueries.size(); i++) { + OrmQueryProperties lazyProps = lazyQueries.get(i); + registerSecondaryQuery(lazyProps); + } + } + } + + /** + * Setup the load context at this path with OrmQueryProperties which is + * used to build the appropriate query for +query or +lazy loading. + */ + private void registerSecondaryQuery(OrmQueryProperties props) { + + String propName = props.getPath(); + ElPropertyValue elGetValue = rootDescriptor.getElGetValue(propName); + + boolean many = elGetValue.getBeanProperty().containsMany(); + registerSecondaryNode(many, props); + } + + + public ObjectGraphNode getObjectGraphNode(String path) { + + ObjectGraphNode node = nodePathMap.get(path); + if (node == null){ + node = createObjectGraphNode(path); + nodePathMap.put(path, node); + } + + return node; + } + + private ObjectGraphNode createObjectGraphNode(String path) { + + if (relativePath != null){ + if (path == null){ + path = relativePath; + } else { + path = relativePath+"."+path; + } + } + return new ObjectGraphNode(origin, path); + } + + public boolean isUseAutofetchManager() { + return useAutofetchManager; + } + + protected String getFullPath(String path) { + if (relativePath == null) { + return path; + } else { + return relativePath + "." + path; + } + } + + protected SpiEbeanServer getEbeanServer() { + return ebeanServer; + } + + /** + * Return the parent state which defines the sharedInstance and readOnly status + * which needs to be propagated to other beans and collections. + */ + protected Boolean isReadOnly() { + return readOnly; + } + + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + public void resetPersistenceContext(PersistenceContext persistenceContext) { + this.persistenceContext = persistenceContext; + // clear the load contexts for beans and beanCollections + for (DLoadBeanContext beanContext : beanMap.values()) { + beanContext.clear(); + } + for (DLoadManyContext manyContext : manyMap.values()) { + manyContext.clear(); + } + this.rootBeanContext.clear(); + } + + public void register(String path, EntityBeanIntercept ebi){ + getBeanContext(path).register(ebi); + } + + public void register(String path, BeanCollection bc){ + getManyContext(path).register(bc); + } + + private DLoadBeanContext getBeanContext(String path) { + if (path == null){ + return rootBeanContext; + } + DLoadBeanContext beanContext = beanMap.get(path); + if (beanContext == null){ + beanContext = createBeanContext(path, defaultBatchSize, null); + beanMap.put(path, beanContext); + } + return beanContext; + } + + private void registerSecondaryNode(boolean many, OrmQueryProperties props) { + + String path = props.getPath(); + int lazyJoinBatch = props.getLazyFetchBatch(); + int batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize; + + if (many){ + DLoadManyContext manyContext = createManyContext(path, batchSize, props); + manyMap.put(path, manyContext); + } else { + DLoadBeanContext beanContext = createBeanContext(path, batchSize, props); + beanMap.put(path, beanContext); + } + } + + private DLoadManyContext getManyContext(String path) { + if (path == null){ + throw new RuntimeException("path is null?"); + } + DLoadManyContext ctx = manyMap.get(path); + if (ctx == null){ + ctx = createManyContext(path, defaultBatchSize, null); + manyMap.put(path, ctx); + } + return ctx; + } + + private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) { + + BeanPropertyAssocMany p = (BeanPropertyAssocMany)getBeanProperty(rootDescriptor, path); + + return new DLoadManyContext(this, p, path, batchSize, queryProps); + } + + private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) { + + BeanPropertyAssoc p = (BeanPropertyAssoc)getBeanProperty(rootDescriptor, path); + BeanDescriptor targetDescriptor = p.getTargetDescriptor(); + + return new DLoadBeanContext(this, targetDescriptor, path, batchSize, queryProps); + } + + private BeanProperty getBeanProperty(BeanDescriptor desc, String path){ + return desc.getBeanPropertyFromPath(path); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java index cbf3da204..004727d5c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -1,221 +1,221 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.BeanCollectionLoader; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.api.LoadManyBuffer; -import com.avaje.ebeaninternal.api.LoadManyContext; -import com.avaje.ebeaninternal.api.LoadManyRequest; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.core.OrmQueryRequest; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; - -public class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { - - protected final BeanPropertyAssocMany property; - - private List bufferList; - - private LoadBuffer currentBuffer; - - public DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany property, - String path, int defaultBatchSize, OrmQueryProperties queryProps) { - - super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps); - - this.property = property; - // bufferList only required when using query joins (queryFetch) - this.bufferList = (!queryFetch) ? null : new ArrayList(); - this.currentBuffer = createBuffer(firstBatchSize); - } - - private LoadBuffer createBuffer(int size) { - LoadBuffer buffer = new LoadBuffer(this, size); - if (bufferList != null) { - bufferList.add(buffer); - } - return buffer; - } - - /** - * Reset the buffers for a query iterator reset. - */ - public void clear() { - if (bufferList != null) { - bufferList.clear(); - } - currentBuffer = createBuffer(secondaryBatchSize); - } - - public void configureQuery(SpiQuery query){ - - // propagate the readOnly state - if (parent.isReadOnly() != null){ - query.setReadOnly(parent.isReadOnly()); - } - query.setParentNode(objectGraphNode); - - if (queryProps != null){ - queryProps.configureBeanQuery(query); - } - - if (parent.isUseAutofetchManager()){ - query.setAutofetch(true); - } - } - - public BeanPropertyAssocMany getBeanProperty() { - return property; - } - - public BeanDescriptor getBeanDescriptor() { - return desc; - } - - - public String getName() { - return parent.getEbeanServer().getName(); - } - - public void register(BeanCollection bc){ - - if (currentBuffer.isFull()) { - currentBuffer = createBuffer(secondaryBatchSize); - } - currentBuffer.add(bc); - bc.setLoader(currentBuffer); - } - - public void loadSecondaryQuery(OrmQueryRequest parentRequest) { - - if (!queryFetch) { - throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); - } - synchronized (this) { - if (bufferList != null) { - for (LoadBuffer loadBuffer : bufferList) { - if (!loadBuffer.list.isEmpty()) { - LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest, false, false, false); - parent.getEbeanServer().loadMany(req); - if (!queryProps.isQueryFetchAll()) { - // Stop - only fetch the first batch ... the rest will be lazy loaded - break; - } - } - } - - // this is only run once - secondary query is a one shot deal - this.bufferList = null; - } - } - } - - /** - * A buffer for batch loading bean collections on a given path. - * Supports batch lazy loading and secondary query loading. - */ - public static class LoadBuffer implements BeanCollectionLoader, LoadManyBuffer { - - private final PersistenceContext persistenceContext; - private final DLoadManyContext context; - private final int batchSize; - private final List> list; - - public LoadBuffer(DLoadManyContext context, int batchSize) { - this.context = context; - // set the persistence context as at this moment in - // case it changes as part of a findIterate etc - this.persistenceContext = context.getPersistenceContext(); - this.batchSize = batchSize; - this.list = new ArrayList>(batchSize); - } - - public int getBatchSize() { - return batchSize; - } - - /** - * Return true if the buffer is full. - */ - public boolean isFull() { - return batchSize == list.size(); - } - - /** - * Return true if the buffer is full. - */ - public void add(BeanCollection bc) { - list.add(bc); - } - - @Override - public List> getBatch() { - return list; - } - - @Override - public BeanPropertyAssocMany getBeanProperty() { - return context.property; - } - - @Override - public ObjectGraphNode getObjectGraphNode() { - return context.objectGraphNode; - } - - @Override - public void configureQuery(SpiQuery query){ - context.configureQuery(query); - } - - @Override - public String getName() { - return context.serverName; - } - - @Override - public BeanDescriptor getBeanDescriptor() { - return context.desc; - } - - @Override - public PersistenceContext getPersistenceContext() { - return persistenceContext; - } - - @Override - public String getFullPath() { - return context.fullPath; - } - - public void loadMany(BeanCollection bc, boolean onlyIds) { - - synchronized (this) { - boolean useCache = context.hitCache && !onlyIds; - if (useCache) { - EntityBean ownerBean = bc.getOwnerBean(); - BeanDescriptor parentDesc = context.desc.getBeanDescriptor(ownerBean.getClass()); - Object parentId = parentDesc.getId(ownerBean); - if (parentDesc.cacheManyPropLoad(context.property, bc, parentId, context.parent.isReadOnly())) { - // we loaded the bean from cache - list.remove(bc); - return; - } - } - - // Should reduce the list by checking each beanCollection in the L2 first before executing the query - - LoadManyRequest req = new LoadManyRequest(this, true, onlyIds, useCache); - context.parent.getEbeanServer().loadMany(req); - } - } - - } -} +package com.avaje.ebeaninternal.server.loadcontext; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadManyBuffer; +import com.avaje.ebeaninternal.api.LoadManyContext; +import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.core.OrmQueryRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +public class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { + + protected final BeanPropertyAssocMany property; + + private List bufferList; + + private LoadBuffer currentBuffer; + + public DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany property, + String path, int defaultBatchSize, OrmQueryProperties queryProps) { + + super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps); + + this.property = property; + // bufferList only required when using query joins (queryFetch) + this.bufferList = (!queryFetch) ? null : new ArrayList(); + this.currentBuffer = createBuffer(firstBatchSize); + } + + private LoadBuffer createBuffer(int size) { + LoadBuffer buffer = new LoadBuffer(this, size); + if (bufferList != null) { + bufferList.add(buffer); + } + return buffer; + } + + /** + * Reset the buffers for a query iterator reset. + */ + public void clear() { + if (bufferList != null) { + bufferList.clear(); + } + currentBuffer = createBuffer(secondaryBatchSize); + } + + public void configureQuery(SpiQuery query){ + + // propagate the readOnly state + if (parent.isReadOnly() != null){ + query.setReadOnly(parent.isReadOnly()); + } + query.setParentNode(objectGraphNode); + + if (queryProps != null){ + queryProps.configureBeanQuery(query); + } + + if (parent.isUseAutofetchManager()){ + query.setAutofetch(true); + } + } + + public BeanPropertyAssocMany getBeanProperty() { + return property; + } + + public BeanDescriptor getBeanDescriptor() { + return desc; + } + + + public String getName() { + return parent.getEbeanServer().getName(); + } + + public void register(BeanCollection bc){ + + if (currentBuffer.isFull()) { + currentBuffer = createBuffer(secondaryBatchSize); + } + currentBuffer.add(bc); + bc.setLoader(currentBuffer); + } + + public void loadSecondaryQuery(OrmQueryRequest parentRequest) { + + if (!queryFetch) { + throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); + } + synchronized (this) { + if (bufferList != null) { + for (LoadBuffer loadBuffer : bufferList) { + if (!loadBuffer.list.isEmpty()) { + LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest, false, false, false); + parent.getEbeanServer().loadMany(req); + if (!queryProps.isQueryFetchAll()) { + // Stop - only fetch the first batch ... the rest will be lazy loaded + break; + } + } + } + + // this is only run once - secondary query is a one shot deal + this.bufferList = null; + } + } + } + + /** + * A buffer for batch loading bean collections on a given path. + * Supports batch lazy loading and secondary query loading. + */ + public static class LoadBuffer implements BeanCollectionLoader, LoadManyBuffer { + + private final PersistenceContext persistenceContext; + private final DLoadManyContext context; + private final int batchSize; + private final List> list; + + public LoadBuffer(DLoadManyContext context, int batchSize) { + this.context = context; + // set the persistence context as at this moment in + // case it changes as part of a findIterate etc + this.persistenceContext = context.getPersistenceContext(); + this.batchSize = batchSize; + this.list = new ArrayList>(batchSize); + } + + public int getBatchSize() { + return batchSize; + } + + /** + * Return true if the buffer is full. + */ + public boolean isFull() { + return batchSize == list.size(); + } + + /** + * Return true if the buffer is full. + */ + public void add(BeanCollection bc) { + list.add(bc); + } + + @Override + public List> getBatch() { + return list; + } + + @Override + public BeanPropertyAssocMany getBeanProperty() { + return context.property; + } + + @Override + public ObjectGraphNode getObjectGraphNode() { + return context.objectGraphNode; + } + + @Override + public void configureQuery(SpiQuery query){ + context.configureQuery(query); + } + + @Override + public String getName() { + return context.serverName; + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return context.desc; + } + + @Override + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + @Override + public String getFullPath() { + return context.fullPath; + } + + public void loadMany(BeanCollection bc, boolean onlyIds) { + + synchronized (this) { + boolean useCache = context.hitCache && !onlyIds; + if (useCache) { + EntityBean ownerBean = bc.getOwnerBean(); + BeanDescriptor parentDesc = context.desc.getBeanDescriptor(ownerBean.getClass()); + Object parentId = parentDesc.getId(ownerBean); + if (parentDesc.cacheManyPropLoad(context.property, bc, parentId, context.parent.isReadOnly())) { + // we loaded the bean from cache + list.remove(bc); + return; + } + } + + // Should reduce the list by checking each beanCollection in the L2 first before executing the query + + LoadManyRequest req = new LoadManyRequest(this, true, onlyIds, useCache); + context.parent.getEbeanServer().loadMany(req); + } + } + + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java index 3bc6b1b3b..c76277461 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java @@ -1,303 +1,303 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequest; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; - -/** - * Controls the batch ordering of persist requests. - *

- * Persist requests include bean inserts updates deletes and UpdateSql and - * CallableSql requests. - *

- *

- * This object queues up the requests into appropriate entries according to the - * 'depth' and the 'type' of the requests. The depth relates to how saves and - * deletes cascade following the associations of a bean. For saving Associated - * One cascades reduce the depth (-1) and associated many's increase the depth. - * The initial depth of a request is 0. - *

- */ -public final class BatchControl { - - /** - * Used to sort queue entries by depth. - */ - private static final BatchDepthComparator depthComparator = new BatchDepthComparator(); - - /** - * Controls batching of the PreparedStatements. This should be flushed after - * each 'depth'. - */ - private final BatchedPstmtHolder pstmtHolder = new BatchedPstmtHolder(); - - /** - * Map of the BatchedBeanHolder objects. They each have a depth and are later - * sorted by their depth to get the execution order. - */ - private final HashMap beanHoldMap = new HashMap(); - - private final SpiTransaction transaction; - - /** - * The size at which the batch queue will flush. This should be close to the - * number of statements that are batched into a single PreparedStatement. This - * size relates to the size of a list in a BatchQueueEntry and not the total - * number of request which could be more than that. - */ - private int batchSize; - - /** - * If true try to get generated keys from inserts. - */ - private boolean getGeneratedKeys; - - private boolean batchFlushOnMixed = true; - - /** - * Create for a given transaction, PersistExecute, default size and getGeneratedKeys. - */ - public BatchControl(SpiTransaction t, int batchSize, boolean getGenKeys) { - this.transaction = t; - this.batchSize = batchSize; - this.getGeneratedKeys = getGenKeys; - transaction.setBatchControl(this); - } - - /** - * Set this flag to false to allow batching of a mix of Beans and UpdateSql - * (or CallableSql). Normally if you mix the two this will result in an - * automatic flush. - *

- * Note that UpdateSql and CallableSql will ALWAYS flush first. This is due to - * it already having been bound to a PreparedStatement where as the Beans go - * through a 2 step process when they are flushed (delayed binding). - *

- */ - public void setBatchFlushOnMixed(boolean flushBatchOnMixed) { - this.batchFlushOnMixed = flushBatchOnMixed; - } - - /** - * Return the batchSize. - */ - public int getBatchSize() { - return batchSize; - } - - /** - * Set the size of batch execution. - *

- * The user can set this via the Transaction. - *

- */ - public void setBatchSize(int batchSize) { - if (batchSize > 1) { - this.batchSize = batchSize; - } - } - - /** - * Set whether or not to use getGeneratedKeys for this batch execution. - *

- * The user can set this via the transaction - *

- */ - public void setGetGeneratedKeys(Boolean getGeneratedKeys) { - if (getGeneratedKeys != null) { - this.getGeneratedKeys = getGeneratedKeys; - } - } - - /** - * Execute a Orm Update, SqlUpdate or CallableSql. - *

- * These all go straight to jdbc and use addBatch(). Entity beans goto a queue - * and wait there so that the jdbc is executed in the correct order according - * to the depth. - *

- */ - public int executeStatementOrBatch(PersistRequest request, boolean batch) { - if (!batch || (batchFlushOnMixed && !isBeansEmpty())) { - // flush when mixing beans and updateSql - flush(); - } - if (!batch) { - // execute the request immediately without batching - return request.executeNow(); - } - - if (pstmtHolder.getMaxSize() >= batchSize) { - flush(); - } - // for OrmUpdate, SqlUpdate, CallableSql there is no queue... - // so straight to jdbc prepared statement and use addBatch(). - // aka executeNow() may use addBatch(). - request.executeNow(); - return -1; - } - - /** - * Entity Bean insert, update or delete. This will either execute the request - * immediately or queue it for batch processing later. The queue is flushed - * according to the depth (object graph depth). - */ - public int executeOrQueue(PersistRequestBean request, boolean batch) { - - if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) { - // flush when mixing beans and updateSql - flush(); - } - if (!batch) { - return request.executeNow(); - } - if (addToBatch(request)) { - // flush as the top level has hit the batch size - flush(); - } - return -1; - } - - /** - * Add the request to the batch and return true if we should flush. - */ - private boolean addToBatch(PersistRequestBean request) { - - BatchedBeanHolder beanHolder = getBeanHolder(request); - int bufferSize = beanHolder.append(request); - - // return true if top level has hit batch size - return bufferSize == batchSize && beanHolder.getOrder() == 100; - } - - /** - * Return the actual batch of PreparedStatements. - */ - public BatchedPstmtHolder getPstmtHolder() { - return pstmtHolder; - } - - /** - * Return true if the queue is empty. - */ - public boolean isEmpty() { - return (isBeansEmpty() && pstmtHolder.isEmpty()); - } - - /** - * Flush any batched PreparedStatements. - */ - protected void flushPstmtHolder() { - pstmtHolder.flush(getGeneratedKeys); - } - - /** - * Execute all the requests contained in the list. - */ - protected void executeNow(ArrayList list) { - for (int i = 0; i < list.size(); i++) { - if (i % batchSize == 0) { - // hit the batch size so flush - flushPstmtHolder(); - } - list.get(i).executeNow(); - } - flushPstmtHolder(); - } - - /** - * Flush without resetting the topOrder (maintains the depth info). - */ - public void flush() throws PersistenceException { - flush(false); - } - - /** - * Flush with a reset the topOrder (fully empty the batch). - */ - public void flushReset() throws PersistenceException { - flush(true); - } - - /** - * execute all the requests currently queued or batched. - */ - private void flush(boolean resetTop) throws PersistenceException { - - if (!pstmtHolder.isEmpty()) { - // Flush existing pstmts (updateSql or callableSql) - flushPstmtHolder(); - } - if (isEmpty()) { - // Nothing in queue to flush - return; - } - - // convert entry map to array for sorting - BatchedBeanHolder[] bsArray = getBeanHolderArray(); - // sort the entries by depth - Arrays.sort(bsArray, depthComparator); - - if (transaction.isLogSummary()) { - transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray)); - } - for (int i = 0; i < bsArray.length; i++) { - bsArray[i].executeNow(); - } - - if (resetTop) { - beanHoldMap.clear(); - } - } - - /** - * Return an entry for the given type description. The type description is - * typically the bean class name (or table name for MapBeans). - */ - private BatchedBeanHolder getBeanHolder(PersistRequestBean request) { - - BeanDescriptor beanDescriptor = request.getBeanDescriptor(); - BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName()); - if (batchBeanHolder == null) { - int relativeDepth = transaction.depth(); - if (relativeDepth == 0 && !beanHoldMap.isEmpty()) { - // flush and reset the batch as we are changing the type of our top level - // bean so just keep it simple and flush and reset the top - flushReset(); - } - - batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, 100 + relativeDepth); - beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder); - } - return batchBeanHolder; - } - - /** - * Return true if this holds no persist requests. - */ - private boolean isBeansEmpty() { - if (beanHoldMap.isEmpty()) { - return true; - } - for (BatchedBeanHolder beanHolder : beanHoldMap.values()) { - if (!beanHolder.isEmpty()) { - return false; - } - } - return true; - } - - /** - * Return the BatchedBeanHolder's ready for sorting and executing. - */ - private BatchedBeanHolder[] getBeanHolderArray() { - return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]); - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Controls the batch ordering of persist requests. + *

+ * Persist requests include bean inserts updates deletes and UpdateSql and + * CallableSql requests. + *

+ *

+ * This object queues up the requests into appropriate entries according to the + * 'depth' and the 'type' of the requests. The depth relates to how saves and + * deletes cascade following the associations of a bean. For saving Associated + * One cascades reduce the depth (-1) and associated many's increase the depth. + * The initial depth of a request is 0. + *

+ */ +public final class BatchControl { + + /** + * Used to sort queue entries by depth. + */ + private static final BatchDepthComparator depthComparator = new BatchDepthComparator(); + + /** + * Controls batching of the PreparedStatements. This should be flushed after + * each 'depth'. + */ + private final BatchedPstmtHolder pstmtHolder = new BatchedPstmtHolder(); + + /** + * Map of the BatchedBeanHolder objects. They each have a depth and are later + * sorted by their depth to get the execution order. + */ + private final HashMap beanHoldMap = new HashMap(); + + private final SpiTransaction transaction; + + /** + * The size at which the batch queue will flush. This should be close to the + * number of statements that are batched into a single PreparedStatement. This + * size relates to the size of a list in a BatchQueueEntry and not the total + * number of request which could be more than that. + */ + private int batchSize; + + /** + * If true try to get generated keys from inserts. + */ + private boolean getGeneratedKeys; + + private boolean batchFlushOnMixed = true; + + /** + * Create for a given transaction, PersistExecute, default size and getGeneratedKeys. + */ + public BatchControl(SpiTransaction t, int batchSize, boolean getGenKeys) { + this.transaction = t; + this.batchSize = batchSize; + this.getGeneratedKeys = getGenKeys; + transaction.setBatchControl(this); + } + + /** + * Set this flag to false to allow batching of a mix of Beans and UpdateSql + * (or CallableSql). Normally if you mix the two this will result in an + * automatic flush. + *

+ * Note that UpdateSql and CallableSql will ALWAYS flush first. This is due to + * it already having been bound to a PreparedStatement where as the Beans go + * through a 2 step process when they are flushed (delayed binding). + *

+ */ + public void setBatchFlushOnMixed(boolean flushBatchOnMixed) { + this.batchFlushOnMixed = flushBatchOnMixed; + } + + /** + * Return the batchSize. + */ + public int getBatchSize() { + return batchSize; + } + + /** + * Set the size of batch execution. + *

+ * The user can set this via the Transaction. + *

+ */ + public void setBatchSize(int batchSize) { + if (batchSize > 1) { + this.batchSize = batchSize; + } + } + + /** + * Set whether or not to use getGeneratedKeys for this batch execution. + *

+ * The user can set this via the transaction + *

+ */ + public void setGetGeneratedKeys(Boolean getGeneratedKeys) { + if (getGeneratedKeys != null) { + this.getGeneratedKeys = getGeneratedKeys; + } + } + + /** + * Execute a Orm Update, SqlUpdate or CallableSql. + *

+ * These all go straight to jdbc and use addBatch(). Entity beans goto a queue + * and wait there so that the jdbc is executed in the correct order according + * to the depth. + *

+ */ + public int executeStatementOrBatch(PersistRequest request, boolean batch) { + if (!batch || (batchFlushOnMixed && !isBeansEmpty())) { + // flush when mixing beans and updateSql + flush(); + } + if (!batch) { + // execute the request immediately without batching + return request.executeNow(); + } + + if (pstmtHolder.getMaxSize() >= batchSize) { + flush(); + } + // for OrmUpdate, SqlUpdate, CallableSql there is no queue... + // so straight to jdbc prepared statement and use addBatch(). + // aka executeNow() may use addBatch(). + request.executeNow(); + return -1; + } + + /** + * Entity Bean insert, update or delete. This will either execute the request + * immediately or queue it for batch processing later. The queue is flushed + * according to the depth (object graph depth). + */ + public int executeOrQueue(PersistRequestBean request, boolean batch) { + + if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) { + // flush when mixing beans and updateSql + flush(); + } + if (!batch) { + return request.executeNow(); + } + if (addToBatch(request)) { + // flush as the top level has hit the batch size + flush(); + } + return -1; + } + + /** + * Add the request to the batch and return true if we should flush. + */ + private boolean addToBatch(PersistRequestBean request) { + + BatchedBeanHolder beanHolder = getBeanHolder(request); + int bufferSize = beanHolder.append(request); + + // return true if top level has hit batch size + return bufferSize == batchSize && beanHolder.getOrder() == 100; + } + + /** + * Return the actual batch of PreparedStatements. + */ + public BatchedPstmtHolder getPstmtHolder() { + return pstmtHolder; + } + + /** + * Return true if the queue is empty. + */ + public boolean isEmpty() { + return (isBeansEmpty() && pstmtHolder.isEmpty()); + } + + /** + * Flush any batched PreparedStatements. + */ + protected void flushPstmtHolder() { + pstmtHolder.flush(getGeneratedKeys); + } + + /** + * Execute all the requests contained in the list. + */ + protected void executeNow(ArrayList list) { + for (int i = 0; i < list.size(); i++) { + if (i % batchSize == 0) { + // hit the batch size so flush + flushPstmtHolder(); + } + list.get(i).executeNow(); + } + flushPstmtHolder(); + } + + /** + * Flush without resetting the topOrder (maintains the depth info). + */ + public void flush() throws PersistenceException { + flush(false); + } + + /** + * Flush with a reset the topOrder (fully empty the batch). + */ + public void flushReset() throws PersistenceException { + flush(true); + } + + /** + * execute all the requests currently queued or batched. + */ + private void flush(boolean resetTop) throws PersistenceException { + + if (!pstmtHolder.isEmpty()) { + // Flush existing pstmts (updateSql or callableSql) + flushPstmtHolder(); + } + if (isEmpty()) { + // Nothing in queue to flush + return; + } + + // convert entry map to array for sorting + BatchedBeanHolder[] bsArray = getBeanHolderArray(); + // sort the entries by depth + Arrays.sort(bsArray, depthComparator); + + if (transaction.isLogSummary()) { + transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray)); + } + for (int i = 0; i < bsArray.length; i++) { + bsArray[i].executeNow(); + } + + if (resetTop) { + beanHoldMap.clear(); + } + } + + /** + * Return an entry for the given type description. The type description is + * typically the bean class name (or table name for MapBeans). + */ + private BatchedBeanHolder getBeanHolder(PersistRequestBean request) { + + BeanDescriptor beanDescriptor = request.getBeanDescriptor(); + BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName()); + if (batchBeanHolder == null) { + int relativeDepth = transaction.depth(); + if (relativeDepth == 0 && !beanHoldMap.isEmpty()) { + // flush and reset the batch as we are changing the type of our top level + // bean so just keep it simple and flush and reset the top + flushReset(); + } + + batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, 100 + relativeDepth); + beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder); + } + return batchBeanHolder; + } + + /** + * Return true if this holds no persist requests. + */ + private boolean isBeansEmpty() { + if (beanHoldMap.isEmpty()) { + return true; + } + for (BatchedBeanHolder beanHolder : beanHoldMap.values()) { + if (!beanHolder.isEmpty()) { + return false; + } + } + return true; + } + + /** + * Return the BatchedBeanHolder's ready for sorting and executing. + */ + private BatchedBeanHolder[] getBeanHolderArray() { + return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java index d5414b4e8..ad6dba553 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchDepthComparator.java @@ -1,30 +1,30 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.io.Serializable; -import java.util.Comparator; - -/** - * Used to sort BatchedBeanHolder by their depth. - *

- * Beans are queued and put into BatchedBeanHolder along with their depth. This - * delays the actually binding to PreparedStatements until the - * BatchedBeanHolder's are flushed. This is so that we can get the generated - * keys from inserts. These values are required to persist the 'detail' beans. - *

- */ -public class BatchDepthComparator implements Comparator, Serializable { - - private static final long serialVersionUID = 264611821665757991L; - - public int compare(BatchedBeanHolder b1, BatchedBeanHolder b2) { - - if (b1.getOrder() < b2.getOrder()) { - return -1; - } - if (b1.getOrder() == b2.getOrder()) { - return 0; - } - return 1; - } - -} +package com.avaje.ebeaninternal.server.persist; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Used to sort BatchedBeanHolder by their depth. + *

+ * Beans are queued and put into BatchedBeanHolder along with their depth. This + * delays the actually binding to PreparedStatements until the + * BatchedBeanHolder's are flushed. This is so that we can get the generated + * keys from inserts. These values are required to persist the 'detail' beans. + *

+ */ +public class BatchDepthComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 264611821665757991L; + + public int compare(BatchedBeanHolder b1, BatchedBeanHolder b2) { + + if (b1.getOrder() < b2.getOrder()) { + return -1; + } + if (b1.getOrder() == b2.getOrder()) { + return 0; + } + return 1; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java index bd1afdb26..82acbadef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchPostExecute.java @@ -1,36 +1,36 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.SQLException; - -/** - * Handles the processing required after batch execution. - *

- * This includes concurrency checking, generated keys on inserts, transaction - * logging, transaction event table modifcation and for beans resetting their - * 'loaded' status. - *

- */ -public interface BatchPostExecute { - - - /** - * Check that the rowCount is correct for this execute. This is for - * performing concurrency checking in batch execution. - */ - public void checkRowCount(int rowCount) throws SQLException; - - /** - * For inserts with generated keys. Otherwise not used. - */ - public void setGeneratedKey(Object idValue); - - /** - * Execute the post execute processing. - *

- * This includes transaction logging, transaction event table modification - * and for beans resetting their 'loaded' status. - *

- */ - public void postExecute() throws SQLException; - -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.SQLException; + +/** + * Handles the processing required after batch execution. + *

+ * This includes concurrency checking, generated keys on inserts, transaction + * logging, transaction event table modifcation and for beans resetting their + * 'loaded' status. + *

+ */ +public interface BatchPostExecute { + + + /** + * Check that the rowCount is correct for this execute. This is for + * performing concurrency checking in batch execution. + */ + public void checkRowCount(int rowCount) throws SQLException; + + /** + * For inserts with generated keys. Otherwise not used. + */ + public void setGeneratedKey(Object idValue); + + /** + * Execute the post execute processing. + *

+ * This includes transaction logging, transaction event table modification + * and for beans resetting their 'loaded' status. + *

+ */ + public void postExecute() throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java index 28b5e21a9..88a60f359 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java @@ -1,166 +1,166 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.IdentityHashMap; - -import com.avaje.ebeaninternal.server.core.PersistRequest; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; - -/** - * Holds lists of persist requests for beans of a given type. - *

- * 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. - *

- *

- * Has a depth which is used to determine the order in which it should be - * executed. The lowest depth is executed first. - *

- */ -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 inserts; - - /** - * The list of bean update requests. - */ - private ArrayList updates; - - /** - * The list of bean delete requests. - */ - private ArrayList 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 IdentityHashMap 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. - *

- * This will Batch all the similar requests into one or more BatchStatements - * and then execute them. - *

- */ - public void executeNow() { - // 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(); - } - - 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: - 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(); - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.IdentityHashMap; + +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Holds lists of persist requests for beans of a given type. + *

+ * 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. + *

+ *

+ * Has a depth which is used to determine the order in which it should be + * executed. The lowest depth is executed first. + *

+ */ +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 inserts; + + /** + * The list of bean update requests. + */ + private ArrayList updates; + + /** + * The list of bean delete requests. + */ + private ArrayList 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 IdentityHashMap 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. + *

+ * This will Batch all the similar requests into one or more BatchStatements + * and then execute them. + *

+ */ + public void executeNow() { + // 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(); + } + + 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: + 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(); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java index 05d33ef1a..1e3420ef4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmt.java @@ -1,157 +1,157 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; - -import com.avaje.ebeaninternal.server.core.PstmtBatch; - -/** - * A batched statement that is held in BatchedPstmtHolder. It has a list of - * BatchPostExecute which it will process after the statement is executed. - *

- * This can hold CallableStatements as well. - *

- */ -public class BatchedPstmt { - - /** - * The underlying statement. - */ - private PreparedStatement pstmt; - - /** - * True if an insert that uses generated keys. - */ - private final boolean isGenKeys; - - /** - * The list of BatchPostExecute used to perform post processing. - */ - private final ArrayList list = new ArrayList(); - - private final String sql; - - private final PstmtBatch pstmtBatch; - - private final boolean occCheck; - - - /** - * Create with a given statement. - * @param isGenKeys true if an insert that uses generatedKeys - */ - public BatchedPstmt(PreparedStatement pstmt, boolean isGenKeys, String sql, PstmtBatch pstmtBatch, boolean occCheck) { - - this.pstmt = pstmt; - this.isGenKeys = isGenKeys; - this.sql = sql; - this.pstmtBatch = pstmtBatch; - this.occCheck = occCheck; - } - - /** - * Return the number of batched statements. - */ - public int size() { - return list.size(); - } - - /** - * Return the sql - */ - public String getSql() { - return sql; - } - - /** - * Return the statement. - */ - public PreparedStatement getStatement() { - return pstmt; - } - - /** - * Add the BatchPostExecute to the list for post execute processing. - */ - public void add(BatchPostExecute batchExecute){ - list.add(batchExecute); - } - - /** - * Execute the statement using executeBatch(). - * Run any post processing including getGeneratedKeys. - */ - public void executeBatch(boolean getGeneratedKeys) throws SQLException { - - executeAndCheckRowCounts(); - if (isGenKeys && getGeneratedKeys){ - getGeneratedKeys(); - } - postExecute(); - close(); - } - - /** - * Close the underlying statement. - */ - public void close() throws SQLException { - if (pstmt != null){ - pstmt.close(); - pstmt = null; - } - } - - private void postExecute() throws SQLException { - for (int i = 0; i < list.size(); i++) { - list.get(i).postExecute(); - } - } - - private void executeAndCheckRowCounts() throws SQLException { - - if (pstmtBatch != null){ - // oracle specific JDBC batch processing - int rc = pstmtBatch.executeBatch(pstmt, list.size(), sql, occCheck); - if (list.size() == 1){ - list.get(0).checkRowCount(rc); - } - // the optimistic concurrency row count check - // has already been done by pstmtBatch so just return - return; - - } - - // normal JDBC batch processing - int[] results = pstmt.executeBatch(); - - if (results.length != list.size()){ - String s = "results array error "+results.length+" "+list.size(); - throw new SQLException(s); - } - - // check for concurrency exceptions... - for (int i = 0; i < results.length; i++) { - list.get(i).checkRowCount(results[i]); - } - } - - private void getGeneratedKeys() throws SQLException { - - int index = 0; - ResultSet rset = pstmt.getGeneratedKeys(); - try { - while(rset.next()) { - Object idValue = rset.getObject(1); - list.get(index).setGeneratedKey(idValue); - index++; - } - } finally { - if (rset != null){ - rset.close(); - } - } - } - -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import com.avaje.ebeaninternal.server.core.PstmtBatch; + +/** + * A batched statement that is held in BatchedPstmtHolder. It has a list of + * BatchPostExecute which it will process after the statement is executed. + *

+ * This can hold CallableStatements as well. + *

+ */ +public class BatchedPstmt { + + /** + * The underlying statement. + */ + private PreparedStatement pstmt; + + /** + * True if an insert that uses generated keys. + */ + private final boolean isGenKeys; + + /** + * The list of BatchPostExecute used to perform post processing. + */ + private final ArrayList list = new ArrayList(); + + private final String sql; + + private final PstmtBatch pstmtBatch; + + private final boolean occCheck; + + + /** + * Create with a given statement. + * @param isGenKeys true if an insert that uses generatedKeys + */ + public BatchedPstmt(PreparedStatement pstmt, boolean isGenKeys, String sql, PstmtBatch pstmtBatch, boolean occCheck) { + + this.pstmt = pstmt; + this.isGenKeys = isGenKeys; + this.sql = sql; + this.pstmtBatch = pstmtBatch; + this.occCheck = occCheck; + } + + /** + * Return the number of batched statements. + */ + public int size() { + return list.size(); + } + + /** + * Return the sql + */ + public String getSql() { + return sql; + } + + /** + * Return the statement. + */ + public PreparedStatement getStatement() { + return pstmt; + } + + /** + * Add the BatchPostExecute to the list for post execute processing. + */ + public void add(BatchPostExecute batchExecute){ + list.add(batchExecute); + } + + /** + * Execute the statement using executeBatch(). + * Run any post processing including getGeneratedKeys. + */ + public void executeBatch(boolean getGeneratedKeys) throws SQLException { + + executeAndCheckRowCounts(); + if (isGenKeys && getGeneratedKeys){ + getGeneratedKeys(); + } + postExecute(); + close(); + } + + /** + * Close the underlying statement. + */ + public void close() throws SQLException { + if (pstmt != null){ + pstmt.close(); + pstmt = null; + } + } + + private void postExecute() throws SQLException { + for (int i = 0; i < list.size(); i++) { + list.get(i).postExecute(); + } + } + + private void executeAndCheckRowCounts() throws SQLException { + + if (pstmtBatch != null){ + // oracle specific JDBC batch processing + int rc = pstmtBatch.executeBatch(pstmt, list.size(), sql, occCheck); + if (list.size() == 1){ + list.get(0).checkRowCount(rc); + } + // the optimistic concurrency row count check + // has already been done by pstmtBatch so just return + return; + + } + + // normal JDBC batch processing + int[] results = pstmt.executeBatch(); + + if (results.length != list.size()){ + String s = "results array error "+results.length+" "+list.size(); + throw new SQLException(s); + } + + // check for concurrency exceptions... + for (int i = 0; i < results.length; i++) { + list.get(i).checkRowCount(results[i]); + } + } + + private void getGeneratedKeys() throws SQLException { + + int index = 0; + ResultSet rset = pstmt.getGeneratedKeys(); + try { + while(rset.next()) { + Object idValue = rset.getObject(1); + list.get(index).setGeneratedKey(idValue); + index++; + } + } finally { + if (rset != null){ + rset.close(); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java index b9af30b6e..6b1fb6b19 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedPstmtHolder.java @@ -1,144 +1,144 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.LinkedHashMap; - -import javax.persistence.PersistenceException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Used to hold BatchedPstmt objects for batch based execution. - *

- * The BatchControl 'front ends' the batching by queuing the persist requests - * and ordering them according to depth and type. This object should only batch - * statements of a single 'depth' at any given time. - *

- */ -public class BatchedPstmtHolder { - - private static final Logger logger = LoggerFactory.getLogger(BatchedPstmtHolder.class); - - /** - * A Map of the statements using a String key. This is used so that the same - * Statement,Prepared,Callable is reused. - */ - private LinkedHashMap stmtMap = new LinkedHashMap(); - - /** - * The Max size across all the BatchedPstmt. - */ - private int maxSize; - - public BatchedPstmtHolder() { - - } - - /** - * Return the PreparedStatement if it has already been used in this Batch. - * This will return null if no matching PreparedStatement is found. - */ - public PreparedStatement getStmt(String stmtKey, BatchPostExecute postExecute) { - BatchedPstmt bs = stmtMap.get(stmtKey); - if (bs == null) { - // the PreparedStatement has need been created - return null; - } - // add the post execute processing for this bean/row - bs.add(postExecute); - - // maintain a max batch size for any given batched stmt. - // Used to determine when to flush. - int bsSize = bs.size(); - if (bsSize > maxSize){ - maxSize = bsSize; - } - return bs.getStatement(); - } - - /** - * Add a new PreparedStatement wrapped in the BatchStatement object. - */ - public void addStmt(BatchedPstmt bs, BatchPostExecute postExecute) { - // add the batch post execute to the statement for POST processing - bs.add(postExecute); - - // cache so that getStmt() can find it for additional beans/rows - stmtMap.put(bs.getSql(), bs); - } - - /** - * Return true if the batch has no statements to execute. - */ - public boolean isEmpty() { - return stmtMap.isEmpty(); - } - - /** - * Execute all batched PreparedStatements. - * - * @param getGeneratedKeys - * if true try to get generated keys for inserts - */ - public void flush(boolean getGeneratedKeys) throws PersistenceException { - - SQLException firstError = null; - String errorSql = null; - - // flag set if something fails. Will not execute - // but still need to close PreparedStatements. - boolean isError = false; - - for (BatchedPstmt bs : stmtMap.values()) { - try { - if (!isError) { - bs.executeBatch(getGeneratedKeys); - } - } catch (SQLException ex) { - SQLException next = ex.getNextException(); - while(next != null) { - logger.error("Next Exception during batch execution", next); - next = next.getNextException(); - } - - if (firstError == null) { - firstError = ex; - errorSql = bs.getSql(); - } else { - logger.error(null, ex); - } - isError = true; - - } finally { - try { - bs.close(); - } catch (SQLException ex) { - // error closing PreparedStatement - logger.error(null, ex); - } - } - } - - // clear the batch cache - stmtMap.clear(); - maxSize = 0; - - if (firstError != null) { - String msg = "Error when batch flush on sql: "+errorSql; - throw new PersistenceException(msg, firstError); - } - } - - /** - * Return the size of the biggest batched statement. - *

- * Used to determine when to flush the batch. - *

- */ - public int getMaxSize() { - return maxSize; - } - -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; + +import javax.persistence.PersistenceException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Used to hold BatchedPstmt objects for batch based execution. + *

+ * The BatchControl 'front ends' the batching by queuing the persist requests + * and ordering them according to depth and type. This object should only batch + * statements of a single 'depth' at any given time. + *

+ */ +public class BatchedPstmtHolder { + + private static final Logger logger = LoggerFactory.getLogger(BatchedPstmtHolder.class); + + /** + * A Map of the statements using a String key. This is used so that the same + * Statement,Prepared,Callable is reused. + */ + private LinkedHashMap stmtMap = new LinkedHashMap(); + + /** + * The Max size across all the BatchedPstmt. + */ + private int maxSize; + + public BatchedPstmtHolder() { + + } + + /** + * Return the PreparedStatement if it has already been used in this Batch. + * This will return null if no matching PreparedStatement is found. + */ + public PreparedStatement getStmt(String stmtKey, BatchPostExecute postExecute) { + BatchedPstmt bs = stmtMap.get(stmtKey); + if (bs == null) { + // the PreparedStatement has need been created + return null; + } + // add the post execute processing for this bean/row + bs.add(postExecute); + + // maintain a max batch size for any given batched stmt. + // Used to determine when to flush. + int bsSize = bs.size(); + if (bsSize > maxSize){ + maxSize = bsSize; + } + return bs.getStatement(); + } + + /** + * Add a new PreparedStatement wrapped in the BatchStatement object. + */ + public void addStmt(BatchedPstmt bs, BatchPostExecute postExecute) { + // add the batch post execute to the statement for POST processing + bs.add(postExecute); + + // cache so that getStmt() can find it for additional beans/rows + stmtMap.put(bs.getSql(), bs); + } + + /** + * Return true if the batch has no statements to execute. + */ + public boolean isEmpty() { + return stmtMap.isEmpty(); + } + + /** + * Execute all batched PreparedStatements. + * + * @param getGeneratedKeys + * if true try to get generated keys for inserts + */ + public void flush(boolean getGeneratedKeys) throws PersistenceException { + + SQLException firstError = null; + String errorSql = null; + + // flag set if something fails. Will not execute + // but still need to close PreparedStatements. + boolean isError = false; + + for (BatchedPstmt bs : stmtMap.values()) { + try { + if (!isError) { + bs.executeBatch(getGeneratedKeys); + } + } catch (SQLException ex) { + SQLException next = ex.getNextException(); + while(next != null) { + logger.error("Next Exception during batch execution", next); + next = next.getNextException(); + } + + if (firstError == null) { + firstError = ex; + errorSql = bs.getSql(); + } else { + logger.error(null, ex); + } + isError = true; + + } finally { + try { + bs.close(); + } catch (SQLException ex) { + // error closing PreparedStatement + logger.error(null, ex); + } + } + } + + // clear the batch cache + stmtMap.clear(); + maxSize = 0; + + if (firstError != null) { + String msg = "Error when batch flush on sql: "+errorSql; + throw new PersistenceException(msg, firstError); + } + } + + /** + * Return the size of the biggest batched statement. + *

+ * Used to determine when to flush the batch. + *

+ */ + public int getMaxSize() { + return maxSize; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java index 3f12be0cc..6c11f821a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersister.java @@ -1,27 +1,27 @@ -package com.avaje.ebeaninternal.server.persist; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.server.core.PersistRequestBean; - -/** - * Defines bean insert update and delete implementation. - */ -public interface BeanPersister { - - /** - * execute the insert bean request. - */ - public void insert(PersistRequestBean request) throws PersistenceException; - - /** - * execute the update bean request. - */ - public void update(PersistRequestBean request) throws PersistenceException; - - /** - * execute the delete bean request. - */ - public void delete(PersistRequestBean request) throws PersistenceException; - -} +package com.avaje.ebeaninternal.server.persist; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Defines bean insert update and delete implementation. + */ +public interface BeanPersister { + + /** + * execute the insert bean request. + */ + public void insert(PersistRequestBean request) throws PersistenceException; + + /** + * execute the update bean request. + */ + public void update(PersistRequestBean request) throws PersistenceException; + + /** + * execute the delete bean request. + */ + public void delete(PersistRequestBean request) throws PersistenceException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java index 2a7475057..18130784e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BeanPersisterFactory.java @@ -1,15 +1,15 @@ -package com.avaje.ebeaninternal.server.persist; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; - -/** - * Factory for creating BeanPersister implementations. - */ -public interface BeanPersisterFactory { - - /** - * Create the BeanPersister implemenation for a given type. - */ - public BeanPersister create(BeanDescriptor desc); - -} +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Factory for creating BeanPersister implementations. + */ +public interface BeanPersisterFactory { + + /** + * Create the BeanPersister implemenation for a given type. + */ + public BeanPersister create(BeanDescriptor desc); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java index bfe3f11c1..64bb41952 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BindValues.java @@ -1,116 +1,116 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.util.ArrayList; - -/** - * Holds a list of bind values for binding to a PreparedStatement. - */ -public class BindValues { - - int commentCount; - - final ArrayList list = new ArrayList(); - - /** - * Create with a Binder. - */ - public BindValues(){ - } - - /** - * Return the number of bind values. - */ - public int size() { - return list.size() - commentCount; - } - - /** - * Add a bind value with its JDBC datatype. - * - * @param value the bind value - * @param dbType the type as per java.sql.Types - */ - public void add(Object value, int dbType, String name){ - list.add(new Value(value, dbType, name)); - } - - public void addComment(String comment){ - ++commentCount; - list.add(new Value(comment)); - } - - /** - * List of bind values. - */ - public ArrayList values() { - return list; - } - - /** - * A Value has additionally the JDBC data type. - */ - public static class Value { - - private final Object value; - - private final int dbType; - - private final String name; - - private final boolean isComment; - - /** - * Create a comment. This is so that comments can be put into - * the bind log. - */ - public Value(String comment) { - this.name = comment; - this.isComment = true; - value = null; - dbType = 0; - } - - - /** - * Create the value. - */ - public Value(Object value, int dbType, String name) { - this.isComment = false; - this.value = value; - this.dbType = dbType; - this.name = name; - } - - /** - * This is a comment for the bind log and NOT an actual bind value. - */ - public boolean isComment() { - return isComment; - } - - /** - * Return the type as per java.sql.Types. - */ - public int getDbType() { - return dbType; - } - - /** - * Return the value. - */ - public Object getValue() { - return value; - } - - /** - * Return the property name. - */ - public String getName() { - return name; - } - - public String toString(){ - return ""+value; - } - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; + +/** + * Holds a list of bind values for binding to a PreparedStatement. + */ +public class BindValues { + + int commentCount; + + final ArrayList list = new ArrayList(); + + /** + * Create with a Binder. + */ + public BindValues(){ + } + + /** + * Return the number of bind values. + */ + public int size() { + return list.size() - commentCount; + } + + /** + * Add a bind value with its JDBC datatype. + * + * @param value the bind value + * @param dbType the type as per java.sql.Types + */ + public void add(Object value, int dbType, String name){ + list.add(new Value(value, dbType, name)); + } + + public void addComment(String comment){ + ++commentCount; + list.add(new Value(comment)); + } + + /** + * List of bind values. + */ + public ArrayList values() { + return list; + } + + /** + * A Value has additionally the JDBC data type. + */ + public static class Value { + + private final Object value; + + private final int dbType; + + private final String name; + + private final boolean isComment; + + /** + * Create a comment. This is so that comments can be put into + * the bind log. + */ + public Value(String comment) { + this.name = comment; + this.isComment = true; + value = null; + dbType = 0; + } + + + /** + * Create the value. + */ + public Value(Object value, int dbType, String name) { + this.isComment = false; + this.value = value; + this.dbType = dbType; + this.name = name; + } + + /** + * This is a comment for the bind log and NOT an actual bind value. + */ + public boolean isComment() { + return isComment; + } + + /** + * Return the type as per java.sql.Types. + */ + public int getDbType() { + return dbType; + } + + /** + * Return the value. + */ + public Object getValue() { + return value; + } + + /** + * Return the property name. + */ + public String getName() { + return name; + } + + public String toString(){ + return ""+value; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java index bf45101a1..7dab8c9c9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java @@ -1,382 +1,382 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.math.BigDecimal; -import java.sql.CallableStatement; -import java.sql.SQLException; -import java.sql.Types; -import java.util.ArrayList; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.server.core.Message; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.type.ScalarType; -import com.avaje.ebeaninternal.server.type.TypeManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Binds bean values to a PreparedStatement. - */ -public class Binder { - - private static final Logger logger = LoggerFactory.getLogger(Binder.class); - - //private final Calendar calendar; - - private final TypeManager typeManager; - - /** - * Set the PreparedStatement with which to bind variables to. - */ - public Binder(TypeManager typeManager) { - - this.typeManager = typeManager; - //this.calendar = new GregorianCalendar(); - } - - /** - * Bind the values to the Prepared Statement. - */ - public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) - throws SQLException { - - String logPrefix = ""; - - ArrayList list = bindValues.values(); - for (int i = 0; i < list.size(); i++) { - BindValues.Value bindValue = list.get(i); - if (bindValue.isComment()) { - if (bindBuf != null) { - bindBuf.append(bindValue.getName()); - if (logPrefix.equals("")) { - logPrefix = ", "; - } - } - } else { - Object val = bindValue.getValue(); - int dt = bindValue.getDbType(); - bindObject(dataBind, val, dt); - - if (bindBuf != null) { - bindBuf.append(logPrefix); - if (logPrefix.equals("")) { - logPrefix = ", "; - } - bindBuf.append(bindValue.getName()); - bindBuf.append("="); - if (isLob(dt)) { - bindBuf.append("[LOB]"); - } else { - bindBuf.append(String.valueOf(val)); - } - } - } - } - } - - /** - * Bind the list of positionedParameters in BindParams. - */ - public String bind(BindParams bindParams, DataBind dataBind) - throws SQLException { - - StringBuilder bindLog = new StringBuilder(); - bind(bindParams, dataBind, bindLog); - return bindLog.toString(); - } - - /** - * Bind the list of positionedParameters in BindParams. - */ - public void bind(BindParams bindParams, DataBind dataBind, StringBuilder bindLog) - throws SQLException { - - bind(bindParams.positionedParameters(), dataBind, bindLog); - } - - /** - * Bind the list of parameters.. - */ - public void bind(List list, DataBind dataBind, StringBuilder bindLog) - throws SQLException { - - CallableStatement cstmt = null; - - if (dataBind.getPstmt() instanceof CallableStatement) { - cstmt = (CallableStatement) dataBind.getPstmt(); - } - - // the iterator is assumed to be in the correct order - Object value = null; - try { - for (int i = 0; i < list.size(); i++) { - - BindParams.Param param = list.get(i); - - if (param.isOutParam() && cstmt != null){ - cstmt.registerOutParameter(dataBind.nextPos(), param.getType()); - if (param.isInParam()) { - dataBind.decrementPos(); - } - } - if (param.isInParam()) { - value = param.getInValue(); - if (bindLog != null) { - if (param.isEncryptionKey()){ - bindLog.append("****"); - } else { - bindLog.append(value); - } - bindLog.append(", "); - } - if (value == null) { - // this doesn't work for query predicates - bindObject(dataBind, null, param.getType()); - } else { - bindObject(dataBind, value); - } - } - } - - } catch (SQLException ex) { - logger.warn(Message.msg("fetch.bind.error", "" + (dataBind.currentPos() - 1), value)); - throw ex; - } - } - - /** - * Bind an Object with unknown data type. - */ - public void bindObject(DataBind dataBind, Object value) throws SQLException { - - if (value == null) { - // null of unknown type - bindObject(dataBind, null, Types.OTHER); - - } else { - - ScalarType type = typeManager.getScalarType(value.getClass()); - if (type == null){ - // the type is not registered with the TypeManager. - String msg = "No ScalarType registered for "+value.getClass(); - throw new PersistenceException(msg); - - } else if (!type.isJdbcNative()) { - // convert to a JDBC native type - value = type.toJdbcType(value); - } - - int dbType = type.getJdbcType(); - bindObject(dataBind, value, dbType); - } - } - - /** - * bind a single value. - *

- * Note that java.math.BigInteger is supported by converting it to a Long. - *

- *

- * Note if we get a java.util.Date or java.util.Calendar then these have - * been anonymously passed in (UpdateSql etc). There is a global setting to - * convert then to a java.sql.Date or java.sql.Timestamp for binding. The - * default is that both are converted to java.sql.Timestamp. - *

- */ - public void bindObject(DataBind dataBind, Object data, int dbType) - throws SQLException { - - if (data == null){ - dataBind.setNull(dbType); - return; - } - - switch (dbType) { - case java.sql.Types.LONGVARCHAR: - bindLongVarChar(dataBind, data); - break; - - case java.sql.Types.LONGVARBINARY: - bindLongVarBinary(dataBind, data); - break; - - case java.sql.Types.CLOB: - bindClob(dataBind, data); - break; - - case java.sql.Types.BLOB: - bindBlob(dataBind, data); - break; - - default: - - bindSimpleData(dataBind, dbType, data); - } - } - - /** - * Binds the value to the statement according to the data type. - */ - private void bindSimpleData(DataBind b, int dataType, Object data) - throws SQLException { - - try { - switch (dataType) { - case java.sql.Types.BOOLEAN: - b.setBoolean((Boolean) data); - break; - case java.sql.Types.BIT: - // Types.BIT should map to Java Boolean - b.setBoolean((Boolean) data); - break; - - case java.sql.Types.VARCHAR: - b.setString((String) data); - break; - - case java.sql.Types.CHAR: - b.setString(data.toString()); - break; - - case java.sql.Types.TINYINT: - b.setByte((Byte) data); - break; - - case java.sql.Types.SMALLINT: - b.setShort((Short) data); - break; - - case java.sql.Types.INTEGER: - b.setInt((Integer) data); - break; - - case java.sql.Types.BIGINT: - b.setLong((Long) data); - break; - - case java.sql.Types.REAL: - b.setFloat((Float) data); - break; - - case java.sql.Types.FLOAT: - // DB Float in theory maps to Java Double type - b.setDouble((Double) data); - break; - - case java.sql.Types.DOUBLE: - b.setDouble((Double) data); - break; - - case java.sql.Types.NUMERIC: - b.setBigDecimal((BigDecimal) data); - break; - - case java.sql.Types.DECIMAL: - b.setBigDecimal((BigDecimal) data); - break; - - case java.sql.Types.TIME: - //pstmt.setTime(index, (java.sql.Time) data, calendar); - b.setTime((java.sql.Time) data); - break; - - case java.sql.Types.DATE: - //pstmt.setDate(index, (java.sql.Date) data, calendar); - b.setDate((java.sql.Date) data); - break; - - case java.sql.Types.TIMESTAMP: - //pstmt.setTimestamp(index, (java.sql.Timestamp) data, calendar); - b.setTimestamp((java.sql.Timestamp) data); - break; - - case java.sql.Types.BINARY: - b.setBytes((byte[]) data); - break; - - case java.sql.Types.VARBINARY: - b.setBytes((byte[]) data); - break; - - case java.sql.Types.OTHER: - b.setObject(data); - break; - - case java.sql.Types.JAVA_OBJECT: - // Not too sure about this. - b.setObject(data); - break; - - default: - String msg = Message.msg("persist.bind.datatype", "" + dataType, "" + b.currentPos()); - throw new SQLException(msg); - } - - } catch (Exception e) { - String dataClass = "Data is null?"; - if (data != null) { - dataClass = data.getClass().getName(); - } - String m = "Error with property[" + b.currentPos() + "] dt[" + dataType + "]"; - m += "data[" + data + "][" + dataClass + "]"; - throw new PersistenceException(m, e); - } - } - - /** - * Bind String data to a LONGVARCHAR column. - */ - private void bindLongVarChar(DataBind b, Object data) - throws SQLException { - - String sd = (String) data; - b.setClob(sd); - } - - /** - * Bind byte[] data to a LONGVARBINARY column. - */ - private void bindLongVarBinary(DataBind b, Object data) - throws SQLException { - - byte[] bytes = (byte[]) data; - b.setBlob(bytes); - } - - /** - * Bind String data to a CLOB column. - */ - private void bindClob(DataBind b, Object data) throws SQLException { - - String sd = (String) data; - b.setClob(sd); - } - - /** - * Bind byte[] data to a BLOB column. - */ - private void bindBlob(DataBind b, Object data) throws SQLException { - - byte[] bytes = (byte[]) data; - b.setBlob(bytes); - } - - private boolean isLob(int dbType) { - switch (dbType) { - case Types.CLOB: - return true; - case Types.LONGVARCHAR: - return true; - case Types.BLOB: - return true; - case Types.LONGVARBINARY: - return true; - - default: - return false; - } - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.TypeManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Binds bean values to a PreparedStatement. + */ +public class Binder { + + private static final Logger logger = LoggerFactory.getLogger(Binder.class); + + //private final Calendar calendar; + + private final TypeManager typeManager; + + /** + * Set the PreparedStatement with which to bind variables to. + */ + public Binder(TypeManager typeManager) { + + this.typeManager = typeManager; + //this.calendar = new GregorianCalendar(); + } + + /** + * Bind the values to the Prepared Statement. + */ + public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) + throws SQLException { + + String logPrefix = ""; + + ArrayList list = bindValues.values(); + for (int i = 0; i < list.size(); i++) { + BindValues.Value bindValue = list.get(i); + if (bindValue.isComment()) { + if (bindBuf != null) { + bindBuf.append(bindValue.getName()); + if (logPrefix.equals("")) { + logPrefix = ", "; + } + } + } else { + Object val = bindValue.getValue(); + int dt = bindValue.getDbType(); + bindObject(dataBind, val, dt); + + if (bindBuf != null) { + bindBuf.append(logPrefix); + if (logPrefix.equals("")) { + logPrefix = ", "; + } + bindBuf.append(bindValue.getName()); + bindBuf.append("="); + if (isLob(dt)) { + bindBuf.append("[LOB]"); + } else { + bindBuf.append(String.valueOf(val)); + } + } + } + } + } + + /** + * Bind the list of positionedParameters in BindParams. + */ + public String bind(BindParams bindParams, DataBind dataBind) + throws SQLException { + + StringBuilder bindLog = new StringBuilder(); + bind(bindParams, dataBind, bindLog); + return bindLog.toString(); + } + + /** + * Bind the list of positionedParameters in BindParams. + */ + public void bind(BindParams bindParams, DataBind dataBind, StringBuilder bindLog) + throws SQLException { + + bind(bindParams.positionedParameters(), dataBind, bindLog); + } + + /** + * Bind the list of parameters.. + */ + public void bind(List list, DataBind dataBind, StringBuilder bindLog) + throws SQLException { + + CallableStatement cstmt = null; + + if (dataBind.getPstmt() instanceof CallableStatement) { + cstmt = (CallableStatement) dataBind.getPstmt(); + } + + // the iterator is assumed to be in the correct order + Object value = null; + try { + for (int i = 0; i < list.size(); i++) { + + BindParams.Param param = list.get(i); + + if (param.isOutParam() && cstmt != null){ + cstmt.registerOutParameter(dataBind.nextPos(), param.getType()); + if (param.isInParam()) { + dataBind.decrementPos(); + } + } + if (param.isInParam()) { + value = param.getInValue(); + if (bindLog != null) { + if (param.isEncryptionKey()){ + bindLog.append("****"); + } else { + bindLog.append(value); + } + bindLog.append(", "); + } + if (value == null) { + // this doesn't work for query predicates + bindObject(dataBind, null, param.getType()); + } else { + bindObject(dataBind, value); + } + } + } + + } catch (SQLException ex) { + logger.warn(Message.msg("fetch.bind.error", "" + (dataBind.currentPos() - 1), value)); + throw ex; + } + } + + /** + * Bind an Object with unknown data type. + */ + public void bindObject(DataBind dataBind, Object value) throws SQLException { + + if (value == null) { + // null of unknown type + bindObject(dataBind, null, Types.OTHER); + + } else { + + ScalarType type = typeManager.getScalarType(value.getClass()); + if (type == null){ + // the type is not registered with the TypeManager. + String msg = "No ScalarType registered for "+value.getClass(); + throw new PersistenceException(msg); + + } else if (!type.isJdbcNative()) { + // convert to a JDBC native type + value = type.toJdbcType(value); + } + + int dbType = type.getJdbcType(); + bindObject(dataBind, value, dbType); + } + } + + /** + * bind a single value. + *

+ * Note that java.math.BigInteger is supported by converting it to a Long. + *

+ *

+ * Note if we get a java.util.Date or java.util.Calendar then these have + * been anonymously passed in (UpdateSql etc). There is a global setting to + * convert then to a java.sql.Date or java.sql.Timestamp for binding. The + * default is that both are converted to java.sql.Timestamp. + *

+ */ + public void bindObject(DataBind dataBind, Object data, int dbType) + throws SQLException { + + if (data == null){ + dataBind.setNull(dbType); + return; + } + + switch (dbType) { + case java.sql.Types.LONGVARCHAR: + bindLongVarChar(dataBind, data); + break; + + case java.sql.Types.LONGVARBINARY: + bindLongVarBinary(dataBind, data); + break; + + case java.sql.Types.CLOB: + bindClob(dataBind, data); + break; + + case java.sql.Types.BLOB: + bindBlob(dataBind, data); + break; + + default: + + bindSimpleData(dataBind, dbType, data); + } + } + + /** + * Binds the value to the statement according to the data type. + */ + private void bindSimpleData(DataBind b, int dataType, Object data) + throws SQLException { + + try { + switch (dataType) { + case java.sql.Types.BOOLEAN: + b.setBoolean((Boolean) data); + break; + case java.sql.Types.BIT: + // Types.BIT should map to Java Boolean + b.setBoolean((Boolean) data); + break; + + case java.sql.Types.VARCHAR: + b.setString((String) data); + break; + + case java.sql.Types.CHAR: + b.setString(data.toString()); + break; + + case java.sql.Types.TINYINT: + b.setByte((Byte) data); + break; + + case java.sql.Types.SMALLINT: + b.setShort((Short) data); + break; + + case java.sql.Types.INTEGER: + b.setInt((Integer) data); + break; + + case java.sql.Types.BIGINT: + b.setLong((Long) data); + break; + + case java.sql.Types.REAL: + b.setFloat((Float) data); + break; + + case java.sql.Types.FLOAT: + // DB Float in theory maps to Java Double type + b.setDouble((Double) data); + break; + + case java.sql.Types.DOUBLE: + b.setDouble((Double) data); + break; + + case java.sql.Types.NUMERIC: + b.setBigDecimal((BigDecimal) data); + break; + + case java.sql.Types.DECIMAL: + b.setBigDecimal((BigDecimal) data); + break; + + case java.sql.Types.TIME: + //pstmt.setTime(index, (java.sql.Time) data, calendar); + b.setTime((java.sql.Time) data); + break; + + case java.sql.Types.DATE: + //pstmt.setDate(index, (java.sql.Date) data, calendar); + b.setDate((java.sql.Date) data); + break; + + case java.sql.Types.TIMESTAMP: + //pstmt.setTimestamp(index, (java.sql.Timestamp) data, calendar); + b.setTimestamp((java.sql.Timestamp) data); + break; + + case java.sql.Types.BINARY: + b.setBytes((byte[]) data); + break; + + case java.sql.Types.VARBINARY: + b.setBytes((byte[]) data); + break; + + case java.sql.Types.OTHER: + b.setObject(data); + break; + + case java.sql.Types.JAVA_OBJECT: + // Not too sure about this. + b.setObject(data); + break; + + default: + String msg = Message.msg("persist.bind.datatype", "" + dataType, "" + b.currentPos()); + throw new SQLException(msg); + } + + } catch (Exception e) { + String dataClass = "Data is null?"; + if (data != null) { + dataClass = data.getClass().getName(); + } + String m = "Error with property[" + b.currentPos() + "] dt[" + dataType + "]"; + m += "data[" + data + "][" + dataClass + "]"; + throw new PersistenceException(m, e); + } + } + + /** + * Bind String data to a LONGVARCHAR column. + */ + private void bindLongVarChar(DataBind b, Object data) + throws SQLException { + + String sd = (String) data; + b.setClob(sd); + } + + /** + * Bind byte[] data to a LONGVARBINARY column. + */ + private void bindLongVarBinary(DataBind b, Object data) + throws SQLException { + + byte[] bytes = (byte[]) data; + b.setBlob(bytes); + } + + /** + * Bind String data to a CLOB column. + */ + private void bindClob(DataBind b, Object data) throws SQLException { + + String sd = (String) data; + b.setClob(sd); + } + + /** + * Bind byte[] data to a BLOB column. + */ + private void bindBlob(DataBind b, Object data) throws SQLException { + + byte[] bytes = (byte[]) data; + b.setBlob(bytes); + } + + private boolean isLob(int dbType) { + switch (dbType) { + case Types.CLOB: + return true; + case Types.LONGVARCHAR: + return true; + case Types.BLOB: + return true; + case Types.LONGVARBINARY: + return true; + + default: + return false; + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java index 731ebc1f1..3a316cbf2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Constant.java @@ -1,27 +1,27 @@ -package com.avaje.ebeaninternal.server.persist; - -/** - * Contants used in persist. - */ -public interface Constant { - - /** - * An INSERT clause. - */ - public static final int IN_INSERT = 1; - - /** - * An UPDATE SET clause. - */ - public static final int IN_UPDATE_SET = 2; - - /** - * An UPDATE WHERE clause. - */ - public static final int IN_UPDATE_WHERE = 3; - - /** - * A DELETE WHERE clause. - */ - public static final int IN_DELETE_WHERE = 4; -} +package com.avaje.ebeaninternal.server.persist; + +/** + * Contants used in persist. + */ +public interface Constant { + + /** + * An INSERT clause. + */ + public static final int IN_INSERT = 1; + + /** + * An UPDATE SET clause. + */ + public static final int IN_UPDATE_SET = 2; + + /** + * An UPDATE WHERE clause. + */ + public static final int IN_UPDATE_WHERE = 3; + + /** + * A DELETE WHERE clause. + */ + public static final int IN_DELETE_WHERE = 4; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java index 76607d7ad..bdd6221f3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java @@ -1,113 +1,113 @@ -package com.avaje.ebeaninternal.server.persist; - -import com.avaje.ebean.event.BeanPersistController; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.*; -import com.avaje.ebeaninternal.server.deploy.BeanManager; - -/** - * Default PersistExecute implementation using DML statements. - *

- * Supports the use of PreparedStatement batching. - *

- */ -public final class DefaultPersistExecute implements PersistExecute { - - private final ExeCallableSql exeCallableSql; - - private final ExeUpdateSql exeUpdateSql; - - private final ExeOrmUpdate exeOrmUpdate; - - /** - * The default batch size. - */ - private final int defaultBatchSize; - - /** - * Default for whether to call getGeneratedKeys after batch insert. - */ - private final boolean defaultBatchGenKeys = true; - - /** - * Construct this DmlPersistExecute. - */ - public DefaultPersistExecute(Binder binder, PstmtBatch pstmtBatch, int defaultBatchSize) { - - this.exeOrmUpdate = new ExeOrmUpdate(binder, pstmtBatch); - this.exeUpdateSql = new ExeUpdateSql(binder, pstmtBatch); - this.exeCallableSql = new ExeCallableSql(binder, pstmtBatch); - this.defaultBatchSize = defaultBatchSize; - } - - public BatchControl createBatchControl(SpiTransaction t) { - - // create a BatchControl and set its defaults - return new BatchControl(t, defaultBatchSize, defaultBatchGenKeys); - } - - /** - * execute the bean insert request. - */ - public void executeInsertBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preInsert(request)) { - persister.insert(request); - } - } - - /** - * execute the bean update request. - */ - public void executeUpdateBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preUpdate(request)) { - request.postControllerPrepareUpdate(); - persister.update(request); - } - } - - /** - * execute the bean delete request. - */ - public void executeDeleteBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preDelete(request)) { - persister.delete(request); - } - } - - /** - * Execute the updateSqlRequest - */ - public int executeOrmUpdate(PersistRequestOrmUpdate request) { - return exeOrmUpdate.execute(request); - } - - /** - * Execute the updateSqlRequest - */ - public int executeSqlUpdate(PersistRequestUpdateSql request) { - return exeUpdateSql.execute(request); - } - - /** - * Execute the CallableSqlRequest. - */ - public int executeSqlCallable(PersistRequestCallableSql request) { - return exeCallableSql.execute(request); - } - -} +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebean.event.BeanPersistController; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.*; +import com.avaje.ebeaninternal.server.deploy.BeanManager; + +/** + * Default PersistExecute implementation using DML statements. + *

+ * Supports the use of PreparedStatement batching. + *

+ */ +public final class DefaultPersistExecute implements PersistExecute { + + private final ExeCallableSql exeCallableSql; + + private final ExeUpdateSql exeUpdateSql; + + private final ExeOrmUpdate exeOrmUpdate; + + /** + * The default batch size. + */ + private final int defaultBatchSize; + + /** + * Default for whether to call getGeneratedKeys after batch insert. + */ + private final boolean defaultBatchGenKeys = true; + + /** + * Construct this DmlPersistExecute. + */ + public DefaultPersistExecute(Binder binder, PstmtBatch pstmtBatch, int defaultBatchSize) { + + this.exeOrmUpdate = new ExeOrmUpdate(binder, pstmtBatch); + this.exeUpdateSql = new ExeUpdateSql(binder, pstmtBatch); + this.exeCallableSql = new ExeCallableSql(binder, pstmtBatch); + this.defaultBatchSize = defaultBatchSize; + } + + public BatchControl createBatchControl(SpiTransaction t) { + + // create a BatchControl and set its defaults + return new BatchControl(t, defaultBatchSize, defaultBatchGenKeys); + } + + /** + * execute the bean insert request. + */ + public void executeInsertBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preInsert(request)) { + persister.insert(request); + } + } + + /** + * execute the bean update request. + */ + public void executeUpdateBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preUpdate(request)) { + request.postControllerPrepareUpdate(); + persister.update(request); + } + } + + /** + * execute the bean delete request. + */ + public void executeDeleteBean(PersistRequestBean request) { + + BeanManager mgr = request.getBeanManager(); + BeanPersister persister = mgr.getBeanPersister(); + + BeanPersistController controller = request.getBeanController(); + if (controller == null || controller.preDelete(request)) { + persister.delete(request); + } + } + + /** + * Execute the updateSqlRequest + */ + public int executeOrmUpdate(PersistRequestOrmUpdate request) { + return exeOrmUpdate.execute(request); + } + + /** + * Execute the updateSqlRequest + */ + public int executeSqlUpdate(PersistRequestUpdateSql request) { + return exeUpdateSql.execute(request); + } + + /** + * Execute the CallableSqlRequest. + */ + public int executeSqlCallable(PersistRequestCallableSql request) { + return exeCallableSql.execute(request); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java index 7d1fef57d..c015c3924 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -1,1240 +1,1240 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.persistence.PersistenceException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebean.CallableSql; -import com.avaje.ebean.Query; -import com.avaje.ebean.SqlUpdate; -import com.avaje.ebean.Transaction; -import com.avaje.ebean.Update; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.api.SpiUpdate; -import com.avaje.ebeaninternal.server.core.Message; -import com.avaje.ebeaninternal.server.core.PersistRequest; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; -import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; -import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; -import com.avaje.ebeaninternal.server.core.Persister; -import com.avaje.ebeaninternal.server.core.PstmtBatch; -import com.avaje.ebeaninternal.server.core.PersistRequest.Type; -import com.avaje.ebeaninternal.server.deploy.BeanCollectionUtil; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; -import com.avaje.ebeaninternal.server.deploy.BeanManager; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.deploy.IntersectionRow; -import com.avaje.ebeaninternal.server.deploy.ManyType; - -/** - * Persister implementation using DML. - *

- * This object uses DmlPersistExecute to perform the actual persist execution. - *

- *

- * This object: - *

    - *
  • Determines insert or update for saved beans
  • - *
  • Determines the concurrency mode
  • - *
  • Handles cascading of save and delete
  • - *
  • Handles the batching and queueing
  • - *

    - * - * @see com.avaje.ebeaninternal.server.persist.DefaultPersistExecute - */ -public final class DefaultPersister implements Persister { - - private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class); - - /** - * Actually does the persisting work. - */ - private final PersistExecute persistExecute; - - private final SpiEbeanServer server; - - private final BeanDescriptorManager beanDescriptorManager; - - private final boolean updatesDeleteMissingChildren; - - public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr, PstmtBatch pstmtBatch) { - - this.server = server; - this.updatesDeleteMissingChildren = server.getServerConfig().isUpdatesDeleteMissingChildren(); - this.beanDescriptorManager = descMgr; - this.persistExecute = new DefaultPersistExecute(binder, pstmtBatch, server.getServerConfig().getPersistBatchSize()); - } - - /** - * Execute the CallableSql. - */ - public int executeCallable(CallableSql callSql, Transaction t) { - - PersistRequestCallableSql request = new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute); - try { - request.initTransIfRequired(); - int rc = request.executeOrQueue(); - request.commitTransIfRequired(); - return rc; - - } catch (RuntimeException e) { - request.rollbackTransIfRequired(); - throw e; - } - } - - /** - * Execute the orm update. - */ - public int executeOrmUpdate(Update update, Transaction t) { - - SpiUpdate ormUpdate = (SpiUpdate) update; - - BeanManager mgr = beanDescriptorManager.getBeanManager(ormUpdate.getBeanType()); - - if (mgr == null) { - String msg = "No BeanManager found for type [" + ormUpdate.getBeanType() + "]. Is it an entity?"; - throw new PersistenceException(msg); - } - - PersistRequestOrmUpdate request = new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute); - try { - request.initTransIfRequired(); - int rc = request.executeOrQueue(); - request.commitTransIfRequired(); - return rc; - - } catch (RuntimeException e) { - request.rollbackTransIfRequired(); - throw e; - } - } - - /** - * Execute the updateSql. - */ - public int executeSqlUpdate(SqlUpdate updSql, Transaction t) { - - PersistRequestUpdateSql request = new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute); - try { - request.initTransIfRequired(); - int rc = request.executeOrQueue(); - request.commitTransIfRequired(); - return rc; - - } catch (RuntimeException e) { - request.rollbackTransIfRequired(); - throw e; - } - } - - /** - * Recursively delete the bean. This calls back to the EbeanServer. - */ - private void deleteRecurse(Object detailBean, Transaction t) { - // NB: a new PersistRequest is made - server.delete(detailBean, t); - } - - /** - * Update the bean. - */ - public void update(EntityBean entityBean, Transaction t) { - update(entityBean, t, updatesDeleteMissingChildren); - } - - /** - * Update the bean specifying deleteMissingChildren. - */ - public void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren) { - - PersistRequestBean req = createRequest(entityBean, t, null, PersistRequest.Type.UPDATE); - req.setDeleteMissingChildren(deleteMissingChildren); - try { - req.initTransIfRequiredWithBatchCascade(); - if (req.isReference()) { - // its a reference so see if there are manys to save... - if (req.isPersistCascade()) { - saveAssocMany(false, req, false); - } - req.checkUpdatedManysOnly(); - } else { - update(req); - } - - req.commitTransIfRequired(); - req.flushBatchOnCascade(); - - } catch (RuntimeException ex) { - req.rollbackTransIfRequired(); - throw ex; - } - } - - /** - * Insert or update the bean. - */ - public void save(EntityBean bean, Transaction t) { - if (bean._ebean_getIntercept().isLoaded()) { - // deleteMissingChildren is false when using 'save' on 'loaded' beans - update(bean, t, false); - } else { - insert(bean, t); - } - } - - /** - * Insert this bean. - */ - public void insert(EntityBean bean, Transaction t) { - - PersistRequestBean req = createRequest(bean, t, null, PersistRequest.Type.INSERT); - try { - req.initTransIfRequiredWithBatchCascade(); - insert(req); - req.commitTransIfRequired(); - req.flushBatchOnCascade(); - - } catch (RuntimeException ex) { - req.rollbackTransIfRequired(); - throw ex; - } - } - - private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode) { - - // determine insert or update taking into account stateless updates - PersistRequestBean request = createRequest(bean, t, parentBean, insertMode); - - if (request.isReference()) { - // its a reference... - if (request.isPersistCascade()) { - // save any associated List held beans - saveAssocMany(false, request, insertMode); - } - request.checkUpdatedManysOnly(); - - } else { - if (request.isInsert()) { - insert(request); - } else { - update(request); - } - } - } - - /** - * Insert the bean. - */ - private void insert(PersistRequestBean request) { - - if (request.isRegisteredBean()){ - // skip as already inserted/updated in this request (recursive cascading) - return; - } - - try { - if (request.isPersistCascade()) { - // save associated One beans recursively first - saveAssocOne(request, true); - } - - // set the IDGenerated value if required - setIdGenValue(request); - request.executeOrQueue(); - - if (request.isPersistCascade()) { - // save any associated List held beans - saveAssocMany(true, request, true); - } - } finally { - request.unRegisterBean(); - } - } - - /** - * Update the bean. - */ - private void update(PersistRequestBean request) { - - if (request.isRegisteredBean()){ - // skip as already inserted/updated in this request (recursive cascading) - return; - } - - try { - if (request.isPersistCascade()) { - // save associated One beans recursively first - saveAssocOne(request, false); - } - - if (request.isDirty()) { - request.executeOrQueue(); - - } else { - // skip validation on unchanged bean - if (logger.isDebugEnabled()) { - logger.debug(Message.msg("persist.update.skipped", request.getBean())); - } - } - - if (request.isPersistCascade()) { - // save all the beans in assocMany's after - saveAssocMany(false, request, false); - } - - request.checkUpdatedManysOnly(); - - } finally { - request.unRegisterBean(); - } - } - - /** - * Delete the bean with the explicit transaction. - */ - public void delete(EntityBean bean, Transaction t) { - - PersistRequestBean req = createRequest(bean, t, null, PersistRequest.Type.DELETE); - if (req.isRegisteredForDeleteBean()) { - // skip deleting bean. Used where cascade is on - // both sides of a relationship - if (logger.isDebugEnabled()) { - logger.debug("skipping delete on alreadyRegistered " + bean); - } - return; - } - - try { - req.initTransIfRequiredWithBatchCascade(); - delete(req); - req.commitTransIfRequired(); - req.flushBatchOnCascade(); - - } catch (RuntimeException ex) { - req.rollbackTransIfRequired(); - throw ex; - } - } - - private void deleteList(List beanList, Transaction t) { - for (int i = 0; i < beanList.size(); i++) { - EntityBean bean = (EntityBean)beanList.get(i); - delete(bean, t); - } - } - - /** - * Delete by a List of Id's. - */ - public void deleteMany(Class beanType, Collection ids, Transaction transaction) { - - if (ids == null || ids.size() == 0) { - return; - } - - BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); - - ArrayList idList = new ArrayList(ids.size()); - for (Object id : ids) { - // convert to appropriate type if required - idList.add(descriptor.convertId(id)); - } - - delete(descriptor, null, idList, transaction); - } - - /** - * Delete by Id. - */ - public int delete(Class beanType, Object id, Transaction transaction) { - - BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); - - // convert to appropriate type if required - id = descriptor.convertId(id); - return delete(descriptor, id, null, transaction); - } - - /** - * Delete by Id or a List of Id's. - */ - private int delete(BeanDescriptor descriptor, Object id, List idList, Transaction transaction) { - - SpiTransaction t = (SpiTransaction) transaction; - if (t.isPersistCascade()) { - BeanPropertyAssocOne[] propImportDelete = descriptor.propertiesOneImportedDelete(); - if (propImportDelete.length > 0) { - // We actually need to execute a query to get the foreign key values - // as they are required for the delete cascade. Query back just the - // Id and the appropriate foreign key values - Query q = deleteRequiresQuery(descriptor, propImportDelete); - if (idList != null) { - q.where().idIn(idList); - if (t.isLogSummary()) { - t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values"); - } - List beanList = server.findList(q, t); - deleteList(beanList, t); - return beanList.size(); - - } else { - q.where().idEq(id); - if (t.isLogSummary()) { - t.logSummary("-- DeleteById of " + descriptor.getName() + " id[" + id + "] requires fetch of foreign key values"); - } - EntityBean bean = (EntityBean)server.findUnique(q, t); - if (bean == null) { - return 0; - } else { - delete(bean, t); - return 1; - } - } - } - } - - if (t.isPersistCascade()) { - // OneToOne exported side with delete cascade - BeanPropertyAssocOne[] expOnes = descriptor.propertiesOneExportedDelete(); - for (int i = 0; i < expOnes.length; i++) { - BeanDescriptor targetDesc = expOnes[i].getTargetDescriptor(); - if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { - SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList); - executeSqlUpdate(sqlDelete, t); - } else { - List childIds = expOnes[i].findIdsByParentId(id, idList, t); - deleteChildrenById(t, targetDesc, childIds); - } - } - - // OneToMany's with delete cascade - BeanPropertyAssocMany[] manys = descriptor.propertiesManyDelete(); - for (int i = 0; i < manys.length; i++) { - BeanDescriptor targetDesc = manys[i].getTargetDescriptor(); - if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { - // we can just delete children with a single statement - SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); - executeSqlUpdate(sqlDelete, t); - } else { - // we need to fetch the Id's to delete (recurse or notify L2 cache) - List childIds = manys[i].findIdsByParentId(id, idList, t, null); - if (!childIds.isEmpty()) { - delete(targetDesc, null, childIds, t); - } - } - } - } - - // ManyToMany's ... delete from intersection table - BeanPropertyAssocMany[] manys = descriptor.propertiesManyToMany(); - for (int i = 0; i < manys.length; i++) { - SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); - if (t.isLogSummary()) { - t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName()); - } - executeSqlUpdate(sqlDelete, t); - } - - // delete the bean(s) - SqlUpdate deleteById = descriptor.deleteById(id, idList); - if (t.isLogSummary()) { - if (idList != null) { - t.logSummary("-- Deleting " + descriptor.getName() + " Ids: " + idList); - } else { - t.logSummary("-- Deleting " + descriptor.getName() + " Id: " + id); - } - } - - // use Id's to update L2 cache rather than Bulk table event - deleteById.setAutoTableMod(false); - if (idList != null) { - t.getEvent().addDeleteByIdList(descriptor, idList); - } else { - t.getEvent().addDeleteById(descriptor, id); - } - int rows = executeSqlUpdate(deleteById, t); - - // Delete from the persistence context so that it can't be fetched again later - PersistenceContext persistenceContext = t.getPersistenceContext(); - if (idList != null) { - for (Object idValue : idList) { - persistenceContext.deleted(descriptor.getBeanType(), idValue); - } - } else { - persistenceContext.deleted(descriptor.getBeanType(), id); - } - return rows; - } - - /** - * We need to create and execute a query to get the foreign key values as - * the delete cascades to them (foreign keys). - */ - private Query deleteRequiresQuery(BeanDescriptor desc, BeanPropertyAssocOne[] propImportDelete) { - - Query q = server.createQuery(desc.getBeanType()); - StringBuilder sb = new StringBuilder(30); - for (int i = 0; i < propImportDelete.length; i++) { - sb.append(propImportDelete[i].getName()).append(","); - } - q.setAutofetch(false); - q.select(sb.toString()); - return q; - } - - /** - * Delete the bean. - *

    - * Note that preDelete fires before the deletion of children. - *

    - */ - private void delete(PersistRequestBean request) { - - DeleteUnloadedForeignKeys unloadedForeignKeys = null; - - if (request.isPersistCascade()) { - // delete children first ... register the - // bean to handle bi-directional cascading - request.registerDeleteBean(); - deleteAssocMany(request); - request.unregisterDeleteBean(); - - unloadedForeignKeys = getDeleteUnloadedForeignKeys(request); - if (unloadedForeignKeys != null) { - // there are foreign keys that we don't have on this partially - // populated bean so we actually need to query them (to cascade delete) - unloadedForeignKeys.queryForeignKeys(); - } - } - - request.executeOrQueue(); - - if (request.isPersistCascade()) { - deleteAssocOne(request); - - if (unloadedForeignKeys != null) { - unloadedForeignKeys.deleteCascade(); - } - } - - } - - /** - * Save the associated child beans contained in a List. - *

    - * This will automatically copy over any join properties from the parent - * bean to the child beans. - *

    - */ - private void saveAssocMany(boolean insertedParent, PersistRequestBean request, boolean insertMode) { - - EntityBean parentBean = request.getEntityBean(); - BeanDescriptor desc = request.getBeanDescriptor(); - SpiTransaction t = request.getTransaction(); - - // exported ones with cascade save - BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedSave(); - for (int i = 0; i < expOnes.length; i++) { - BeanPropertyAssocOne prop = expOnes[i]; - - // check for partial beans - if (request.isLoadedProperty(prop)) { - EntityBean detailBean = prop.getValueAsEntityBean(parentBean); - if (detailBean != null) { - if (!prop.isSaveRecurseSkippable(detailBean)) { - t.depth(+1); - prop.setParentBeanToChild(parentBean, detailBean); - saveRecurse(detailBean, t, parentBean, insertMode); - t.depth(-1); - } - } - } - } - - // many's with cascade save - BeanPropertyAssocMany[] manys = desc.propertiesManySave(); - for (int i = 0; i < manys.length; i++) { - // check that property is loaded and not empty uninitialised collection - if (request.isLoadedProperty(manys[i]) && !manys[i].isEmptyBeanCollection(parentBean)) { - saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request), insertMode); - if (!insertedParent) { - request.addUpdatedManyProperty(manys[i]); - } - } - } - } - - /** - * Helper to wrap the details when saving a OneToMany or ManyToMany - * relationship. - */ - private static class SaveManyPropRequest { - private final boolean insertedParent; - private final BeanPropertyAssocMany many; - private final EntityBean parentBean; - private final SpiTransaction transaction; - private final boolean cascade; - private final boolean deleteMissingChildren; - - private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { - this.insertedParent = insertedParent; - this.many = many; - this.cascade = many.getCascadeInfo().isSave(); - this.parentBean = parentBean; - this.transaction = request.getTransaction(); - this.deleteMissingChildren = request.isDeleteMissingChildren(); - } - - private SaveManyPropRequest(BeanPropertyAssocMany many, EntityBean parentBean, SpiTransaction t) { - this.insertedParent = false; - this.many = many; - this.parentBean = parentBean; - this.transaction = t; - this.cascade = true; - this.deleteMissingChildren = false; - } - - public boolean isSaveIntersection() { - return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName()); - } - - private Object getValue() { - return many.getValue(parentBean); - } - - private boolean isModifyListenMode() { - return ModifyListenMode.REMOVALS.equals(many.getModifyListenMode()); - } - - private boolean isDeleteMissingChildren() { - return deleteMissingChildren; - } - - private boolean isInsertedParent() { - return insertedParent; - } - - private BeanPropertyAssocMany getMany() { - return many; - } - - private EntityBean getParentBean() { - return parentBean; - } - - private SpiTransaction getTransaction() { - return transaction; - } - - private boolean isCascade() { - return cascade; - } - } - - private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) { - - if (saveMany.getMany().isManyToMany()) { - - // check if we can save the m2m intersection in this direction - boolean saveIntersectionFromThisDirection = saveMany.isSaveIntersection(); - if (saveMany.isCascade()) { - // Need explicit Cascade to save the beans on other side - saveAssocManyDetails(saveMany, false, insertMode); - } - // for ManyToMany save the 'relationship' via inserts/deletes - // into/from the intersection table - if (saveIntersectionFromThisDirection) { - // only allowed on one direction of a m2m based on beanName - saveAssocManyIntersection(saveMany, saveMany.isDeleteMissingChildren()); - } - } else { - if (saveMany.isModifyListenMode()) { - // delete any removed beans via private owned. Needs to occur before - // a 'deleteMissingChildren' statement occurs - removeAssocManyPrivateOwned(saveMany); - } - if (saveMany.isCascade()) { - // potentially deletes 'missing children' for 'stateless update' - saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren(), insertMode); - } - } - } - - private void removeAssocManyPrivateOwned(SaveManyPropRequest saveMany) { - - Object details = saveMany.getValue(); - - // check that the list is not null and if it is a BeanCollection - // check that is has been populated (don't trigger lazy loading) - if (details instanceof BeanCollection) { - - BeanCollection c = (BeanCollection) details; - Set modifyRemovals = c.getModifyRemovals(); - if (modifyRemovals != null && !modifyRemovals.isEmpty()) { - - SpiTransaction t = saveMany.getTransaction(); - // increase depth for batching order - t.depth(+1); - for (Object removedBean : modifyRemovals) { - if (removedBean instanceof EntityBean) { - EntityBean eb = (EntityBean)removedBean; - if (eb._ebean_getIntercept().isLoaded()) { - // only delete if the bean was loaded meaning that - // it is know to exist in the DB - deleteRecurse(removedBean, t); - } - } - } - t.depth(-1); - } - } - } - - /** - * Save the details from a OneToMany collection. - */ - private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean insertMode) { - - BeanPropertyAssocMany prop = saveMany.getMany(); - - Object details = saveMany.getValue(); - - // check that the list is not null and if it is a BeanCollection - // check that is has been populated (don't trigger lazy loading) - // For a Map this is a collection of Map.Entry objects and not beans - Collection collection = BeanCollectionUtil.getActualEntries(details); - - if (collection == null) { - // nothing to do here - return; - } - - BeanDescriptor targetDescriptor = prop.getTargetDescriptor(); - if (saveMany.isInsertedParent()) { - // performance optimisation for large collections - targetDescriptor.preAllocateIds(collection.size()); - } - - ArrayList detailIds = null; - if (deleteMissingChildren) { - // collect the Id's (to exclude from deleteManyDetails) - detailIds = new ArrayList(); - } - - // increase depth for batching order - SpiTransaction t = saveMany.getTransaction(); - t.depth(+1); - - // if a map, then we get the key value and - // set it to the appropriate property on the - // detail bean before we save it - boolean isMap = ManyType.JAVA_MAP.equals(prop.getManyType()); - EntityBean parentBean = saveMany.getParentBean(); - Object mapKeyValue = null; - - boolean saveSkippable = prop.isSaveRecurseSkippable(); - boolean skipSavingThisBean; - - for (Object detailBean : collection) { - if (isMap) { - // its a map so need the key and value - Map.Entry entry = (Map.Entry) detailBean; - mapKeyValue = entry.getKey(); - detailBean = entry.getValue(); - } - - if (detailBean instanceof EntityBean) { - EntityBean detail = (EntityBean)detailBean; - EntityBeanIntercept ebi = detail._ebean_getIntercept(); - if (prop.isManyToMany()) { - skipSavingThisBean = targetDescriptor.isReference(ebi); - } else { - if (targetDescriptor.isReference(ebi)) { - // we can skip this one - skipSavingThisBean = true; - - } else if (ebi.isNewOrDirty()) { - skipSavingThisBean = false; - // set the parent bean to detailBean - prop.setJoinValuesToChild(parentBean, detail, mapKeyValue); - - } else { - // unmodified so skip depending on prop.isSaveRecurseSkippable(); - skipSavingThisBean = saveSkippable; - } - } - - if (!skipSavingThisBean) { - saveRecurse(detail, t, parentBean, insertMode); - } - if (detailIds != null) { - // remember the Id (other details not in the collection) will be removed - Object id = targetDescriptor.getId(detail); - if (!DmlUtil.isNullOrZero(id)) { - detailIds.add(id); - } - } - } - } - - if (detailIds != null) { - // deleteMissingChildren is true so deleting children that were not just processed - deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds); - } - - t.depth(-1); - } - - public int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) { - - BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); - BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); - return deleteAssocManyIntersection(ownerBean, prop, t); - } - - public void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) { - - BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); - BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); - - saveAssocManyIntersection(new SaveManyPropRequest(prop, ownerBean, (SpiTransaction) t), false); - } - - public void saveAssociation(EntityBean parentBean, String propertyName, Transaction t) { - - BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(parentBean.getClass()); - SpiTransaction trans = (SpiTransaction) t; - - BeanProperty prop = descriptor.getBeanProperty(propertyName); - if (prop == null) { - String msg = "Could not find property [" + propertyName + "] on bean " + parentBean.getClass(); - throw new PersistenceException(msg); - } - - if (prop instanceof BeanPropertyAssocMany) { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) prop; - saveMany(new SaveManyPropRequest(manyProp, parentBean, (SpiTransaction) t), true); - - } else if (prop instanceof BeanPropertyAssocOne) { - BeanPropertyAssocOne oneProp = (BeanPropertyAssocOne) prop; - EntityBean assocBean = oneProp.getValueAsEntityBean(parentBean); - - int depth = oneProp.isOneToOneExported() ? 1 : -1; - int revertDepth = -1 * depth; - - trans.depth(depth); - saveRecurse(assocBean, t, parentBean, true); - trans.depth(revertDepth); - - } else { - String msg = "Expecting [" + prop.getFullBeanName() + "] to be a OneToMany, OneToOne, ManyToOne or ManyToMany property?"; - throw new PersistenceException(msg); - } - - } - - /** - * Save the additions and removals from a ManyToMany collection as inserts - * and deletes from the intersection table. - *

    - * This is done via MapBeans. - *

    - */ - private void saveAssocManyIntersection(SaveManyPropRequest saveManyPropRequest, boolean deleteMissingChildren) { - - BeanPropertyAssocMany prop = saveManyPropRequest.getMany(); - Object value = prop.getValue(saveManyPropRequest.getParentBean()); - if (value == null) { - return; - } - - SpiTransaction t = saveManyPropRequest.getTransaction(); - boolean vanillaCollection = !(value instanceof BeanCollection); - - if (vanillaCollection || deleteMissingChildren) { - // delete all intersection rows and then treat all - // beans in the collection as additions - deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t); - } - - Collection deletions = null; - Collection additions; - - if (saveManyPropRequest.isInsertedParent() || vanillaCollection || deleteMissingChildren) { - // treat everything in the list/set/map as an intersection addition - if (value instanceof Map) { - additions = ((Map) value).values(); - } else if (value instanceof Collection) { - additions = (Collection) value; - } else { - String msg = "Unhandled ManyToMany type " + value.getClass().getName() + " for " + prop.getFullBeanName(); - throw new PersistenceException(msg); - } - if (!vanillaCollection) { - ((BeanCollection) value).modifyReset(); - } - } else { - // BeanCollection so get the additions/deletions - BeanCollection manyValue = (BeanCollection) value; - additions = manyValue.getModifyAdditions(); - deletions = manyValue.getModifyRemovals(); - // reset so the changes are only processed once - manyValue.modifyReset(); - } - - t.depth(+1); - - if (additions != null && !additions.isEmpty()) { - for (Object other : additions) { - EntityBean otherBean = (EntityBean)other; - // the object from the 'other' side of the ManyToMany - if (deletions != null && deletions.remove(otherBean)) { - String m = "Inserting and Deleting same object? " + otherBean; - if (t.isLogSummary()) { - t.logSummary(m); - } - logger.warn(m); - - } else { - if (!prop.hasImportedId(otherBean)) { - String msg = "ManyToMany bean " + otherBean + " does not have an Id value."; - throw new PersistenceException(msg); - - } else { - // build a intersection row for 'insert' - IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean); - SqlUpdate sqlInsert = intRow.createInsert(server); - executeSqlUpdate(sqlInsert, t); - } - } - } - } - if (deletions != null && !deletions.isEmpty()) { - for (Object other : deletions) { - EntityBean otherDelete = (EntityBean)other; - // the object from the 'other' side of the ManyToMany - // build a intersection row for 'delete' - IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete); - SqlUpdate sqlDelete = intRow.createDelete(server); - executeSqlUpdate(sqlDelete, t); - } - } - - // decrease the depth back to what it was - t.depth(-1); - } - - private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany many, Transaction t) { - - // delete all intersection rows for this bean - IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean); - SqlUpdate sqlDelete = intRow.createDeleteChildren(server); - - return executeSqlUpdate(sqlDelete, t); - } - - /** - * Delete beans in any associated many. - *

    - * This is called prior to deleting the parent bean. - *

    - */ - private void deleteAssocMany(PersistRequestBean request) { - - SpiTransaction t = request.getTransaction(); - t.depth(-1); - - BeanDescriptor desc = request.getBeanDescriptor(); - EntityBean parentBean = request.getEntityBean(); - - BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedDelete(); - if (expOnes.length > 0) { - - DeleteUnloadedForeignKeys unloaded = null; - for (int i = 0; i < expOnes.length; i++) { - BeanPropertyAssocOne prop = expOnes[i]; - if (request.isLoadedProperty(prop)) { - Object detailBean = prop.getValue(parentBean); - if (detailBean != null) { - deleteRecurse(detailBean, t); - } - } else { - if (unloaded == null) { - unloaded = new DeleteUnloadedForeignKeys(server, request); - } - unloaded.add(prop); - } - } - if (unloaded != null) { - unloaded.queryForeignKeys(); - unloaded.deleteCascade(); - } - } - - // Many's with delete cascade - BeanPropertyAssocMany[] manys = desc.propertiesManyDelete(); - for (int i = 0; i < manys.length; i++) { - if (manys[i].isManyToMany()) { - // delete associated rows from intersection table - deleteAssocManyIntersection(parentBean, manys[i], t); - - } else { - - if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) { - // PrivateOwned ... - Object details = manys[i].getValue(parentBean); - if (details instanceof BeanCollection) { - Set modifyRemovals = ((BeanCollection) details).getModifyRemovals(); - if (modifyRemovals != null && !modifyRemovals.isEmpty()) { - - // delete the orphans that have been removed from the collection - for (Object detail : modifyRemovals) { - EntityBean detailBean = (EntityBean)detail; - if (manys[i].hasId(detailBean)) { - deleteRecurse(detailBean, t); - } - } - } - } - } - - deleteManyDetails(t, desc, parentBean, manys[i], null); - } - } - - // restore the depth - t.depth(+1); - } - - /** - * Delete the 'many' detail beans for a given parent bean. - *

    - * For stateless updates this deletes details beans that are no longer in - * the many - the excludeDetailIds holds the detail beans that are in the - * collection (and should not be deleted). - *

    - */ - private void deleteManyDetails(SpiTransaction t, BeanDescriptor desc, EntityBean parentBean, - BeanPropertyAssocMany many, ArrayList excludeDetailIds) { - - if (many.getCascadeInfo().isDelete()) { - // cascade delete the beans in the collection - BeanDescriptor targetDesc = many.getTargetDescriptor(); - if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { - // Just delete all the children with one statement - IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds); - SqlUpdate sqlDelete = intRow.createDelete(server); - executeSqlUpdate(sqlDelete, t); - - } else { - // Delete recurse using the Id values of the children - Object parentId = desc.getId(parentBean); - List idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds); - if (!idsByParentId.isEmpty()) { - deleteChildrenById(t, targetDesc, idsByParentId); - } - } - } - } - - /** - * Cascade delete child entities by Id. - *

    - * Will use delete by object if the child entity has manyToMany relationships. - */ - private void deleteChildrenById(SpiTransaction t, BeanDescriptor targetDesc, List childIds) { - - if (targetDesc.propertiesManyToMany().length > 0) { - // convert into a list of reference objects and perform delete by object - List refList = new ArrayList(childIds.size()); - for (Object id : childIds) { - refList.add(targetDesc.createReference(null, id)); - } - deleteList(refList, t); - - } else { - // perform delete by statement if possible - delete(targetDesc, null, childIds, t); - } - } - - /** - * Save any associated one beans. - */ - private void saveAssocOne(PersistRequestBean request, boolean insertMode) { - - BeanDescriptor desc = request.getBeanDescriptor(); - - // imported ones with save cascade - BeanPropertyAssocOne[] ones = desc.propertiesOneImportedSave(); - - for (int i = 0; i < ones.length; i++) { - BeanPropertyAssocOne prop = ones[i]; - - // check for partial objects - if (request.isLoadedProperty(prop)) { - EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean()); - if (detailBean != null - && !prop.isSaveRecurseSkippable(detailBean) - && !prop.isReference(detailBean) - && !request.isParent(detailBean)) { - SpiTransaction t = request.getTransaction(); - t.depth(-1); - saveRecurse(detailBean, t, null, insertMode); - t.depth(+1); - } - } - } - } - - /** - * Support for loading any Imported Associated One properties that are not - * loaded but required for Delete cascade. - */ - private DeleteUnloadedForeignKeys getDeleteUnloadedForeignKeys(PersistRequestBean request) { - - DeleteUnloadedForeignKeys fkeys = null; - - BeanPropertyAssocOne[] ones = request.getBeanDescriptor().propertiesOneImportedDelete(); - for (int i = 0; i < ones.length; i++) { - if (!request.isLoadedProperty(ones[i])) { - // we have cascade Delete on a partially populated bean and - // this property was not loaded (so we are going to have to fetch it) - if (fkeys == null) { - fkeys = new DeleteUnloadedForeignKeys(server, request); - } - fkeys.add(ones[i]); - } - } - - return fkeys; - } - - /** - * Delete any associated one beans. - */ - private void deleteAssocOne(PersistRequestBean request) { - - BeanDescriptor desc = request.getBeanDescriptor(); - BeanPropertyAssocOne[] ones = desc.propertiesOneImportedDelete(); - - for (int i = 0; i < ones.length; i++) { - BeanPropertyAssocOne prop = ones[i]; - if (request.isLoadedProperty(prop)) { - Object detailBean = prop.getValue(request.getEntityBean()); - if (detailBean != null) { - EntityBean detail = (EntityBean)detailBean; - if (prop.hasId(detail)) { - deleteRecurse(detail, request.getTransaction()); - } - } - } - } - } - - /** - * Set Id Generated value for insert. - */ - private void setIdGenValue(PersistRequestBean request) { - - BeanDescriptor desc = request.getBeanDescriptor(); - if (!desc.isUseIdGenerator()) { - return; - } - - BeanProperty idProp = desc.getIdProperty(); - if (idProp == null || idProp.isEmbedded()) { - // not supporting IdGeneration for concatenated or Embedded - return; - } - - EntityBean bean = request.getEntityBean(); - Object uid = idProp.getValue(bean); - - if (DmlUtil.isNullOrZero(uid)) { - - // generate the nextId and set it to the property - Object nextId = desc.nextId(request.getTransaction()); - - // cast the data type if required and set it - desc.convertSetId(nextId, bean); - } - } - - /** - * Create the Persist Request Object that wraps all the objects used to - * perform an insert, update or delete. - */ - private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, PersistRequest.Type type) { - BeanManager mgr = getBeanManager(bean); - if (mgr == null) { - throw new PersistenceException(errNotRegistered(bean.getClass())); - } - return createRequest(bean, t, parentBean, mgr, type, false); - } - - /** - * Create an Insert or Update PersistRequestBean when cascading. - *

    - * This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type). - */ - private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) { - BeanManager mgr = getBeanManager(bean); - if (mgr == null) { - throw new PersistenceException(errNotRegistered(bean.getClass())); - } - BeanDescriptor desc = mgr.getBeanDescriptor(); - EntityBean entityBean = (EntityBean)bean; - // determine Insert or Update based on bean state and insert flag - PersistRequest.Type type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE; - return createRequest(bean, t, parentBean, mgr, type, true); - } - - /** - * Create the Persist Request Object that wraps all the objects used to - * perform an insert, update or delete. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, BeanManager mgr, PersistRequest.Type type, boolean saveRecurse) { - - return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse); - } - - private String errNotRegistered(Class beanClass) { - String msg = "The type [" + beanClass + "] is not a registered entity?"; - msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; - msg += " If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar()."; - return msg; - } - - /** - * Return the BeanDescriptor for a bean that is being persisted. - *

    - * Note that this checks to see if the bean is a MapBean with a tableName. - * If so it will return the table based BeanDescriptor. - *

    - */ - @SuppressWarnings("unchecked") - private BeanManager getBeanManager(T bean) { - - return (BeanManager) beanDescriptorManager.getBeanManager(bean.getClass()); - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.persistence.PersistenceException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebean.CallableSql; +import com.avaje.ebean.Query; +import com.avaje.ebean.SqlUpdate; +import com.avaje.ebean.Transaction; +import com.avaje.ebean.Update; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollection.ModifyListenMode; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; +import com.avaje.ebeaninternal.server.core.Persister; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.core.PersistRequest.Type; +import com.avaje.ebeaninternal.server.deploy.BeanCollectionUtil; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.IntersectionRow; +import com.avaje.ebeaninternal.server.deploy.ManyType; + +/** + * Persister implementation using DML. + *

    + * This object uses DmlPersistExecute to perform the actual persist execution. + *

    + *

    + * This object: + *

      + *
    • Determines insert or update for saved beans
    • + *
    • Determines the concurrency mode
    • + *
    • Handles cascading of save and delete
    • + *
    • Handles the batching and queueing
    • + *

      + * + * @see com.avaje.ebeaninternal.server.persist.DefaultPersistExecute + */ +public final class DefaultPersister implements Persister { + + private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class); + + /** + * Actually does the persisting work. + */ + private final PersistExecute persistExecute; + + private final SpiEbeanServer server; + + private final BeanDescriptorManager beanDescriptorManager; + + private final boolean updatesDeleteMissingChildren; + + public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr, PstmtBatch pstmtBatch) { + + this.server = server; + this.updatesDeleteMissingChildren = server.getServerConfig().isUpdatesDeleteMissingChildren(); + this.beanDescriptorManager = descMgr; + this.persistExecute = new DefaultPersistExecute(binder, pstmtBatch, server.getServerConfig().getPersistBatchSize()); + } + + /** + * Execute the CallableSql. + */ + public int executeCallable(CallableSql callSql, Transaction t) { + + PersistRequestCallableSql request = new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Execute the orm update. + */ + public int executeOrmUpdate(Update update, Transaction t) { + + SpiUpdate ormUpdate = (SpiUpdate) update; + + BeanManager mgr = beanDescriptorManager.getBeanManager(ormUpdate.getBeanType()); + + if (mgr == null) { + String msg = "No BeanManager found for type [" + ormUpdate.getBeanType() + "]. Is it an entity?"; + throw new PersistenceException(msg); + } + + PersistRequestOrmUpdate request = new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Execute the updateSql. + */ + public int executeSqlUpdate(SqlUpdate updSql, Transaction t) { + + PersistRequestUpdateSql request = new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute); + try { + request.initTransIfRequired(); + int rc = request.executeOrQueue(); + request.commitTransIfRequired(); + return rc; + + } catch (RuntimeException e) { + request.rollbackTransIfRequired(); + throw e; + } + } + + /** + * Recursively delete the bean. This calls back to the EbeanServer. + */ + private void deleteRecurse(Object detailBean, Transaction t) { + // NB: a new PersistRequest is made + server.delete(detailBean, t); + } + + /** + * Update the bean. + */ + public void update(EntityBean entityBean, Transaction t) { + update(entityBean, t, updatesDeleteMissingChildren); + } + + /** + * Update the bean specifying deleteMissingChildren. + */ + public void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren) { + + PersistRequestBean req = createRequest(entityBean, t, null, PersistRequest.Type.UPDATE); + req.setDeleteMissingChildren(deleteMissingChildren); + try { + req.initTransIfRequiredWithBatchCascade(); + if (req.isReference()) { + // its a reference so see if there are manys to save... + if (req.isPersistCascade()) { + saveAssocMany(false, req, false); + } + req.checkUpdatedManysOnly(); + } else { + update(req); + } + + req.commitTransIfRequired(); + req.flushBatchOnCascade(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + /** + * Insert or update the bean. + */ + public void save(EntityBean bean, Transaction t) { + if (bean._ebean_getIntercept().isLoaded()) { + // deleteMissingChildren is false when using 'save' on 'loaded' beans + update(bean, t, false); + } else { + insert(bean, t); + } + } + + /** + * Insert this bean. + */ + public void insert(EntityBean bean, Transaction t) { + + PersistRequestBean req = createRequest(bean, t, null, PersistRequest.Type.INSERT); + try { + req.initTransIfRequiredWithBatchCascade(); + insert(req); + req.commitTransIfRequired(); + req.flushBatchOnCascade(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode) { + + // determine insert or update taking into account stateless updates + PersistRequestBean request = createRequest(bean, t, parentBean, insertMode); + + if (request.isReference()) { + // its a reference... + if (request.isPersistCascade()) { + // save any associated List held beans + saveAssocMany(false, request, insertMode); + } + request.checkUpdatedManysOnly(); + + } else { + if (request.isInsert()) { + insert(request); + } else { + update(request); + } + } + } + + /** + * Insert the bean. + */ + private void insert(PersistRequestBean request) { + + if (request.isRegisteredBean()){ + // skip as already inserted/updated in this request (recursive cascading) + return; + } + + try { + if (request.isPersistCascade()) { + // save associated One beans recursively first + saveAssocOne(request, true); + } + + // set the IDGenerated value if required + setIdGenValue(request); + request.executeOrQueue(); + + if (request.isPersistCascade()) { + // save any associated List held beans + saveAssocMany(true, request, true); + } + } finally { + request.unRegisterBean(); + } + } + + /** + * Update the bean. + */ + private void update(PersistRequestBean request) { + + if (request.isRegisteredBean()){ + // skip as already inserted/updated in this request (recursive cascading) + return; + } + + try { + if (request.isPersistCascade()) { + // save associated One beans recursively first + saveAssocOne(request, false); + } + + if (request.isDirty()) { + request.executeOrQueue(); + + } else { + // skip validation on unchanged bean + if (logger.isDebugEnabled()) { + logger.debug(Message.msg("persist.update.skipped", request.getBean())); + } + } + + if (request.isPersistCascade()) { + // save all the beans in assocMany's after + saveAssocMany(false, request, false); + } + + request.checkUpdatedManysOnly(); + + } finally { + request.unRegisterBean(); + } + } + + /** + * Delete the bean with the explicit transaction. + */ + public void delete(EntityBean bean, Transaction t) { + + PersistRequestBean req = createRequest(bean, t, null, PersistRequest.Type.DELETE); + if (req.isRegisteredForDeleteBean()) { + // skip deleting bean. Used where cascade is on + // both sides of a relationship + if (logger.isDebugEnabled()) { + logger.debug("skipping delete on alreadyRegistered " + bean); + } + return; + } + + try { + req.initTransIfRequiredWithBatchCascade(); + delete(req); + req.commitTransIfRequired(); + req.flushBatchOnCascade(); + + } catch (RuntimeException ex) { + req.rollbackTransIfRequired(); + throw ex; + } + } + + private void deleteList(List beanList, Transaction t) { + for (int i = 0; i < beanList.size(); i++) { + EntityBean bean = (EntityBean)beanList.get(i); + delete(bean, t); + } + } + + /** + * Delete by a List of Id's. + */ + public void deleteMany(Class beanType, Collection ids, Transaction transaction) { + + if (ids == null || ids.size() == 0) { + return; + } + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); + + ArrayList idList = new ArrayList(ids.size()); + for (Object id : ids) { + // convert to appropriate type if required + idList.add(descriptor.convertId(id)); + } + + delete(descriptor, null, idList, transaction); + } + + /** + * Delete by Id. + */ + public int delete(Class beanType, Object id, Transaction transaction) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(beanType); + + // convert to appropriate type if required + id = descriptor.convertId(id); + return delete(descriptor, id, null, transaction); + } + + /** + * Delete by Id or a List of Id's. + */ + private int delete(BeanDescriptor descriptor, Object id, List idList, Transaction transaction) { + + SpiTransaction t = (SpiTransaction) transaction; + if (t.isPersistCascade()) { + BeanPropertyAssocOne[] propImportDelete = descriptor.propertiesOneImportedDelete(); + if (propImportDelete.length > 0) { + // We actually need to execute a query to get the foreign key values + // as they are required for the delete cascade. Query back just the + // Id and the appropriate foreign key values + Query q = deleteRequiresQuery(descriptor, propImportDelete); + if (idList != null) { + q.where().idIn(idList); + if (t.isLogSummary()) { + t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values"); + } + List beanList = server.findList(q, t); + deleteList(beanList, t); + return beanList.size(); + + } else { + q.where().idEq(id); + if (t.isLogSummary()) { + t.logSummary("-- DeleteById of " + descriptor.getName() + " id[" + id + "] requires fetch of foreign key values"); + } + EntityBean bean = (EntityBean)server.findUnique(q, t); + if (bean == null) { + return 0; + } else { + delete(bean, t); + return 1; + } + } + } + } + + if (t.isPersistCascade()) { + // OneToOne exported side with delete cascade + BeanPropertyAssocOne[] expOnes = descriptor.propertiesOneExportedDelete(); + for (int i = 0; i < expOnes.length; i++) { + BeanDescriptor targetDesc = expOnes[i].getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { + SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList); + executeSqlUpdate(sqlDelete, t); + } else { + List childIds = expOnes[i].findIdsByParentId(id, idList, t); + deleteChildrenById(t, targetDesc, childIds); + } + } + + // OneToMany's with delete cascade + BeanPropertyAssocMany[] manys = descriptor.propertiesManyDelete(); + for (int i = 0; i < manys.length; i++) { + BeanDescriptor targetDesc = manys[i].getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { + // we can just delete children with a single statement + SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); + executeSqlUpdate(sqlDelete, t); + } else { + // we need to fetch the Id's to delete (recurse or notify L2 cache) + List childIds = manys[i].findIdsByParentId(id, idList, t, null); + if (!childIds.isEmpty()) { + delete(targetDesc, null, childIds, t); + } + } + } + } + + // ManyToMany's ... delete from intersection table + BeanPropertyAssocMany[] manys = descriptor.propertiesManyToMany(); + for (int i = 0; i < manys.length; i++) { + SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList); + if (t.isLogSummary()) { + t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName()); + } + executeSqlUpdate(sqlDelete, t); + } + + // delete the bean(s) + SqlUpdate deleteById = descriptor.deleteById(id, idList); + if (t.isLogSummary()) { + if (idList != null) { + t.logSummary("-- Deleting " + descriptor.getName() + " Ids: " + idList); + } else { + t.logSummary("-- Deleting " + descriptor.getName() + " Id: " + id); + } + } + + // use Id's to update L2 cache rather than Bulk table event + deleteById.setAutoTableMod(false); + if (idList != null) { + t.getEvent().addDeleteByIdList(descriptor, idList); + } else { + t.getEvent().addDeleteById(descriptor, id); + } + int rows = executeSqlUpdate(deleteById, t); + + // Delete from the persistence context so that it can't be fetched again later + PersistenceContext persistenceContext = t.getPersistenceContext(); + if (idList != null) { + for (Object idValue : idList) { + persistenceContext.deleted(descriptor.getBeanType(), idValue); + } + } else { + persistenceContext.deleted(descriptor.getBeanType(), id); + } + return rows; + } + + /** + * We need to create and execute a query to get the foreign key values as + * the delete cascades to them (foreign keys). + */ + private Query deleteRequiresQuery(BeanDescriptor desc, BeanPropertyAssocOne[] propImportDelete) { + + Query q = server.createQuery(desc.getBeanType()); + StringBuilder sb = new StringBuilder(30); + for (int i = 0; i < propImportDelete.length; i++) { + sb.append(propImportDelete[i].getName()).append(","); + } + q.setAutofetch(false); + q.select(sb.toString()); + return q; + } + + /** + * Delete the bean. + *

      + * Note that preDelete fires before the deletion of children. + *

      + */ + private void delete(PersistRequestBean request) { + + DeleteUnloadedForeignKeys unloadedForeignKeys = null; + + if (request.isPersistCascade()) { + // delete children first ... register the + // bean to handle bi-directional cascading + request.registerDeleteBean(); + deleteAssocMany(request); + request.unregisterDeleteBean(); + + unloadedForeignKeys = getDeleteUnloadedForeignKeys(request); + if (unloadedForeignKeys != null) { + // there are foreign keys that we don't have on this partially + // populated bean so we actually need to query them (to cascade delete) + unloadedForeignKeys.queryForeignKeys(); + } + } + + request.executeOrQueue(); + + if (request.isPersistCascade()) { + deleteAssocOne(request); + + if (unloadedForeignKeys != null) { + unloadedForeignKeys.deleteCascade(); + } + } + + } + + /** + * Save the associated child beans contained in a List. + *

      + * This will automatically copy over any join properties from the parent + * bean to the child beans. + *

      + */ + private void saveAssocMany(boolean insertedParent, PersistRequestBean request, boolean insertMode) { + + EntityBean parentBean = request.getEntityBean(); + BeanDescriptor desc = request.getBeanDescriptor(); + SpiTransaction t = request.getTransaction(); + + // exported ones with cascade save + BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedSave(); + for (int i = 0; i < expOnes.length; i++) { + BeanPropertyAssocOne prop = expOnes[i]; + + // check for partial beans + if (request.isLoadedProperty(prop)) { + EntityBean detailBean = prop.getValueAsEntityBean(parentBean); + if (detailBean != null) { + if (!prop.isSaveRecurseSkippable(detailBean)) { + t.depth(+1); + prop.setParentBeanToChild(parentBean, detailBean); + saveRecurse(detailBean, t, parentBean, insertMode); + t.depth(-1); + } + } + } + } + + // many's with cascade save + BeanPropertyAssocMany[] manys = desc.propertiesManySave(); + for (int i = 0; i < manys.length; i++) { + // check that property is loaded and not empty uninitialised collection + if (request.isLoadedProperty(manys[i]) && !manys[i].isEmptyBeanCollection(parentBean)) { + saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request), insertMode); + if (!insertedParent) { + request.addUpdatedManyProperty(manys[i]); + } + } + } + } + + /** + * Helper to wrap the details when saving a OneToMany or ManyToMany + * relationship. + */ + private static class SaveManyPropRequest { + private final boolean insertedParent; + private final BeanPropertyAssocMany many; + private final EntityBean parentBean; + private final SpiTransaction transaction; + private final boolean cascade; + private final boolean deleteMissingChildren; + + private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + this.insertedParent = insertedParent; + this.many = many; + this.cascade = many.getCascadeInfo().isSave(); + this.parentBean = parentBean; + this.transaction = request.getTransaction(); + this.deleteMissingChildren = request.isDeleteMissingChildren(); + } + + private SaveManyPropRequest(BeanPropertyAssocMany many, EntityBean parentBean, SpiTransaction t) { + this.insertedParent = false; + this.many = many; + this.parentBean = parentBean; + this.transaction = t; + this.cascade = true; + this.deleteMissingChildren = false; + } + + public boolean isSaveIntersection() { + return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName()); + } + + private Object getValue() { + return many.getValue(parentBean); + } + + private boolean isModifyListenMode() { + return ModifyListenMode.REMOVALS.equals(many.getModifyListenMode()); + } + + private boolean isDeleteMissingChildren() { + return deleteMissingChildren; + } + + private boolean isInsertedParent() { + return insertedParent; + } + + private BeanPropertyAssocMany getMany() { + return many; + } + + private EntityBean getParentBean() { + return parentBean; + } + + private SpiTransaction getTransaction() { + return transaction; + } + + private boolean isCascade() { + return cascade; + } + } + + private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) { + + if (saveMany.getMany().isManyToMany()) { + + // check if we can save the m2m intersection in this direction + boolean saveIntersectionFromThisDirection = saveMany.isSaveIntersection(); + if (saveMany.isCascade()) { + // Need explicit Cascade to save the beans on other side + saveAssocManyDetails(saveMany, false, insertMode); + } + // for ManyToMany save the 'relationship' via inserts/deletes + // into/from the intersection table + if (saveIntersectionFromThisDirection) { + // only allowed on one direction of a m2m based on beanName + saveAssocManyIntersection(saveMany, saveMany.isDeleteMissingChildren()); + } + } else { + if (saveMany.isModifyListenMode()) { + // delete any removed beans via private owned. Needs to occur before + // a 'deleteMissingChildren' statement occurs + removeAssocManyPrivateOwned(saveMany); + } + if (saveMany.isCascade()) { + // potentially deletes 'missing children' for 'stateless update' + saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren(), insertMode); + } + } + } + + private void removeAssocManyPrivateOwned(SaveManyPropRequest saveMany) { + + Object details = saveMany.getValue(); + + // check that the list is not null and if it is a BeanCollection + // check that is has been populated (don't trigger lazy loading) + if (details instanceof BeanCollection) { + + BeanCollection c = (BeanCollection) details; + Set modifyRemovals = c.getModifyRemovals(); + if (modifyRemovals != null && !modifyRemovals.isEmpty()) { + + SpiTransaction t = saveMany.getTransaction(); + // increase depth for batching order + t.depth(+1); + for (Object removedBean : modifyRemovals) { + if (removedBean instanceof EntityBean) { + EntityBean eb = (EntityBean)removedBean; + if (eb._ebean_getIntercept().isLoaded()) { + // only delete if the bean was loaded meaning that + // it is know to exist in the DB + deleteRecurse(removedBean, t); + } + } + } + t.depth(-1); + } + } + } + + /** + * Save the details from a OneToMany collection. + */ + private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean insertMode) { + + BeanPropertyAssocMany prop = saveMany.getMany(); + + Object details = saveMany.getValue(); + + // check that the list is not null and if it is a BeanCollection + // check that is has been populated (don't trigger lazy loading) + // For a Map this is a collection of Map.Entry objects and not beans + Collection collection = BeanCollectionUtil.getActualEntries(details); + + if (collection == null) { + // nothing to do here + return; + } + + BeanDescriptor targetDescriptor = prop.getTargetDescriptor(); + if (saveMany.isInsertedParent()) { + // performance optimisation for large collections + targetDescriptor.preAllocateIds(collection.size()); + } + + ArrayList detailIds = null; + if (deleteMissingChildren) { + // collect the Id's (to exclude from deleteManyDetails) + detailIds = new ArrayList(); + } + + // increase depth for batching order + SpiTransaction t = saveMany.getTransaction(); + t.depth(+1); + + // if a map, then we get the key value and + // set it to the appropriate property on the + // detail bean before we save it + boolean isMap = ManyType.JAVA_MAP.equals(prop.getManyType()); + EntityBean parentBean = saveMany.getParentBean(); + Object mapKeyValue = null; + + boolean saveSkippable = prop.isSaveRecurseSkippable(); + boolean skipSavingThisBean; + + for (Object detailBean : collection) { + if (isMap) { + // its a map so need the key and value + Map.Entry entry = (Map.Entry) detailBean; + mapKeyValue = entry.getKey(); + detailBean = entry.getValue(); + } + + if (detailBean instanceof EntityBean) { + EntityBean detail = (EntityBean)detailBean; + EntityBeanIntercept ebi = detail._ebean_getIntercept(); + if (prop.isManyToMany()) { + skipSavingThisBean = targetDescriptor.isReference(ebi); + } else { + if (targetDescriptor.isReference(ebi)) { + // we can skip this one + skipSavingThisBean = true; + + } else if (ebi.isNewOrDirty()) { + skipSavingThisBean = false; + // set the parent bean to detailBean + prop.setJoinValuesToChild(parentBean, detail, mapKeyValue); + + } else { + // unmodified so skip depending on prop.isSaveRecurseSkippable(); + skipSavingThisBean = saveSkippable; + } + } + + if (!skipSavingThisBean) { + saveRecurse(detail, t, parentBean, insertMode); + } + if (detailIds != null) { + // remember the Id (other details not in the collection) will be removed + Object id = targetDescriptor.getId(detail); + if (!DmlUtil.isNullOrZero(id)) { + detailIds.add(id); + } + } + } + } + + if (detailIds != null) { + // deleteMissingChildren is true so deleting children that were not just processed + deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds); + } + + t.depth(-1); + } + + public int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); + return deleteAssocManyIntersection(ownerBean, prop, t); + } + + public void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass()); + BeanPropertyAssocMany prop = (BeanPropertyAssocMany) descriptor.getBeanProperty(propertyName); + + saveAssocManyIntersection(new SaveManyPropRequest(prop, ownerBean, (SpiTransaction) t), false); + } + + public void saveAssociation(EntityBean parentBean, String propertyName, Transaction t) { + + BeanDescriptor descriptor = beanDescriptorManager.getBeanDescriptor(parentBean.getClass()); + SpiTransaction trans = (SpiTransaction) t; + + BeanProperty prop = descriptor.getBeanProperty(propertyName); + if (prop == null) { + String msg = "Could not find property [" + propertyName + "] on bean " + parentBean.getClass(); + throw new PersistenceException(msg); + } + + if (prop instanceof BeanPropertyAssocMany) { + BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) prop; + saveMany(new SaveManyPropRequest(manyProp, parentBean, (SpiTransaction) t), true); + + } else if (prop instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne oneProp = (BeanPropertyAssocOne) prop; + EntityBean assocBean = oneProp.getValueAsEntityBean(parentBean); + + int depth = oneProp.isOneToOneExported() ? 1 : -1; + int revertDepth = -1 * depth; + + trans.depth(depth); + saveRecurse(assocBean, t, parentBean, true); + trans.depth(revertDepth); + + } else { + String msg = "Expecting [" + prop.getFullBeanName() + "] to be a OneToMany, OneToOne, ManyToOne or ManyToMany property?"; + throw new PersistenceException(msg); + } + + } + + /** + * Save the additions and removals from a ManyToMany collection as inserts + * and deletes from the intersection table. + *

      + * This is done via MapBeans. + *

      + */ + private void saveAssocManyIntersection(SaveManyPropRequest saveManyPropRequest, boolean deleteMissingChildren) { + + BeanPropertyAssocMany prop = saveManyPropRequest.getMany(); + Object value = prop.getValue(saveManyPropRequest.getParentBean()); + if (value == null) { + return; + } + + SpiTransaction t = saveManyPropRequest.getTransaction(); + boolean vanillaCollection = !(value instanceof BeanCollection); + + if (vanillaCollection || deleteMissingChildren) { + // delete all intersection rows and then treat all + // beans in the collection as additions + deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t); + } + + Collection deletions = null; + Collection additions; + + if (saveManyPropRequest.isInsertedParent() || vanillaCollection || deleteMissingChildren) { + // treat everything in the list/set/map as an intersection addition + if (value instanceof Map) { + additions = ((Map) value).values(); + } else if (value instanceof Collection) { + additions = (Collection) value; + } else { + String msg = "Unhandled ManyToMany type " + value.getClass().getName() + " for " + prop.getFullBeanName(); + throw new PersistenceException(msg); + } + if (!vanillaCollection) { + ((BeanCollection) value).modifyReset(); + } + } else { + // BeanCollection so get the additions/deletions + BeanCollection manyValue = (BeanCollection) value; + additions = manyValue.getModifyAdditions(); + deletions = manyValue.getModifyRemovals(); + // reset so the changes are only processed once + manyValue.modifyReset(); + } + + t.depth(+1); + + if (additions != null && !additions.isEmpty()) { + for (Object other : additions) { + EntityBean otherBean = (EntityBean)other; + // the object from the 'other' side of the ManyToMany + if (deletions != null && deletions.remove(otherBean)) { + String m = "Inserting and Deleting same object? " + otherBean; + if (t.isLogSummary()) { + t.logSummary(m); + } + logger.warn(m); + + } else { + if (!prop.hasImportedId(otherBean)) { + String msg = "ManyToMany bean " + otherBean + " does not have an Id value."; + throw new PersistenceException(msg); + + } else { + // build a intersection row for 'insert' + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean); + SqlUpdate sqlInsert = intRow.createInsert(server); + executeSqlUpdate(sqlInsert, t); + } + } + } + } + if (deletions != null && !deletions.isEmpty()) { + for (Object other : deletions) { + EntityBean otherDelete = (EntityBean)other; + // the object from the 'other' side of the ManyToMany + // build a intersection row for 'delete' + IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete); + SqlUpdate sqlDelete = intRow.createDelete(server); + executeSqlUpdate(sqlDelete, t); + } + } + + // decrease the depth back to what it was + t.depth(-1); + } + + private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany many, Transaction t) { + + // delete all intersection rows for this bean + IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean); + SqlUpdate sqlDelete = intRow.createDeleteChildren(server); + + return executeSqlUpdate(sqlDelete, t); + } + + /** + * Delete beans in any associated many. + *

      + * This is called prior to deleting the parent bean. + *

      + */ + private void deleteAssocMany(PersistRequestBean request) { + + SpiTransaction t = request.getTransaction(); + t.depth(-1); + + BeanDescriptor desc = request.getBeanDescriptor(); + EntityBean parentBean = request.getEntityBean(); + + BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedDelete(); + if (expOnes.length > 0) { + + DeleteUnloadedForeignKeys unloaded = null; + for (int i = 0; i < expOnes.length; i++) { + BeanPropertyAssocOne prop = expOnes[i]; + if (request.isLoadedProperty(prop)) { + Object detailBean = prop.getValue(parentBean); + if (detailBean != null) { + deleteRecurse(detailBean, t); + } + } else { + if (unloaded == null) { + unloaded = new DeleteUnloadedForeignKeys(server, request); + } + unloaded.add(prop); + } + } + if (unloaded != null) { + unloaded.queryForeignKeys(); + unloaded.deleteCascade(); + } + } + + // Many's with delete cascade + BeanPropertyAssocMany[] manys = desc.propertiesManyDelete(); + for (int i = 0; i < manys.length; i++) { + if (manys[i].isManyToMany()) { + // delete associated rows from intersection table + deleteAssocManyIntersection(parentBean, manys[i], t); + + } else { + + if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) { + // PrivateOwned ... + Object details = manys[i].getValue(parentBean); + if (details instanceof BeanCollection) { + Set modifyRemovals = ((BeanCollection) details).getModifyRemovals(); + if (modifyRemovals != null && !modifyRemovals.isEmpty()) { + + // delete the orphans that have been removed from the collection + for (Object detail : modifyRemovals) { + EntityBean detailBean = (EntityBean)detail; + if (manys[i].hasId(detailBean)) { + deleteRecurse(detailBean, t); + } + } + } + } + } + + deleteManyDetails(t, desc, parentBean, manys[i], null); + } + } + + // restore the depth + t.depth(+1); + } + + /** + * Delete the 'many' detail beans for a given parent bean. + *

      + * For stateless updates this deletes details beans that are no longer in + * the many - the excludeDetailIds holds the detail beans that are in the + * collection (and should not be deleted). + *

      + */ + private void deleteManyDetails(SpiTransaction t, BeanDescriptor desc, EntityBean parentBean, + BeanPropertyAssocMany many, ArrayList excludeDetailIds) { + + if (many.getCascadeInfo().isDelete()) { + // cascade delete the beans in the collection + BeanDescriptor targetDesc = many.getTargetDescriptor(); + if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) { + // Just delete all the children with one statement + IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds); + SqlUpdate sqlDelete = intRow.createDelete(server); + executeSqlUpdate(sqlDelete, t); + + } else { + // Delete recurse using the Id values of the children + Object parentId = desc.getId(parentBean); + List idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds); + if (!idsByParentId.isEmpty()) { + deleteChildrenById(t, targetDesc, idsByParentId); + } + } + } + } + + /** + * Cascade delete child entities by Id. + *

      + * Will use delete by object if the child entity has manyToMany relationships. + */ + private void deleteChildrenById(SpiTransaction t, BeanDescriptor targetDesc, List childIds) { + + if (targetDesc.propertiesManyToMany().length > 0) { + // convert into a list of reference objects and perform delete by object + List refList = new ArrayList(childIds.size()); + for (Object id : childIds) { + refList.add(targetDesc.createReference(null, id)); + } + deleteList(refList, t); + + } else { + // perform delete by statement if possible + delete(targetDesc, null, childIds, t); + } + } + + /** + * Save any associated one beans. + */ + private void saveAssocOne(PersistRequestBean request, boolean insertMode) { + + BeanDescriptor desc = request.getBeanDescriptor(); + + // imported ones with save cascade + BeanPropertyAssocOne[] ones = desc.propertiesOneImportedSave(); + + for (int i = 0; i < ones.length; i++) { + BeanPropertyAssocOne prop = ones[i]; + + // check for partial objects + if (request.isLoadedProperty(prop)) { + EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean()); + if (detailBean != null + && !prop.isSaveRecurseSkippable(detailBean) + && !prop.isReference(detailBean) + && !request.isParent(detailBean)) { + SpiTransaction t = request.getTransaction(); + t.depth(-1); + saveRecurse(detailBean, t, null, insertMode); + t.depth(+1); + } + } + } + } + + /** + * Support for loading any Imported Associated One properties that are not + * loaded but required for Delete cascade. + */ + private DeleteUnloadedForeignKeys getDeleteUnloadedForeignKeys(PersistRequestBean request) { + + DeleteUnloadedForeignKeys fkeys = null; + + BeanPropertyAssocOne[] ones = request.getBeanDescriptor().propertiesOneImportedDelete(); + for (int i = 0; i < ones.length; i++) { + if (!request.isLoadedProperty(ones[i])) { + // we have cascade Delete on a partially populated bean and + // this property was not loaded (so we are going to have to fetch it) + if (fkeys == null) { + fkeys = new DeleteUnloadedForeignKeys(server, request); + } + fkeys.add(ones[i]); + } + } + + return fkeys; + } + + /** + * Delete any associated one beans. + */ + private void deleteAssocOne(PersistRequestBean request) { + + BeanDescriptor desc = request.getBeanDescriptor(); + BeanPropertyAssocOne[] ones = desc.propertiesOneImportedDelete(); + + for (int i = 0; i < ones.length; i++) { + BeanPropertyAssocOne prop = ones[i]; + if (request.isLoadedProperty(prop)) { + Object detailBean = prop.getValue(request.getEntityBean()); + if (detailBean != null) { + EntityBean detail = (EntityBean)detailBean; + if (prop.hasId(detail)) { + deleteRecurse(detail, request.getTransaction()); + } + } + } + } + } + + /** + * Set Id Generated value for insert. + */ + private void setIdGenValue(PersistRequestBean request) { + + BeanDescriptor desc = request.getBeanDescriptor(); + if (!desc.isUseIdGenerator()) { + return; + } + + BeanProperty idProp = desc.getIdProperty(); + if (idProp == null || idProp.isEmbedded()) { + // not supporting IdGeneration for concatenated or Embedded + return; + } + + EntityBean bean = request.getEntityBean(); + Object uid = idProp.getValue(bean); + + if (DmlUtil.isNullOrZero(uid)) { + + // generate the nextId and set it to the property + Object nextId = desc.nextId(request.getTransaction()); + + // cast the data type if required and set it + desc.convertSetId(nextId, bean); + } + } + + /** + * Create the Persist Request Object that wraps all the objects used to + * perform an insert, update or delete. + */ + private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, PersistRequest.Type type) { + BeanManager mgr = getBeanManager(bean); + if (mgr == null) { + throw new PersistenceException(errNotRegistered(bean.getClass())); + } + return createRequest(bean, t, parentBean, mgr, type, false); + } + + /** + * Create an Insert or Update PersistRequestBean when cascading. + *

      + * This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type). + */ + private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) { + BeanManager mgr = getBeanManager(bean); + if (mgr == null) { + throw new PersistenceException(errNotRegistered(bean.getClass())); + } + BeanDescriptor desc = mgr.getBeanDescriptor(); + EntityBean entityBean = (EntityBean)bean; + // determine Insert or Update based on bean state and insert flag + PersistRequest.Type type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE; + return createRequest(bean, t, parentBean, mgr, type, true); + } + + /** + * Create the Persist Request Object that wraps all the objects used to + * perform an insert, update or delete. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, BeanManager mgr, PersistRequest.Type type, boolean saveRecurse) { + + return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse); + } + + private String errNotRegistered(Class beanClass) { + String msg = "The type [" + beanClass + "] is not a registered entity?"; + msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; + msg += " If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar()."; + return msg; + } + + /** + * Return the BeanDescriptor for a bean that is being persisted. + *

      + * Note that this checks to see if the bean is a MapBean with a tableName. + * If so it will return the table based BeanDescriptor. + *

      + */ + @SuppressWarnings("unchecked") + private BeanManager getBeanManager(T bean) { + + return (BeanManager) beanDescriptorManager.getBeanManager(bean.getClass()); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java index b528ee821..644fb9904 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DmlUtil.java @@ -1,23 +1,23 @@ -package com.avaje.ebeaninternal.server.persist; - - -/** - * Utility object with helper methods for DML. - */ -public class DmlUtil { - - /** - * Return true if the value is null or a Numeric 0 (for primitive int's and long's) or Option empty. - */ - public static boolean isNullOrZero(Object value){ - if (value == null){ - return true; - } - - if (value instanceof Number){ - return ((Number)value).longValue() == 0l; - } - - return false; - } -} +package com.avaje.ebeaninternal.server.persist; + + +/** + * Utility object with helper methods for DML. + */ +public class DmlUtil { + + /** + * Return true if the value is null or a Numeric 0 (for primitive int's and long's) or Option empty. + */ + public static boolean isNullOrZero(Object value){ + if (value == null){ + return true; + } + + if (value instanceof Number){ + return ((Number)value).longValue() == 0l; + } + + return false; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java index 93c472f92..afb343812 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java @@ -1,111 +1,111 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.CallableStatement; -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.SpiCallableSql; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; -import com.avaje.ebeaninternal.server.core.PstmtBatch; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.util.BindParamsParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Handles the execution of CallableSql requests. - */ -public class ExeCallableSql { - - private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class); - - private final Binder binder; - - private final PstmtFactory pstmtFactory; - - public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) { - this.binder = binder; - // no batch support for CallableStatement in Oracle anyway - this.pstmtFactory = new PstmtFactory(null); - } - - /** - * execute the CallableSql requests. - */ - public int execute(PersistRequestCallableSql request) { - - boolean batchThisRequest = request.isBatchThisRequest(); - - CallableStatement cstmt = null; - try { - cstmt = bindStmt(request, batchThisRequest); - if (batchThisRequest) { - cstmt.addBatch(); - // return -1 to indicate batch mode - return -1; - } else { - // handles executeOverride() and also - // reading of registered OUT parameters - int rowCount = request.executeUpdate(); - request.postExecute(); - return rowCount; - } - - } catch (SQLException ex) { - throw new PersistenceException(ex); - - } finally { - if (!batchThisRequest && cstmt != null) { - try { - cstmt.close(); - } catch (SQLException e) { - logger.error(null, e); - } - } - } - } - - - private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException { - - SpiCallableSql callableSql = request.getCallableSql(); - SpiTransaction t = request.getTransaction(); - - String sql = callableSql.getSql(); - - BindParams bindParams = callableSql.getBindParams(); - - // process named parameters if required - sql = BindParamsParser.parse(bindParams, sql); - - boolean logSql = request.isLogSql(); - - CallableStatement cstmt; - if (batchThisRequest) { - cstmt = pstmtFactory.getCstmt(t, logSql, sql, request); - } else { - if (logSql) { - t.logSql(sql); - } - cstmt = pstmtFactory.getCstmt(t, sql); - } - - if (callableSql.getTimeout() > 0) { - cstmt.setQueryTimeout(callableSql.getTimeout()); - } - - String bindLog = null; - if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(cstmt)); - } - - request.setBindLog(bindLog); - - // required to read OUT params later - request.setBound(bindParams, cstmt); - return cstmt; - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.CallableStatement; +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiCallableSql; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the execution of CallableSql requests. + */ +public class ExeCallableSql { + + private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) { + this.binder = binder; + // no batch support for CallableStatement in Oracle anyway + this.pstmtFactory = new PstmtFactory(null); + } + + /** + * execute the CallableSql requests. + */ + public int execute(PersistRequestCallableSql request) { + + boolean batchThisRequest = request.isBatchThisRequest(); + + CallableStatement cstmt = null; + try { + cstmt = bindStmt(request, batchThisRequest); + if (batchThisRequest) { + cstmt.addBatch(); + // return -1 to indicate batch mode + return -1; + } else { + // handles executeOverride() and also + // reading of registered OUT parameters + int rowCount = request.executeUpdate(); + request.postExecute(); + return rowCount; + } + + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + if (!batchThisRequest && cstmt != null) { + try { + cstmt.close(); + } catch (SQLException e) { + logger.error(null, e); + } + } + } + } + + + private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException { + + SpiCallableSql callableSql = request.getCallableSql(); + SpiTransaction t = request.getTransaction(); + + String sql = callableSql.getSql(); + + BindParams bindParams = callableSql.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + + boolean logSql = request.isLogSql(); + + CallableStatement cstmt; + if (batchThisRequest) { + cstmt = pstmtFactory.getCstmt(t, logSql, sql, request); + } else { + if (logSql) { + t.logSql(sql); + } + cstmt = pstmtFactory.getCstmt(t, sql); + } + + if (callableSql.getTimeout() > 0) { + cstmt.setQueryTimeout(callableSql.getTimeout()); + } + + String bindLog = null; + if (!bindParams.isEmpty()) { + bindLog = binder.bind(bindParams, new DataBind(cstmt)); + } + + request.setBindLog(bindLog); + + // required to read OUT params later + request.setBound(bindParams, cstmt); + return cstmt; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java index b935f6900..37fda4da1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java @@ -1,129 +1,129 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.api.SpiUpdate; -import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; -import com.avaje.ebeaninternal.server.core.PstmtBatch; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.util.BindParamsParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Executes the UpdateSql requests. - */ -public class ExeOrmUpdate { - - private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class); - - private final Binder binder; - - private final PstmtFactory pstmtFactory; - - /** - * Create with a given binder. - */ - public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) { - this.pstmtFactory = new PstmtFactory(pstmtBatch); - this.binder = binder; - } - - /** - * Execute the orm update request. - */ - public int execute(PersistRequestOrmUpdate request) { - - boolean batchThisRequest = request.isBatchThisRequest(); - - PreparedStatement pstmt = null; - try { - pstmt = bindStmt(request, batchThisRequest); - if (batchThisRequest) { - PstmtBatch pstmtBatch = request.getPstmtBatch(); - if (pstmtBatch != null) { - pstmtBatch.addBatch(pstmt); - } else { - pstmt.addBatch(); - } - // return -1 to indicate batch mode - return -1; - } else { - SpiUpdate ormUpdate = request.getOrmUpdate(); - if (ormUpdate.getTimeout() > 0) { - pstmt.setQueryTimeout(ormUpdate.getTimeout()); - } - int rowCount = pstmt.executeUpdate(); - request.checkRowCount(rowCount); - request.postExecute(); - return rowCount; - } - - } catch (SQLException ex) { - throw new PersistenceException("Error executing: " + request.getOrmUpdate().getGeneratedSql(), ex); - - } finally { - if (!batchThisRequest && pstmt != null) { - try { - pstmt.close(); - } catch (SQLException e) { - logger.error(null, e); - } - } - } - } - - /** - * Convert bean and property names to db table and columns. - */ - private String translate(PersistRequestOrmUpdate request, String sql) { - - BeanDescriptor descriptor = request.getBeanDescriptor(); - return descriptor.convertOrmUpdateToSql(sql); - } - - private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException { - - SpiUpdate ormUpdate = request.getOrmUpdate(); - SpiTransaction t = request.getTransaction(); - - String sql = ormUpdate.getUpdateStatement(); - - // convert bean and property names to table and - // column names if required - sql = translate(request, sql); - - BindParams bindParams = ormUpdate.getBindParams(); - - // process named parameters if required - sql = BindParamsParser.parse(bindParams, sql); - - ormUpdate.setGeneratedSql(sql); - - boolean logSql = request.isLogSql(); - - PreparedStatement pstmt; - if (batchThisRequest) { - pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); - } else { - if (logSql) { - t.logSql(sql); - } - pstmt = pstmtFactory.getPstmt(t, sql); - } - - String bindLog = null; - if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); - } - - request.setBindLog(bindLog); - return pstmt; - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Executes the UpdateSql requests. + */ +public class ExeOrmUpdate { + + private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + /** + * Create with a given binder. + */ + public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) { + this.pstmtFactory = new PstmtFactory(pstmtBatch); + this.binder = binder; + } + + /** + * Execute the orm update request. + */ + public int execute(PersistRequestOrmUpdate request) { + + boolean batchThisRequest = request.isBatchThisRequest(); + + PreparedStatement pstmt = null; + try { + pstmt = bindStmt(request, batchThisRequest); + if (batchThisRequest) { + PstmtBatch pstmtBatch = request.getPstmtBatch(); + if (pstmtBatch != null) { + pstmtBatch.addBatch(pstmt); + } else { + pstmt.addBatch(); + } + // return -1 to indicate batch mode + return -1; + } else { + SpiUpdate ormUpdate = request.getOrmUpdate(); + if (ormUpdate.getTimeout() > 0) { + pstmt.setQueryTimeout(ormUpdate.getTimeout()); + } + int rowCount = pstmt.executeUpdate(); + request.checkRowCount(rowCount); + request.postExecute(); + return rowCount; + } + + } catch (SQLException ex) { + throw new PersistenceException("Error executing: " + request.getOrmUpdate().getGeneratedSql(), ex); + + } finally { + if (!batchThisRequest && pstmt != null) { + try { + pstmt.close(); + } catch (SQLException e) { + logger.error(null, e); + } + } + } + } + + /** + * Convert bean and property names to db table and columns. + */ + private String translate(PersistRequestOrmUpdate request, String sql) { + + BeanDescriptor descriptor = request.getBeanDescriptor(); + return descriptor.convertOrmUpdateToSql(sql); + } + + private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException { + + SpiUpdate ormUpdate = request.getOrmUpdate(); + SpiTransaction t = request.getTransaction(); + + String sql = ormUpdate.getUpdateStatement(); + + // convert bean and property names to table and + // column names if required + sql = translate(request, sql); + + BindParams bindParams = ormUpdate.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + + ormUpdate.setGeneratedSql(sql); + + boolean logSql = request.isLogSql(); + + PreparedStatement pstmt; + if (batchThisRequest) { + pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); + } else { + if (logSql) { + t.logSql(sql); + } + pstmt = pstmtFactory.getPstmt(t, sql); + } + + String bindLog = null; + if (!bindParams.isEmpty()) { + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + request.setBindLog(bindLog); + return pstmt; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java index 680833d15..d812c7cad 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java @@ -1,190 +1,190 @@ -package com.avaje.ebeaninternal.server.persist; - -import com.avaje.ebeaninternal.api.BindParams; -import com.avaje.ebeaninternal.api.SpiSqlUpdate; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; -import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql.SqlType; -import com.avaje.ebeaninternal.server.core.PstmtBatch; -import com.avaje.ebeaninternal.server.type.DataBind; -import com.avaje.ebeaninternal.server.util.BindParamsParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.PersistenceException; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -/** - * Executes the UpdateSql requests. - */ -public class ExeUpdateSql { - - private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class); - - private final Binder binder; - - private final PstmtFactory pstmtFactory; - - private final PstmtBatch pstmtBatch; - - private int defaultBatchSize = 20; - - /** - * Create with a given binder. - */ - public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) { - this.binder = binder; - this.pstmtBatch = pstmtBatch; - this.pstmtFactory = new PstmtFactory(pstmtBatch); - } - - /** - * Execute the UpdateSql request. - */ - public int execute(PersistRequestUpdateSql request) { - - boolean batchThisRequest = request.isBatchThisRequest(); - - PreparedStatement pstmt = null; - try { - - pstmt = bindStmt(request, batchThisRequest); - - if (batchThisRequest) { - if (pstmtBatch != null) { - pstmtBatch.addBatch(pstmt); - } else { - pstmt.addBatch(); - } - // return -1 to indicate batch mode - return -1; - } else { - int rowCount = pstmt.executeUpdate(); - request.checkRowCount(rowCount); - request.postExecute(); - return rowCount; - } - } catch (SQLException ex) { - throw new PersistenceException(ex); - - } finally { - if (!batchThisRequest && pstmt != null) { - try { - pstmt.close(); - } catch (SQLException e) { - logger.error(null, e); - } - } - } - } - - private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException { - - SpiSqlUpdate updateSql = request.getUpdateSql(); - SpiTransaction t = request.getTransaction(); - - String sql = updateSql.getSql(); - - BindParams bindParams = updateSql.getBindParams(); - - // process named parameters if required - sql = BindParamsParser.parse(bindParams, sql); - updateSql.setGeneratedSql(sql); - - boolean logSql = request.isLogSql(); - - PreparedStatement pstmt; - if (batchThisRequest) { - pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); - if (pstmtBatch != null) { - // oracle specific JDBC setting batch size ahead of time - int batchSize = t.getBatchSize(); - if (batchSize < 1) { - batchSize = defaultBatchSize; - } - pstmtBatch.setBatchSize(pstmt, batchSize); - } - } else { - if (logSql) { - t.logSql(sql); - } - pstmt = pstmtFactory.getPstmt(t, sql); - } - - if (updateSql.getTimeout() > 0) { - pstmt.setQueryTimeout(updateSql.getTimeout()); - } - - String bindLog = null; - if (!bindParams.isEmpty()) { - bindLog = binder.bind(bindParams, new DataBind(pstmt)); - } - - request.setBindLog(bindLog); - - // derive the statement type (for TransactionEvent) - parseUpdate(sql, request); - return pstmt; - } - - - private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) { - - if (word1.equalsIgnoreCase("UPDATE")) { - request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql"); - - } else if (word1.equalsIgnoreCase("DELETE")) { - request.setType(SqlType.SQL_DELETE, word3, "DeleteSql"); - - } else if (word1.equalsIgnoreCase("INSERT")) { - request.setType(SqlType.SQL_INSERT, word3, "InsertSql"); - - } else { - request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql"); - } - } - - private void parseUpdate(String sql, PersistRequestUpdateSql request) { - - int start = leadingTrim(sql); - - int[] pos = new int[3]; - int spaceCount = 0; - - int len = sql.length(); - for (int i = start; i < len; i++) { - char c = sql.charAt(i); - if (Character.isWhitespace(c)) { - pos[spaceCount] = i; - spaceCount++; - if (spaceCount > 2) { - break; - } - } - } - - String firstWord = sql.substring(0, pos[0]); - String secWord = sql.substring(pos[0] + 1, pos[1]); - String thirdWord; - if (pos[2] == 0) { - // there is nothing after the table name - thirdWord = sql.substring(pos[1] + 1); - } else { - thirdWord = sql.substring(pos[1] + 1, pos[2]); - } - - determineType(firstWord, secWord, thirdWord, request); - } - - private int leadingTrim(String s) { - int len = s.length(); - int i; - for (i = 0; i < len; i++) { - if (!Character.isWhitespace(s.charAt(i))) { - return i; - } - } - return 0; - } -} +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.api.SpiSqlUpdate; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql.SqlType; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.type.DataBind; +import com.avaje.ebeaninternal.server.util.BindParamsParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.persistence.PersistenceException; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Executes the UpdateSql requests. + */ +public class ExeUpdateSql { + + private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class); + + private final Binder binder; + + private final PstmtFactory pstmtFactory; + + private final PstmtBatch pstmtBatch; + + private int defaultBatchSize = 20; + + /** + * Create with a given binder. + */ + public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) { + this.binder = binder; + this.pstmtBatch = pstmtBatch; + this.pstmtFactory = new PstmtFactory(pstmtBatch); + } + + /** + * Execute the UpdateSql request. + */ + public int execute(PersistRequestUpdateSql request) { + + boolean batchThisRequest = request.isBatchThisRequest(); + + PreparedStatement pstmt = null; + try { + + pstmt = bindStmt(request, batchThisRequest); + + if (batchThisRequest) { + if (pstmtBatch != null) { + pstmtBatch.addBatch(pstmt); + } else { + pstmt.addBatch(); + } + // return -1 to indicate batch mode + return -1; + } else { + int rowCount = pstmt.executeUpdate(); + request.checkRowCount(rowCount); + request.postExecute(); + return rowCount; + } + } catch (SQLException ex) { + throw new PersistenceException(ex); + + } finally { + if (!batchThisRequest && pstmt != null) { + try { + pstmt.close(); + } catch (SQLException e) { + logger.error(null, e); + } + } + } + } + + private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException { + + SpiSqlUpdate updateSql = request.getUpdateSql(); + SpiTransaction t = request.getTransaction(); + + String sql = updateSql.getSql(); + + BindParams bindParams = updateSql.getBindParams(); + + // process named parameters if required + sql = BindParamsParser.parse(bindParams, sql); + updateSql.setGeneratedSql(sql); + + boolean logSql = request.isLogSql(); + + PreparedStatement pstmt; + if (batchThisRequest) { + pstmt = pstmtFactory.getPstmt(t, logSql, sql, request); + if (pstmtBatch != null) { + // oracle specific JDBC setting batch size ahead of time + int batchSize = t.getBatchSize(); + if (batchSize < 1) { + batchSize = defaultBatchSize; + } + pstmtBatch.setBatchSize(pstmt, batchSize); + } + } else { + if (logSql) { + t.logSql(sql); + } + pstmt = pstmtFactory.getPstmt(t, sql); + } + + if (updateSql.getTimeout() > 0) { + pstmt.setQueryTimeout(updateSql.getTimeout()); + } + + String bindLog = null; + if (!bindParams.isEmpty()) { + bindLog = binder.bind(bindParams, new DataBind(pstmt)); + } + + request.setBindLog(bindLog); + + // derive the statement type (for TransactionEvent) + parseUpdate(sql, request); + return pstmt; + } + + + private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) { + + if (word1.equalsIgnoreCase("UPDATE")) { + request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql"); + + } else if (word1.equalsIgnoreCase("DELETE")) { + request.setType(SqlType.SQL_DELETE, word3, "DeleteSql"); + + } else if (word1.equalsIgnoreCase("INSERT")) { + request.setType(SqlType.SQL_INSERT, word3, "InsertSql"); + + } else { + request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql"); + } + } + + private void parseUpdate(String sql, PersistRequestUpdateSql request) { + + int start = leadingTrim(sql); + + int[] pos = new int[3]; + int spaceCount = 0; + + int len = sql.length(); + for (int i = start; i < len; i++) { + char c = sql.charAt(i); + if (Character.isWhitespace(c)) { + pos[spaceCount] = i; + spaceCount++; + if (spaceCount > 2) { + break; + } + } + } + + String firstWord = sql.substring(0, pos[0]); + String secWord = sql.substring(pos[0] + 1, pos[1]); + String thirdWord; + if (pos[2] == 0) { + // there is nothing after the table name + thirdWord = sql.substring(pos[1] + 1); + } else { + thirdWord = sql.substring(pos[1] + 1, pos[2]); + } + + determineType(firstWord, secWord, thirdWord, request); + } + + private int leadingTrim(String s) { + int len = s.length(); + int i; + for (i = 0; i < len; i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + return 0; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java index 306680eee..b1df4f926 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java @@ -1,54 +1,54 @@ -package com.avaje.ebeaninternal.server.persist; - -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; -import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; -import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; - -/** - * The actual execution of persist requests. - *

      - * A Persister 'front-ends' this object and handles the - * batching, cascading, concurrency mode detection etc. - *

      - * - */ -public interface PersistExecute { - - /** - * Create a BatchControl for the current transaction. - */ - public BatchControl createBatchControl(SpiTransaction t); - - /** - * Execute a Bean (or MapBean) insert. - */ - public void executeInsertBean(PersistRequestBean request); - - /** - * Execute a Bean (or MapBean) update. - */ - public void executeUpdateBean(PersistRequestBean request); - - /** - * Execute a Bean (or MapBean) delete. - */ - public void executeDeleteBean(PersistRequestBean request); - - /** - * Execute a Update. - */ - public int executeOrmUpdate(PersistRequestOrmUpdate request); - - /** - * Execute a CallableSql. - */ - public int executeSqlCallable(PersistRequestCallableSql request); - - /** - * Execute a UpdateSql. - */ - public int executeSqlUpdate(PersistRequestUpdateSql request); - -} +package com.avaje.ebeaninternal.server.persist; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; + +/** + * The actual execution of persist requests. + *

      + * A Persister 'front-ends' this object and handles the + * batching, cascading, concurrency mode detection etc. + *

      + * + */ +public interface PersistExecute { + + /** + * Create a BatchControl for the current transaction. + */ + public BatchControl createBatchControl(SpiTransaction t); + + /** + * Execute a Bean (or MapBean) insert. + */ + public void executeInsertBean(PersistRequestBean request); + + /** + * Execute a Bean (or MapBean) update. + */ + public void executeUpdateBean(PersistRequestBean request); + + /** + * Execute a Bean (or MapBean) delete. + */ + public void executeDeleteBean(PersistRequestBean request); + + /** + * Execute a Update. + */ + public int executeOrmUpdate(PersistRequestOrmUpdate request); + + /** + * Execute a CallableSql. + */ + public int executeSqlCallable(PersistRequestCallableSql request); + + /** + * Execute a UpdateSql. + */ + public int executeSqlUpdate(PersistRequestUpdateSql request); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java index 7dd0f30e6..ccbae3539 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/PstmtFactory.java @@ -1,96 +1,96 @@ -package com.avaje.ebeaninternal.server.persist; - -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PstmtBatch; - -/** - * Factory for creating Statements. - *

      - * This is only used by CallableSql and UpdateSql requests and does not support - * getGeneratedKeys. - *

      - */ -public class PstmtFactory { - - - private final PstmtBatch pstmtBatch; - - public PstmtFactory(PstmtBatch pstmtBatch) { - this.pstmtBatch = pstmtBatch; - } - - /** - * Get a callable statement without any batching. - */ - public CallableStatement getCstmt(SpiTransaction t, String sql) throws SQLException { - Connection conn = t.getInternalConnection(); - return conn.prepareCall(sql); - } - - /** - * Get a prepared statement without any batching. - */ - public PreparedStatement getPstmt(SpiTransaction t, String sql) throws SQLException { - Connection conn = t.getInternalConnection(); - return conn.prepareStatement(sql); - } - - /** - * Return a prepared statement taking into account batch requirements. - */ - public PreparedStatement getPstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) - throws SQLException { - - BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); - PreparedStatement stmt = batch.getStmt(sql, batchExe); - - if (stmt != null) { - return stmt; - } - - if (logSql){ - t.logSql(sql); - } - - Connection conn = t.getInternalConnection(); - stmt = conn.prepareStatement(sql); - - if (pstmtBatch != null){ - pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); - } - - BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); - batch.addStmt(bs, batchExe); - return stmt; - } - - /** - * Return a callable statement taking into account batch requirements. - */ - public CallableStatement getCstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) - throws SQLException { - - BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); - CallableStatement stmt = (CallableStatement) batch.getStmt(sql, batchExe); - - if (stmt != null) { - return stmt; - } - - if (logSql){ - t.logSql(sql); - } - - Connection conn = t.getInternalConnection(); - stmt = conn.prepareCall(sql); - - BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); - batch.addStmt(bs, batchExe); - return stmt; - } -} +package com.avaje.ebeaninternal.server.persist; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PstmtBatch; + +/** + * Factory for creating Statements. + *

      + * This is only used by CallableSql and UpdateSql requests and does not support + * getGeneratedKeys. + *

      + */ +public class PstmtFactory { + + + private final PstmtBatch pstmtBatch; + + public PstmtFactory(PstmtBatch pstmtBatch) { + this.pstmtBatch = pstmtBatch; + } + + /** + * Get a callable statement without any batching. + */ + public CallableStatement getCstmt(SpiTransaction t, String sql) throws SQLException { + Connection conn = t.getInternalConnection(); + return conn.prepareCall(sql); + } + + /** + * Get a prepared statement without any batching. + */ + public PreparedStatement getPstmt(SpiTransaction t, String sql) throws SQLException { + Connection conn = t.getInternalConnection(); + return conn.prepareStatement(sql); + } + + /** + * Return a prepared statement taking into account batch requirements. + */ + public PreparedStatement getPstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) + throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + PreparedStatement stmt = batch.getStmt(sql, batchExe); + + if (stmt != null) { + return stmt; + } + + if (logSql){ + t.logSql(sql); + } + + Connection conn = t.getInternalConnection(); + stmt = conn.prepareStatement(sql); + + if (pstmtBatch != null){ + pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); + } + + BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); + batch.addStmt(bs, batchExe); + return stmt; + } + + /** + * Return a callable statement taking into account batch requirements. + */ + public CallableStatement getCstmt(SpiTransaction t, boolean logSql, String sql, BatchPostExecute batchExe) + throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + CallableStatement stmt = (CallableStatement) batch.getStmt(sql, batchExe); + + if (stmt != null) { + return stmt; + } + + if (logSql){ + t.logSql(sql); + } + + Connection conn = t.getInternalConnection(); + stmt = conn.prepareCall(sql); + + BatchedPstmt bs = new BatchedPstmt(stmt, false, sql, pstmtBatch, false); + batch.addStmt(bs, batchExe); + return stmt; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java index 09d676d4f..0e2c814ac 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java @@ -1,56 +1,56 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import javax.persistence.OptimisticLockException; - -import com.avaje.ebeaninternal.api.DerivedRelationshipData; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.type.DataBind; - -/** - * Delete bean handler. - */ -public class DeleteHandler extends DmlHandler { - - private final DeleteMeta meta; - - public DeleteHandler(PersistRequestBean persist, DeleteMeta meta) { - super(persist, meta.isEmptyStringAsNull()); - this.meta = meta; - } - - /** - * Generate and bind the delete statement. - */ - public void bind() throws SQLException { - - sql = meta.getSql(persistRequest); - SpiTransaction t = persistRequest.getTransaction(); - - PreparedStatement pstmt; - if (persistRequest.isBatched()) { - pstmt = getPstmt(t, sql, persistRequest, false); - } else { - pstmt = getPstmt(t, sql, false); - } - dataBind = new DataBind(pstmt); - meta.bind(persistRequest, this); - logSql(sql); - } - - /** - * Execute the delete non-batch. - */ - public void execute() throws SQLException, OptimisticLockException { - int rowCount = dataBind.executeUpdate(); - checkRowCount(rowCount); - } - - public void registerDerivedRelationship(DerivedRelationshipData assocBean) { - throw new RuntimeException("Never called on delete"); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Delete bean handler. + */ +public class DeleteHandler extends DmlHandler { + + private final DeleteMeta meta; + + public DeleteHandler(PersistRequestBean persist, DeleteMeta meta) { + super(persist, meta.isEmptyStringAsNull()); + this.meta = meta; + } + + /** + * Generate and bind the delete statement. + */ + public void bind() throws SQLException { + + sql = meta.getSql(persistRequest); + SpiTransaction t = persistRequest.getTransaction(); + + PreparedStatement pstmt; + if (persistRequest.isBatched()) { + pstmt = getPstmt(t, sql, persistRequest, false); + } else { + pstmt = getPstmt(t, sql, false); + } + dataBind = new DataBind(pstmt); + meta.bind(persistRequest, this); + logSql(sql); + } + + /** + * Execute the delete non-batch. + */ + public void execute() throws SQLException, OptimisticLockException { + int rowCount = dataBind.executeUpdate(); + checkRowCount(rowCount); + } + + public void registerDerivedRelationship(DerivedRelationshipData assocBean) { + throw new RuntimeException("Never called on delete"); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java index 8bbf28b9a..f3855e6d2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteMeta.java @@ -1,112 +1,112 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; - -import com.avaje.ebean.annotation.ConcurrencyMode; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; - -/** - * Meta data for delete handler. The meta data is for a particular bean type. It - * is considered immutable and is thread safe. - */ -public final class DeleteMeta { - - private final String sqlVersion; - - private final String sqlNone; - - private final BindableId id; - - private final Bindable version; - - private final String tableName; - - private final boolean emptyStringAsNull; - - public DeleteMeta(boolean emptyStringAsNull, BeanDescriptor desc, BindableId id, Bindable version) { - this.emptyStringAsNull = emptyStringAsNull; - this.tableName = desc.getBaseTable(); - this.id = id; - this.version = version; - this.sqlNone = genSql(ConcurrencyMode.NONE); - this.sqlVersion = genSql(ConcurrencyMode.VERSION); - } - - public boolean isEmptyStringAsNull() { - return emptyStringAsNull; - } - - /** - * Return the table name. - */ - public String getTableName() { - return tableName; - } - - /** - * Bind the request based on the concurrency mode. - */ - public void bind(PersistRequestBean persist, DmlHandler bind) throws SQLException { - - EntityBean bean = persist.getEntityBean(); - - id.dmlBind(bind, bean); - - switch (persist.getConcurrencyMode()) { - case VERSION: - version.dmlBind(bind, bean); - break; - - default: - break; - } - } - - /** - * get or generate the sql based on the concurrency mode. - */ - public String getSql(PersistRequestBean request) throws SQLException { - - if (id.isEmpty()) { - throw new IllegalStateException("Can not deleteById on " + request.getFullName() + " as no @Id property"); - } - - switch (request.determineConcurrencyMode()) { - case NONE: - return sqlNone; - - case VERSION: - return sqlVersion; - - default: - throw new RuntimeException("Invalid mode " + request.determineConcurrencyMode()); - } - } - - private String genSql(ConcurrencyMode conMode) { - - // delete ... where bcol=? and bc1=? and bc2 is null and ... - - GenerateDmlRequest request = new GenerateDmlRequest(emptyStringAsNull); - - request.append("delete from ").append(tableName); - request.append(" where "); - - request.setWhereIdMode(); - id.dmlAppend(request); - - if (ConcurrencyMode.VERSION.equals(conMode)) { - if (version == null) { - return null; - } - version.dmlAppend(request); - } - - return request.toString(); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; + +/** + * Meta data for delete handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class DeleteMeta { + + private final String sqlVersion; + + private final String sqlNone; + + private final BindableId id; + + private final Bindable version; + + private final String tableName; + + private final boolean emptyStringAsNull; + + public DeleteMeta(boolean emptyStringAsNull, BeanDescriptor desc, BindableId id, Bindable version) { + this.emptyStringAsNull = emptyStringAsNull; + this.tableName = desc.getBaseTable(); + this.id = id; + this.version = version; + this.sqlNone = genSql(ConcurrencyMode.NONE); + this.sqlVersion = genSql(ConcurrencyMode.VERSION); + } + + public boolean isEmptyStringAsNull() { + return emptyStringAsNull; + } + + /** + * Return the table name. + */ + public String getTableName() { + return tableName; + } + + /** + * Bind the request based on the concurrency mode. + */ + public void bind(PersistRequestBean persist, DmlHandler bind) throws SQLException { + + EntityBean bean = persist.getEntityBean(); + + id.dmlBind(bind, bean); + + switch (persist.getConcurrencyMode()) { + case VERSION: + version.dmlBind(bind, bean); + break; + + default: + break; + } + } + + /** + * get or generate the sql based on the concurrency mode. + */ + public String getSql(PersistRequestBean request) throws SQLException { + + if (id.isEmpty()) { + throw new IllegalStateException("Can not deleteById on " + request.getFullName() + " as no @Id property"); + } + + switch (request.determineConcurrencyMode()) { + case NONE: + return sqlNone; + + case VERSION: + return sqlVersion; + + default: + throw new RuntimeException("Invalid mode " + request.determineConcurrencyMode()); + } + } + + private String genSql(ConcurrencyMode conMode) { + + // delete ... where bcol=? and bc1=? and bc2 is null and ... + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringAsNull); + + request.append("delete from ").append(tableName); + request.append(" where "); + + request.setWhereIdMode(); + id.dmlAppend(request); + + if (ConcurrencyMode.VERSION.equals(conMode)) { + if (version == null) { + return null; + } + version.dmlAppend(request); + } + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java index 3e148e129..26d25fa4a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java @@ -1,103 +1,103 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; - -import javax.persistence.PersistenceException; - -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequest; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.lib.util.StringHelper; -import com.avaje.ebeaninternal.server.persist.BeanPersister; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Bean persister that uses the Handler and Meta objects. - *

      - * The design of this is based on the immutable Meta objects. They hold a - * information in the form of lists of Bindable objects. This effectively - * flattens the structure of the bean with embedded and associated objects into - * a flat list of Bindable objects. - *

      - */ -public final class DmlBeanPersister implements BeanPersister { - - private static final Logger logger = LoggerFactory.getLogger(DmlBeanPersister.class); - - private final UpdateMeta updateMeta; - - private final InsertMeta insertMeta; - - private final DeleteMeta deleteMeta; - - - public DmlBeanPersister(UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) { - - this.updateMeta = updateMeta; - this.insertMeta = insertMeta; - this.deleteMeta = deleteMeta; - } - - /** - * execute the bean delete request. - */ - public void delete(PersistRequestBean request) { - - DeleteHandler delete = new DeleteHandler(request, deleteMeta); - execute(request, delete); - } - - /** - * execute the bean insert request. - */ - public void insert(PersistRequestBean request) { - - InsertHandler insert = new InsertHandler(request, insertMeta); - execute(request, insert); - } - - /** - * execute the bean update request. - */ - public void update(PersistRequestBean request) { - - UpdateHandler update = new UpdateHandler(request, updateMeta); - execute(request, update); - } - - /** - * execute request taking batching into account. - */ - private void execute(PersistRequestBean request, PersistHandler handler) { - - boolean batched = request.isBatched(); - try { - handler.bind(); - if (batched) { - handler.addBatch(); - } else { - handler.execute(); - } - - } catch (SQLException e) { - // log the error to the transaction log - String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n "); - String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]"; - if (request.getTransaction().isLogSummary()) { - request.getTransaction().logSummary(msg); - } - throw new PersistenceException(msg, e); - - } finally { - if (!batched && handler != null) { - try { - handler.close(); - } catch (SQLException e) { - logger.error(null, e); - } - } - } - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequest; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.lib.util.StringHelper; +import com.avaje.ebeaninternal.server.persist.BeanPersister; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Bean persister that uses the Handler and Meta objects. + *

      + * The design of this is based on the immutable Meta objects. They hold a + * information in the form of lists of Bindable objects. This effectively + * flattens the structure of the bean with embedded and associated objects into + * a flat list of Bindable objects. + *

      + */ +public final class DmlBeanPersister implements BeanPersister { + + private static final Logger logger = LoggerFactory.getLogger(DmlBeanPersister.class); + + private final UpdateMeta updateMeta; + + private final InsertMeta insertMeta; + + private final DeleteMeta deleteMeta; + + + public DmlBeanPersister(UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) { + + this.updateMeta = updateMeta; + this.insertMeta = insertMeta; + this.deleteMeta = deleteMeta; + } + + /** + * execute the bean delete request. + */ + public void delete(PersistRequestBean request) { + + DeleteHandler delete = new DeleteHandler(request, deleteMeta); + execute(request, delete); + } + + /** + * execute the bean insert request. + */ + public void insert(PersistRequestBean request) { + + InsertHandler insert = new InsertHandler(request, insertMeta); + execute(request, insert); + } + + /** + * execute the bean update request. + */ + public void update(PersistRequestBean request) { + + UpdateHandler update = new UpdateHandler(request, updateMeta); + execute(request, update); + } + + /** + * execute request taking batching into account. + */ + private void execute(PersistRequestBean request, PersistHandler handler) { + + boolean batched = request.isBatched(); + try { + handler.bind(); + if (batched) { + handler.addBatch(); + } else { + handler.execute(); + } + + } catch (SQLException e) { + // log the error to the transaction log + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n "); + String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]"; + if (request.getTransaction().isLogSummary()) { + request.getTransaction().logSummary(msg); + } + throw new PersistenceException(msg, e); + + } finally { + if (!batched && handler != null) { + try { + handler.close(); + } catch (SQLException e) { + logger.error(null, e); + } + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java index 0e36acc70..5bfcf5957 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java @@ -1,33 +1,33 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.persist.BeanPersister; -import com.avaje.ebeaninternal.server.persist.BeanPersisterFactory; - -/** - * Factory for creating a DmlBeanPersister for a bean type. - */ -public class DmlBeanPersisterFactory implements BeanPersisterFactory { - - private final MetaFactory metaFactory; - - public DmlBeanPersisterFactory(DatabasePlatform dbPlatform) { - this.metaFactory = new MetaFactory(dbPlatform); - } - - - /** - * Create a DmlBeanPersister for the given bean type. - */ - public BeanPersister create(BeanDescriptor desc) { - - UpdateMeta updMeta = metaFactory.createUpdate(desc); - DeleteMeta delMeta = metaFactory.createDelete(desc); - InsertMeta insMeta = metaFactory.createInsert(desc); - - return new DmlBeanPersister(updMeta, insMeta, delMeta); - - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.persist.BeanPersister; +import com.avaje.ebeaninternal.server.persist.BeanPersisterFactory; + +/** + * Factory for creating a DmlBeanPersister for a bean type. + */ +public class DmlBeanPersisterFactory implements BeanPersisterFactory { + + private final MetaFactory metaFactory; + + public DmlBeanPersisterFactory(DatabasePlatform dbPlatform) { + this.metaFactory = new MetaFactory(dbPlatform); + } + + + /** + * Create a DmlBeanPersister for the given bean type. + */ + public BeanPersister create(BeanDescriptor desc) { + + UpdateMeta updMeta = metaFactory.createUpdate(desc); + DeleteMeta delMeta = metaFactory.createDelete(desc); + InsertMeta insMeta = metaFactory.createInsert(desc); + + return new DmlBeanPersister(updMeta, insMeta, delMeta); + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java index 016b3bf8f..7668103f3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java @@ -1,307 +1,307 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; - -import javax.persistence.OptimisticLockException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.core.PstmtBatch; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.lib.util.Str; -import com.avaje.ebeaninternal.server.persist.BatchedPstmt; -import com.avaje.ebeaninternal.server.persist.BatchedPstmtHolder; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; -import com.avaje.ebeaninternal.server.transaction.TransactionManager; -import com.avaje.ebeaninternal.server.type.DataBind; - -/** - * Base class for Handler implementations. - */ -public abstract class DmlHandler implements PersistHandler, BindableRequest { - - private static final Logger logger = LoggerFactory.getLogger(DmlHandler.class); - - /** - * The originating request. - */ - protected final PersistRequestBean persistRequest; - - protected final StringBuilder bindLog; - - protected final SpiTransaction transaction; - - protected final boolean emptyStringToNull; - - protected final boolean logLevelSql; - - /** - * The PreparedStatement used for the dml. - */ - protected DataBind dataBind; - - protected String sql; - - protected ArrayList updateGenValues; - - protected DmlHandler(PersistRequestBean persistRequest, boolean emptyStringToNull) { - this.persistRequest = persistRequest; - this.emptyStringToNull = emptyStringToNull; - this.transaction = persistRequest.getTransaction(); - this.logLevelSql = transaction.isLogSql(); - if (logLevelSql) { - this.bindLog = new StringBuilder(50); - } else { - this.bindLog = null; - } - } - - public PersistRequestBean getPersistRequest() { - return persistRequest; - } - - /** - * Get the sql and bind the statement. - */ - public abstract void bind() throws SQLException; - - /** - * Execute now for non-batch execution. - */ - public abstract void execute() throws SQLException; - - /** - * Check the rowCount. - */ - protected void checkRowCount(int rowCount) throws SQLException, OptimisticLockException { - try { - persistRequest.checkRowCount(rowCount); - persistRequest.postExecute(); - } catch (OptimisticLockException e) { - // add the SQL and bind values to error message - String m = e.getMessage() + " sql[" + sql + "] bind[" + bindLog + "]"; - persistRequest.getTransaction().logSummary("OptimisticLockException:" + m); - throw new OptimisticLockException(m, null, e.getEntity()); - } - } - - /** - * Add this for batch execution. - */ - public void addBatch() throws SQLException { - PstmtBatch pstmtBatch = persistRequest.getPstmtBatch(); - if (pstmtBatch != null) { - pstmtBatch.addBatch(dataBind.getPstmt()); - } else { - dataBind.getPstmt().addBatch(); - } - } - - /** - * Close the underlying statement. - */ - public void close() { - try { - if (dataBind != null) { - dataBind.close(); - } - } catch (SQLException ex) { - logger.error(null, ex); - } - } - - /** - * Return the bind log. - */ - public String getBindLog() { - return bindLog == null ? "" : bindLog.toString(); - } - - /** - * Set the Id value that was bound. This value is used for logging summary - * level information. - */ - public void setIdValue(Object idValue) { - persistRequest.setBoundId(idValue); - } - - /** - * Log the sql to the transaction log. - */ - protected void logSql(String sql) { - if (logLevelSql) { - if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { - sql = Str.add(sql, "; --bind(", bindLog.toString(), ")"); - } - transaction.logSql(sql); - } - } - - /** - * Bind a raw value. Used to bind the discriminator column. - */ - public Object bind(String propName, Object value, int sqlType) throws SQLException { - if (logLevelSql) { - if (value == null) { - bindLog.append("null"); - } else { - String sval = value.toString(); - if (sval.length() > 50) { - bindLog.append(sval.substring(0, 47)).append("..."); - } else { - bindLog.append(sval); - } - } - bindLog.append(","); - } - dataBind.setObject(value, sqlType); - return value; - } - - public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException { - if (logLevelSql) { - bindLog.append(logPlaceHolder).append(" "); - } - dataBind.setObject(value, sqlType); - return value; - } - - /** - * Bind the value to the preparedStatement. - */ - public Object bind(Object value, BeanProperty prop) throws SQLException { - return bindInternal(logLevelSql, value, prop); - } - - /** - * Bind the value to the preparedStatement without logging. - */ - public Object bindNoLog(Object value, BeanProperty prop) throws SQLException { - return bindInternal(false, value, prop); - } - - private Object bindInternal(boolean log, Object value, BeanProperty prop) throws SQLException { - - if (log) { - if (prop.isLob()) { - bindLog.append("[LOB]"); - } else { - String sv = String.valueOf(value); - if (sv.length() > 50) { - sv = sv.substring(0, 47) + "..."; - } - bindLog.append(sv); - } - bindLog.append(","); - } - // do the actual binding to PreparedStatement - prop.bind(dataBind, value); - return value; - } - - /** - * Register a generated value on a update. This can not be set to the bean - * until after the where clause has been bound for concurrency checking. - *

      - * GeneratedProperty values are likely going to be used for optimistic - * concurrency checking. This includes 'counter' and 'update timestamp' - * generation. - *

      - */ - public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value) { - if (updateGenValues == null) { - updateGenValues = new ArrayList(); - } - updateGenValues.add(new UpdateGenValue(prop, bean, value)); - } - - /** - * Set any update generated values to the bean. Must be called after where - * clause has been bound. - */ - public void setUpdateGenValues() { - if (updateGenValues != null) { - for (int i = 0; i < updateGenValues.size(); i++) { - UpdateGenValue updGenVal = updateGenValues.get(i); - updGenVal.setValue(); - } - } - } - - /** - * Check with useGeneratedKeys to get appropriate PreparedStatement. - */ - protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys) throws SQLException { - - Connection conn = t.getInternalConnection(); - if (genKeys) { - // the Id generated is always the first column - // Required to stop Oracle10 giving us Oracle rowId?? - // Other jdbc drivers seem fine without this hint. - int[] columns = { 1 }; - return conn.prepareStatement(sql, columns); - - } else { - return conn.prepareStatement(sql); - } - } - - /** - * Return a prepared statement taking into account batch requirements. - */ - protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean request, - boolean genKeys) throws SQLException { - - BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); - PreparedStatement stmt = batch.getStmt(sql, request); - - if (stmt != null) { - return stmt; - } - - stmt = getPstmt(t, sql, genKeys); - - PstmtBatch pstmtBatch = request.getPstmtBatch(); - if (pstmtBatch != null) { - pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); - } - - BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, request.getPstmtBatch(), true); - batch.addStmt(bs, request); - return stmt; - } - - /** - * Hold the values from GeneratedValue that need to be set to the bean - * property after the where clause has been built. - */ - private static final class UpdateGenValue { - - private final BeanProperty property; - - private final EntityBean bean; - - private final Object value; - - private UpdateGenValue(BeanProperty property, EntityBean bean, Object value) { - this.property = property; - this.bean = bean; - this.value = value; - } - - /** - * Set the value to the bean property. - */ - private void setValue() { - // support PropertyChangeSupport - property.setValueIntercept(bean, value); - } - } -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.persistence.OptimisticLockException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.core.PstmtBatch; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.lib.util.Str; +import com.avaje.ebeaninternal.server.persist.BatchedPstmt; +import com.avaje.ebeaninternal.server.persist.BatchedPstmtHolder; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest; +import com.avaje.ebeaninternal.server.transaction.TransactionManager; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Base class for Handler implementations. + */ +public abstract class DmlHandler implements PersistHandler, BindableRequest { + + private static final Logger logger = LoggerFactory.getLogger(DmlHandler.class); + + /** + * The originating request. + */ + protected final PersistRequestBean persistRequest; + + protected final StringBuilder bindLog; + + protected final SpiTransaction transaction; + + protected final boolean emptyStringToNull; + + protected final boolean logLevelSql; + + /** + * The PreparedStatement used for the dml. + */ + protected DataBind dataBind; + + protected String sql; + + protected ArrayList updateGenValues; + + protected DmlHandler(PersistRequestBean persistRequest, boolean emptyStringToNull) { + this.persistRequest = persistRequest; + this.emptyStringToNull = emptyStringToNull; + this.transaction = persistRequest.getTransaction(); + this.logLevelSql = transaction.isLogSql(); + if (logLevelSql) { + this.bindLog = new StringBuilder(50); + } else { + this.bindLog = null; + } + } + + public PersistRequestBean getPersistRequest() { + return persistRequest; + } + + /** + * Get the sql and bind the statement. + */ + public abstract void bind() throws SQLException; + + /** + * Execute now for non-batch execution. + */ + public abstract void execute() throws SQLException; + + /** + * Check the rowCount. + */ + protected void checkRowCount(int rowCount) throws SQLException, OptimisticLockException { + try { + persistRequest.checkRowCount(rowCount); + persistRequest.postExecute(); + } catch (OptimisticLockException e) { + // add the SQL and bind values to error message + String m = e.getMessage() + " sql[" + sql + "] bind[" + bindLog + "]"; + persistRequest.getTransaction().logSummary("OptimisticLockException:" + m); + throw new OptimisticLockException(m, null, e.getEntity()); + } + } + + /** + * Add this for batch execution. + */ + public void addBatch() throws SQLException { + PstmtBatch pstmtBatch = persistRequest.getPstmtBatch(); + if (pstmtBatch != null) { + pstmtBatch.addBatch(dataBind.getPstmt()); + } else { + dataBind.getPstmt().addBatch(); + } + } + + /** + * Close the underlying statement. + */ + public void close() { + try { + if (dataBind != null) { + dataBind.close(); + } + } catch (SQLException ex) { + logger.error(null, ex); + } + } + + /** + * Return the bind log. + */ + public String getBindLog() { + return bindLog == null ? "" : bindLog.toString(); + } + + /** + * Set the Id value that was bound. This value is used for logging summary + * level information. + */ + public void setIdValue(Object idValue) { + persistRequest.setBoundId(idValue); + } + + /** + * Log the sql to the transaction log. + */ + protected void logSql(String sql) { + if (logLevelSql) { + if (TransactionManager.SQL_LOGGER.isTraceEnabled()) { + sql = Str.add(sql, "; --bind(", bindLog.toString(), ")"); + } + transaction.logSql(sql); + } + } + + /** + * Bind a raw value. Used to bind the discriminator column. + */ + public Object bind(String propName, Object value, int sqlType) throws SQLException { + if (logLevelSql) { + if (value == null) { + bindLog.append("null"); + } else { + String sval = value.toString(); + if (sval.length() > 50) { + bindLog.append(sval.substring(0, 47)).append("..."); + } else { + bindLog.append(sval); + } + } + bindLog.append(","); + } + dataBind.setObject(value, sqlType); + return value; + } + + public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException { + if (logLevelSql) { + bindLog.append(logPlaceHolder).append(" "); + } + dataBind.setObject(value, sqlType); + return value; + } + + /** + * Bind the value to the preparedStatement. + */ + public Object bind(Object value, BeanProperty prop) throws SQLException { + return bindInternal(logLevelSql, value, prop); + } + + /** + * Bind the value to the preparedStatement without logging. + */ + public Object bindNoLog(Object value, BeanProperty prop) throws SQLException { + return bindInternal(false, value, prop); + } + + private Object bindInternal(boolean log, Object value, BeanProperty prop) throws SQLException { + + if (log) { + if (prop.isLob()) { + bindLog.append("[LOB]"); + } else { + String sv = String.valueOf(value); + if (sv.length() > 50) { + sv = sv.substring(0, 47) + "..."; + } + bindLog.append(sv); + } + bindLog.append(","); + } + // do the actual binding to PreparedStatement + prop.bind(dataBind, value); + return value; + } + + /** + * Register a generated value on a update. This can not be set to the bean + * until after the where clause has been bound for concurrency checking. + *

      + * GeneratedProperty values are likely going to be used for optimistic + * concurrency checking. This includes 'counter' and 'update timestamp' + * generation. + *

      + */ + public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value) { + if (updateGenValues == null) { + updateGenValues = new ArrayList(); + } + updateGenValues.add(new UpdateGenValue(prop, bean, value)); + } + + /** + * Set any update generated values to the bean. Must be called after where + * clause has been bound. + */ + public void setUpdateGenValues() { + if (updateGenValues != null) { + for (int i = 0; i < updateGenValues.size(); i++) { + UpdateGenValue updGenVal = updateGenValues.get(i); + updGenVal.setValue(); + } + } + } + + /** + * Check with useGeneratedKeys to get appropriate PreparedStatement. + */ + protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys) throws SQLException { + + Connection conn = t.getInternalConnection(); + if (genKeys) { + // the Id generated is always the first column + // Required to stop Oracle10 giving us Oracle rowId?? + // Other jdbc drivers seem fine without this hint. + int[] columns = { 1 }; + return conn.prepareStatement(sql, columns); + + } else { + return conn.prepareStatement(sql); + } + } + + /** + * Return a prepared statement taking into account batch requirements. + */ + protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean request, + boolean genKeys) throws SQLException { + + BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); + PreparedStatement stmt = batch.getStmt(sql, request); + + if (stmt != null) { + return stmt; + } + + stmt = getPstmt(t, sql, genKeys); + + PstmtBatch pstmtBatch = request.getPstmtBatch(); + if (pstmtBatch != null) { + pstmtBatch.setBatchSize(stmt, t.getBatchControl().getBatchSize()); + } + + BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, request.getPstmtBatch(), true); + batch.addStmt(bs, request); + return stmt; + } + + /** + * Hold the values from GeneratedValue that need to be set to the bean + * property after the where clause has been built. + */ + private static final class UpdateGenValue { + + private final BeanProperty property; + + private final EntityBean bean; + + private final Object value; + + private UpdateGenValue(BeanProperty property, EntityBean bean, Object value) { + this.property = property; + this.bean = bean; + this.value = value; + } + + /** + * Set the value to the bean property. + */ + private void setValue() { + // support PropertyChangeSupport + property.setValueIntercept(bean, value); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java index 5aeccace0..0a1783640 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java @@ -1,234 +1,234 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; - -import javax.persistence.OptimisticLockException; -import javax.persistence.PersistenceException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.DerivedRelationshipData; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.Message; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.DmlUtil; -import com.avaje.ebeaninternal.server.type.DataBind; - -/** - * Insert bean handler. - */ -public class InsertHandler extends DmlHandler { - private static final Logger logger = LoggerFactory.getLogger(InsertHandler.class); - - /** - * The associated InsertMeta data. - */ - private final InsertMeta meta; - - /** - * Set to true when the key is concatenated. - */ - private final boolean concatinatedKey; - - /** - * Flag set when using getGeneratedKeys. - */ - private boolean useGeneratedKeys; - - /** - * A SQL Select used to fetch back the Id where generatedKeys is not - * supported. - */ - private String selectLastInsertedId; - - /** - * Create to handle the insert execution. - */ - public InsertHandler(PersistRequestBean persist, InsertMeta meta) { - super(persist, meta.isEmptyStringToNull()); - this.meta = meta; - this.concatinatedKey = meta.isConcatinatedKey(); - } - - /** - * Generate and bind the insert statement. - */ - public void bind() throws SQLException { - - BeanDescriptor desc = persistRequest.getBeanDescriptor(); - EntityBean bean = persistRequest.getEntityBean(); - - Object idValue = desc.getId(bean); - - boolean withId = !DmlUtil.isNullOrZero(idValue); - - // check to see if we are going to use generated keys - if (!withId) { - if (concatinatedKey) { - // expecting a concatenated key that can - // be built from supplied AssocOne beans - withId = meta.deriveConcatenatedId(persistRequest); - - } else if (meta.supportsGetGeneratedKeys()) { - // Identity with getGeneratedKeys - useGeneratedKeys = true; - } else { - // use a query to get the last inserted id - selectLastInsertedId = meta.getSelectLastInsertedId(); - } - } - - SpiTransaction t = persistRequest.getTransaction(); - - // get the appropriate sql - sql = meta.getSql(withId); - - PreparedStatement pstmt; - if (persistRequest.isBatched()) { - pstmt = getPstmt(t, sql, persistRequest, useGeneratedKeys); - } else { - pstmt = getPstmt(t, sql, useGeneratedKeys); - } - dataBind = new DataBind(pstmt); - - // bind the bean property values - meta.bind(this, bean, withId); - - logSql(sql); - } - - /** - * Check with useGeneratedKeys to get appropriate PreparedStatement. - */ - @Override - protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean useGeneratedKeys) throws SQLException { - Connection conn = t.getInternalConnection(); - if (useGeneratedKeys) { - return conn.prepareStatement(sql, meta.getIdentityDbColumns()); - - } else { - return conn.prepareStatement(sql); - } - } - - /** - * Execute the insert in a normal non batch fashion. Additionally using - * getGeneratedKeys if required. - */ - public void execute() throws SQLException, OptimisticLockException { - int rc = dataBind.executeUpdate(); - if (useGeneratedKeys) { - // get the auto-increment value back and set into the bean - getGeneratedKeys(); - - } else if (selectLastInsertedId != null) { - // fetch back the Id using a query - fetchGeneratedKeyUsingSelect(); - } - - checkRowCount(rc); - executeDerivedRelationships(); - persistRequest.postInsert(); - } - - protected void executeDerivedRelationships() { - List derivedRelationships = persistRequest.getDerivedRelationships(); - if (derivedRelationships != null) { - - SpiEbeanServer ebeanServer = (SpiEbeanServer)persistRequest.getEbeanServer(); - - for (int i = 0; i < derivedRelationships.size(); i++) { - DerivedRelationshipData derivedRelationshipData = derivedRelationships.get(i); - - BeanDescriptor beanDescriptor = ebeanServer.getBeanDescriptor(derivedRelationshipData.getBean().getClass()); - - BeanProperty prop = beanDescriptor.getBeanProperty(derivedRelationshipData.getLogicalName()); - EntityBean entityBean = (EntityBean)derivedRelationshipData.getBean(); - entityBean._ebean_getIntercept().markPropertyAsChanged(prop.getPropertyIndex()); - - ebeanServer.update(entityBean, transaction); - } - } - } - - /** - * For non batch insert with generated keys. - */ - private void getGeneratedKeys() throws SQLException { - - ResultSet rset = dataBind.getPstmt().getGeneratedKeys(); - try { - if (rset.next()) { - Object idValue = rset.getObject(1); - if (idValue != null) { - persistRequest.setGeneratedKey(idValue); - } - - } else { - throw new PersistenceException(Message.msg("persist.autoinc.norows")); - } - } finally { - try { - rset.close(); - } catch (SQLException ex) { - String msg = "Error closing rset for returning generatedKeys?"; - logger.warn(msg, ex); - } - } - } - - /** - * For non batch insert with DBs that do not support getGeneratedKeys. Use a - * SQL select to fetch back the Id value. - */ - private void fetchGeneratedKeyUsingSelect() throws SQLException { - - Connection conn = transaction.getConnection(); - - PreparedStatement stmt = null; - ResultSet rset = null; - try { - stmt = conn.prepareStatement(selectLastInsertedId); - rset = stmt.executeQuery(); - if (rset.next()) { - Object idValue = rset.getObject(1); - if (idValue != null) { - persistRequest.setGeneratedKey(idValue); - } - } else { - throw new PersistenceException(Message.msg("persist.autoinc.norows")); - } - } finally { - try { - if (rset != null) { - rset.close(); - } - } catch (SQLException ex) { - String msg = "Error closing rset for fetchGeneratedKeyUsingSelect?"; - logger.warn(msg, ex); - } - try { - if (stmt != null) { - stmt.close(); - } - } catch (SQLException ex) { - String msg = "Error closing stmt for fetchGeneratedKeyUsingSelect?"; - logger.warn(msg, ex); - } - } - } - - public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { - persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.server.core.Message; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.DmlUtil; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Insert bean handler. + */ +public class InsertHandler extends DmlHandler { + private static final Logger logger = LoggerFactory.getLogger(InsertHandler.class); + + /** + * The associated InsertMeta data. + */ + private final InsertMeta meta; + + /** + * Set to true when the key is concatenated. + */ + private final boolean concatinatedKey; + + /** + * Flag set when using getGeneratedKeys. + */ + private boolean useGeneratedKeys; + + /** + * A SQL Select used to fetch back the Id where generatedKeys is not + * supported. + */ + private String selectLastInsertedId; + + /** + * Create to handle the insert execution. + */ + public InsertHandler(PersistRequestBean persist, InsertMeta meta) { + super(persist, meta.isEmptyStringToNull()); + this.meta = meta; + this.concatinatedKey = meta.isConcatinatedKey(); + } + + /** + * Generate and bind the insert statement. + */ + public void bind() throws SQLException { + + BeanDescriptor desc = persistRequest.getBeanDescriptor(); + EntityBean bean = persistRequest.getEntityBean(); + + Object idValue = desc.getId(bean); + + boolean withId = !DmlUtil.isNullOrZero(idValue); + + // check to see if we are going to use generated keys + if (!withId) { + if (concatinatedKey) { + // expecting a concatenated key that can + // be built from supplied AssocOne beans + withId = meta.deriveConcatenatedId(persistRequest); + + } else if (meta.supportsGetGeneratedKeys()) { + // Identity with getGeneratedKeys + useGeneratedKeys = true; + } else { + // use a query to get the last inserted id + selectLastInsertedId = meta.getSelectLastInsertedId(); + } + } + + SpiTransaction t = persistRequest.getTransaction(); + + // get the appropriate sql + sql = meta.getSql(withId); + + PreparedStatement pstmt; + if (persistRequest.isBatched()) { + pstmt = getPstmt(t, sql, persistRequest, useGeneratedKeys); + } else { + pstmt = getPstmt(t, sql, useGeneratedKeys); + } + dataBind = new DataBind(pstmt); + + // bind the bean property values + meta.bind(this, bean, withId); + + logSql(sql); + } + + /** + * Check with useGeneratedKeys to get appropriate PreparedStatement. + */ + @Override + protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean useGeneratedKeys) throws SQLException { + Connection conn = t.getInternalConnection(); + if (useGeneratedKeys) { + return conn.prepareStatement(sql, meta.getIdentityDbColumns()); + + } else { + return conn.prepareStatement(sql); + } + } + + /** + * Execute the insert in a normal non batch fashion. Additionally using + * getGeneratedKeys if required. + */ + public void execute() throws SQLException, OptimisticLockException { + int rc = dataBind.executeUpdate(); + if (useGeneratedKeys) { + // get the auto-increment value back and set into the bean + getGeneratedKeys(); + + } else if (selectLastInsertedId != null) { + // fetch back the Id using a query + fetchGeneratedKeyUsingSelect(); + } + + checkRowCount(rc); + executeDerivedRelationships(); + persistRequest.postInsert(); + } + + protected void executeDerivedRelationships() { + List derivedRelationships = persistRequest.getDerivedRelationships(); + if (derivedRelationships != null) { + + SpiEbeanServer ebeanServer = (SpiEbeanServer)persistRequest.getEbeanServer(); + + for (int i = 0; i < derivedRelationships.size(); i++) { + DerivedRelationshipData derivedRelationshipData = derivedRelationships.get(i); + + BeanDescriptor beanDescriptor = ebeanServer.getBeanDescriptor(derivedRelationshipData.getBean().getClass()); + + BeanProperty prop = beanDescriptor.getBeanProperty(derivedRelationshipData.getLogicalName()); + EntityBean entityBean = (EntityBean)derivedRelationshipData.getBean(); + entityBean._ebean_getIntercept().markPropertyAsChanged(prop.getPropertyIndex()); + + ebeanServer.update(entityBean, transaction); + } + } + } + + /** + * For non batch insert with generated keys. + */ + private void getGeneratedKeys() throws SQLException { + + ResultSet rset = dataBind.getPstmt().getGeneratedKeys(); + try { + if (rset.next()) { + Object idValue = rset.getObject(1); + if (idValue != null) { + persistRequest.setGeneratedKey(idValue); + } + + } else { + throw new PersistenceException(Message.msg("persist.autoinc.norows")); + } + } finally { + try { + rset.close(); + } catch (SQLException ex) { + String msg = "Error closing rset for returning generatedKeys?"; + logger.warn(msg, ex); + } + } + } + + /** + * For non batch insert with DBs that do not support getGeneratedKeys. Use a + * SQL select to fetch back the Id value. + */ + private void fetchGeneratedKeyUsingSelect() throws SQLException { + + Connection conn = transaction.getConnection(); + + PreparedStatement stmt = null; + ResultSet rset = null; + try { + stmt = conn.prepareStatement(selectLastInsertedId); + rset = stmt.executeQuery(); + if (rset.next()) { + Object idValue = rset.getObject(1); + if (idValue != null) { + persistRequest.setGeneratedKey(idValue); + } + } else { + throw new PersistenceException(Message.msg("persist.autoinc.norows")); + } + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException ex) { + String msg = "Error closing rset for fetchGeneratedKeyUsingSelect?"; + logger.warn(msg, ex); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (SQLException ex) { + String msg = "Error closing stmt for fetchGeneratedKeyUsingSelect?"; + logger.warn(msg, ex); + } + } + } + + public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { + persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java index b202bce00..c6c7cbe17 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertMeta.java @@ -1,195 +1,195 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.InheritInfo; -import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableDiscriminator; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; - -/** - * Meta data for insert handler. The meta data is for a particular bean type. It - * is considered immutable and is thread safe. - */ -public final class InsertMeta { - - private final String sqlNullId; - - private final String sqlWithId; - - private final BindableId id; - - private final Bindable discriminator; - - private final Bindable all; - - private final boolean supportsGetGeneratedKeys; - - private final boolean concatinatedKey; - - private final String tableName; - - /** - * Used for DB that do not support getGeneratedKeys. - */ - private final String selectLastInsertedId; - - private final Bindable shadowFKey; - - private final String[] identityDbColumns; - - private final boolean emptyStringToNull; - - public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor desc, Bindable shadowFKey, BindableId id, Bindable all) { - - this.emptyStringToNull = dbPlatform.isTreatEmptyStringsAsNull(); - this.tableName = desc.getBaseTable(); - this.discriminator = getDiscriminator(desc); - this.id = id; - this.all = all; - this.shadowFKey = shadowFKey; - - this.sqlWithId = genSql(false); - - // only available for single Id property - if (id.isConcatenated()) { - // concatenated key - this.concatinatedKey = true; - this.identityDbColumns = null; - this.sqlNullId = null; - this.supportsGetGeneratedKeys = false; - this.selectLastInsertedId = null; - - } else { - // insert sql for db identity or sequence insert - this.concatinatedKey = false; - if (id.getIdentityColumn() == null) { - this.identityDbColumns = new String[]{}; - this.supportsGetGeneratedKeys = false; - this.selectLastInsertedId = null; - } else { - this.identityDbColumns = new String[]{id.getIdentityColumn()}; - this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys(); - this.selectLastInsertedId = desc.getSelectLastInsertedId(); - } - this.sqlNullId = genSql(true); - } - } - - private static Bindable getDiscriminator(BeanDescriptor desc){ - InheritInfo inheritInfo = desc.getInheritInfo(); - if (inheritInfo != null){ - return new BindableDiscriminator(inheritInfo); - } else { - return null; - } - } - - /** - * Return true if empty strings should be treated as null. - */ - public boolean isEmptyStringToNull() { - return emptyStringToNull; - } - - /** - * Return true if this is a concatenated key. - */ - public boolean isConcatinatedKey() { - return concatinatedKey; - } - - public String[] getIdentityDbColumns() { - return identityDbColumns; - } - - /** - * Returns sql that is used to fetch back the last inserted id. This will - * return null if it should not be used. - *

      - * This is only for DB's that do not support getGeneratedKeys. For MS - * SQLServer 2000 this could return "SELECT (at)(at)IDENTITY as id". - *

      - */ - public String getSelectLastInsertedId() { - return selectLastInsertedId; - } - - /** - * Return true if getGeneratedKeys is supported by the underlying jdbc - * driver and database. - */ - public boolean supportsGetGeneratedKeys() { - return supportsGetGeneratedKeys; - } - - /** - * Return true if the Id can be derived from other property values. - */ - public boolean deriveConcatenatedId(PersistRequestBean persist) { - return id.deriveConcatenatedId(persist); - } - - /** - * Bind the request based on whether the id value(s) are null. - */ - public void bind(DmlHandler request, EntityBean bean, boolean withId) throws SQLException { - - if (withId) { - id.dmlBind(request, bean); - } - if (shadowFKey != null){ - shadowFKey.dmlBind(request, bean); - } - if (discriminator != null){ - discriminator.dmlBind(request, bean); - } - all.dmlBind(request, bean); - } - - /** - * get the sql based whether the id value(s) are null. - */ - public String getSql(boolean withId) { - - if (withId) { - return sqlWithId; - } else { - return sqlNullId; - } - } - - private String genSql(boolean nullId) { - - GenerateDmlRequest request = new GenerateDmlRequest(emptyStringToNull, null, true); - request.setInsertSetMode(); - - request.append("insert into ").append(tableName); - request.append(" ("); - - if (!nullId) { - id.dmlAppend(request); - } - - if (shadowFKey != null){ - shadowFKey.dmlAppend(request); - } - - if (discriminator != null){ - discriminator.dmlAppend(request); - } - - all.dmlAppend(request); - - request.append(") values ("); - request.append(request.getInsertBindBuffer()); - request.append(")"); - - return request.toString(); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableDiscriminator; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; + +/** + * Meta data for insert handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class InsertMeta { + + private final String sqlNullId; + + private final String sqlWithId; + + private final BindableId id; + + private final Bindable discriminator; + + private final Bindable all; + + private final boolean supportsGetGeneratedKeys; + + private final boolean concatinatedKey; + + private final String tableName; + + /** + * Used for DB that do not support getGeneratedKeys. + */ + private final String selectLastInsertedId; + + private final Bindable shadowFKey; + + private final String[] identityDbColumns; + + private final boolean emptyStringToNull; + + public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor desc, Bindable shadowFKey, BindableId id, Bindable all) { + + this.emptyStringToNull = dbPlatform.isTreatEmptyStringsAsNull(); + this.tableName = desc.getBaseTable(); + this.discriminator = getDiscriminator(desc); + this.id = id; + this.all = all; + this.shadowFKey = shadowFKey; + + this.sqlWithId = genSql(false); + + // only available for single Id property + if (id.isConcatenated()) { + // concatenated key + this.concatinatedKey = true; + this.identityDbColumns = null; + this.sqlNullId = null; + this.supportsGetGeneratedKeys = false; + this.selectLastInsertedId = null; + + } else { + // insert sql for db identity or sequence insert + this.concatinatedKey = false; + if (id.getIdentityColumn() == null) { + this.identityDbColumns = new String[]{}; + this.supportsGetGeneratedKeys = false; + this.selectLastInsertedId = null; + } else { + this.identityDbColumns = new String[]{id.getIdentityColumn()}; + this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys(); + this.selectLastInsertedId = desc.getSelectLastInsertedId(); + } + this.sqlNullId = genSql(true); + } + } + + private static Bindable getDiscriminator(BeanDescriptor desc){ + InheritInfo inheritInfo = desc.getInheritInfo(); + if (inheritInfo != null){ + return new BindableDiscriminator(inheritInfo); + } else { + return null; + } + } + + /** + * Return true if empty strings should be treated as null. + */ + public boolean isEmptyStringToNull() { + return emptyStringToNull; + } + + /** + * Return true if this is a concatenated key. + */ + public boolean isConcatinatedKey() { + return concatinatedKey; + } + + public String[] getIdentityDbColumns() { + return identityDbColumns; + } + + /** + * Returns sql that is used to fetch back the last inserted id. This will + * return null if it should not be used. + *

      + * This is only for DB's that do not support getGeneratedKeys. For MS + * SQLServer 2000 this could return "SELECT (at)(at)IDENTITY as id". + *

      + */ + public String getSelectLastInsertedId() { + return selectLastInsertedId; + } + + /** + * Return true if getGeneratedKeys is supported by the underlying jdbc + * driver and database. + */ + public boolean supportsGetGeneratedKeys() { + return supportsGetGeneratedKeys; + } + + /** + * Return true if the Id can be derived from other property values. + */ + public boolean deriveConcatenatedId(PersistRequestBean persist) { + return id.deriveConcatenatedId(persist); + } + + /** + * Bind the request based on whether the id value(s) are null. + */ + public void bind(DmlHandler request, EntityBean bean, boolean withId) throws SQLException { + + if (withId) { + id.dmlBind(request, bean); + } + if (shadowFKey != null){ + shadowFKey.dmlBind(request, bean); + } + if (discriminator != null){ + discriminator.dmlBind(request, bean); + } + all.dmlBind(request, bean); + } + + /** + * get the sql based whether the id value(s) are null. + */ + public String getSql(boolean withId) { + + if (withId) { + return sqlWithId; + } else { + return sqlNullId; + } + } + + private String genSql(boolean nullId) { + + GenerateDmlRequest request = new GenerateDmlRequest(emptyStringToNull, null, true); + request.setInsertSetMode(); + + request.append("insert into ").append(tableName); + request.append(" ("); + + if (!nullId) { + id.dmlAppend(request); + } + + if (shadowFKey != null){ + shadowFKey.dmlAppend(request); + } + + if (discriminator != null){ + discriminator.dmlAppend(request); + } + + all.dmlAppend(request); + + request.append(") values ("); + request.append(request.getInsertBindBuffer()); + request.append(")"); + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java index 5d232221b..76edf43cb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/MetaFactory.java @@ -1,112 +1,112 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.config.dbplatform.DbEncrypt; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableUnidirectional; -import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryAssocOnes; -import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryBaseProperties; -import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryEmbedded; -import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryId; -import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryVersion; - -/** - * Factory for creating InsertMeta UpdateMeta and DeleteMeta. - */ -public class MetaFactory { - - private final FactoryBaseProperties baseFact; - private final FactoryEmbedded embeddedFact; - private final FactoryVersion versionFact = new FactoryVersion(); - private final FactoryAssocOnes assocOneFact = new FactoryAssocOnes(); - - private final FactoryId idFact = new FactoryId(); - - /** - * Include Lobs in the base statement. Generally true. Oracle9 used to require - * a separate statement for Clobs and Blobs. - */ - private static final boolean includeLobs = true; - - private final DatabasePlatform dbPlatform; - - private final boolean emptyStringAsNull; - - public MetaFactory(DatabasePlatform dbPlatform) { - this.dbPlatform = dbPlatform; - this.emptyStringAsNull = dbPlatform.isTreatEmptyStringsAsNull(); - - // to bind encryption data before or after the encryption key - DbEncrypt dbEncrypt = dbPlatform.getDbEncrypt(); - boolean bindEncryptDataFirst = dbEncrypt == null ? true : dbEncrypt.isBindEncryptDataFirst(); - - this.baseFact = new FactoryBaseProperties(bindEncryptDataFirst); - this.embeddedFact = new FactoryEmbedded(bindEncryptDataFirst); - } - - /** - * Create the UpdateMeta for the given bean type. - */ - public UpdateMeta createUpdate(BeanDescriptor desc) { - - List setList = new ArrayList(); - - baseFact.create(setList, desc, DmlMode.UPDATE, includeLobs); - embeddedFact.create(setList, desc, DmlMode.UPDATE, includeLobs); - assocOneFact.create(setList, desc, DmlMode.UPDATE); - - BindableId id = idFact.createId(desc); - - Bindable ver = versionFact.create(desc); - - BindableList setBindable = new BindableList(setList); - - return new UpdateMeta(emptyStringAsNull, desc, setBindable, id, ver); - } - - /** - * Create the DeleteMeta for the given bean type. - */ - public DeleteMeta createDelete(BeanDescriptor desc) { - - BindableId id = idFact.createId(desc); - - Bindable ver = versionFact.create(desc); - - return new DeleteMeta(emptyStringAsNull, desc, id, ver); - } - - /** - * Create the InsertMeta for the given bean type. - */ - public InsertMeta createInsert(BeanDescriptor desc) { - - BindableId id = idFact.createId(desc); - - List allList = new ArrayList(); - - baseFact.create(allList, desc, DmlMode.INSERT, includeLobs); - embeddedFact.create(allList, desc, DmlMode.INSERT, includeLobs); - assocOneFact.create(allList, desc, DmlMode.INSERT); - - Bindable allBindable = new BindableList(allList); - - BeanPropertyAssocOne unidirectional = desc.getUnidirectional(); - - Bindable shadowFkey; - if (unidirectional == null) { - shadowFkey = null; - } else { - shadowFkey = new BindableUnidirectional(desc, unidirectional); - } - - return new InsertMeta(dbPlatform, desc, shadowFkey, id, allBindable); - } -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.DbEncrypt; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableUnidirectional; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryAssocOnes; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryBaseProperties; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryEmbedded; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryId; +import com.avaje.ebeaninternal.server.persist.dmlbind.FactoryVersion; + +/** + * Factory for creating InsertMeta UpdateMeta and DeleteMeta. + */ +public class MetaFactory { + + private final FactoryBaseProperties baseFact; + private final FactoryEmbedded embeddedFact; + private final FactoryVersion versionFact = new FactoryVersion(); + private final FactoryAssocOnes assocOneFact = new FactoryAssocOnes(); + + private final FactoryId idFact = new FactoryId(); + + /** + * Include Lobs in the base statement. Generally true. Oracle9 used to require + * a separate statement for Clobs and Blobs. + */ + private static final boolean includeLobs = true; + + private final DatabasePlatform dbPlatform; + + private final boolean emptyStringAsNull; + + public MetaFactory(DatabasePlatform dbPlatform) { + this.dbPlatform = dbPlatform; + this.emptyStringAsNull = dbPlatform.isTreatEmptyStringsAsNull(); + + // to bind encryption data before or after the encryption key + DbEncrypt dbEncrypt = dbPlatform.getDbEncrypt(); + boolean bindEncryptDataFirst = dbEncrypt == null ? true : dbEncrypt.isBindEncryptDataFirst(); + + this.baseFact = new FactoryBaseProperties(bindEncryptDataFirst); + this.embeddedFact = new FactoryEmbedded(bindEncryptDataFirst); + } + + /** + * Create the UpdateMeta for the given bean type. + */ + public UpdateMeta createUpdate(BeanDescriptor desc) { + + List setList = new ArrayList(); + + baseFact.create(setList, desc, DmlMode.UPDATE, includeLobs); + embeddedFact.create(setList, desc, DmlMode.UPDATE, includeLobs); + assocOneFact.create(setList, desc, DmlMode.UPDATE); + + BindableId id = idFact.createId(desc); + + Bindable ver = versionFact.create(desc); + + BindableList setBindable = new BindableList(setList); + + return new UpdateMeta(emptyStringAsNull, desc, setBindable, id, ver); + } + + /** + * Create the DeleteMeta for the given bean type. + */ + public DeleteMeta createDelete(BeanDescriptor desc) { + + BindableId id = idFact.createId(desc); + + Bindable ver = versionFact.create(desc); + + return new DeleteMeta(emptyStringAsNull, desc, id, ver); + } + + /** + * Create the InsertMeta for the given bean type. + */ + public InsertMeta createInsert(BeanDescriptor desc) { + + BindableId id = idFact.createId(desc); + + List allList = new ArrayList(); + + baseFact.create(allList, desc, DmlMode.INSERT, includeLobs); + embeddedFact.create(allList, desc, DmlMode.INSERT, includeLobs); + assocOneFact.create(allList, desc, DmlMode.INSERT); + + Bindable allBindable = new BindableList(allList); + + BeanPropertyAssocOne unidirectional = desc.getUnidirectional(); + + Bindable shadowFkey; + if (unidirectional == null) { + shadowFkey = null; + } else { + shadowFkey = new BindableUnidirectional(desc, unidirectional); + } + + return new InsertMeta(dbPlatform, desc, shadowFkey, id, allBindable); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java index f431f4b71..731953250 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/PersistHandler.java @@ -1,34 +1,34 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; - -/** - * Implementation API for insert update and delete handlers. - */ -public interface PersistHandler { - - /** - * Return the bind log. - */ - public String getBindLog(); - - /** - * Get the sql and bind the statement. - */ - public void bind() throws SQLException; - - /** - * Add this for batch execution. - */ - public void addBatch() throws SQLException; - - /** - * Execute now for non-batch execution. - */ - public void execute() throws SQLException; - - /** - * Close resources including underlying preparedStatement. - */ - public void close() throws SQLException; -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +/** + * Implementation API for insert update and delete handlers. + */ +public interface PersistHandler { + + /** + * Return the bind log. + */ + public String getBindLog(); + + /** + * Get the sql and bind the statement. + */ + public void bind() throws SQLException; + + /** + * Add this for batch execution. + */ + public void addBatch() throws SQLException; + + /** + * Execute now for non-batch execution. + */ + public void execute() throws SQLException; + + /** + * Close resources including underlying preparedStatement. + */ + public void close() throws SQLException; +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java index 948180c02..ed60eee0e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java @@ -1,81 +1,81 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.PreparedStatement; -import java.sql.SQLException; - -import javax.persistence.OptimisticLockException; - -import com.avaje.ebeaninternal.api.DerivedRelationshipData; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.api.SpiUpdatePlan; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.type.DataBind; - -/** - * Update bean handler. - */ -public class UpdateHandler extends DmlHandler { - - private final UpdateMeta meta; - - private boolean emptySetClause; - - public UpdateHandler(PersistRequestBean persist, UpdateMeta meta) { - super(persist, meta.isEmptyStringAsNull()); - this.meta = meta; - } - - /** - * Generate and bind the update statement. - */ - public void bind() throws SQLException { - - SpiUpdatePlan updatePlan = meta.getUpdatePlan(persistRequest); - - if (updatePlan.isEmptySetClause()) { - emptySetClause = true; - return; - } - - sql = updatePlan.getSql(); - - SpiTransaction t = persistRequest.getTransaction(); - - PreparedStatement pstmt; - if (persistRequest.isBatched()) { - pstmt = getPstmt(t, sql, persistRequest, false); - } else { - pstmt = getPstmt(t, sql, false); - } - dataBind = new DataBind(pstmt); - - meta.bind(persistRequest, this, updatePlan); - - setUpdateGenValues(); - - logSql(sql); - } - - @Override - public void addBatch() throws SQLException { - if (!emptySetClause){ - super.addBatch(); - } - } - - /** - * Execute the update in non-batch. - */ - @Override - public void execute() throws SQLException, OptimisticLockException { - if (!emptySetClause){ - int rowCount = dataBind.executeUpdate(); - checkRowCount(rowCount); - } - } - - public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { - persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import javax.persistence.OptimisticLockException; + +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.api.SpiTransaction; +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.type.DataBind; + +/** + * Update bean handler. + */ +public class UpdateHandler extends DmlHandler { + + private final UpdateMeta meta; + + private boolean emptySetClause; + + public UpdateHandler(PersistRequestBean persist, UpdateMeta meta) { + super(persist, meta.isEmptyStringAsNull()); + this.meta = meta; + } + + /** + * Generate and bind the update statement. + */ + public void bind() throws SQLException { + + SpiUpdatePlan updatePlan = meta.getUpdatePlan(persistRequest); + + if (updatePlan.isEmptySetClause()) { + emptySetClause = true; + return; + } + + sql = updatePlan.getSql(); + + SpiTransaction t = persistRequest.getTransaction(); + + PreparedStatement pstmt; + if (persistRequest.isBatched()) { + pstmt = getPstmt(t, sql, persistRequest, false); + } else { + pstmt = getPstmt(t, sql, false); + } + dataBind = new DataBind(pstmt); + + meta.bind(persistRequest, this, updatePlan); + + setUpdateGenValues(); + + logSql(sql); + } + + @Override + public void addBatch() throws SQLException { + if (!emptySetClause){ + super.addBatch(); + } + } + + /** + * Execute the update in non-batch. + */ + @Override + public void execute() throws SQLException, OptimisticLockException { + if (!emptySetClause){ + int rowCount = dataBind.executeUpdate(); + checkRowCount(rowCount); + } + } + + public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { + persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java index 4a3a303c1..afadc9090 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java @@ -1,191 +1,191 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebean.annotation.ConcurrencyMode; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebeaninternal.api.SpiUpdatePlan; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; -import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; - -/** - * Meta data for update handler. The meta data is for a particular bean type. It - * is considered immutable and is thread safe. - */ -public final class UpdateMeta { - - private final String sqlVersion; - - private final String sqlNone; - - private final BindableList set; - private final BindableId id; - private final Bindable version; - - private final String tableName; - - private final UpdatePlan modeNoneUpdatePlan; - private final UpdatePlan modeVersionUpdatePlan; - - private final boolean emptyStringAsNull; - - public UpdateMeta(boolean emptyStringAsNull, BeanDescriptor desc, BindableList set, BindableId id, Bindable version) { - this.emptyStringAsNull = emptyStringAsNull; - this.tableName = desc.getBaseTable(); - this.set = set; - this.id = id; - this.version = version; - - this.sqlNone = genSql(ConcurrencyMode.NONE, null, set); - this.sqlVersion = genSql(ConcurrencyMode.VERSION, null, set); - - this.modeNoneUpdatePlan = new UpdatePlan(ConcurrencyMode.NONE, sqlNone, set); - this.modeVersionUpdatePlan = new UpdatePlan(ConcurrencyMode.VERSION, sqlVersion, set); - } - - /** - * Return true if empty strings should be treated as null. - */ - public boolean isEmptyStringAsNull() { - return emptyStringAsNull; - } - - /** - * Return the base table name. - */ - public String getTableName() { - return tableName; - } - - /** - * Bind the request based on the concurrency mode. - */ - public void bind(PersistRequestBean persist, DmlHandler bind, SpiUpdatePlan updatePlan) throws SQLException { - - EntityBean bean = persist.getEntityBean(); - - updatePlan.bindSet(bind, bean); - - id.dmlBind(bind, bean); - - switch (persist.getConcurrencyMode()) { - case VERSION: - version.dmlBind(bind, bean); - break; - - default: - break; - } - } - - /** - * get or generate the sql based on the concurrency mode. - */ - public SpiUpdatePlan getUpdatePlan(PersistRequestBean request) { - - ConcurrencyMode mode = request.determineConcurrencyMode(); - if (request.isDynamicUpdateSql()) { - return getDynamicUpdatePlan(mode, request); - } - - // 'full bean' update... - switch (mode) { - case NONE: - return modeNoneUpdatePlan; - - case VERSION: - return modeVersionUpdatePlan; - - default: - throw new RuntimeException("Invalid mode " + mode); - } - } - - private SpiUpdatePlan getDynamicUpdatePlan(ConcurrencyMode mode, PersistRequestBean persistRequest) { - - - // we can use a cached UpdatePlan for the changed properties - - EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept(); - int hash = ebi.getDirtyPropertyHash(); - - BeanDescriptor beanDescriptor = persistRequest.getBeanDescriptor(); - - BeanProperty versionProperty = beanDescriptor.getVersionProperty(); - if (versionProperty != null) { - if (ebi.isLoadedProperty(versionProperty.getPropertyIndex())) { - hash = hash * 31 + 7; - } - } - - Integer key = Integer.valueOf(hash); - - SpiUpdatePlan updatePlan = beanDescriptor.getUpdatePlan(key); - if (updatePlan != null) { - return updatePlan; - } - - // build a new UpdatePlan and cache it - - // build a bindableList that only contains the changed properties - List list = new ArrayList(); - set.addToUpdate(persistRequest, list); - BindableList bindableList = new BindableList(list); - - // build the SQL for this update statement - String sql = genSql(mode, persistRequest, bindableList); - - updatePlan = new UpdatePlan(key, mode, sql, bindableList); - - // add the UpdatePlan to the cache - beanDescriptor.putUpdatePlan(key, updatePlan); - - return updatePlan; - } - - private String genSql(ConcurrencyMode conMode, PersistRequestBean persistRequest, BindableList bindableList) { - - // update set col0=?, col1=?, col2=? where bcol=? and bc1=? and bc2=? - - GenerateDmlRequest request; - if (persistRequest == null) { - // For generation of None and Version DML/SQL - request = new GenerateDmlRequest(emptyStringAsNull); - } else { - request = persistRequest.createGenerateDmlRequest(emptyStringAsNull); - } - - request.append("update ").append(tableName).append(" set "); - - request.setUpdateSetMode(); - bindableList.dmlAppend(request); - - if (request.getBindColumnCount() == 0) { - // update properties must have been updatable=false - // with the result that nothing is in the set clause - return null; - } - - request.append(" where "); - - request.setWhereIdMode(); - id.dmlAppend(request); - - if (ConcurrencyMode.VERSION.equals(conMode)) { - if (version == null) { - return null; - } - version.dmlAppend(request); - } - - return request.toString(); - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId; +import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList; + +/** + * Meta data for update handler. The meta data is for a particular bean type. It + * is considered immutable and is thread safe. + */ +public final class UpdateMeta { + + private final String sqlVersion; + + private final String sqlNone; + + private final BindableList set; + private final BindableId id; + private final Bindable version; + + private final String tableName; + + private final UpdatePlan modeNoneUpdatePlan; + private final UpdatePlan modeVersionUpdatePlan; + + private final boolean emptyStringAsNull; + + public UpdateMeta(boolean emptyStringAsNull, BeanDescriptor desc, BindableList set, BindableId id, Bindable version) { + this.emptyStringAsNull = emptyStringAsNull; + this.tableName = desc.getBaseTable(); + this.set = set; + this.id = id; + this.version = version; + + this.sqlNone = genSql(ConcurrencyMode.NONE, null, set); + this.sqlVersion = genSql(ConcurrencyMode.VERSION, null, set); + + this.modeNoneUpdatePlan = new UpdatePlan(ConcurrencyMode.NONE, sqlNone, set); + this.modeVersionUpdatePlan = new UpdatePlan(ConcurrencyMode.VERSION, sqlVersion, set); + } + + /** + * Return true if empty strings should be treated as null. + */ + public boolean isEmptyStringAsNull() { + return emptyStringAsNull; + } + + /** + * Return the base table name. + */ + public String getTableName() { + return tableName; + } + + /** + * Bind the request based on the concurrency mode. + */ + public void bind(PersistRequestBean persist, DmlHandler bind, SpiUpdatePlan updatePlan) throws SQLException { + + EntityBean bean = persist.getEntityBean(); + + updatePlan.bindSet(bind, bean); + + id.dmlBind(bind, bean); + + switch (persist.getConcurrencyMode()) { + case VERSION: + version.dmlBind(bind, bean); + break; + + default: + break; + } + } + + /** + * get or generate the sql based on the concurrency mode. + */ + public SpiUpdatePlan getUpdatePlan(PersistRequestBean request) { + + ConcurrencyMode mode = request.determineConcurrencyMode(); + if (request.isDynamicUpdateSql()) { + return getDynamicUpdatePlan(mode, request); + } + + // 'full bean' update... + switch (mode) { + case NONE: + return modeNoneUpdatePlan; + + case VERSION: + return modeVersionUpdatePlan; + + default: + throw new RuntimeException("Invalid mode " + mode); + } + } + + private SpiUpdatePlan getDynamicUpdatePlan(ConcurrencyMode mode, PersistRequestBean persistRequest) { + + + // we can use a cached UpdatePlan for the changed properties + + EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept(); + int hash = ebi.getDirtyPropertyHash(); + + BeanDescriptor beanDescriptor = persistRequest.getBeanDescriptor(); + + BeanProperty versionProperty = beanDescriptor.getVersionProperty(); + if (versionProperty != null) { + if (ebi.isLoadedProperty(versionProperty.getPropertyIndex())) { + hash = hash * 31 + 7; + } + } + + Integer key = Integer.valueOf(hash); + + SpiUpdatePlan updatePlan = beanDescriptor.getUpdatePlan(key); + if (updatePlan != null) { + return updatePlan; + } + + // build a new UpdatePlan and cache it + + // build a bindableList that only contains the changed properties + List list = new ArrayList(); + set.addToUpdate(persistRequest, list); + BindableList bindableList = new BindableList(list); + + // build the SQL for this update statement + String sql = genSql(mode, persistRequest, bindableList); + + updatePlan = new UpdatePlan(key, mode, sql, bindableList); + + // add the UpdatePlan to the cache + beanDescriptor.putUpdatePlan(key, updatePlan); + + return updatePlan; + } + + private String genSql(ConcurrencyMode conMode, PersistRequestBean persistRequest, BindableList bindableList) { + + // update set col0=?, col1=?, col2=? where bcol=? and bc1=? and bc2=? + + GenerateDmlRequest request; + if (persistRequest == null) { + // For generation of None and Version DML/SQL + request = new GenerateDmlRequest(emptyStringAsNull); + } else { + request = persistRequest.createGenerateDmlRequest(emptyStringAsNull); + } + + request.append("update ").append(tableName).append(" set "); + + request.setUpdateSetMode(); + bindableList.dmlAppend(request); + + if (request.getBindColumnCount() == 0) { + // update properties must have been updatable=false + // with the result that nothing is in the set clause + return null; + } + + request.append(" where "); + + request.setWhereIdMode(); + id.dmlAppend(request); + + if (ConcurrencyMode.VERSION.equals(conMode)) { + if (version == null) { + return null; + } + version.dmlAppend(request); + } + + return request.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java index 16602ae83..093764da7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdatePlan.java @@ -1,128 +1,128 @@ -package com.avaje.ebeaninternal.server.persist.dml; - -import java.sql.SQLException; - -import com.avaje.ebean.annotation.ConcurrencyMode; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.SpiUpdatePlan; -import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; - -/** - * Plan for executing bean updates for a given set of changed properties. - */ -public class UpdatePlan implements SpiUpdatePlan { - - /** - * Special plan used when there is nothing in the set clause and the update should in fact be - * skipped. Occurs when the updated properties have updatable=false in their deployment. - */ - public static final UpdatePlan EMPTY_SET_CLAUSE = new UpdatePlan(); - - private final Integer key; - - private final ConcurrencyMode mode; - - private final String sql; - - private final Bindable set; - - private final long timeCreated; - - private final boolean emptySetClause; - - private Long timeLastUsed; - - /** - * Create a non cached UpdatePlan. - */ - public UpdatePlan(ConcurrencyMode mode, String sql, Bindable set) { - - this(null, mode, sql, set); - } - - /** - * Create a UpdatePlan with a given key. - */ - public UpdatePlan(Integer key, ConcurrencyMode mode, String sql, Bindable set) { - - this.emptySetClause = (sql == null); - this.key = key; - this.mode = mode; - this.sql = sql; - this.set = set; - this.timeCreated = System.currentTimeMillis(); - } - - /** - * Special constructor for emptySetClause=true instance. - */ - private UpdatePlan() { - this.emptySetClause = true; - this.key = Integer.valueOf(0); - this.mode = ConcurrencyMode.NONE; - this.sql = null; - this.set = null; - this.timeCreated = 0; - } - - public boolean isEmptySetClause() { - return emptySetClause; - } - - /** - * Run the prepared statement binding for the 'update set' properties. - */ - public void bindSet(DmlHandler bind, EntityBean bean) throws SQLException { - - set.dmlBind(bind, bean); - - // not strictly 'thread safe' but object assignment is atomic - Long touched = Long.valueOf(System.currentTimeMillis()); - this.timeLastUsed = touched; - } - - /** - * Return the time this plan was created. - */ - public long getTimeCreated() { - return timeCreated; - } - - /** - * Return the time this plan was last used. - */ - public Long getTimeLastUsed() { - - // not thread safe but atomic - return timeLastUsed; - } - - /** - * Return the hash key. - */ - public Integer getKey() { - return key; - } - - /** - * Return the concurrency mode for this plan. - */ - public ConcurrencyMode getMode() { - return mode; - } - - /** - * Return the DML statement. - */ - public String getSql() { - return sql; - } - - /** - * Return the Bindable properties for the update set. - */ - public Bindable getSet() { - return set; - } - -} +package com.avaje.ebeaninternal.server.persist.dml; + +import java.sql.SQLException; + +import com.avaje.ebean.annotation.ConcurrencyMode; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiUpdatePlan; +import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable; + +/** + * Plan for executing bean updates for a given set of changed properties. + */ +public class UpdatePlan implements SpiUpdatePlan { + + /** + * Special plan used when there is nothing in the set clause and the update should in fact be + * skipped. Occurs when the updated properties have updatable=false in their deployment. + */ + public static final UpdatePlan EMPTY_SET_CLAUSE = new UpdatePlan(); + + private final Integer key; + + private final ConcurrencyMode mode; + + private final String sql; + + private final Bindable set; + + private final long timeCreated; + + private final boolean emptySetClause; + + private Long timeLastUsed; + + /** + * Create a non cached UpdatePlan. + */ + public UpdatePlan(ConcurrencyMode mode, String sql, Bindable set) { + + this(null, mode, sql, set); + } + + /** + * Create a UpdatePlan with a given key. + */ + public UpdatePlan(Integer key, ConcurrencyMode mode, String sql, Bindable set) { + + this.emptySetClause = (sql == null); + this.key = key; + this.mode = mode; + this.sql = sql; + this.set = set; + this.timeCreated = System.currentTimeMillis(); + } + + /** + * Special constructor for emptySetClause=true instance. + */ + private UpdatePlan() { + this.emptySetClause = true; + this.key = Integer.valueOf(0); + this.mode = ConcurrencyMode.NONE; + this.sql = null; + this.set = null; + this.timeCreated = 0; + } + + public boolean isEmptySetClause() { + return emptySetClause; + } + + /** + * Run the prepared statement binding for the 'update set' properties. + */ + public void bindSet(DmlHandler bind, EntityBean bean) throws SQLException { + + set.dmlBind(bind, bean); + + // not strictly 'thread safe' but object assignment is atomic + Long touched = Long.valueOf(System.currentTimeMillis()); + this.timeLastUsed = touched; + } + + /** + * Return the time this plan was created. + */ + public long getTimeCreated() { + return timeCreated; + } + + /** + * Return the time this plan was last used. + */ + public Long getTimeLastUsed() { + + // not thread safe but atomic + return timeLastUsed; + } + + /** + * Return the hash key. + */ + public Integer getKey() { + return key; + } + + /** + * Return the concurrency mode for this plan. + */ + public ConcurrencyMode getMode() { + return mode; + } + + /** + * Return the DML statement. + */ + public String getSql() { + return sql; + } + + /** + * Return the Bindable properties for the update set. + */ + public Bindable getSet() { + return set; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java index 861d69253..a0823a0c9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/Bindable.java @@ -1,42 +1,42 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Item held by Meta objects used to generate and bind bean insert update and - * delete statements. - *

      - * An implementation is expected to be immutable and thread safe. - *

      - *

      - * The design is to take a bean structure with embedded and associated objects - * etc and flatten that into lists of Bindable objects. These are put into - * InsertMeta UpdateMeta and DeleteMeta objects to support the generation of DML - * and binding of statements in a fast and painless manor. - *

      - */ -public interface Bindable { - - /** - * For Updates including only changed properties add the Bindable to the - * list if it should be included in the 'update set'. - */ - public void addToUpdate(PersistRequestBean request, List list); - - /** - * append sql to the buffer with prefix and suffix options. - */ - public void dmlAppend(GenerateDmlRequest request); - - /** - * Bind given the request and bean. The bean could be the oldValues bean - * when binding a update or delete where clause with ALL concurrency mode. - */ - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException; - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Item held by Meta objects used to generate and bind bean insert update and + * delete statements. + *

      + * An implementation is expected to be immutable and thread safe. + *

      + *

      + * The design is to take a bean structure with embedded and associated objects + * etc and flatten that into lists of Bindable objects. These are put into + * InsertMeta UpdateMeta and DeleteMeta objects to support the generation of DML + * and binding of statements in a fast and painless manor. + *

      + */ +public interface Bindable { + + /** + * For Updates including only changed properties add the Bindable to the + * list if it should be included in the 'update set'. + */ + public void addToUpdate(PersistRequestBean request, List list); + + /** + * append sql to the buffer with prefix and suffix options. + */ + public void dmlAppend(GenerateDmlRequest request); + + /** + * Bind given the request and bean. The bean could be the oldValues bean + * when binding a update or delete where clause with ALL concurrency mode. + */ + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java index b7c639aeb..4b0c6aa59 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableAssocOne.java @@ -1,56 +1,56 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.DerivedRelationshipData; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.deploy.id.ImportedId; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for an ManyToOne or OneToOne associated bean. - */ -public class BindableAssocOne implements Bindable { - - private final BeanPropertyAssocOne assocOne; - - private final ImportedId importedId; - - public BindableAssocOne(BeanPropertyAssocOne assocOne) { - this.assocOne = assocOne; - this.importedId = assocOne.getImportedId(); - } - - public String toString() { - return "BindableAssocOne " + assocOne; - } - - public void addToUpdate(PersistRequestBean request, List list) { - if (request.isAddToUpdate(assocOne)) { - list.add(this); - } - } - - public void dmlAppend(GenerateDmlRequest request) { - importedId.dmlAppend(request); - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - EntityBean assocBean = (EntityBean)assocOne.getValue(bean); - Object boundValue = importedId.bind(request, assocBean); - if (boundValue == null && assocBean != null) { - // this is the scenario for a derived foreign key - // which will require an additional update - // register for post insert of assocBean - // update of bean set ... importedId.getLogicalName(); - // value of assocBean.getId - DerivedRelationshipData d = new DerivedRelationshipData(assocBean, assocOne.getName(), bean); - request.registerDerivedRelationship(d); - } - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for an ManyToOne or OneToOne associated bean. + */ +public class BindableAssocOne implements Bindable { + + private final BeanPropertyAssocOne assocOne; + + private final ImportedId importedId; + + public BindableAssocOne(BeanPropertyAssocOne assocOne) { + this.assocOne = assocOne; + this.importedId = assocOne.getImportedId(); + } + + public String toString() { + return "BindableAssocOne " + assocOne; + } + + public void addToUpdate(PersistRequestBean request, List list) { + if (request.isAddToUpdate(assocOne)) { + list.add(this); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + importedId.dmlAppend(request); + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + EntityBean assocBean = (EntityBean)assocOne.getValue(bean); + Object boundValue = importedId.bind(request, assocBean); + if (boundValue == null && assocBean != null) { + // this is the scenario for a derived foreign key + // which will require an additional update + // register for post insert of assocBean + // update of bean set ... importedId.getLogicalName(); + // value of assocBean.getId + DerivedRelationshipData d = new DerivedRelationshipData(assocBean, assocOne.getName(), bean); + request.registerDerivedRelationship(d); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java index a70604b29..8f631c50d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableCompound.java @@ -1,54 +1,54 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a Immutable Compound value object. - */ -public class BindableCompound implements Bindable { - - private final BindableProperty[] items; - - private final BeanPropertyCompound compound; - - public BindableCompound(BeanPropertyCompound embProp, List list) { - this.compound = embProp; - this.items = list.toArray(new BindableProperty[list.size()]); - } - - public String toString() { - return "BindableCompound " + compound + " items:" + Arrays.toString(items); - } - - public void dmlAppend(GenerateDmlRequest request) { - - for (int i = 0; i < items.length; i++) { - items[i].dmlAppend(request); - } - } - - public void addToUpdate(PersistRequestBean request, List list) { - if (request.isAddToUpdate(compound)) { - list.add(this); - } - } - - public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { - - // get the compound type value - Object valueObject = compound.getValue(bean); - - // bind each of the underlying scalar values for this compound type - for (int i = 0; i < items.length; i++) { - items[i].dmlBindObject(bindRequest, valueObject); - } - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a Immutable Compound value object. + */ +public class BindableCompound implements Bindable { + + private final BindableProperty[] items; + + private final BeanPropertyCompound compound; + + public BindableCompound(BeanPropertyCompound embProp, List list) { + this.compound = embProp; + this.items = list.toArray(new BindableProperty[list.size()]); + } + + public String toString() { + return "BindableCompound " + compound + " items:" + Arrays.toString(items); + } + + public void dmlAppend(GenerateDmlRequest request) { + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request); + } + } + + public void addToUpdate(PersistRequestBean request, List list) { + if (request.isAddToUpdate(compound)) { + list.add(this); + } + } + + public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { + + // get the compound type value + Object valueObject = compound.getValue(bean); + + // bind each of the underlying scalar values for this compound type + for (int i = 0; i < items.length; i++) { + items[i].dmlBindObject(bindRequest, valueObject); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java index 6d7574e50..dd9a7ad6b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableDiscriminator.java @@ -1,45 +1,45 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.InheritInfo; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for inserting a discriminator value. - */ -public class BindableDiscriminator implements Bindable { - - private final String columnName; - private final Object discValue; - private final int sqlType; - - public BindableDiscriminator(InheritInfo inheritInfo) { - this.columnName = inheritInfo.getDiscriminatorColumn(); - this.discValue = inheritInfo.getDiscriminatorValue(); - this.sqlType = inheritInfo.getDiscriminatorType(); - } - - public String toString() { - return columnName + " = " + discValue; - } - - public void addToUpdate(PersistRequestBean request, List list) { - throw new PersistenceException("Never called (only for inserts)"); - } - - public void dmlAppend(GenerateDmlRequest request) { - request.appendColumn(columnName); - } - - public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { - - bindRequest.bind(columnName, discValue, sqlType); - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.InheritInfo; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for inserting a discriminator value. + */ +public class BindableDiscriminator implements Bindable { + + private final String columnName; + private final Object discValue; + private final int sqlType; + + public BindableDiscriminator(InheritInfo inheritInfo) { + this.columnName = inheritInfo.getDiscriminatorColumn(); + this.discValue = inheritInfo.getDiscriminatorValue(); + this.sqlType = inheritInfo.getDiscriminatorType(); + } + + public String toString() { + return columnName + " = " + discValue; + } + + public void addToUpdate(PersistRequestBean request, List list) { + throw new PersistenceException("Never called (only for inserts)"); + } + + public void dmlAppend(GenerateDmlRequest request) { + request.appendColumn(columnName); + } + + public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { + + bindRequest.bind(columnName, discValue, sqlType); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java index e4b0697ae..bec5aab8c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEmbedded.java @@ -1,60 +1,60 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a Embedded bean. - */ -public class BindableEmbedded implements Bindable { - - private final Bindable[] items; - - private final BeanPropertyAssocOne embProp; - - public BindableEmbedded(BeanPropertyAssocOne embProp, List bindList) { - this.embProp = embProp; - this.items = bindList.toArray(new Bindable[bindList.size()]); //this.props = propList.toArray(new BeanProperty[propList.size()]); - } - - public String toString() { - return "BindableEmbedded " + embProp + " items:" + Arrays.toString(items); - } - - public void dmlAppend(GenerateDmlRequest request) { - - for (int i = 0; i < items.length; i++) { - items[i].dmlAppend(request); - } - } - - public void addToUpdate(PersistRequestBean request, List list) { - if (request.isAddToUpdate(embProp)) { - list.add(this); - } - } - - public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { - - // get the embedded bean - EntityBean embBean = (EntityBean)embProp.getValue(bean); - if (embBean == null) { - for (int i = 0; i < items.length; i++) { - items[i].dmlBind(bindRequest, null); - } - } else { - //EntityBeanIntercept ebi = embBean._ebean_getIntercept(); - for (int i = 0; i < items.length; i++) { - //if (ebi.isLoadedProperty(props[i].getPropertyIndex())) { - items[i].dmlBind(bindRequest, embBean); - //} - } - } - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a Embedded bean. + */ +public class BindableEmbedded implements Bindable { + + private final Bindable[] items; + + private final BeanPropertyAssocOne embProp; + + public BindableEmbedded(BeanPropertyAssocOne embProp, List bindList) { + this.embProp = embProp; + this.items = bindList.toArray(new Bindable[bindList.size()]); //this.props = propList.toArray(new BeanProperty[propList.size()]); + } + + public String toString() { + return "BindableEmbedded " + embProp + " items:" + Arrays.toString(items); + } + + public void dmlAppend(GenerateDmlRequest request) { + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request); + } + } + + public void addToUpdate(PersistRequestBean request, List list) { + if (request.isAddToUpdate(embProp)) { + list.add(this); + } + } + + public void dmlBind(BindableRequest bindRequest, EntityBean bean) throws SQLException { + + // get the embedded bean + EntityBean embBean = (EntityBean)embProp.getValue(bean); + if (embBean == null) { + for (int i = 0; i < items.length; i++) { + items[i].dmlBind(bindRequest, null); + } + } else { + //EntityBeanIntercept ebi = embBean._ebean_getIntercept(); + for (int i = 0; i < items.length; i++) { + //if (ebi.isLoadedProperty(props[i].getPropertyIndex())) { + items[i].dmlBind(bindRequest, embBean); + //} + } + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java index f32212d7f..44a091299 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableEncryptedProperty.java @@ -1,69 +1,69 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a DB encrypted BeanProperty. - */ -public class BindableEncryptedProperty implements Bindable { - - private final BeanProperty prop; - - private final boolean bindEncryptDataFirst; - - public BindableEncryptedProperty(BeanProperty prop, boolean bindEncryptDataFirst) { - this.prop = prop; - this.bindEncryptDataFirst = bindEncryptDataFirst; - } - - public String toString() { - return prop.toString(); - } - - public void addToUpdate(PersistRequestBean request, List list) { - if (request.isAddToUpdate(prop)) { - list.add(this); - } - } - - public void dmlAppend(GenerateDmlRequest request) { - - // columnName = AES_ENCRYPT(?,?) - request.appendColumn(prop.getDbColumn(), prop.getDbBind()); - } - - - /** - * Bind a value in a Insert SET clause. - */ - public void dmlBind(BindableRequest request, EntityBean bean) - throws SQLException { - - Object value = null; - if (bean != null) { - value = prop.getValue(bean); - } - - // get Encrypt key - String encryptKeyValue = prop.getEncryptKey().getStringValue(); - - if (!bindEncryptDataFirst){ - // H2 encrypt function ... different parameter order - request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); - } - request.bindNoLog(value, prop); - - if (bindEncryptDataFirst){ - // MySql, Postgres, Oracle - request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); - } - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a DB encrypted BeanProperty. + */ +public class BindableEncryptedProperty implements Bindable { + + private final BeanProperty prop; + + private final boolean bindEncryptDataFirst; + + public BindableEncryptedProperty(BeanProperty prop, boolean bindEncryptDataFirst) { + this.prop = prop; + this.bindEncryptDataFirst = bindEncryptDataFirst; + } + + public String toString() { + return prop.toString(); + } + + public void addToUpdate(PersistRequestBean request, List list) { + if (request.isAddToUpdate(prop)) { + list.add(this); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + + // columnName = AES_ENCRYPT(?,?) + request.appendColumn(prop.getDbColumn(), prop.getDbBind()); + } + + + /** + * Bind a value in a Insert SET clause. + */ + public void dmlBind(BindableRequest request, EntityBean bean) + throws SQLException { + + Object value = null; + if (bean != null) { + value = prop.getValue(bean); + } + + // get Encrypt key + String encryptKeyValue = prop.getEncryptKey().getStringValue(); + + if (!bindEncryptDataFirst){ + // H2 encrypt function ... different parameter order + request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); + } + request.bindNoLog(value, prop); + + if (bindEncryptDataFirst){ + // MySql, Postgres, Oracle + request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****"); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java index d5f5243b9..4ae2966db 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableId.java @@ -1,42 +1,42 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import com.avaje.ebeaninternal.server.core.PersistRequestBean; - -/** - * Adds support for id creation for concatenated ids on intersection tables. - *

      - * Specifically if the concatenated id object is null on insert this can be - * built from the matching ManyToOne associated beans. For example RoleUserId - * embeddedId object could be built from the associated Role and User beans. - *

      - *

      - * This is only attempted if the id is null when it gets to the insert. - *

      - */ -public interface BindableId extends Bindable { - - /** - * Return true if there is no Id properties at all. - */ - public boolean isEmpty(); - - /** - * Return true if this is a concatenated key. - */ - public boolean isConcatenated(); - - /** - * Return the DB Column to use with genGeneratedKeys. - */ - public String getIdentityColumn(); - - /** - * Create the concatenated id for inserts with PFK relationships. - *

      - * Really only where there are ManyToOne assoc beans that make up the - * primary key and the values can be got from those. - *

      - */ - public boolean deriveConcatenatedId(PersistRequestBean persist); - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; + +/** + * Adds support for id creation for concatenated ids on intersection tables. + *

      + * Specifically if the concatenated id object is null on insert this can be + * built from the matching ManyToOne associated beans. For example RoleUserId + * embeddedId object could be built from the associated Role and User beans. + *

      + *

      + * This is only attempted if the id is null when it gets to the insert. + *

      + */ +public interface BindableId extends Bindable { + + /** + * Return true if there is no Id properties at all. + */ + public boolean isEmpty(); + + /** + * Return true if this is a concatenated key. + */ + public boolean isConcatenated(); + + /** + * Return the DB Column to use with genGeneratedKeys. + */ + public String getIdentityColumn(); + + /** + * Create the concatenated id for inserts with PFK relationships. + *

      + * Really only where there are ManyToOne assoc beans that make up the + * primary key and the values can be got from those. + *

      + */ + public boolean deriveConcatenatedId(PersistRequestBean persist); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java index 17db1a309..be1ce40d4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java @@ -1,101 +1,101 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.Arrays; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a EmbeddedId. - */ -public final class BindableIdEmbedded implements BindableId { - - private final BeanPropertyAssocOne embId; - - private final BeanProperty[] props; - - private final MatchedImportedProperty[] matches; - - public BindableIdEmbedded(BeanPropertyAssocOne embId, BeanDescriptor desc) { - this.embId = embId; - this.props = embId.getProperties(); - matches = MatchedImportedProperty.build(props, desc); - } - - public boolean isEmpty() { - return false; - } - - public boolean isConcatenated() { - return true; - } - - public String getIdentityColumn() { - // return null for concatenated keys - return null; - } - - @Override - public String toString() { - return embId + " props:" + Arrays.toString(props); - } - - /** - * Does nothing for BindableId. - */ - public void addToUpdate(PersistRequestBean request, List list) { - // do nothing (id not changing) - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - EntityBean idValue = (EntityBean)embId.getValue(bean); - - for (int i = 0; i < props.length; i++) { - - Object value = props[i].getValue(idValue); - request.bind(value, props[i]); - } - - request.setIdValue(idValue); - } - - public void dmlAppend(GenerateDmlRequest request) { - for (int i = 0; i < props.length; i++) { - request.appendColumn(props[i].getDbColumn()); - } - } - - public boolean deriveConcatenatedId(PersistRequestBean persist) { - - if (matches == null) { - String m = "Matches for the concatinated key columns where not found?" - + " I expect that the concatinated key was null, and this bean does" - + " not have ManyToOne assoc beans matching the primary key columns?"; - throw new PersistenceException(m); - } - - EntityBean bean = persist.getEntityBean(); - - // create the new id - EntityBean newId = (EntityBean)embId.createEmbeddedId(); - - // populate it from the assoc one id values... - for (int i = 0; i < matches.length; i++) { - matches[i].populate(bean, newId); - } - - // support PropertyChangeSupport - embId.setValueIntercept(bean, newId); - return true; - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a EmbeddedId. + */ +public final class BindableIdEmbedded implements BindableId { + + private final BeanPropertyAssocOne embId; + + private final BeanProperty[] props; + + private final MatchedImportedProperty[] matches; + + public BindableIdEmbedded(BeanPropertyAssocOne embId, BeanDescriptor desc) { + this.embId = embId; + this.props = embId.getProperties(); + matches = MatchedImportedProperty.build(props, desc); + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return true; + } + + public String getIdentityColumn() { + // return null for concatenated keys + return null; + } + + @Override + public String toString() { + return embId + " props:" + Arrays.toString(props); + } + + /** + * Does nothing for BindableId. + */ + public void addToUpdate(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + EntityBean idValue = (EntityBean)embId.getValue(bean); + + for (int i = 0; i < props.length; i++) { + + Object value = props[i].getValue(idValue); + request.bind(value, props[i]); + } + + request.setIdValue(idValue); + } + + public void dmlAppend(GenerateDmlRequest request) { + for (int i = 0; i < props.length; i++) { + request.appendColumn(props[i].getDbColumn()); + } + } + + public boolean deriveConcatenatedId(PersistRequestBean persist) { + + if (matches == null) { + String m = "Matches for the concatinated key columns where not found?" + + " I expect that the concatinated key was null, and this bean does" + + " not have ManyToOne assoc beans matching the primary key columns?"; + throw new PersistenceException(m); + } + + EntityBean bean = persist.getEntityBean(); + + // create the new id + EntityBean newId = (EntityBean)embId.createEmbeddedId(); + + // populate it from the assoc one id values... + for (int i = 0; i < matches.length; i++) { + matches[i].populate(bean, newId); + } + + // support PropertyChangeSupport + embId.setValueIntercept(bean, newId); + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java index b824020b6..dc33dd9b6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdMap.java @@ -1,95 +1,95 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a concatenated id that is not embedded. - */ -public final class BindableIdMap implements BindableId { - - private final BeanProperty[] uids; - - private final MatchedImportedProperty[] matches; - - public BindableIdMap(BeanProperty[] uids, BeanDescriptor desc) { - this.uids = uids; - matches = MatchedImportedProperty.build(uids, desc); - } - - public boolean isEmpty() { - return false; - } - - public boolean isConcatenated() { - return true; - } - - public String getIdentityColumn() { - // return null for concatenated keys - return null; - } - - @Override - public String toString() { - return Arrays.toString(uids); - } - - /** - * Does nothing for BindableId. - */ - public void addToUpdate(PersistRequestBean request, List list) { - // do nothing (id not changing) - } - - public void dmlAppend(GenerateDmlRequest request) { - for (int i = 0; i < uids.length; i++) { - request.appendColumn(uids[i].getDbColumn()); - } - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - LinkedHashMap mapId = new LinkedHashMap(); - for (int i = 0; i < uids.length; i++) { - Object value = uids[i].getValue(bean); - - request.bind(value, uids[i]); - - // putting logicalType into map rather than - // the dbType (which may have been converted). - mapId.put(uids[i].getName(), value); - } - request.setIdValue(mapId); - } - - public boolean deriveConcatenatedId(PersistRequestBean persist) { - - if (matches == null) { - String m = "Matches for the concatinated key columns where not found?" - + " I expect that the concatinated key was null, and this bean does" - + " not have ManyToOne assoc beans matching the primary key columns?"; - throw new PersistenceException(m); - } - - EntityBean bean = persist.getEntityBean(); - - // populate it from the assoc one id values... - for (int i = 0; i < matches.length; i++) { - matches[i].populate(bean, bean); - } - - return true; - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a concatenated id that is not embedded. + */ +public final class BindableIdMap implements BindableId { + + private final BeanProperty[] uids; + + private final MatchedImportedProperty[] matches; + + public BindableIdMap(BeanProperty[] uids, BeanDescriptor desc) { + this.uids = uids; + matches = MatchedImportedProperty.build(uids, desc); + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return true; + } + + public String getIdentityColumn() { + // return null for concatenated keys + return null; + } + + @Override + public String toString() { + return Arrays.toString(uids); + } + + /** + * Does nothing for BindableId. + */ + public void addToUpdate(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + public void dmlAppend(GenerateDmlRequest request) { + for (int i = 0; i < uids.length; i++) { + request.appendColumn(uids[i].getDbColumn()); + } + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + LinkedHashMap mapId = new LinkedHashMap(); + for (int i = 0; i < uids.length; i++) { + Object value = uids[i].getValue(bean); + + request.bind(value, uids[i]); + + // putting logicalType into map rather than + // the dbType (which may have been converted). + mapId.put(uids[i].getName(), value); + } + request.setIdValue(mapId); + } + + public boolean deriveConcatenatedId(PersistRequestBean persist) { + + if (matches == null) { + String m = "Matches for the concatinated key columns where not found?" + + " I expect that the concatinated key was null, and this bean does" + + " not have ManyToOne assoc beans matching the primary key columns?"; + throw new PersistenceException(m); + } + + EntityBean bean = persist.getEntityBean(); + + // populate it from the assoc one id values... + for (int i = 0; i < matches.length; i++) { + matches[i].populate(bean, bean); + } + + return true; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java index 5da2cd89b..e17946d92 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableIdScalar.java @@ -1,70 +1,70 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a single scalar id property. - */ -public final class BindableIdScalar implements BindableId { - - private final BeanProperty uidProp; - - public BindableIdScalar(BeanProperty uidProp) { - this.uidProp = uidProp; - } - - public boolean isEmpty() { - return false; - } - - public boolean isConcatenated() { - return false; - } - - public String getIdentityColumn() { - return uidProp.getDbColumn(); - } - - @Override - public String toString() { - return uidProp.toString(); - } - - /** - * Does nothing for BindableId. - */ - public void addToUpdate(PersistRequestBean request, List list) { - // do nothing (id not changing) - } - - /** - * Should not be called as this is really only for concatenated keys. - */ - public boolean deriveConcatenatedId(PersistRequestBean persist) { - throw new PersistenceException("Should not be called? only for concatinated keys"); - } - - public void dmlAppend(GenerateDmlRequest request) { - - request.appendColumn(uidProp.getDbColumn()); - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - Object value = uidProp.getValue(bean); - - request.bind(value, uidProp); - - // used for summary logging - request.setIdValue(value); - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a single scalar id property. + */ +public final class BindableIdScalar implements BindableId { + + private final BeanProperty uidProp; + + public BindableIdScalar(BeanProperty uidProp) { + this.uidProp = uidProp; + } + + public boolean isEmpty() { + return false; + } + + public boolean isConcatenated() { + return false; + } + + public String getIdentityColumn() { + return uidProp.getDbColumn(); + } + + @Override + public String toString() { + return uidProp.toString(); + } + + /** + * Does nothing for BindableId. + */ + public void addToUpdate(PersistRequestBean request, List list) { + // do nothing (id not changing) + } + + /** + * Should not be called as this is really only for concatenated keys. + */ + public boolean deriveConcatenatedId(PersistRequestBean persist) { + throw new PersistenceException("Should not be called? only for concatinated keys"); + } + + public void dmlAppend(GenerateDmlRequest request) { + + request.appendColumn(uidProp.getDbColumn()); + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + Object value = uidProp.getValue(bean); + + request.bind(value, uidProp); + + // used for summary logging + request.setIdValue(value); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java index 641f0367b..e2cbf2be8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableList.java @@ -1,48 +1,48 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * List of Bindable items. - */ -public class BindableList implements Bindable { - - private final Bindable[] items; - - public BindableList(List list) { - items = list.toArray(new Bindable[list.size()]); - } - - public void addAll(List list) { - for (int i = 0; i < items.length; i++) { - list.add(items[i]); - } - } - - public void addToUpdate(PersistRequestBean request, List list) { - for (int i = 0; i < items.length; i++) { - items[i].addToUpdate(request, list); - } - } - - public void dmlAppend(GenerateDmlRequest request) { - - for (int i = 0; i < items.length; i++) { - items[i].dmlAppend(request); - } - } - - public void dmlBind(BindableRequest bindRequest, EntityBean bean) - throws SQLException { - - for (int i = 0; i < items.length; i++) { - items[i].dmlBind(bindRequest, bean); - } - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * List of Bindable items. + */ +public class BindableList implements Bindable { + + private final Bindable[] items; + + public BindableList(List list) { + items = list.toArray(new Bindable[list.size()]); + } + + public void addAll(List list) { + for (int i = 0; i < items.length; i++) { + list.add(items[i]); + } + } + + public void addToUpdate(PersistRequestBean request, List list) { + for (int i = 0; i < items.length; i++) { + items[i].addToUpdate(request, list); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + + for (int i = 0; i < items.length; i++) { + items[i].dmlAppend(request); + } + } + + public void dmlBind(BindableRequest bindRequest, EntityBean bean) + throws SQLException { + + for (int i = 0; i < items.length; i++) { + items[i].dmlBind(bindRequest, bean); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java index 98f12be16..44ac14a17 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableProperty.java @@ -1,59 +1,59 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a single BeanProperty. - */ -public class BindableProperty implements Bindable { - - protected final BeanProperty prop; - - public BindableProperty(BeanProperty prop) { - this.prop = prop; - } - - public String toString() { - return prop.toString(); - } - - public void addToUpdate(PersistRequestBean request, List list) { - if (request.isAddToUpdate(prop)) { - list.add(this); - } - } - - public void dmlAppend(GenerateDmlRequest request) { - request.appendColumn(prop.getDbColumn()); - } - - /** - * Normal binding of a property value from the bean. - */ - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - Object value = null; - if (bean != null) { - value = prop.getValue(bean); - } - request.bind(value, prop); - } - - /** - * For compound types bind one of the underlying scalar values for a compound type. - */ - public void dmlBindObject(BindableRequest request, Object bean) throws SQLException { - - Object value = null; - if (bean != null) { - value = prop.getValueObject(bean); - } - request.bind(value, prop); - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a single BeanProperty. + */ +public class BindableProperty implements Bindable { + + protected final BeanProperty prop; + + public BindableProperty(BeanProperty prop) { + this.prop = prop; + } + + public String toString() { + return prop.toString(); + } + + public void addToUpdate(PersistRequestBean request, List list) { + if (request.isAddToUpdate(prop)) { + list.add(this); + } + } + + public void dmlAppend(GenerateDmlRequest request) { + request.appendColumn(prop.getDbColumn()); + } + + /** + * Normal binding of a property value from the bean. + */ + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + Object value = null; + if (bean != null) { + value = prop.getValue(bean); + } + request.bind(value, prop); + } + + /** + * For compound types bind one of the underlying scalar values for a compound type. + */ + public void dmlBindObject(BindableRequest request, Object bean) throws SQLException { + + Object value = null; + if (bean != null) { + value = prop.getValueObject(bean); + } + request.bind(value, prop); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java index 86c3da57c..9a4f88340 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyInsertGenerated.java @@ -1,46 +1,46 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for insert on a property with a GeneratedProperty. - *

      - * This is typically a 'insert timestamp', 'update timestamp' or 'counter'. - *

      - */ -public class BindablePropertyInsertGenerated extends BindableProperty { - - private final GeneratedProperty gen; - - public BindablePropertyInsertGenerated(BeanProperty prop, GeneratedProperty gen) { - super(prop); - this.gen = gen; - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - Object value = gen.getInsertValue(prop, bean); - - // generated value should be the correct type - if (bean != null){ - // support PropertyChangeSupport - //prop.setValueIntercept(bean, value); - prop.setValue(bean, value); - } - request.bind(value, prop); - } - - /** - * Always bind on Insert SET. - */ - @Override - public void dmlAppend(GenerateDmlRequest request){ - request.appendColumn(prop.getDbColumn()); - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for insert on a property with a GeneratedProperty. + *

      + * This is typically a 'insert timestamp', 'update timestamp' or 'counter'. + *

      + */ +public class BindablePropertyInsertGenerated extends BindableProperty { + + private final GeneratedProperty gen; + + public BindablePropertyInsertGenerated(BeanProperty prop, GeneratedProperty gen) { + super(prop); + this.gen = gen; + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + Object value = gen.getInsertValue(prop, bean); + + // generated value should be the correct type + if (bean != null){ + // support PropertyChangeSupport + //prop.setValueIntercept(bean, value); + prop.setValue(bean, value); + } + request.bind(value, prop); + } + + /** + * Always bind on Insert SET. + */ + @Override + public void dmlAppend(GenerateDmlRequest request){ + request.appendColumn(prop.getDbColumn()); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java index 080bfdc67..15b9afd81 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java @@ -1,64 +1,64 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for update on a property with a GeneratedProperty. - *

      - * This is typically a 'update timestamp' or 'counter'. - *

      - */ -public class BindablePropertyUpdateGenerated extends BindableProperty { - - private final GeneratedProperty gen; - - public BindablePropertyUpdateGenerated(BeanProperty prop, GeneratedProperty gen) { - super(prop); - this.gen = gen; - } - - /** - * Add BindablePropertyUpdateGenerated if the property is loaded. - */ - public void addToUpdate(PersistRequestBean request, List list) { - if (gen.includeInAllUpdates()) { - list.add(this); - } else if (request.isLoadedProperty(prop)) { - list.add(this); - } - } - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - Object value = gen.getUpdateValue(prop, bean); - - // generated value should be the correct type - request.bind(value, prop); - - // only register the update value if it was included - // in the bean in the first place - if (request.getPersistRequest().isLoadedProperty(prop)) { - //if (request.isIncluded(prop)) { - // need to set the generated value to the bean later - // after the where clause has been generated - request.registerUpdateGenValue(prop, bean, value); - } - } - - /** - * Always bind on Insert SET. - */ - @Override - public void dmlAppend(GenerateDmlRequest request){ - request.appendColumn(prop.getDbColumn()); - } - - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for update on a property with a GeneratedProperty. + *

      + * This is typically a 'update timestamp' or 'counter'. + *

      + */ +public class BindablePropertyUpdateGenerated extends BindableProperty { + + private final GeneratedProperty gen; + + public BindablePropertyUpdateGenerated(BeanProperty prop, GeneratedProperty gen) { + super(prop); + this.gen = gen; + } + + /** + * Add BindablePropertyUpdateGenerated if the property is loaded. + */ + public void addToUpdate(PersistRequestBean request, List list) { + if (gen.includeInAllUpdates()) { + list.add(this); + } else if (request.isLoadedProperty(prop)) { + list.add(this); + } + } + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + Object value = gen.getUpdateValue(prop, bean); + + // generated value should be the correct type + request.bind(value, prop); + + // only register the update value if it was included + // in the bean in the first place + if (request.getPersistRequest().isLoadedProperty(prop)) { + //if (request.isIncluded(prop)) { + // need to set the generated value to the bean later + // after the where clause has been generated + request.registerUpdateGenValue(prop, bean, value); + } + } + + /** + * Always bind on Insert SET. + */ + @Override + public void dmlAppend(GenerateDmlRequest request){ + request.appendColumn(prop.getDbColumn()); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java index bb9b4e0d1..0eb4b20c0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java @@ -1,60 +1,60 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.DerivedRelationshipData; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; - -/** - * Request object passed to bindables. - */ -public interface BindableRequest { - - /** - * Set the id for use with summary level logging. - */ - public void setIdValue(Object idValue); - - /** - * Bind the value to a PreparedStatement. - *

      - * Takes into account logicalType to dbType conversion if required. - *

      - *

      - * Returns the value that was bound (and was potentially converted from - * logicalType to dbType. - *

      - */ - public Object bind(Object value, BeanProperty prop) throws SQLException; - - /** - * Bind a raw value. Used to bind the discriminator column. - */ - public Object bind(String propName, Object value, int sqlType) throws SQLException; - - /** - * Bind a raw value with a placeHolder to put into the transaction log. - */ - public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException; - - /** - * Bind the value to the preparedStatement without logging. - */ - public Object bindNoLog(Object value, BeanProperty prop) throws SQLException; - - - /** - * Register the value from a update GeneratedValue. This can only be set to - * the bean property after the where clause has bean built. - */ - public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value); - - /** - * Return the original PersistRequest. - */ - public PersistRequestBean getPersistRequest(); - - public void registerDerivedRelationship(DerivedRelationshipData assocBean); -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.DerivedRelationshipData; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Request object passed to bindables. + */ +public interface BindableRequest { + + /** + * Set the id for use with summary level logging. + */ + public void setIdValue(Object idValue); + + /** + * Bind the value to a PreparedStatement. + *

      + * Takes into account logicalType to dbType conversion if required. + *

      + *

      + * Returns the value that was bound (and was potentially converted from + * logicalType to dbType. + *

      + */ + public Object bind(Object value, BeanProperty prop) throws SQLException; + + /** + * Bind a raw value. Used to bind the discriminator column. + */ + public Object bind(String propName, Object value, int sqlType) throws SQLException; + + /** + * Bind a raw value with a placeHolder to put into the transaction log. + */ + public Object bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException; + + /** + * Bind the value to the preparedStatement without logging. + */ + public Object bindNoLog(Object value, BeanProperty prop) throws SQLException; + + + /** + * Register the value from a update GeneratedValue. This can only be set to + * the bean property after the where clause has bean built. + */ + public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value); + + /** + * Return the original PersistRequest. + */ + public PersistRequestBean getPersistRequest(); + + public void registerDerivedRelationship(DerivedRelationshipData assocBean); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java index faa2f36ec..f27bb195c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableUnidirectional.java @@ -1,68 +1,68 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.sql.SQLException; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.deploy.id.ImportedId; -import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; - -/** - * Bindable for a unidirectional relationship. - *

      - * This inserts the foreign key value that is retrieved from the id of the - * parentBean. - *

      - */ -public class BindableUnidirectional implements Bindable { - - private final BeanPropertyAssocOne unidirectional; - - private final ImportedId importedId; - - private final BeanDescriptor desc; - - public BindableUnidirectional(BeanDescriptor desc, BeanPropertyAssocOne unidirectional) { - this.desc = desc; - this.unidirectional = unidirectional; - this.importedId = unidirectional.getImportedId(); - - } - - public String toString() { - return "BindableShadowFKey " + unidirectional; - } - - public void addToUpdate(PersistRequestBean request, List list) { - throw new PersistenceException("Never called (for insert only)"); - } - - public void dmlAppend(GenerateDmlRequest request) { - // always included (in insert) - importedId.dmlAppend(request); - } - - - public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { - - PersistRequestBean persistRequest = request.getPersistRequest(); - Object parentBean = persistRequest.getParentBean(); - - if (parentBean == null) { - Class localType = desc.getBeanType(); - Class targetType = unidirectional.getTargetType(); - - String msg = "Error inserting bean [" + localType + "] with unidirectional relationship. "; - msg += "For inserts you must use cascade save on the master bean [" + targetType + "]."; - throw new PersistenceException(msg); - } - - importedId.bind(request, (EntityBean)parentBean); - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.sql.SQLException; +import java.util.List; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.id.ImportedId; +import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest; + +/** + * Bindable for a unidirectional relationship. + *

      + * This inserts the foreign key value that is retrieved from the id of the + * parentBean. + *

      + */ +public class BindableUnidirectional implements Bindable { + + private final BeanPropertyAssocOne unidirectional; + + private final ImportedId importedId; + + private final BeanDescriptor desc; + + public BindableUnidirectional(BeanDescriptor desc, BeanPropertyAssocOne unidirectional) { + this.desc = desc; + this.unidirectional = unidirectional; + this.importedId = unidirectional.getImportedId(); + + } + + public String toString() { + return "BindableShadowFKey " + unidirectional; + } + + public void addToUpdate(PersistRequestBean request, List list) { + throw new PersistenceException("Never called (for insert only)"); + } + + public void dmlAppend(GenerateDmlRequest request) { + // always included (in insert) + importedId.dmlAppend(request); + } + + + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + + PersistRequestBean persistRequest = request.getPersistRequest(); + Object parentBean = persistRequest.getParentBean(); + + if (parentBean == null) { + Class localType = desc.getBeanType(); + Class targetType = unidirectional.getTargetType(); + + String msg = "Error inserting bean [" + localType + "] with unidirectional relationship. "; + msg += "For inserts you must use cascade save on the master bean [" + targetType + "]."; + throw new PersistenceException(msg); + } + + importedId.bind(request, (EntityBean)parentBean); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java index e9054c334..e40175931 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java @@ -1,47 +1,47 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.util.List; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.persist.dml.DmlMode; - -/** - * A factory that builds Bindable for BeanPropertyAssocOne properties. - */ -public class FactoryAssocOnes { - - public FactoryAssocOnes() { - } - - /** - * Add foreign key columns from associated one beans. - */ - public List create(List list, BeanDescriptor desc, DmlMode mode) { - - BeanPropertyAssocOne[] ones = desc.propertiesOneImported(); - - for (int i = 0; i < ones.length; i++) { - if (ones[i].isImportedPrimaryKey()){ - // excluded as already part of the primary key - - } else { - switch (mode) { - case INSERT: - if (!ones[i].isInsertable()) { - continue; - } - break; - case UPDATE: - if (!ones[i].isUpdateable()) { - continue; - } - break; - } - list.add(new BindableAssocOne(ones[i])); - } - } - - return list; - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * A factory that builds Bindable for BeanPropertyAssocOne properties. + */ +public class FactoryAssocOnes { + + public FactoryAssocOnes() { + } + + /** + * Add foreign key columns from associated one beans. + */ + public List create(List list, BeanDescriptor desc, DmlMode mode) { + + BeanPropertyAssocOne[] ones = desc.propertiesOneImported(); + + for (int i = 0; i < ones.length; i++) { + if (ones[i].isImportedPrimaryKey()){ + // excluded as already part of the primary key + + } else { + switch (mode) { + case INSERT: + if (!ones[i].isInsertable()) { + continue; + } + break; + case UPDATE: + if (!ones[i].isUpdateable()) { + continue; + } + break; + } + list.add(new BindableAssocOne(ones[i])); + } + } + + return list; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java index 69de1e8f1..efd36a064 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java @@ -1,67 +1,67 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; -import com.avaje.ebeaninternal.server.persist.dml.DmlMode; - -/** - * Add base properties to the BindableList for a bean type. - *

      - * This excludes unique embedded and associated properties. - *

      - */ -public class FactoryBaseProperties { - - private final FactoryProperty factoryProperty; - - - public FactoryBaseProperties(boolean bindEncryptDataFirst) { - factoryProperty = new FactoryProperty(bindEncryptDataFirst); - } - - /** - * Add Bindable for the base properties to the list. - */ - public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { - - add(desc.propertiesBaseScalar(), list, desc, mode, withLobs); - - BeanPropertyCompound[] compoundProps = desc.propertiesBaseCompound(); - for (int i = 0; i < compoundProps.length; i++) { - BeanProperty[] props = compoundProps[i].getScalarProperties(); - - List newList = new ArrayList(props.length); - addCompound(props, newList, desc, mode, withLobs); - - BindableCompound compoundBindable = new BindableCompound(compoundProps[i], newList); - - list.add(compoundBindable); - } - } - - private void add(BeanProperty[] props, List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { - - for (int i = 0; i < props.length; i++) { - Bindable item = factoryProperty.create(props[i], mode, withLobs); - if (item != null) { - list.add(item); - } - } - } - - private void addCompound(BeanProperty[] props, List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { - - for (int i = 0; i < props.length; i++) { - BindableProperty item = (BindableProperty) factoryProperty.create(props[i], mode, withLobs); - if (item != null) { - list.add(item); - } - } - - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * Add base properties to the BindableList for a bean type. + *

      + * This excludes unique embedded and associated properties. + *

      + */ +public class FactoryBaseProperties { + + private final FactoryProperty factoryProperty; + + + public FactoryBaseProperties(boolean bindEncryptDataFirst) { + factoryProperty = new FactoryProperty(bindEncryptDataFirst); + } + + /** + * Add Bindable for the base properties to the list. + */ + public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + add(desc.propertiesBaseScalar(), list, desc, mode, withLobs); + + BeanPropertyCompound[] compoundProps = desc.propertiesBaseCompound(); + for (int i = 0; i < compoundProps.length; i++) { + BeanProperty[] props = compoundProps[i].getScalarProperties(); + + List newList = new ArrayList(props.length); + addCompound(props, newList, desc, mode, withLobs); + + BindableCompound compoundBindable = new BindableCompound(compoundProps[i], newList); + + list.add(compoundBindable); + } + } + + private void add(BeanProperty[] props, List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + for (int i = 0; i < props.length; i++) { + Bindable item = factoryProperty.create(props[i], mode, withLobs); + if (item != null) { + list.add(item); + } + } + } + + private void addCompound(BeanProperty[] props, List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + for (int i = 0; i < props.length; i++) { + BindableProperty item = (BindableProperty) factoryProperty.create(props[i], mode, withLobs); + if (item != null) { + list.add(item); + } + } + + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java index 0480f62c5..f4981e30f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java @@ -1,47 +1,47 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import java.util.ArrayList; -import java.util.List; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.ebeaninternal.server.persist.dml.DmlMode; - -/** - * A factory that builds Bindable for embedded bean properties. - */ -public class FactoryEmbedded { - - private final FactoryProperty factoryProperty; - - public FactoryEmbedded(boolean bindEncryptDataFirst) { - factoryProperty = new FactoryProperty(bindEncryptDataFirst); - } - - /** - * Add bindable for the embedded properties to the list. - */ - public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { - - BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); - - for (int j = 0; j < embedded.length; j++) { - - BeanProperty[] props = embedded[j].getProperties(); - - List bindList = new ArrayList(props.length); - - for (int i = 0; i < props.length; i++) { - Bindable item = factoryProperty.create(props[i], mode, withLobs); - if (item != null){ - bindList.add(item); - } - } - - list.add(new BindableEmbedded(embedded[j], bindList)); - } - } - - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * A factory that builds Bindable for embedded bean properties. + */ +public class FactoryEmbedded { + + private final FactoryProperty factoryProperty; + + public FactoryEmbedded(boolean bindEncryptDataFirst) { + factoryProperty = new FactoryProperty(bindEncryptDataFirst); + } + + /** + * Add bindable for the embedded properties to the list. + */ + public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { + + BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); + + for (int j = 0; j < embedded.length; j++) { + + BeanProperty[] props = embedded[j].getProperties(); + + List bindList = new ArrayList(props.length); + + for (int i = 0; i < props.length; i++) { + Bindable item = factoryProperty.create(props[i], mode, withLobs); + if (item != null){ + bindList.add(item); + } + } + + list.add(new BindableEmbedded(embedded[j], bindList)); + } + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java index 9b85bde19..fb703db2b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryId.java @@ -1,33 +1,33 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; - -/** - * Create a Bindable for the ids of a bean type. - */ -public class FactoryId { - - public FactoryId() { - } - - /** - * Add uniqueId properties. - */ - public BindableId createId(BeanDescriptor desc) { - - BeanProperty id = desc.getIdProperty(); - if (id == null) { - return new BindableIdEmpty(); - - } - if (!id.isEmbedded()) { - return new BindableIdScalar(id); - - } else { - BeanPropertyAssocOne embId = (BeanPropertyAssocOne) id; - return new BindableIdEmbedded(embId, desc); - } - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Create a Bindable for the ids of a bean type. + */ +public class FactoryId { + + public FactoryId() { + } + + /** + * Add uniqueId properties. + */ + public BindableId createId(BeanDescriptor desc) { + + BeanProperty id = desc.getIdProperty(); + if (id == null) { + return new BindableIdEmpty(); + + } + if (!id.isEmbedded()) { + return new BindableIdScalar(id); + + } else { + BeanPropertyAssocOne embId = (BeanPropertyAssocOne) id; + return new BindableIdEmbedded(embId, desc); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java index 0e7a1705e..1582a9d70 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryProperty.java @@ -1,65 +1,65 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; -import com.avaje.ebeaninternal.server.persist.dml.DmlMode; - -/** - * Creates the appropriate Bindable for a BeanProperty. - *

      - * Lob properties can be excluded and it creates BindablePropertyInsertGenerated - * and BindablePropertyUpdateGenerated as required. - *

      - */ -public class FactoryProperty { - - private final boolean bindEncryptDataFirst; - - public FactoryProperty(boolean bindEncryptDataFirst) { - this.bindEncryptDataFirst = bindEncryptDataFirst; - } - - /** - * Create a Bindable for the property given the mode and withLobs flag. - */ - public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs) { - - if (DmlMode.INSERT.equals(mode) && !prop.isDbInsertable()){ - return null; - } - if (DmlMode.UPDATE.equals(mode) && !prop.isDbUpdatable()){ - return null; - } - - if (prop.isLob()) { - if (!withLobs) { - // Lob exclusion - return null; - } else { - return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); - } - } - - GeneratedProperty gen = prop.getGeneratedProperty(); - if (gen != null) { - if (DmlMode.INSERT.equals(mode)) { - if (gen.includeInInsert()) { - return new BindablePropertyInsertGenerated(prop, gen); - } else { - return null; - } - - } - if (DmlMode.UPDATE.equals(mode)) { - if (gen.includeInUpdate()) { - return new BindablePropertyUpdateGenerated(prop, gen); - } else { - // An 'Insert Timestamp' is never updated - return null; - } - } - } - - return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; +import com.avaje.ebeaninternal.server.persist.dml.DmlMode; + +/** + * Creates the appropriate Bindable for a BeanProperty. + *

      + * Lob properties can be excluded and it creates BindablePropertyInsertGenerated + * and BindablePropertyUpdateGenerated as required. + *

      + */ +public class FactoryProperty { + + private final boolean bindEncryptDataFirst; + + public FactoryProperty(boolean bindEncryptDataFirst) { + this.bindEncryptDataFirst = bindEncryptDataFirst; + } + + /** + * Create a Bindable for the property given the mode and withLobs flag. + */ + public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs) { + + if (DmlMode.INSERT.equals(mode) && !prop.isDbInsertable()){ + return null; + } + if (DmlMode.UPDATE.equals(mode) && !prop.isDbUpdatable()){ + return null; + } + + if (prop.isLob()) { + if (!withLobs) { + // Lob exclusion + return null; + } else { + return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); + } + } + + GeneratedProperty gen = prop.getGeneratedProperty(); + if (gen != null) { + if (DmlMode.INSERT.equals(mode)) { + if (gen.includeInInsert()) { + return new BindablePropertyInsertGenerated(prop, gen); + } else { + return null; + } + + } + if (DmlMode.UPDATE.equals(mode)) { + if (gen.includeInUpdate()) { + return new BindablePropertyUpdateGenerated(prop, gen); + } else { + // An 'Insert Timestamp' is never updated + return null; + } + } + } + + return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java index 2ace73efa..767a0cbd7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/FactoryVersion.java @@ -1,27 +1,27 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; - -/** - * Creates a Bindable to support version concurrency where clauses. - */ -public class FactoryVersion { - - - public FactoryVersion() { - } - - /** - * Create a Bindable for the version property(s) for a bean type. - */ - public Bindable create(BeanDescriptor desc) { - - BeanProperty versionProperty = desc.getVersionProperty(); - if (versionProperty == null) { - return null; - } - - return new BindableProperty(versionProperty); - } -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; + +/** + * Creates a Bindable to support version concurrency where clauses. + */ +public class FactoryVersion { + + + public FactoryVersion() { + } + + /** + * Create a Bindable for the version property(s) for a bean type. + */ + public Bindable create(BeanDescriptor desc) { + + BeanProperty versionProperty = desc.getVersionProperty(); + if (versionProperty == null) { + return null; + } + + return new BindableProperty(versionProperty); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java index 2b329710b..2211e9d3d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java @@ -1,86 +1,86 @@ -package com.avaje.ebeaninternal.server.persist.dmlbind; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; - -/** - * Matches local embedded id properties to 'matching' properties from a - * ManyToOne associated bean that is a 'imported primary key'. - *

      - * This object is designed to help BindableIdEmbedded and BindableIdMap to - * create a concatenated id from the id values from ManyToOne associated beans. - * This can be done when those ManyToOne associated beans make up the primary - * key. This typically means the BindableIdEmbedded is for a intersection table - * of a Many to Many relationship. - *

      - */ -class MatchedImportedProperty { - - private final BeanPropertyAssocOne assocOne; - - private final BeanProperty foreignProp; - - private final BeanProperty localProp; - - protected MatchedImportedProperty(BeanPropertyAssocOne assocOne, BeanProperty foreignProp, - BeanProperty localProp) { - this.assocOne = assocOne; - this.foreignProp = foreignProp; - this.localProp = localProp; - } - - protected void populate(EntityBean sourceBean, EntityBean destBean) { - Object assocBean = assocOne.getValue(sourceBean); - if (assocBean == null) { - String msg = "The assoc bean for " + assocOne + " is null?"; - throw new NullPointerException(msg); - } - - Object value = foreignProp.getValue((EntityBean)assocBean); - localProp.setValue(destBean, value); - } - - /** - * Create the array of matchedImportedProperty based on the properties and descriptor. - */ - protected static MatchedImportedProperty[] build(BeanProperty[] props, BeanDescriptor desc) { - - MatchedImportedProperty[] matches = new MatchedImportedProperty[props.length]; - - for (int i = 0; i < props.length; i++) { - // find matching assoc one property for dbColumn - matches[i] = MatchedImportedProperty.findMatch(props[i], desc); - if (matches[i] == null) { - // ok, the assoc ones are not on the bean? - return null; - } - } - return matches; - } - - private static MatchedImportedProperty findMatch(BeanProperty prop, BeanDescriptor desc) { - - // find matching against the local database column - String dbColumn = prop.getDbColumn(); - - BeanPropertyAssocOne[] assocOnes = desc.propertiesOne(); - for (int i = 0; i < assocOnes.length; i++) { - if (assocOnes[i].isImportedPrimaryKey()) { - - // search using the ImportedId from the assoc one - BeanProperty foreignMatch = assocOnes[i].getImportedId().findMatchImport(dbColumn); - - if (foreignMatch != null) { - return new MatchedImportedProperty(assocOnes[i], foreignMatch, prop); - } - } - } - - // there was no matching assoc one property. - // example UserRole bean missing assoc one to User? - return null; - } - -} +package com.avaje.ebeaninternal.server.persist.dmlbind; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Matches local embedded id properties to 'matching' properties from a + * ManyToOne associated bean that is a 'imported primary key'. + *

      + * This object is designed to help BindableIdEmbedded and BindableIdMap to + * create a concatenated id from the id values from ManyToOne associated beans. + * This can be done when those ManyToOne associated beans make up the primary + * key. This typically means the BindableIdEmbedded is for a intersection table + * of a Many to Many relationship. + *

      + */ +class MatchedImportedProperty { + + private final BeanPropertyAssocOne assocOne; + + private final BeanProperty foreignProp; + + private final BeanProperty localProp; + + protected MatchedImportedProperty(BeanPropertyAssocOne assocOne, BeanProperty foreignProp, + BeanProperty localProp) { + this.assocOne = assocOne; + this.foreignProp = foreignProp; + this.localProp = localProp; + } + + protected void populate(EntityBean sourceBean, EntityBean destBean) { + Object assocBean = assocOne.getValue(sourceBean); + if (assocBean == null) { + String msg = "The assoc bean for " + assocOne + " is null?"; + throw new NullPointerException(msg); + } + + Object value = foreignProp.getValue((EntityBean)assocBean); + localProp.setValue(destBean, value); + } + + /** + * Create the array of matchedImportedProperty based on the properties and descriptor. + */ + protected static MatchedImportedProperty[] build(BeanProperty[] props, BeanDescriptor desc) { + + MatchedImportedProperty[] matches = new MatchedImportedProperty[props.length]; + + for (int i = 0; i < props.length; i++) { + // find matching assoc one property for dbColumn + matches[i] = MatchedImportedProperty.findMatch(props[i], desc); + if (matches[i] == null) { + // ok, the assoc ones are not on the bean? + return null; + } + } + return matches; + } + + private static MatchedImportedProperty findMatch(BeanProperty prop, BeanDescriptor desc) { + + // find matching against the local database column + String dbColumn = prop.getDbColumn(); + + BeanPropertyAssocOne[] assocOnes = desc.propertiesOne(); + for (int i = 0; i < assocOnes.length; i++) { + if (assocOnes[i].isImportedPrimaryKey()) { + + // search using the ImportedId from the assoc one + BeanProperty foreignMatch = assocOnes[i].getImportedId().findMatchImport(dbColumn); + + if (foreignMatch != null) { + return new MatchedImportedProperty(assocOnes[i], foreignMatch, prop); + } + } + } + + // there was no matching assoc one property. + // example UserRole bean missing assoc one to User? + return null; + } + +}