From 69f2302c957a737564988aff2d9ae7b66287fe9d Mon Sep 17 00:00:00 2001 From: James Moger Date: Tue, 30 Aug 2011 19:26:27 -0400 Subject: [PATCH] Undeprecated interface configuration. Added more Define methods. I've revised my thinking about interface configuration. I think its a good option and should be supported. Field scope is now ignored across the board. If the developer does not want to map a field and is using the interface configuration approach, then the field should be annotated with IQIgnore. --- docs/01_model_classes.mkd | 120 ++++++++++++++++-- docs/05_releases.mkd | 14 +- docs/06_jaqu_comparison.mkd | 9 +- src/com/iciql/Constants.java | 6 +- src/com/iciql/Define.java | 54 +++++--- src/com/iciql/Iciql.java | 13 +- src/com/iciql/TableDefinition.java | 94 ++++++++++---- tests/com/iciql/test/AnnotationsTest.java | 3 + tests/com/iciql/test/models/Product.java | 2 +- .../test/models/ProductMixedAnnotation.java | 6 +- 10 files changed, 255 insertions(+), 66 deletions(-) diff --git a/docs/01_model_classes.mkd b/docs/01_model_classes.mkd index 94fdcc2..5babdcc 100644 --- a/docs/01_model_classes.mkd +++ b/docs/01_model_classes.mkd @@ -1,15 +1,15 @@ ## Model Classes A model class represents a single table within your database. Fields within your model class represent columns in the table. The object types of your fields are reflectively mapped to SQL types by iciql at runtime. -Models can be manually written using one of two approaches: *annotation configuration* or *interface configuration*. Both approaches can be used within a project and both can be used within a single model class, although that is discouraged. +Models can be manually written using one of three approaches: *annotation configuration*, *interface configuration*, or *POJO configuration*. All approaches can be used within a project and all can be used within a single model class, although that is discouraged. Alternatively, model classes can be automatically generated by iciql using the model generation tool. Please see the [tools](tools.html) page for details. ### Configuration Requirements and Limitations 1. Your model class **must** provide a public default constructor. -2. All **Object** fields are assumed NULLABLE unless explicitly set *@IQColumn(nullable = false)*. -3. All **Primitive** fields are assumed NOT NULLABLE unless explicitly set *@IQColumn(nullable = true)*. +2. All **Object** fields are assumed NULLABLE unless explicitly set *@IQColumn(nullable = false)* or *Define.nullable(field, false)*. +3. All **Primitive** fields are assumed NOT NULLABLE unless explicitly set *@IQColumn(nullable = true)* or *Define.nullable(field, true)*. 4. Only the specified types are supported. Any other types are not supported. 5. Triggers, views, and other advanced database features are not supported. @@ -89,8 +89,6 @@ The recommended approach to setup a model class is to annotate the class and fie ### advantages -- annotated fields may have any scope -- annotated fields may specify default values - annotated models support annotated field inheritance making it possible to design a single base class that defines the fields and then create table subclasses that specify the table mappings. - model runtime dependency is limited to the small, portable `com.iciql.Iciql` class file which contains the annotation definitions @@ -100,9 +98,15 @@ The recommended approach to setup a model class is to annotate the class and fie - indexes are defined using "fragile" string column names - compound primary keys are defined using "fragile" string column names +### field mapping + +- By default, **ONLY** fields annotated with *@IQColumn* are mapped.
+- scope is irrelevant.
+- transient is irrelevant. + ### default values -You may specify default values for an @IQColumn by either: +You may specify default values for an *@IQColumn* by either: 1. specifying the default value string within your annotation
**NOTE:**
@@ -123,7 +127,7 @@ Date myDate = new Date(100, 0, 1); int myId; %ENDCODE% -If you want to specify a database-specific variable or function as your default value (e.g. CURRENT_TIMESTAMP) you must do that within the annotation. Also note that the IQColumn.defaultValue must be a well-formatted SQL DEFAULT expression whereas object defaults will be automatically converted to an SQL DEFAULT expression. +If you want to specify a database-specific variable or function as your default value (e.g. CURRENT_TIMESTAMP) you must do that within the annotation. Also note that the *IQColumn.defaultValue* must be a well-formatted SQL DEFAULT expression whereas object defaults will be automatically converted to an SQL DEFAULT expression. ### Special Case: primitive autoincrement fields and 0 %BEGINCODE% @@ -173,6 +177,9 @@ public class Product { @IQColumn private Availability availability; + + // ignored because it is not annotated AND the class is @IQTable annotated + private Integer ignoredField; public Product() { // default constructor @@ -180,16 +187,20 @@ public class Product { } %ENDCODE% -## Interface Configuration (deprecated) -Alternatively, you may map your model classes using the original JaQu interface approach by implementing the `com.iciql.Iciql` interface. +## Interface Configuration +Alternatively, you may map your model classes using the interface approach by implementing the `com.iciql.Iciql` interface. This is a less verbose configuration style, but it comes at the expense of introducing a compile-time dependency on the logic of the iciql library. This might be a deterrent, for example, if you were serializing your model classes to another process that may not have the iciql library. The `com.iciql.Iciql` interface specifies a single method, *defineIQ()*. In your implementation of *defineIQ()* you would use static method calls to set: +- the schema name - the table name (if it's not the class name) - the column name (if it's not the field name) -- the max length of a string field +- the max length and trim of a string field +- the precision and scale of a decimal field +- the autoincrement flag of a long or integer field +- the nullable flag of a field - the primaryKey (single field or compound) - any indexes (single field or compound) @@ -201,13 +212,43 @@ The `com.iciql.Iciql` interface specifies a single method, *defineIQ()*. In you ### disadvantages -- only **public** fields of the model class are reflectively mapped as columns. all other scoped fields and inherited fields are ignored. - model runtime dependency on entire iciql library - *defineIQ()* is called from a static synchronized block which may be a bottleneck for highly concurrent systems +### field mapping + +- **ALL** fields are mapped unless annotated with *@IQIgnore*.
+- scope is irrelevant.
+- transient is irrelevant. + +### default values + +You may specify default values for an field by either: + +1. specifying the default value string within your *defineIQ()* method
+**NOTE:**
+The defineIQ() value always takes priority over a field default value. +%BEGINCODE% +Date myDate; + +public void defineIQ() { + // notice the single ticks! + Define.defaultValue(myDate, "'2000-01-01 00:00:00'"); +} +%ENDCODE% +2. setting a default value on the field
+**NOTE:**
+Primitive types have an implicit default value of *0* or *false*. +%BEGINCODE% +Date myDate = new Date(100, 0, 1); + +int myId; +%ENDCODE% + ### Example Interface Model %BEGINCODE% import com.iciql.Iciql; +import com.iciql.Iciql.IQIgnore; public class Product implements Iciql { public Integer productId; @@ -216,7 +257,7 @@ public class Product implements Iciql { public Double unitPrice; public Integer unitsInStock; - // this field is ignored because it is not public + @IQIgnore Integer reorderQuantity; public Product() { @@ -231,4 +272,59 @@ public class Product implements Iciql { com.iciql.Define.index(productName, category); } } +%ENDCODE% + +## POJO (Plain Old Java Object) Configuration + +This approach is very similar to the *interface configuration* approach; it is the least verbose and also the least useful. + +This approach would be suitable for quickly modeling an existing table where only SELECT and INSERT statements will be generated. + +### advantages + +- nearly zero-configuration + +### disadvantages + +- can not execute DELETE, UPDATE, or MERGE statements (they require a primary key specification) +- table name MUST MATCH model class name +- column names MUST MATCH model field names +- can not specify any column attributes +- can not specify indexes + +### field mapping + +- **ALL** fields are mapped unless annotated with *@IQIgnore*.
+- scope is irrelevant.
+- transient is irrelevant. + +### default values + +You may specify a default value on the field. + +**NOTE:**
+Primitive types have an implicit default value of *0* or *false*. +%BEGINCODE% +Date myDate = new Date(100, 0, 1); + +int myId; +%ENDCODE% + +### Example POJO Model +%BEGINCODE% +import com.iciql.Iciql.IQIgnore; + +public class Product { + public Integer productId; + public String productName; + public String category; + public Double unitPrice; + public Integer units; + + @IQIgnore + Integer reorderQuantity; + + public Product() { + } +} %ENDCODE% \ No newline at end of file diff --git a/docs/05_releases.mkd b/docs/05_releases.mkd index bd3c853..3d50a5b 100644 --- a/docs/05_releases.mkd +++ b/docs/05_releases.mkd @@ -6,6 +6,18 @@ **%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%))   *released %BUILDDATE%* +- api change release (API v7) +- Undeprecated interface configuration +- Interface configuration now maps ALL fields, not just public fields +- Added @IQIgnore annotation to explicitly skip fields for interface configuration +- Created additional Define static methods to bring interface configuration to near-parity with annotation configuration +- Documented POJO configuration option (limited subset of interface configuration) +- Fix to PostgreSQL dialect when creating autoincrement columns + +### Older Releases + +**0.7.0**   *released 2011-08-17* + - api change release (API v6) - Finished MySQL dialect implementation. MySQL 5.0.51b passes 100% of tests. - Added PostgreSQL dialect. PostgreSQL 9.0 passes all but the boolean-as-int tests. @@ -15,8 +27,6 @@ - Added IciqlLogger.warn method - Added IciqlLogger.drop method -### Older Releases - **0.6.6**   *released 2011-08-15* - api change release (API v5) diff --git a/docs/06_jaqu_comparison.mkd b/docs/06_jaqu_comparison.mkd index da7da41..33ca975 100644 --- a/docs/06_jaqu_comparison.mkd +++ b/docs/06_jaqu_comparison.mkd @@ -7,8 +7,8 @@ This is an overview of the fundamental differences between the original JaQu pro IciqlJaQu core deploymentsmall, discrete librarydepends on H2 database jar file -databasessupports H2, HSQL, Derby, MySQL, and PostreSQLsupports H2 only -loggingsupports console, SLF4J, or custom loggingsupports console logging +databasesH2, HSQL, Derby, MySQL, and PostreSQLH2 only +loggingconsole, SLF4J, or custom loggingconsole logging exceptionsalways includes generated statement in exception, when available-- syntax and api dynamic queriesmethods and where clauses for dynamic queries that build iciql objects-- @@ -21,5 +21,8 @@ This is an overview of the fundamental differences between the original JaQu pro BOOLEANflexible mapping of boolean as bool, varchar, or int-- BLOBpartially supported *(can not be used in a WHERE clause)*-- UUIDfully supported *(H2 only)* -- -DEFAULT valuesset from annotations or *default object values*set from annotations +configuration +DEFAULT valuesset from annotation, *default object values*, or Define.defaultValue()set from annotations +Interface Configuration
Mapped Fields*all fields* are mapped regardless of scope
fields are ignored by annotating with @IQIgnore*all public fields* are mapped
fields are ignored by reducing their scope +Index namescan be set-- \ No newline at end of file diff --git a/src/com/iciql/Constants.java b/src/com/iciql/Constants.java index b8d38dd..53dfc56 100644 --- a/src/com/iciql/Constants.java +++ b/src/com/iciql/Constants.java @@ -25,14 +25,14 @@ public class Constants { // The build script extracts this exact line so be careful editing it // and only use A-Z a-z 0-9 .-_ in the string. - public static final String VERSION = "0.7.0"; + public static final String VERSION = "0.7.1-SNAPSHOT"; // The build script extracts this exact line so be careful editing it // and only use A-Z a-z 0-9 .-_ in the string. - public static final String VERSION_DATE = "2011-08-17"; + public static final String VERSION_DATE = "PENDING"; // The build script extracts this exact line so be careful editing it // and only use A-Z a-z 0-9 .-_ in the string. - public static final String API_CURRENT = "6"; + public static final String API_CURRENT = "7"; } diff --git a/src/com/iciql/Define.java b/src/com/iciql/Define.java index 4216ea1..3b58231 100644 --- a/src/com/iciql/Define.java +++ b/src/com/iciql/Define.java @@ -29,29 +29,39 @@ public class Define { private static TableDefinition currentTableDefinition; private static Iciql currentTable; - public static void primaryKey(Object... columns) { + public static void skipCreate() { checkInDefine(); - currentTableDefinition.definePrimaryKey(columns); + currentTableDefinition.defineSkipCreate(); } - public static void index(Object... columns) { + public static void index(IndexType type, Object... columns) { checkInDefine(); - currentTableDefinition.defineIndex(IndexType.STANDARD, columns); + currentTableDefinition.defineIndex(null, type, columns); } - public static void uniqueIndex(Object... columns) { + public static void index(String name, IndexType type, Object... columns) { checkInDefine(); - currentTableDefinition.defineIndex(IndexType.UNIQUE, columns); + currentTableDefinition.defineIndex(name, type, columns); } - public static void hashIndex(Object column) { + public static void primaryKey(Object... columns) { + checkInDefine(); + currentTableDefinition.definePrimaryKey(columns); + } + + public static void schemaName(String schemaName) { checkInDefine(); - currentTableDefinition.defineIndex(IndexType.HASH, new Object[] { column }); + currentTableDefinition.defineSchemaName(schemaName); + } + + public static void tableName(String tableName) { + checkInDefine(); + currentTableDefinition.defineTableName(tableName); } - public static void uniqueHashIndex(Object column) { + public static void memoryTable() { checkInDefine(); - currentTableDefinition.defineIndex(IndexType.UNIQUE_HASH, new Object[] { column }); + currentTableDefinition.defineMemoryTable(); } public static void columnName(Object column, String columnName) { @@ -59,6 +69,11 @@ public class Define { currentTableDefinition.defineColumnName(column, columnName); } + public static void autoIncrement(Object column) { + checkInDefine(); + currentTableDefinition.defineAutoIncrement(column); + } + public static void length(Object column, int length) { checkInDefine(); currentTableDefinition.defineLength(column, length); @@ -68,13 +83,22 @@ public class Define { checkInDefine(); currentTableDefinition.defineScale(column, scale); } - - public static void tableName(String tableName) { + + public static void trim(Object column) { checkInDefine(); - currentTableDefinition.defineTableName(tableName); + currentTableDefinition.defineTrim(column); } - - @SuppressWarnings("deprecation") + + public static void nullable(Object column, boolean isNullable) { + checkInDefine(); + currentTableDefinition.defineNullable(column, isNullable); + } + + public static void defaultValue(Object column, String defaultValue) { + checkInDefine(); + currentTableDefinition.defineDefaultValue(column, defaultValue); + } + static synchronized void define(TableDefinition tableDefinition, Iciql table) { currentTableDefinition = tableDefinition; currentTable = table; diff --git a/src/com/iciql/Iciql.java b/src/com/iciql/Iciql.java index 9ca8bf7..eaa256a 100644 --- a/src/com/iciql/Iciql.java +++ b/src/com/iciql/Iciql.java @@ -215,7 +215,7 @@ public interface Iciql { public @interface IQVersion { /** - * If set to a non-zero value, iciql maintains a "_iq_versions" table + * If set to a non-zero value, iciql maintains a "iq_versions" table * within your database. The version number is used to call to a * registered DbUpgrader implementation to perform relevant ALTER * statements. Default: 0. You must specify a DbUpgrader on your Db @@ -441,7 +441,7 @@ public interface Iciql { */ String defaultValue() default ""; - } + } /** * Interface for using the EnumType.ENUMID enumeration mapping strategy. @@ -499,10 +499,17 @@ public interface Iciql { EnumType value() default EnumType.NAME; } + /** + * Annotation to define an ignored field. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface IQIgnore{ + } + /** * This method is called to let the table define the primary key, indexes, * and the table name. */ - @Deprecated void defineIQ(); } diff --git a/src/com/iciql/TableDefinition.java b/src/com/iciql/TableDefinition.java index 9b3666c..05d312c 100644 --- a/src/com/iciql/TableDefinition.java +++ b/src/com/iciql/TableDefinition.java @@ -18,7 +18,6 @@ package com.iciql; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -31,14 +30,15 @@ import com.iciql.Iciql.EnumId; import com.iciql.Iciql.EnumType; import com.iciql.Iciql.IQColumn; import com.iciql.Iciql.IQEnum; +import com.iciql.Iciql.IQIgnore; import com.iciql.Iciql.IQIndex; import com.iciql.Iciql.IQIndexes; import com.iciql.Iciql.IQSchema; import com.iciql.Iciql.IQTable; import com.iciql.Iciql.IQVersion; import com.iciql.Iciql.IndexType; -import com.iciql.util.StatementBuilder; import com.iciql.util.IciqlLogger; +import com.iciql.util.StatementBuilder; import com.iciql.util.StringUtils; import com.iciql.util.Utils; @@ -157,6 +157,14 @@ public class TableDefinition { this.tableName = tableName; } + void defineMemoryTable() { + this.memoryTable = true; + } + + void defineSkipCreate() { + this.createIfRequired = false; + } + /** * Define a primary key by the specified model fields. * @@ -198,14 +206,16 @@ public class TableDefinition { /** * Defines an index with the specified model fields. * + * @param name + * the index name (optional) * @param type * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH) * @param modelFields * the ordered list of model fields */ - void defineIndex(IndexType type, Object[] modelFields) { + void defineIndex(String name, IndexType type, Object[] modelFields) { List columnNames = mapColumnNames(modelFields); - addIndex(null, type, columnNames); + addIndex(name, type, columnNames); } /** @@ -235,6 +245,13 @@ public class TableDefinition { } } + void defineAutoIncrement(Object column) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.isAutoIncrement = true; + } + } + void defineLength(Object column, int length) { FieldDefinition def = fieldMap.get(column); if (def != null) { @@ -249,6 +266,27 @@ public class TableDefinition { } } + void defineTrim(Object column) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.trim = true; + } + } + + void defineNullable(Object column, boolean isNullable) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.nullable = isNullable; + } + } + + void defineDefaultValue(Object column, String defaultValue) { + FieldDefinition def = fieldMap.get(column); + if (def != null) { + def.defaultValue = defaultValue; + } + } + void mapFields() { boolean byAnnotationsOnly = false; boolean inheritColumns = false; @@ -267,6 +305,11 @@ public class TableDefinition { T defaultObject = Db.instance(clazz); for (Field f : classFields) { + // check if we should skip this field + if (f.isAnnotationPresent(IQIgnore.class)) { + continue; + } + // default to field name String columnName = f.getName(); boolean isAutoIncrement = false; @@ -292,6 +335,25 @@ public class TableDefinition { } } + // try using default object + try { + f.setAccessible(true); + Object value = f.get(defaultObject); + if (value != null) { + if (value.getClass().isEnum()) { + // enum default, convert to target type + Enum anEnum = (Enum) value; + Object o = Utils.convertEnum(anEnum, enumType); + defaultValue = ModelUtils.formatDefaultValue(o); + } else { + // object default + defaultValue = ModelUtils.formatDefaultValue(value); + } + } + } catch (IllegalAccessException e) { + throw new IciqlException(e, "failed to get default object for {0}", columnName); + } + boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class); if (hasAnnotation) { IQColumn col = f.getAnnotation(IQColumn.class); @@ -305,33 +367,13 @@ public class TableDefinition { trim = col.trim(); nullable = col.nullable(); - // try using default object - try { - f.setAccessible(true); - Object value = f.get(defaultObject); - if (value != null) { - if (value.getClass().isEnum()) { - // enum default, convert to target type - Enum anEnum = (Enum) value; - Object o = Utils.convertEnum(anEnum, enumType); - defaultValue = ModelUtils.formatDefaultValue(o); - } else { - // object default - defaultValue = ModelUtils.formatDefaultValue(value); - } - } - } catch (IllegalAccessException e) { - throw new IciqlException(e, "failed to get default object for {0}", columnName); - } - // annotation overrides if (!StringUtils.isNullOrEmpty(col.defaultValue())) { defaultValue = col.defaultValue(); } } - boolean isPublic = Modifier.isPublic(f.getModifiers()); - boolean reflectiveMatch = isPublic && !byAnnotationsOnly; + boolean reflectiveMatch = !byAnnotationsOnly; if (reflectiveMatch || hasAnnotation) { FieldDefinition fieldDef = new FieldDefinition(); fieldDef.isPrimitive = f.getType().isPrimitive(); @@ -402,7 +444,7 @@ public class TableDefinition { } return value; } - + // return the value unchanged return value; } diff --git a/tests/com/iciql/test/AnnotationsTest.java b/tests/com/iciql/test/AnnotationsTest.java index 30e46bb..ad229d9 100644 --- a/tests/com/iciql/test/AnnotationsTest.java +++ b/tests/com/iciql/test/AnnotationsTest.java @@ -135,6 +135,9 @@ public class AnnotationsTest { // public String mappedField is reflectively mapped by iciql assertEquals(10, db.from(p).where(p.mappedField).is("mapped").selectCount()); + // test IQIgnore annotation + assertEquals(null, db.from(p).selectFirst().productDescription); + // test IQColumn.primaryKey=true try { db.insertAll(ProductMixedAnnotation.getList()); diff --git a/tests/com/iciql/test/models/Product.java b/tests/com/iciql/test/models/Product.java index 106e51d..241a3d3 100644 --- a/tests/com/iciql/test/models/Product.java +++ b/tests/com/iciql/test/models/Product.java @@ -56,7 +56,7 @@ public class Product implements Iciql { primaryKey(productId); length(productName, 255); length(category, 255); - index(productName, category); + index("MyIndex", IndexType.STANDARD, productName, category); } private static Product create(int productId, String productName, String category, double unitPrice, diff --git a/tests/com/iciql/test/models/ProductMixedAnnotation.java b/tests/com/iciql/test/models/ProductMixedAnnotation.java index 703f8cd..a893a69 100644 --- a/tests/com/iciql/test/models/ProductMixedAnnotation.java +++ b/tests/com/iciql/test/models/ProductMixedAnnotation.java @@ -37,6 +37,9 @@ public class ProductMixedAnnotation implements Iciql { public Integer unitsInStock; public String mappedField; + @IQIgnore + public String productDescription; + @IQColumn(name = "cat", length = 255) public String category; @@ -45,7 +48,7 @@ public class ProductMixedAnnotation implements Iciql { @IQColumn(name = "name", length = 255) private String productName; - + public ProductMixedAnnotation() { // public constructor } @@ -58,6 +61,7 @@ public class ProductMixedAnnotation implements Iciql { this.unitPrice = unitPrice; this.unitsInStock = unitsInStock; this.mappedField = mappedField; + this.productDescription = category + ": " + productName; } private static ProductMixedAnnotation create(int productId, String productName, String category, -- 2.39.5