diff --git a/src/main/java/com/avaje/ebean/annotation/DbEnumType.java b/src/main/java/com/avaje/ebean/annotation/DbEnumType.java new file mode 100644 index 000000000..88d7b8f6c --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DbEnumType.java @@ -0,0 +1,17 @@ +package com.avaje.ebean.annotation; + +/** + * Specify the DB storage type used to with @DbEnumValue. + */ +public enum DbEnumType { + + /** + * Store values as database INTEGER. + */ + INTEGER, + + /** + * Store values as database VARCHAR. + */ + VARCHAR +} diff --git a/src/main/java/com/avaje/ebean/annotation/DbEnumValue.java b/src/main/java/com/avaje/ebean/annotation/DbEnumValue.java new file mode 100644 index 000000000..b4c1a1679 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/DbEnumValue.java @@ -0,0 +1,44 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify a method on an Enum that returns the value that should be stored in the DB. + *

+ * This is the preferred option for mapping Enum's to DB values (preferred over the JPA + * standard @Enumerated and Ebean's @EnumValue annotations). + *

+ *

Example:

+ *
{@code
+ *
+ *   public enum Status {
+ *     NEW("N"),
+ *     ACTIVE("A"),
+ *     INACTIVE("I");
+ *
+ *     String dbValue;
+ *     Status(String dbValue) {
+ *       this.dbValue = dbValue;
+ *     }
+ *
+ *     @DbEnumValue
+ *     public String getValue() {
+ *       return dbValue;
+ *     }
+ *   }
+ *
+ * }
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DbEnumValue { + + /** + * Specify the database type used to store the values (VARCHAR or INTEGER). + */ + DbEnumType storage() default DbEnumType.VARCHAR; + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/annotation/EnumValue.java b/src/main/java/com/avaje/ebean/annotation/EnumValue.java index d617088d9..8dce61fdc 100644 --- a/src/main/java/com/avaje/ebean/annotation/EnumValue.java +++ b/src/main/java/com/avaje/ebean/annotation/EnumValue.java @@ -25,7 +25,7 @@ import java.lang.annotation.Target; * } *

* This is an alternative to using the JPA standard approach or Ebean's - * {@link EnumMapping} annotation. + * {@link DbEnumValue} annotation. *

*

* Note that if all the EnumValue values are parsable as Integers then Ebean diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java index 272c644fb..01b89783a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -111,8 +111,8 @@ public class DeployUtil { } ScalarType scalarType = typeManager.getScalarType(enumType); if (scalarType == null) { - // see if it has a Mapping in avaje.properties - scalarType = typeManager.createEnumScalarType(enumType); + // look for @DbEnumValue or @EnumValue annotations etc + scalarType = typeManager.createEnumScalarType((Class>)enumType); if (scalarType == null) { // use JPA normal Enum type (without mapping) EnumType type = enumerated != null ? enumerated.value() : null; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index 149be0069..0a81879f1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -1,5 +1,7 @@ package com.avaje.ebeaninternal.server.type; +import com.avaje.ebean.annotation.DbEnumType; +import com.avaje.ebean.annotation.DbEnumValue; import com.avaje.ebean.annotation.EnumMapping; import com.avaje.ebean.annotation.EnumValue; import com.avaje.ebean.config.*; @@ -21,6 +23,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -478,7 +481,16 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { * much shorter codes used in the DB. *

*/ - public ScalarType createEnumScalarType(Class enumType) { + public ScalarType createEnumScalarType(Class> enumType) { + + Method[] methods = enumType.getMethods(); + for (int i = 0; i + * Return null if the EnumValue annotations are not present/used. + *

+ */ + private ScalarType createEnumScalarTypeDbValue(Class> enumType, Method method, boolean integerType) { + + Map nameValueMap = new HashMap(); + + Enum[] enumConstants = enumType.getEnumConstants(); + for (int i = 0; i < enumConstants.length; i++) { + try { + Object value = method.invoke(enumConstants[i]); + nameValueMap.put(enumConstants[i].name(), value.toString()); + } catch (Exception e) { + throw new IllegalArgumentException("Error trying to invoke DbEnumValue method on "+enumConstants[i], e); + } + } + if (nameValueMap.isEmpty()) { + // Not using EnumValue here + return null; + } + + return createEnumScalarType(enumType, nameValueMap, integerType, 0); + } + /** * Given the name value mapping and integer/string type and explicit DB column * length create the ScalarType for the Enum. diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java index 71cd5db12..7ed5fa261 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java @@ -55,7 +55,7 @@ public interface TypeManager { * Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal * or String which has limitations). */ - ScalarType createEnumScalarType(Class enumType); + ScalarType createEnumScalarType(Class> enumType); /** * Return the ScalarType used to handle JSON content. diff --git a/src/test/java/com/avaje/tests/basic/type/TestEnumValueAnnotation.java b/src/test/java/com/avaje/tests/basic/type/TestEnumValueAnnotation.java index 1a93a0871..4e065707b 100644 --- a/src/test/java/com/avaje/tests/basic/type/TestEnumValueAnnotation.java +++ b/src/test/java/com/avaje/tests/basic/type/TestEnumValueAnnotation.java @@ -1,5 +1,6 @@ package com.avaje.tests.basic.type; +import com.avaje.tests.model.basic.EBasicEnumInt; import org.junit.Assert; import org.junit.Test; @@ -11,6 +12,9 @@ import com.avaje.tests.model.basic.EBasic; import com.avaje.tests.model.basic.EBasic.Status; import com.avaje.tests.model.basic.EBasicEnumId; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + public class TestEnumValueAnnotation extends BaseTestCase { @Test @@ -28,7 +32,7 @@ public class TestEnumValueAnnotation extends BaseTestCase { SqlRow sqlRow = q.findUnique(); String strStatus = sqlRow.getString("status"); - Assert.assertEquals("N", strStatus); + assertEquals("N", strStatus); EBasic b2 = new EBasic(); b2.setName("Apple"); @@ -40,9 +44,9 @@ public class TestEnumValueAnnotation extends BaseTestCase { b3.setName("Orange"); Ebean.save(b3); - } + @Test public void testAsId() { EBasicEnumId b = new EBasicEnumId(); b.setName("Banana"); @@ -56,7 +60,7 @@ public class TestEnumValueAnnotation extends BaseTestCase { SqlRow sqlRow = q.findUnique(); String strStatus = sqlRow.getString("status"); - Assert.assertEquals("N", strStatus); + assertEquals("N", strStatus); try { b = Ebean.find(EBasicEnumId.class, b.getStatus()); @@ -64,7 +68,32 @@ public class TestEnumValueAnnotation extends BaseTestCase { Assert.fail("The use of an enum as id should work : " + iae.getLocalizedMessage()); } - Assert.assertEquals(EBasicEnumId.Status.NEW, b.getStatus()); + assertEquals(EBasicEnumId.Status.NEW, b.getStatus()); } + @Test + public void testDbEnumValueInt() { + + EBasicEnumInt b = new EBasicEnumInt(); + b.setName("Banana"); + b.setStatus(EBasicEnumInt.Status.NEW); + + Ebean.save(b); + + SqlQuery q = Ebean.createSqlQuery("select * from e_basic_eni where id = :id"); + q.setParameter("id", b.getId()); + + SqlRow sqlRow = q.findUnique(); + Integer intStatus = sqlRow.getInteger("status"); + + assertEquals(Integer.valueOf(1), intStatus); + + + EBasicEnumInt b2 = Ebean.find(EBasicEnumInt.class) + .where().eq("id", b.getId()) + .eq("status", EBasicEnumInt.Status.NEW) + .findUnique(); + + assertNotNull(b2); + } } diff --git a/src/test/java/com/avaje/tests/model/basic/Customer.java b/src/test/java/com/avaje/tests/model/basic/Customer.java index b3bfaa918..1314fa6d2 100644 --- a/src/test/java/com/avaje/tests/model/basic/Customer.java +++ b/src/test/java/com/avaje/tests/model/basic/Customer.java @@ -2,7 +2,7 @@ package com.avaje.tests.model.basic; import com.avaje.ebean.annotation.ChangeLog; import com.avaje.ebean.annotation.ChangeLogInsertMode; -import com.avaje.ebean.annotation.EnumValue; +import com.avaje.ebean.annotation.DbEnumValue; import com.avaje.ebean.annotation.JsonIgnore; import com.avaje.ebean.annotation.Where; import com.avaje.tests.model.basic.finder.CustomerFinder; @@ -36,14 +36,19 @@ public class Customer extends BasicDomain { * EnumValue is an Ebean specific mapping for enums. */ public enum Status { - @EnumValue("N") - NEW, + NEW("N"), + ACTIVE("A"), + INACTIVE("I"); - @EnumValue("A") - ACTIVE, + String dbValue; + Status(String dbValue) { + this.dbValue = dbValue; + } - @EnumValue("I") - INACTIVE + @DbEnumValue + public String getValue() { + return dbValue; + } } @Transient diff --git a/src/test/java/com/avaje/tests/model/basic/EBasicEnumInt.java b/src/test/java/com/avaje/tests/model/basic/EBasicEnumInt.java new file mode 100644 index 000000000..fc06c8b9e --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/EBasicEnumInt.java @@ -0,0 +1,81 @@ +package com.avaje.tests.model.basic; + +import com.avaje.ebean.annotation.DbEnumType; +import com.avaje.ebean.annotation.DbEnumValue; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.sql.Timestamp; + +@Entity +@Table(name = "e_basic_eni") +public class EBasicEnumInt { + + public enum Status { + NEW("1"), + ACTIVE("2"), + INACTIVE("3"); + + String value; + Status(String value) { + this.value = value; + } + @DbEnumValue(storage = DbEnumType.INTEGER) + public String getValue() { + return value; + } + } + + @Id + Integer id; + + Status status; + + String name; + + String description; + + Timestamp someDate; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Timestamp getSomeDate() { + return someDate; + } + + public void setSomeDate(Timestamp someDate) { + this.someDate = someDate; + } + +} diff --git a/src/test/java/com/avaje/tests/update/TestUpdatePartial.java b/src/test/java/com/avaje/tests/update/TestUpdatePartial.java index 9309de714..4d8b4749c 100644 --- a/src/test/java/com/avaje/tests/update/TestUpdatePartial.java +++ b/src/test/java/com/avaje/tests/update/TestUpdatePartial.java @@ -1,11 +1,13 @@ package com.avaje.tests.update; -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; +import com.avaje.ebean.SqlQuery; +import com.avaje.ebean.SqlRow; import com.avaje.tests.model.basic.Customer; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; public class TestUpdatePartial extends BaseTestCase { @@ -18,6 +20,7 @@ public class TestUpdatePartial extends BaseTestCase { c.setSmallnote("a note"); Ebean.save(c); + checkDbStatusValue(c.getId(), "A"); Customer c2 = Ebean.find(Customer.class) .select("status, smallnote") @@ -28,6 +31,7 @@ public class TestUpdatePartial extends BaseTestCase { c2.setSmallnote("2nd note"); Ebean.save(c2); + checkDbStatusValue(c.getId(), "I"); Customer c3 = Ebean.find(Customer.class) .select("status") @@ -38,7 +42,15 @@ public class TestUpdatePartial extends BaseTestCase { c3.setSmallnote("3rd note"); Ebean.save(c3); + checkDbStatusValue(c.getId(), "N"); + } + private void checkDbStatusValue(Integer custId, String dbStatus) { + SqlQuery sqlQuery = Ebean.createSqlQuery("select id, status from o_customer where id = ?"); + sqlQuery.setParameter(1, custId); + SqlRow sqlRow = sqlQuery.findUnique(); + String status = sqlRow.getString("status"); + assertEquals(dbStatus, status); } /** @@ -57,6 +69,6 @@ public class TestUpdatePartial extends BaseTestCase { Ebean.save(customerWithoutChanges); // assert - Assert.assertEquals(customer.getUpdtime().getTime(), customerWithoutChanges.getUpdtime().getTime()); + assertEquals(customer.getUpdtime().getTime(), customerWithoutChanges.getUpdtime().getTime()); } }