NEW: Ebean.checkUniqueness - you can check a bean, if save would work (#1303)

This commit is contained in:
Roland Praml
2018-03-02 11:57:44 +13:00
committed by Rob Bygrave
parent 0c368ed7b3
commit efe325034b
7 changed files with 256 additions and 1 deletions
+28 -1
View File
@@ -3,11 +3,13 @@ package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.plugin.Property;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -15,6 +17,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
@@ -116,7 +119,7 @@ public final class Ebean {
static {
EbeanVersion.getVersion(); // initalizes the version class and logs the version.
}
/**
* Manages creation and cache of EbeanServers.
*/
@@ -641,6 +644,30 @@ public final class Ebean {
return serverMgr.getDefaultServer().saveAll(beans);
}
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
* properties that violates an unique / primary key. This may be done in an UI save action to
* validate if the user has entered correct values.
*
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
*
* TODO: it checks only the root bean!
* @param bean
* @return a set of Properties if constraint validation was detected or empty list.
*/
@Nonnull
public static Set<Property> checkUniqueness(Object bean) {
return serverMgr.getDefaultServer().checkUniqueness(bean);
}
/**
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
*/
@Nonnull
public static Set<Property> checkUniqueness(Object bean, Transaction transaction) {
return serverMgr.getDefaultServer().checkUniqueness(bean, transaction);
}
/**
* Delete the bean.
* <p>
+21
View File
@@ -4,6 +4,7 @@ import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
@@ -1461,6 +1462,26 @@ public interface EbeanServer {
*/
int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
* properties that violates an unique / primary key. This may be done in an UI save action to
* validate if the user has entered correct values.
*
* Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update.
*
* TODO: it checks only the root bean!
* @param bean
* @return a set of Properties if constraint validation was detected or empty list.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean);
/**
* Same as {@link #checkUniqueness(Object)}. but with given transaction.
*/
@Nonnull
Set<Property> checkUniqueness(Object bean, Transaction transaction);
/**
* Marks the entity bean as dirty.
* <p>
@@ -6,6 +6,7 @@ import io.ebean.BeanState;
import io.ebean.CallableSql;
import io.ebean.DocumentStore;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.Filter;
import io.ebean.FutureIds;
import io.ebean.FutureList;
@@ -49,6 +50,7 @@ import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
@@ -104,7 +106,10 @@ import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -2109,4 +2114,78 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return transactionManager.collectTransactionStatistics(reset);
}
@Override
public Set<Property> checkUniqueness(Object bean) {
return checkUniqueness(bean, null);
}
@Override
public Set<Property> checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
BeanDescriptor<?> beanDesc = getBeanDescriptor(entityBean.getClass());
BeanProperty idProperty = beanDesc.getIdProperty();
// if the ID of the Property is null we are unable to check uniqueness
if (idProperty == null) {
return Collections.emptySet();
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_intercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
// new.
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
query.setId(id);
if (findCount(query, transaction) > 0) {
Set<Property> ret = new HashSet<>();
ret.add(idProperty);
return ret;
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
Set<Property> ret = checkUniqueness(entityBean, beanDesc, props, transaction);
if (ret != null) {
return ret;
}
}
return Collections.emptySet();
}
/**
* Returns a set of properties if saving the bean will violate the unique constraints
* (definded by given properties).
*/
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
Transaction transaction) {
BeanProperty idProperty = beanDesc.getIdProperty();
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
ExpressionList<?> exprList = query.where();
if (!entityBean._ebean_intercept().isNew()) {
// if model is not new, exclude ourself.
exprList.ne(idProperty.getName(), idProperty.getVal(entityBean));
}
for (Property prop : props) {
Object value = prop.getVal(entityBean);
if (value == null) {
return null;
}
exprList.eq(prop.getName(), value);
}
if (findCount(query, transaction) > 0) {
Set<Property> ret = new LinkedHashSet<>();
for (Property prop : props) {
ret.add(prop);
}
return ret;
}
return null;
}
}
@@ -350,6 +350,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
protected final BeanProperty[] propertiesIndex;
private final BeanProperty[] propertiesGenInsert;
private final BeanProperty[] propertiesGenUpdate;
private final List<BeanProperty[]> propertiesUnique = new ArrayList<>();
/**
* The bean class name or the table name for MapBeans.
@@ -769,9 +770,43 @@ public class BeanDescriptor<T> implements BeanType<T> {
* Perform last initialisation for the descriptor.
*/
public void initLast() {
for (BeanProperty prop : propertiesNonTransient) {
if (prop.isUnique()) {
propertiesUnique.add(new BeanProperty[] { prop });
}
}
// convert unique columns to properties
if (indexDefinitions != null) {
for (IndexDefinition indexDef : indexDefinitions) {
if (indexDef.isUnique()) {
addUniqueColumns(indexDef);
}
}
}
docStoreEmbeddedInvalidation = docStoreAdapter.hasEmbeddedInvalidation();
}
private void addUniqueColumns(IndexDefinition indexDef) {
String[] cols = indexDef.getColumns();
BeanProperty[] props = new BeanProperty[cols.length];
for (int i = 0; i < cols.length; i++) {
String propName = findBeanPath("", cols[i]);
if (propName == null) {
return;
}
props[i] = findBeanProperty(propName);
}
if (props.length == 1) {
for (BeanProperty[] inserted : propertiesUnique) {
if (inserted.length == 1 && inserted[0].equals(props[0])) {
return; // do not insert duplicates
}
}
}
propertiesUnique.add(props);
}
/**
* Initialise the document mapping.
*/
@@ -3220,4 +3255,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
return jsonHelp.jsonReadObject(jsonRead, path);
}
public List<BeanProperty[]> getUniqueProps() {
return propertiesUnique;
}
}