From 7d0ed261b534fc958df58e34e58bc1fb0d69323f Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 23 Feb 2018 16:55:07 +1300 Subject: [PATCH] #1271 - ENH: Add BeanFinder and BeanRepository ... such that there is a read to go base class for DI repository style --- src/main/java/io/ebean/BeanFinder.java | 169 +++++++++++++++++ src/main/java/io/ebean/BeanRepository.java | 178 ++++++++++++++++++ .../tests/repository/CustomerRepository.java | 36 ++++ .../tests/repository/TestBeanRepository.java | 61 ++++++ 4 files changed, 444 insertions(+) create mode 100644 src/main/java/io/ebean/BeanFinder.java create mode 100644 src/main/java/io/ebean/BeanRepository.java create mode 100644 src/test/java/org/tests/repository/CustomerRepository.java create mode 100644 src/test/java/org/tests/repository/TestBeanRepository.java diff --git a/src/main/java/io/ebean/BeanFinder.java b/src/main/java/io/ebean/BeanFinder.java new file mode 100644 index 000000000..0afcc7227 --- /dev/null +++ b/src/main/java/io/ebean/BeanFinder.java @@ -0,0 +1,169 @@ +package io.ebean; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +/** + * Provides finder functionality for use with "Dependency Injection style" use of Ebean. + *

+ * Note that typically users would extend BeanRepository rather than BeanFinder. + *

+ *
{@code
+ *
+ * public class CustomerFinder extends BeanFinder {
+ *
+ *   @Inject
+ *   public CustomerFinder(EbeanServer server) {
+ *     super(Customer.class, server);
+ *   }
+ *
+ *   // ... add customer specific finders
+ * }
+ *
+ * }
+ * + * @param The ID type + * @param The Bean type + */ +public abstract class BeanFinder { + + protected final EbeanServer server; + + protected final Class type; + + /** + * Create with the given bean type and EbeanServer instance. + * + * @param type The bean type + * @param server The EbeanServer instance typically created via Spring factory or equivalent. + */ + protected BeanFinder(Class type, EbeanServer server) { + this.type = type; + this.server = server; + } + + /** + * Return the EbeanServer to use. + */ + public EbeanServer db() { + return server; + } + + /** + * Return the current transaction. + */ + public Transaction currentTransaction() { + return db().currentTransaction(); + } + + /** + * Flush the JDBC batch on the current transaction. + */ + public void flush() { + db().flush(); + } + + /** + * Return typically a different EbeanServer to the default. + *

+ * This is equivalent to {@link Ebean#getServer(String)} + * + * @param server The name of the EbeanServer. If this is null then the default EbeanServer is + * returned. + */ + public EbeanServer db(String server) { + return Ebean.getServer(server); + } + + /** + * Creates an entity reference for this ID. + *

+ * Equivalent to {@link EbeanServer#getReference(Class, Object)} + */ + @Nonnull + public T ref(I id) { + return db().getReference(type, id); + } + + /** + * Retrieves an entity by ID. + *

+ * Equivalent to {@link EbeanServer#find(Class, Object)} + */ + @Nullable + public T findById(I id) { + return db().find(type, id); + } + + /** + * Find an entity by ID returning an Optional. + */ + @Nullable + public Optional findByIdOrEmpty(I id) { + return db().find(type).setId(id).findOneOrEmpty(); + } + + /** + * Delete a bean by Id. + *

+ * Equivalent to {@link EbeanServer#delete(Class, Object)} + */ + public void deleteById(I id) { + db().delete(type, id); + } + + /** + * Retrieves all entities of the given type. + */ + @Nonnull + public List findAll() { + return query().findList(); + } + + /** + * Creates an update query. + * + *

{@code
+   *
+   *  int rows =
+   *      updateQuery()
+   *      .set("status", Customer.Status.ACTIVE)
+   *      .set("updtime", new Timestamp(System.currentTimeMillis()))
+   *      .where()
+   *        .gt("id", 1000)
+   *        .update();
+   *
+   * }
+ * + *

+ * Equivalent to {@link EbeanServer#update(Class)} + */ + protected UpdateQuery updateQuery() { + return db().update(type); + } + + /** + * Creates a query. + *

+ * Equivalent to {@link EbeanServer#find(Class)} + */ + protected Query query() { + return db().find(type); + } + + /** + * Creates a native sql query. + */ + protected Query nativeSql(String nativeSql) { + return db().findNative(type, nativeSql); + } + + /** + * Creates a query using the ORM query language. + */ + protected Query query(String ormQuery) { + return db().createQuery(type, ormQuery); + } +} diff --git a/src/main/java/io/ebean/BeanRepository.java b/src/main/java/io/ebean/BeanRepository.java new file mode 100644 index 000000000..3c588b65c --- /dev/null +++ b/src/main/java/io/ebean/BeanRepository.java @@ -0,0 +1,178 @@ +package io.ebean; + +import io.ebean.bean.EntityBean; + +/** + * Provides finder functionality for use with "Dependency Injection style" use of Ebean. + *

+ *

{@code
+ *
+ * @Repository
+ * public class CustomerRepository extends BeanRepository {
+ *
+ *   @Inject
+ *   public CustomerRepository(EbeanServer server) {
+ *     super(Customer.class, server);
+ *   }
+ *
+ *   // ... add customer specific finders and persist logic
+ *
+ *   public List findByName(String nameStart) {
+ *     return query().where()
+ *             .istartsWith("name", nameStart)
+ *             .findList();
+ *   }
+ *
+ * }
+ * }
+ * + * @param The ID type + * @param The Bean type + */ +public abstract class BeanRepository extends BeanFinder { + + /** + * Create with the given bean type and EbeanServer instance. + *

+ * Typically users would extend BeanRepository rather than BeanFinder. + *

+ *
{@code
+   *
+   *   @Inject
+   *   public CustomerRepository(EbeanServer server) {
+   *     super(Customer.class, server);
+   *   }
+   *
+   * }
+ * + * @param type The bean type + * @param server The EbeanServer instance typically created via Spring factory or equivalent + */ + protected BeanRepository(Class type, EbeanServer server) { + super(type, server); + } + + /** + * Marks the entity bean as dirty. + *

+ * This is used so that when a bean that is otherwise unmodified is updated the version + * property is updated. + *

+ * An unmodified bean that is saved or updated is normally skipped and this marks the bean as + * dirty so that it is not skipped. + *

+ *

{@code
+   *
+   * Customer customer = customerRepository.byId(id);
+   *
+   * // mark the bean as dirty so that a save() or update() will
+   * // increment the version property
+   *
+   * customerRepository.markAsDirty(customer);
+   * customerRepository.save(customer);
+   *
+   * }
+ * + * @see EbeanServer#markAsDirty(Object) + */ + public void markAsDirty(T bean) { + db().markAsDirty(bean); + } + + /** + * Mark the property as unset or 'not loaded'. + *

+ * This would be used to specify a property that we did not wish to include in a stateless update. + *

+ *
{@code
+   *
+   *   // populate an entity bean from JSON or whatever
+   *   Customer customer = ...;
+   *
+   *   // mark the email property as 'unset' so that it is not
+   *   // included in a 'stateless update'
+   *   customerRepository.markPropertyUnset(customer, "email");
+   *
+   *   customerRepository.update(customer);
+   *
+   * }
+ * + * @param propertyName the name of the property on the bean to be marked as 'unset' + */ + public void markPropertyUnset(T bean, String propertyName) { + ((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false); + } + + /** + * Insert or update this entity depending on its state. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see EbeanServer#save(Object) + */ + public void save(T bean) { + db().save(bean); + } + + /** + * Update this entity. + * + * @see EbeanServer#update(Object) + */ + public void update(T bean) { + db().update(bean); + } + + /** + * Insert this entity. + * + * @see EbeanServer#insert(Object) + */ + public void insert(T bean) { + db().insert(bean); + } + + /** + * Delete this bean. + *

+ * This will return true if the bean was deleted successfully or JDBC batch is being used. + *

+ *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * If the Bean does not have a version property (or loaded version property) and + * the bean does not exist then this returns false indicating that nothing was + * deleted. Note that, if JDBC batch mode is used then this always returns true. + *

+ * + * @see EbeanServer#delete(Object) + */ + public boolean delete(T bean) { + return db().delete(bean); + } + + /** + * Delete a bean permanently without soft delete. + *

+ * This is used when the bean contains a @SoftDelete property and we + * want to perform a hard/permanent delete. + *

+ * + * @see EbeanServer#deletePermanent(Object) + */ + public boolean deletePermanent(T bean) { + return db().deletePermanent(bean); + } + + /** + * Refreshes this entity from the database. + * + * @see EbeanServer#refresh(Object) + */ + public void refresh(T bean) { + db().refresh(bean); + } +} diff --git a/src/test/java/org/tests/repository/CustomerRepository.java b/src/test/java/org/tests/repository/CustomerRepository.java new file mode 100644 index 000000000..31d18ef96 --- /dev/null +++ b/src/test/java/org/tests/repository/CustomerRepository.java @@ -0,0 +1,36 @@ +package org.tests.repository; + +import io.ebean.BeanRepository; +import io.ebean.EbeanServer; +import org.tests.model.basic.Customer; + +import javax.inject.Inject; +import java.util.List; + +public class CustomerRepository extends BeanRepository { + + @Inject + public CustomerRepository(EbeanServer server) { + super(Customer.class, server); + } + + public List findByName(String nameStart) { + return query().where() + .istartsWith("name", nameStart) + .findList(); + } + + public Customer findMatchName(String matchName) { + return nativeSql("select id, name from o_customer where name = ?") + .setParameter(1, matchName) + .findOne(); + } + + public int updateNotes(String blah, String whot) { + + return updateQuery() + .set("smallnote", whot) + .where().eq("name", blah) + .update(); + } +} diff --git a/src/test/java/org/tests/repository/TestBeanRepository.java b/src/test/java/org/tests/repository/TestBeanRepository.java new file mode 100644 index 000000000..cd2f7f666 --- /dev/null +++ b/src/test/java/org/tests/repository/TestBeanRepository.java @@ -0,0 +1,61 @@ +package org.tests.repository; + +import io.ebean.BaseTestCase; +import org.junit.Test; +import org.tests.model.basic.Customer; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestBeanRepository extends BaseTestCase { + + @Test + public void test() { + + CustomerRepository repository = new CustomerRepository(server()); + + Customer customer = new Customer(); + customer.setName("RepoCustomer"); + + repository.save(customer); + + Customer fetchCustomer = repository.findById(customer.getId()); + fetchCustomer.setSmallnote("yeah maybe"); + + repository.update(fetchCustomer); + repository.delete(fetchCustomer); + } + + @Test + public void findByName() { + + CustomerRepository repository = new CustomerRepository(server()); + + Customer blah = new Customer(); + blah.setName("Blah"); + + repository.markAsDirty(blah); + repository.markPropertyUnset(blah, "smallnote"); + repository.save(blah); + + Optional maybe = repository.findByIdOrEmpty(blah.getId()); + assertThat(maybe.isPresent()).isTrue(); + + List names = repository.findByName("bla"); + assertThat(names).hasSize(1); + + Customer matchName = repository.findMatchName("Blah"); + assertThat(matchName).isNotNull(); + + int rows = repository.updateNotes("Blah", "whot"); + assertThat(rows).isEqualTo(1); + + repository.deletePermanent(blah); + + repository.deleteById(1099); + repository.findAll(); + + } +}