From 1a13fc97420fbc724f277e9ff8098c9bb58f1606 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Sat, 26 Apr 2014 23:43:08 +1200
Subject: [PATCH] Fixes for #94 - Additional API - added update(Collection
beans); and insert(Collection beans); and #93 - ebean.properties
defaultDeleteMissingChildren moved to updatesDeleteMissingChildren
---
src/main/java/com/avaje/ebean/Ebean.java | 75 ++++---
.../java/com/avaje/ebean/EbeanServer.java | 194 +++++++++---------
.../avaje/ebean/bean/EntityBeanIntercept.java | 2 +-
.../com/avaje/ebean/config/ServerConfig.java | 80 ++++++--
.../ebeaninternal/api/SpiEbeanServer.java | 9 +-
.../server/core/BeanRequest.java | 10 +-
.../server/core/DefaultServer.java | 124 +++++++----
.../server/core/PersistRequest.java | 6 +-
.../server/core/PersistRequestBean.java | 61 +++---
.../ebeaninternal/server/core/Persister.java | 11 +-
.../server/deploy/BeanDescriptor.java | 11 +-
.../server/deploy/BeanPropertyAssocOne.java | 7 +
.../server/persist/DefaultPersister.java | 155 +++++++-------
.../tests/compositekeys/TestCKeyLazyLoad.java | 4 +-
...ascadeDeleteChildrenWithCompositeKeys.java | 4 +-
.../tests/inheritance/TestSkippable.java | 18 +-
.../tests/insert/TestInsertCollection.java | 69 +++++++
.../tests/model/basic/ResetBasicData.java | 12 +-
.../tests/update/TestStatelessUpdate.java | 119 +++++++++++
19 files changed, 627 insertions(+), 344 deletions(-)
create mode 100644 src/test/java/com/avaje/tests/insert/TestInsertCollection.java
diff --git a/src/main/java/com/avaje/ebean/Ebean.java b/src/main/java/com/avaje/ebean/Ebean.java
index e24f760bf..68fe10ae2 100644
--- a/src/main/java/com/avaje/ebean/Ebean.java
+++ b/src/main/java/com/avaje/ebean/Ebean.java
@@ -5,20 +5,20 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* This Ebean object is effectively a singleton that holds a map of registered
@@ -462,48 +462,59 @@ public final class Ebean {
}
/**
- * Force an update using the bean updating the non-null properties.
+ * Insert a collection of beans.
+ */
+ public static void insert(Collection> beans) {
+ serverMgr.getPrimaryServer().insert(beans);
+ }
+
+ /**
+ * Saves the bean using an update. If you know you are updating a bean then it is preferrable to
+ * use this update() method rather than save().
*
- * You can use this method to FORCE an update to occur (even on a bean that
- * has not been fetched but say built from JSON or XML). When
- * {@link Ebean#save(Object)} is used Ebean determines whether to use an
- * insert or an update based on the state of the bean. Using this method will
- * force an update to occur.
+ * Stateless updates: Note that the bean does not have to be previously fetched to call
+ * update().You can create a new instance and set some of its properties programmatically for via
+ * JSON/XML marshalling etc. This is described as a 'stateless update'.
*
*
- * It is expected that this method is most useful in stateless REST services
- * or web applications where you have the values you wish to update but no
- * existing bean.
+ * Optimistic Locking: Note that if the version property is not set when update() is
+ * called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
*
*
- * For updates against beans that have not been fetched (say built from JSON
- * or XML) this will treat deleteMissingChildren=true and will delete any
- * 'missing children'. Refer to
- * {@link EbeanServer#update(Object, Set, Transaction, boolean, boolean)}.
+ * {@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: When cascade saving to a
+ * OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children
+ * that are in the database but are not in the collection are deleted.
+ *
+ *
+ * {@link ServerConfig#setUpdateChangesOnly(boolean)}: The updateChangesOnly setting
+ * controls if only the changed properties are included in the update or if all the loaded
+ * properties are included instead.
*
*
*
*
- * Customer c = new Customer();
- * c.setId(7);
- * c.setName("ModifiedNameNoOCC");
- *
- * // generally you should set the version property
- * // so that Optimistic Concurrency Checking is used.
- * // If a version property is not set then no Optimistic
- * // Concurrency Checking occurs for the update
- * // c.setLastUpdate(lastUpdateTime);
- *
- * // by default the Non-null properties
- * // are included in the update
- * Ebean.update(c);
+ * // A 'stateless update' example
+ * Customer customer = new Customer();
+ * customer.setId(7);
+ * customer.setName("ModifiedNameNoOCC");
+ * ebeanServer.update(customer);
*
*
+ *
+ * @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
+ * @see ServerConfig#setUpdateChangesOnly(boolean)
*/
- public static void update(Object bean) {
+ public static void update(Object bean) throws OptimisticLockException {
serverMgr.getPrimaryServer().update(bean);
}
+ /**
+ * Update the beans in the collection.
+ */
+ public static void update(Collection> beans) throws OptimisticLockException {
+ serverMgr.getPrimaryServer().update(beans);
+ }
+
/**
* Save all the beans from an Iterator.
*/
@@ -514,8 +525,8 @@ public final class Ebean {
/**
* Save all the beans from a Collection.
*/
- public static int save(Collection> c) throws OptimisticLockException {
- return save(c.iterator());
+ public static int save(Collection> beans) throws OptimisticLockException {
+ return serverMgr.getPrimaryServer().save(beans);
}
/**
diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
index b2fb352f4..10deb53c9 100644
--- a/src/main/java/com/avaje/ebean/EbeanServer.java
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -85,9 +85,12 @@ import com.avaje.ebean.text.json.JsonContext;
public interface EbeanServer {
/**
- * Shutdown the EbeanServer.
+ * Shutdown the EbeanServer programmatically.
*
- * If the under underlying DataSource is the EbeanORM implementation then you
+ * This method is not normally required. Ebean registers a shutdown hook and shuts down cleanly.
+ *
+ *
+ * If the under underlying DataSource is the Ebean implementation then you
* also have the option of shutting down the DataSource and deregistering the
* JDBC driver.
*
@@ -410,12 +413,25 @@ public interface EbeanServer {
public T find(Class beanType, Object uid);
/**
- * Get a reference Object (see {@link Ebean#getReference(Class, Object)}.
+ * Get a reference bean (see {@link Ebean#getReference(Class, Object)}.
*
* This will not perform a query against the database.
*
+ *
+ * Product product = Ebean.getReference(Product.class, 1);
*
- * @see Ebean#getReference(Class, Object)
+ * // You can get the id without causing a fetch/lazy load
+ * Integer productId = product.getId();
+ *
+ * // If you try to get any other property a fetch/lazy loading will occur
+ * // This will cause a query to execute...
+ * String name = product.getName();
+ *
+ *
+ * @param beanType
+ * the type of entity bean
+ * @param id
+ * the id value
*/
public T getReference(Class beanType, Object uid);
@@ -428,21 +444,21 @@ public interface EbeanServer {
/**
* Return the Id values of the query as a List.
*/
- public List
*/
- public void testQuery()
- {
+ public void testQuery() {
+
// Setup the data first
final ListAttributeValue value1 = new ListAttributeValue();
final ListAttributeValue value2 = new ListAttributeValue();
@@ -30,20 +33,21 @@ public class TestSkippable extends TestCase {
final ListAttribute listAttribute = new ListAttribute();
listAttribute.add(value1);
Ebean.save(listAttribute);
-
+ logger.info(" -- seeded data");
+
final ListAttribute listAttributeDB = Ebean.find(ListAttribute.class, listAttribute.getId());
Assert.assertNotNull(listAttributeDB);
final ListAttributeValue value1_DB = listAttributeDB.getValues().iterator().next();
-
-
Assert.assertTrue(value1.getId().equals(value1_DB.getId()));
+ logger.info(" -- asserted data in db");
final AttributeHolder holder = new AttributeHolder();
holder.add(listAttributeDB);
Ebean.save(holder);
+ logger.info(" -- saved holder");
// Now change the M2M listAttribute.values and save the holder
// The save should cascade as follows
@@ -53,11 +57,11 @@ public class TestSkippable extends TestCase {
// Save the holder - should cascade down to the listAtribute and save the values
Ebean.save(holder);
+ logger.info(" -- M2M detected delete of value1 and add of value2 ?");
final ListAttribute listAttributeDB_2 = Ebean.find(ListAttribute.class, listAttributeDB.getId());
Assert.assertNotNull(listAttributeDB_2);
-
final ListAttributeValue value2_DB_2 = listAttributeDB_2.getValues().iterator().next();
diff --git a/src/test/java/com/avaje/tests/insert/TestInsertCollection.java b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java
new file mode 100644
index 000000000..583f027d8
--- /dev/null
+++ b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java
@@ -0,0 +1,69 @@
+package com.avaje.tests.insert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.tests.model.basic.Customer;
+
+public class TestInsertCollection extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ Customer cust1 = new Customer();
+ cust1.setName("jim");
+
+ Customer cust2 = new Customer();
+ cust2.setName("bob");
+
+ List customers = new ArrayList();
+ customers.add(cust1);
+ customers.add(cust2);
+
+ Ebean.insert(customers);
+
+ Assert.assertNotNull(cust1.getId());
+ Assert.assertNotNull(cust2.getId());
+
+ Customer cust1Check = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals(cust1.getName(), cust1Check.getName());
+ Customer cust2Check = Ebean.find(Customer.class, cust2.getId());
+ Assert.assertEquals(cust2.getName(), cust2Check.getName());
+
+ cust1.setName("jim-changed");
+ cust2.setName("bob-changed");
+
+ Ebean.update(customers);
+
+ Customer cust1Check2 = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals("jim-changed", cust1Check2.getName());
+ Customer cust2Check2 = Ebean.find(Customer.class, cust2.getId());
+ Assert.assertEquals("bob-changed", cust2Check2.getName());
+
+
+ cust1Check2.setName("jim-updated");
+ Customer cust3 = new Customer();
+ cust3.setName("mac");
+
+ List saveList = new ArrayList();
+ saveList.add(cust1Check2);
+ saveList.add(cust3);
+
+ Ebean.save(saveList);
+
+
+ Customer cust1Check3 = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals("jim-updated", cust1Check3.getName());
+ Customer cust3Check = Ebean.find(Customer.class, cust3.getId());
+ Assert.assertEquals("mac", cust3Check.getName());
+
+ }
+
+
+
+}
diff --git a/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java b/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
index 4eb1eb360..8762b7e61 100644
--- a/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
+++ b/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
@@ -80,12 +80,12 @@ public class ResetBasicData {
Country c = new Country();
c.setCode("NZ");
c.setName("New Zealand");
- server.insert(c);
+ server.save(c);
Country au = new Country();
au.setCode("AU");
au.setName("Australia");
- server.insert(au);
+ server.save(au);
}
});
}
@@ -99,25 +99,25 @@ public class ResetBasicData {
p.setId(1);
p.setName("Chair");
p.setSku("C001");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(2);
p.setName("Desk");
p.setSku("DSK1");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(3);
p.setName("Computer");
p.setSku("C002");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(4);
p.setName("Printer");
p.setSku("C003");
- server.insert(p);
+ server.save(p);
}
});
}
diff --git a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
index 3cabfa29b..5c0fb9bd9 100644
--- a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
+++ b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
@@ -2,6 +2,9 @@ package com.avaje.tests.update;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
@@ -251,4 +254,120 @@ public class TestStatelessUpdate extends BaseTestCase {
// maybe check if update instead of insert has been executed,
// currently "Unique index or primary key violation" PersistenceException is throwing
}
+
+ @Test
+ public void testStatelessRecursiveUpdateWithChangesInDetailOnly() {
+ // arrange
+ Contact contact1 = new Contact();
+ contact1.setLastName("contact1");
+
+ Contact contact2 = new Contact();
+ contact2.setLastName("contact2");
+
+ Customer customer = new Customer();
+ customer.setName("something");
+ customer.getContacts().add(contact1);
+ customer.getContacts().add(contact2);
+
+ server.save(customer);
+
+
+
+ // act
+ Contact updateContact1 = new Contact();
+ updateContact1.setId(contact1.getId());
+ updateContact1.setLastName("contact1-changed");
+
+
+ Contact updateContact3 = new Contact();
+ //updateContact3.setId(contact3.getId());
+ updateContact3.setLastName("contact3-added");
+
+ Customer updateCustomer = new Customer();
+ updateCustomer.setId(customer.getId());
+ updateCustomer.getContacts().add(updateContact1);
+ updateCustomer.getContacts().add(updateContact3);
+
+ // not adding contact2 so it will get deleted
+ //updateCustomer.getContacts().add(updateContact2);
+
+ server.update(updateCustomer);
+
+
+ // assert
+ Customer assCustomer = server.find(Customer.class, customer.getId());
+ List assContacts = assCustomer.getContacts();
+ Assert.assertEquals(2, assContacts.size());
+ Set ids = new LinkedHashSet();
+ Set names = new LinkedHashSet();
+ for (Contact contact : assContacts) {
+ ids.add(contact.getId());
+ names.add(contact.getLastName());
+ }
+ Assert.assertTrue(ids.contains(contact1.getId()));
+ Assert.assertTrue(ids.contains(updateContact3.getId()));
+ Assert.assertFalse(ids.contains(contact2.getId()));
+
+ Assert.assertTrue(names.contains(updateContact1.getLastName()));
+ Assert.assertTrue(names.contains(updateContact3.getLastName()));
+ }
+
+
+ @Test
+ public void testStatelessRecursiveUpdateWithChangesInDetailOnlyAnd() {
+ // arrange
+ Contact contact1 = new Contact();
+ contact1.setLastName("contact1");
+
+ Contact contact2 = new Contact();
+ contact2.setLastName("contact2");
+
+ Customer customer = new Customer();
+ customer.setName("something");
+ customer.getContacts().add(contact1);
+ customer.getContacts().add(contact2);
+
+ server.save(customer);
+
+
+ // act
+ Contact updateContact1 = new Contact();
+ updateContact1.setId(contact1.getId());
+ updateContact1.setLastName("contact1-changed");
+
+
+ Contact updateContact3 = new Contact();
+ updateContact3.setLastName("contact3-added");
+
+ Customer updateCustomer = new Customer();
+ updateCustomer.setId(customer.getId());
+ updateCustomer.getContacts().add(updateContact1);
+ updateCustomer.getContacts().add(updateContact3);
+
+ // not adding contact2 but it won't be deleted in this case
+ boolean deleteMissingChildren = false;
+ server.update(updateCustomer, null, deleteMissingChildren);
+
+
+ // assert
+ Customer assCustomer = server.find(Customer.class, customer.getId());
+ List assContacts = assCustomer.getContacts();
+
+ // contact 2 was not deleted this time
+ Assert.assertEquals(3, assContacts.size());
+
+ Set ids = new LinkedHashSet();
+ Set names = new LinkedHashSet();
+ for (Contact contact : assContacts) {
+ ids.add(contact.getId());
+ names.add(contact.getLastName());
+ }
+ Assert.assertTrue(ids.contains(contact1.getId()));
+ Assert.assertTrue(ids.contains(updateContact3.getId()));
+ Assert.assertTrue(ids.contains(contact2.getId()));
+
+ Assert.assertTrue(names.contains(updateContact1.getLastName()));
+ Assert.assertTrue(names.contains(contact2.getLastName()));
+ Assert.assertTrue(names.contains(updateContact3.getLastName()));
+ }
}