diff --git a/src/main/java/com/avaje/ebean/Model.java b/src/main/java/com/avaje/ebean/Model.java index a09a98cf1..6aaa19766 100644 --- a/src/main/java/com/avaje/ebean/Model.java +++ b/src/main/java/com/avaje/ebean/Model.java @@ -1,11 +1,13 @@ package com.avaje.ebean; import com.avaje.ebean.text.PathProperties; +import com.avaje.ebean.util.ClassUtil; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; import javax.persistence.MappedSuperclass; @@ -34,7 +36,7 @@ import javax.persistence.MappedSuperclass; * *
* If you choose to use the Model mapped superclass you will probably also chose to additionally add - * a {@link Finder} as a public static field to complete the active record pattern and provide a + * a {@link Find} as a public static field to complete the active record pattern and provide a * relatively nice clean way to write queries. * *
{@code
*
* // Active record style ... save(), delete() etc
- * Account account = new Account();
- * account.setName("AC234");
+ * Customer customer = new Customer();
+ * customer.setName("AC234");
*
* // save() method inherited from Model
- * account.save();
+ * customer.save();
*
* }
*
- * {@code
*
* // find byId
- * Account account = Account.find.byId(42);
+ * Customer customer = Customer.find.byId(42);
*
* }
*
- * {@code
*
* // find where ...
- * List accounts =
- * Account.find
+ * List customers =
+ * Customer.find
* .where().gt("startDate", lastMonth)
* .findList();
*
@@ -124,7 +122,8 @@ public abstract class Model {
*
*
* Example:
- *
+ * {@code
+ *
* Transaction transaction = Customer.db().beginTransaction();
* try {
*
@@ -141,7 +140,7 @@ public abstract class Model {
* customer.save();
*
* Customer otherCustomer = new Customer();
- * otherCustomer.setName("Franko");
+ * otherCustomer.setName("Franko");
* otherCustomer.save();
*
* transaction.commit();
@@ -150,7 +149,7 @@ public abstract class Model {
* transaction.end();
* }
*
- *
+ * }
*/
public static EbeanServer db() {
return Ebean.getServer(null);
@@ -179,7 +178,7 @@ public abstract class Model {
* 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 = Customer.find.byId(id);
*
@@ -188,7 +187,7 @@ public abstract class Model {
* customer.markAsDirty();
* customer.save();
*
- *
+ * }
*
* @see EbeanServer#markAsDirty(Object)
*/
@@ -266,46 +265,193 @@ public abstract class Model {
db().refresh(this);
}
+ /**
+ * A concrete implementation of Find.
+ *
+ * It should be preferred to use {@link Find} instead of Finder as that can use reflection to determine the class
+ * literal type of the entity bean.
+ *
+ * @param type of the Id property
+ * @param type of the entity bean
+ */
+ public static class Finder extends Find {
+
+ /**
+ * Create with the type of the entity bean.
+ *
+ * {@code
+ *
+ * @Entity
+ * public class Customer extends BaseModel {
+ *
+ * public static final Finder find = new Finder(Customer.class);
+ * ...
+ *
+ * }
+ *
+ *
+ * The preferred approach is to instead use Find as below. This approach is more DRY in that it does
+ * not require the class literal Customer.class to be passed into the constructor.
+ *
+ * {@code
+ *
+ * @Entity
+ * public class Customer extends BaseModel {
+ *
+ * public static final Find find = new Find(){};
+ * ...
+ *
+ * }
+ */
+ public Finder(Class type) {
+ super(null, type);
+ }
+
+ /**
+ * Create with the type of the entity bean and specific server name.
+ */
+ public Finder(String serverName, Class type) {
+ super(serverName, type);
+ }
+
+ /**
+ * Please migrate to use {@link Find} or constructor Finder(Class) that
+ * does not have the idType parameter.
+ *
+ * Create with the type of the ID property and entity bean and specific server name.
+ *
+ * @deprecated
+ */
+ public Finder(Class idType, Class type) {
+ super(null, type);
+ }
+
+ /**
+ * Please migrate to use the constructor Finder(String, Class) that
+ * does not have the idType parameter.
+ *
+ * Create with the type of the ID property and entity bean and specific server name.
+ *
+ * @deprecated
+ */
+ public Finder(String serverName, Class idType, Class type) {
+ super(serverName, type);
+ }
+ }
+
/**
* Helper object for performing queries.
*
*
- * Typically a Finder is defined as a public static field on an entity bean class to provide a
+ * Typically a Find instance is defined as a public static field on an entity bean class to provide a
* nice way to write queries.
- *
+ *
+ *
Example use:
+ *
+ * {@code
+ *
+ * @Entity
+ * public class Customer extends BaseModel {
+ *
+ * public static final Find find = new Find(){};
+ *
+ * ...
+ *
+ * }
+ *
+ * This enables you to write code like:
+ * {@code
+ *
+ * Customer customer = Customer.find.byId(42L);
+ *
+ * List customers =
+ * Customer.find
+ * .select("name, dateOfBirth")
+ * .findList();
+ *
+ * }
+ *
+ * Kotlin
+ * In Kotlin you would typically create Find as a companion object.
+ * {@code
+ *
+ * // kotlin
+ * companion object : Model.Find() {}
+ *
+ * }
* @param
* The Id type. This is most often a {@link Long} but is also often a {@link UUID} or
* {@link String}.
*
* @param
- * The bean type
+ * The entity bean type
*/
- public static class Finder {
+ public static abstract class Find {
- private final Class idType;
+ /**
+ * The entity bean type.
+ */
private final Class type;
+
+ /**
+ * The name of the EbeanServer, null for the default server.
+ */
private final String serverName;
/**
* Creates a finder for entity of type T with ID of type I.
- *
- *
- * Typically you use this constructor to have a static "find" field on each entity bean.
+ *
+ * Typically you create Find as a public static field on each entity bean as the example below.
+ *
+ *
+ * Note that Find is an abstract class and hence {} is required. This is done so
+ * that the type (class literal) of the entity bean can be derived from the generics parameter.
+ *
+ * {@code
+ *
+ * @Entity
+ * public class Customer extends BaseModel {
+ *
+ * // Note the trailing {} as Find is an abstract class.
+ * // We do this so that we can derive the type literal Customer.class
+ * // via reflection
+ * public static final Find find = new Find(){};
+ * ...
+ *
+ * }
+ *
+ * This enables you to write code like:
+ * {@code
+ *
+ * Customer customer = Customer.find.byId(42L);
+ *
+ * List customers =
+ * Customer.find
+ * .select("name, email, dateOfBirth")
+ * .findList();
+ *
+ * }
+ *
+ * Kotlin
+ * In Kotlin you would typically create it as a companion object.
+ *
+ * {@code
+ *
+ * // kotlin
+ * companion object : Model.Find() {}
+ *
+ * }
*/
- public Finder(Class idType, Class type) {
- this(null, idType, type);
+ public Find() {
+ this.serverName = null;
+ this.type = (Class)ClassUtil.getSecondArgumentType(getClass());
}
/**
- * Creates a finder for entity of type T with ID of type I, using a
- * specific EbeanServer.
- *
- *
- * Typically you don't need to use this method.
+ * Construct passing the class literal type of the entity type.
*/
- public Finder(String serverName, Class idType, Class type) {
+ protected Find(String serverName, Class type) {
this.type = type;
- this.idType = idType;
this.serverName = serverName;
}
@@ -335,12 +481,12 @@ public abstract class Model {
/**
* Creates a Finder for the named EbeanServer.
- *
+ *
*
* Create and return a new Finder for a different server.
*/
public Finder on(String server) {
- return new Finder(server, idType, type);
+ return new Finder(server, type);
}
/**
diff --git a/src/main/java/com/avaje/ebean/util/ClassUtil.java b/src/main/java/com/avaje/ebean/util/ClassUtil.java
index f823a539a..60ce5e08f 100644
--- a/src/main/java/com/avaje/ebean/util/ClassUtil.java
+++ b/src/main/java/com/avaje/ebean/util/ClassUtil.java
@@ -1,9 +1,11 @@
package com.avaje.ebean.util;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+
/**
* Helper to find classes taking into account the context class loader.
- *
- * @author rbygrave
*/
public class ClassUtil {
@@ -20,4 +22,40 @@ public class ClassUtil {
}
}
+
+ /**
+ * Returns the raw type for the 2nd generic parameter for a subclass.
+ */
+ public static Class> getSecondArgumentType(Class> subclass) {
+ Type[] typeArguments = getSuperclassTypeParameter(subclass);
+ if (typeArguments.length != 2) {
+ throw new IllegalArgumentException("Expected type with 2 generic argument types but got "
+ + typeArguments.length + " - " + Arrays.toString(typeArguments));
+ }
+
+ return getRawType(typeArguments[1]);
+ }
+
+ static Type[] getSuperclassTypeParameter(Class> subclass) {
+ Type superclass = subclass.getGenericSuperclass();
+ if (superclass instanceof Class) {
+ throw new RuntimeException("Missing generics type parameters on subclass " + subclass);
+ }
+ return ((ParameterizedType) superclass).getActualTypeArguments();
+ }
+
+ private static Class> getRawType(Type type) {
+
+ if (type instanceof Class>) {
+ return (Class>) type;
+
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ Type rawType = parameterizedType.getRawType();
+ if (rawType instanceof Class>) {
+ return (Class>) rawType;
+ }
+ }
+ throw new RuntimeException("Unable to obtain raw class type from " + type);
+ }
}
diff --git a/src/test/java/com/avaje/tests/model/info/InfoCompany.java b/src/test/java/com/avaje/tests/model/info/InfoCompany.java
index 202bd5cba..c8fa175e3 100644
--- a/src/test/java/com/avaje/tests/model/info/InfoCompany.java
+++ b/src/test/java/com/avaje/tests/model/info/InfoCompany.java
@@ -9,7 +9,7 @@ import java.util.List;
@Entity
public class InfoCompany extends Model {
- public static Finder find = new Finder(Long.class, InfoCompany.class);
+ public static Finder find = new Finder(InfoCompany.class);
@Id
Long id;
diff --git a/src/test/java/com/avaje/tests/model/info/InfoCustomer.java b/src/test/java/com/avaje/tests/model/info/InfoCustomer.java
index 252a6283c..033b2785d 100644
--- a/src/test/java/com/avaje/tests/model/info/InfoCustomer.java
+++ b/src/test/java/com/avaje/tests/model/info/InfoCustomer.java
@@ -7,7 +7,7 @@ import javax.persistence.*;
@Entity
public class InfoCustomer extends Model {
- public static Finder find = new Finder(Long.class, InfoCustomer.class);
+ public static Finder find = new Finder(InfoCustomer.class);
@Id
Long id;
diff --git a/src/test/java/com/avaje/tests/model/onetoone/Account.java b/src/test/java/com/avaje/tests/model/onetoone/Account.java
index 29923e161..ba4ab835f 100644
--- a/src/test/java/com/avaje/tests/model/onetoone/Account.java
+++ b/src/test/java/com/avaje/tests/model/onetoone/Account.java
@@ -1,16 +1,16 @@
package com.avaje.tests.model.onetoone;
+import com.avaje.tests.model.BaseModel;
+
import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.persistence.Table;
-import com.avaje.tests.model.BaseModel;
-
@Entity
@Table(name="oto_account")
public class Account extends BaseModel {
- public static final Finder find = new Finder(Long.class, Account.class);
+ public static final Find find = new Find(){};
String name;
diff --git a/src/test/java/com/avaje/tests/model/onetoone/User.java b/src/test/java/com/avaje/tests/model/onetoone/User.java
index a459d4232..e1cfd8b6c 100644
--- a/src/test/java/com/avaje/tests/model/onetoone/User.java
+++ b/src/test/java/com/avaje/tests/model/onetoone/User.java
@@ -10,7 +10,7 @@ import com.avaje.tests.model.BaseModel;
@Table(name="oto_user")
public class User extends BaseModel {
- public static final Finder find = new Finder(Long.class, User.class);
+ public static final Find find = new Find(){};
String name;