From 49e3882ae6552a05fd2cd56e7ecd560d3f795ece Mon Sep 17 00:00:00 2001 From: James Moger Date: Mon, 25 Mar 2013 22:49:16 -0400 Subject: [PATCH] Documentation --- build.xml | 5 +- releases.moxie | 2 + src/site/custom.less | 29 +++++++++--- src/site/examples.mkd | 37 +++++++-------- src/site/index.mkd | 4 +- src/site/jaqu_comparison.mkd | 1 + src/site/model_classes.mkd | 89 ++++++++++++++++++++++++++++-------- src/site/tools.mkd | 8 ++-- src/site/usage.mkd | 88 +++++++++++++++++++++++++---------- 9 files changed, 182 insertions(+), 81 deletions(-) diff --git a/build.xml b/build.xml index 1a3e627..8344fa0 100644 --- a/build.xml +++ b/build.xml @@ -10,7 +10,7 @@ Retrieve Moxie Toolkit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - + @@ -145,7 +145,7 @@ - @@ -153,7 +153,6 @@ - diff --git a/releases.moxie b/releases.moxie index abe9b95..3cacc60 100644 --- a/releases.moxie +++ b/releases.moxie @@ -30,6 +30,8 @@ Iciql artifacts may now be retrieved by your favorite Maven 2-compatible build t View models may be specified using the IQView annotation or Iciql.define(). Views can either be created automatically as part of a query of the view OR views may be constructed from a fluent statement. '' - Support inheritance of IQVersion for DbUpgrader implementations (issue 10) + - Added @IQConstraintForeignKey annotation (issue 13) + - Added MS SQL Server dialect (issue 14) } # diff --git a/src/site/custom.less b/src/site/custom.less index f7130d4..b9c21a8 100644 --- a/src/site/custom.less +++ b/src/site/custom.less @@ -2,18 +2,18 @@ // GLOBAL VALUES // -------------------------------------------------- @standardGray: #ccc; -@cornflower: #99cbff; +@iciql: #95C7F9; @white: #fff; // Dropdown // ------------------------- -@dropdownLinkBackgroundHover: @cornflower; +@dropdownLinkBackgroundHover: @iciql; // Navbar // ------------------------- -@navbarHeight: 55px; -@navbarBackground: @cornflower; -@navbarBackgroundHighlight: @cornflower; +@navbarHeight: 50px; +@navbarBackground: @iciql; +@navbarBackgroundHighlight: @iciql; @navbarText: @white; @navbarLinkColor: @white; @navbarLinkColorHover: @white; @@ -24,15 +24,30 @@ .navbar { .brand { @elementHeight: 48px; - padding: 7px; + padding: 5px; } } .navbar .nav > li > a { - font-size: @baseFontSize + 2; + font-size: @baseFontSize + 1; text-shadow: 0 1px 0 #6b94df; } + +.navbar .nav > li > a:hover { + text-shadow: 0 0 1em white; +} +.navbar .nav > .active > a, + .navbar .nav > .active > a:hover, + .navbar .nav > .active > a:focus { + box-shadow: none; + text-decoration: underline; +} + +.dropdown-submenu > a:after { + margin-right: -5px; +} + body { padding-top: @navbarHeight + 15 } /* 60px to make the container go all the way to the bottom of the topbar */ footer { margin-top: 25px; padding: 15px 0 16px; border-top: 1px solid #E5E5E5; } diff --git a/src/site/examples.mkd b/src/site/examples.mkd index 33cb9c4..d8d3dfd 100644 --- a/src/site/examples.mkd +++ b/src/site/examples.mkd @@ -1,6 +1,6 @@ ## Select Statements -%BEGINCODE% +---JAVA--- // select * from products List allProducts = db.from(p).select(); @@ -23,11 +23,11 @@ List productPrices = category = p.category; price = p.unitPrice; }}); -%ENDCODE% +---JAVA--- ## Insert Statements -%BEGINCODE% +---JAVA--- // single record insertion db.insert(singleProduct); @@ -39,11 +39,11 @@ db.insertAll(myProducts); // batch insertion with primary key retrieval List myKeys = db.insertAllAndGetKeys(list); -%ENDCODE% +---JAVA--- ## Update Statements -%BEGINCODE% +---JAVA--- // single record update db.update(singleProduct); @@ -59,22 +59,21 @@ db.from(p).set(p.productName).to("updated") // reusable, parameterized update query String q = db.from(p).set(p.productName).toParameter().where(p.productId).is(1).toSQL(); db.executeUpdate(q, "Lettuce"); - -%ENDCODE% +---JAVA--- ## Merge Statements Merge statements currently generate the [H2 merge syntax](http://h2database.com/html/grammar.html#merge). -%BEGINCODE% +---JAVA--- Product pChang = db.from(p).where(p.productName).is("Chang").selectFirst(); pChang.unitPrice = 19.5; pChang.unitsInStock = 16; db.merge(pChang); -%ENDCODE% +---JAVA--- ## Delete Statements -%BEGINCODE% +---JAVA--- // single record deletion db.delete(singleProduct); @@ -83,12 +82,11 @@ db.deleteAll(myProducts); // delete query db.from(p).where(p.productId).atLeast(10).delete(); - -%ENDCODE% +---JAVA--- ## Inner Join Statements -%BEGINCODE% +---JAVA--- final Customer c = new Customer(); final Order o = new Order(); @@ -98,7 +96,6 @@ List customersWithLargeOrders = where(o.total).greaterThan(new BigDecimal("500.00")). groupBy(c.customerId).select(); - List orders = db.from(c). innerJoin(o).on(c.customerId).is(o.customerId). @@ -109,11 +106,11 @@ List orders = orderId = o.orderId; total = o.total; }}); -%ENDCODE% +---JAVA--- ## View Statements -%BEGINCODE% +---JAVA--- // the view named "ProductView" is created from the "Products" table @IQView(viewTableName = "Products") public class ProductView { @@ -163,14 +160,13 @@ db.from(p).where(p.id).atLeast(250L).and(p.id).lessThan(350L).replaceView(Produc // now drop the view from the database db.dropView(ProductViewInherited.class); - -%ENDCODE% +---JAVA--- ## Dynamic Queries Dynamic queries skip all field type checking and, depending on which approach you use, may skip model class/table name checking too. -%BEGINCODE% +---JAVA--- // where fragment with object parameters List restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select(); @@ -192,5 +188,4 @@ List restock = db.executeQuery(Product.class, "select * from products w ResultSet rs = db.executeQuery("select * from products"); List allProducts = db.buildObjects(Product.class, rs); JdbcUtils.closeSilently(rs, true); - -%ENDCODE% \ No newline at end of file +---JAVA--- diff --git a/src/site/index.mkd b/src/site/index.mkd index 1c2bbd3..7e489b3 100644 --- a/src/site/index.mkd +++ b/src/site/index.mkd @@ -22,11 +22,11 @@ iciql **is not**... -%BEGINCODE% +---JAVA--- Product p = new Product(); List restock = db.from(p).where(p.unitsInStock).is(0).select(); List all = db.executeQuery(Product.class, "select * from products"); -%ENDCODE% +---JAVA---
diff --git a/src/site/jaqu_comparison.mkd b/src/site/jaqu_comparison.mkd index 20df5d5..3b060e5 100644 --- a/src/site/jaqu_comparison.mkd +++ b/src/site/jaqu_comparison.mkd @@ -14,6 +14,7 @@ This is an overview of the fundamental differences between the original JaQu pro savepointsbulk operations (insert, update, delete) use savepoints with rollback in the event of failure-- syntax and api VIEWscreate readonly views either from a class definition or from a fluent statement-- +Foreign Key Constraintsmodel classes may be annotated with foreign key constraints-- dynamic queriesmethods and where clauses for dynamic queries that build iciql objects-- DROPsyntax to drop a table or view BETWEENsyntax for specifying a BETWEEN x AND y clause-- diff --git a/src/site/model_classes.mkd b/src/site/model_classes.mkd index 8fedf18..ea91bb0 100644 --- a/src/site/model_classes.mkd +++ b/src/site/model_classes.mkd @@ -113,35 +113,35 @@ You may specify default values for an *@IQColumn* by either: 1. specifying the default value string within your annotation
**NOTE:**
The annotated default value always takes priority over a field default value. -%BEGINCODE% +---JAVA--- // notice the single ticks! @IQColumn(defaultValue="'2000-01-01 00:00:00'") Date myDate; -%ENDCODE% +---JAVA--- 2. setting a default value on the field
**NOTE:**
Primitive types have an implicit default value of *0* or *false*. -%BEGINCODE% +---JAVA--- @IQColumn Date myDate = new Date(100, 0, 1); @IQColumn int myId; -%ENDCODE% +---JAVA--- 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% +---JAVA--- @IQColumn(autoIncrement = true) int myId; -%ENDCODE% +---JAVA--- Because primitive types have implicit default values, this field will be excluded from an INSERT statement if its value is 0. Iciql can not differentiate an implicit/uninitialized 0 from a explicitly assigned 0. ### Example Annotated Model -%BEGINCODE% +---JAVA--- import com.iciql.Iciql.EnumType; import com.iciql.Iciql.IQColumn; import com.iciql.Iciql.IQEnum; @@ -188,7 +188,59 @@ public class Product { // default constructor } } -%ENDCODE% +---JAVA--- + +### Foreign Keys + +---JAVA--- +@IQTable(name = "AnnotatedProduct", primaryKey = "id") +@IQIndexes({ @IQIndex({ "name", "cat" }), @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") }) +@IQContraintForeignKey( + foreignColumns= { "cat" }, + referenceName = "AnnotatedCategory", + referenceColumns = { "categ" }, + deleteType = ConstraintDeleteType.CASCADE +) +public class ProductAnnotationOnlyWithForeignKey { + + public String unmappedField; + + @IQColumn(name = "id", autoIncrement = true) + public Long productId; + + @IQColumn(name = "cat", length = 15, trim = true) + public String category; + + @IQColumn(name = "name", length = 50) + public String productName; + + @SuppressWarnings("unused") + @IQColumn + private Double unitPrice; + + @IQColumn + private Integer unitsInStock; +} +---JAVA--- + +### Views with Field Constraints +---JAVA--- +@IQView(name = "AnnotatedProductView", tableName = "AnnotatedProduct") +public class ProductView { + + public String unmappedField; + + @IQColumn(name = "id", autoIncrement = true) + @IQConstraint("this <= 7 AND this > 2") + public Long productId; + @IQColumn(name = "name") + public String productName; + + public String toString() { + return productName + " (" + productId + ")"; + } +} +---JAVA--- ## Interface Configuration Alternatively, you may map your model classes using the interface approach by implementing the `com.iciql.Iciql` interface. @@ -231,26 +283,26 @@ 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% +---JAVA--- Date myDate; public void defineIQ() { // notice the single ticks! Define.defaultValue(myDate, "'2000-01-01 00:00:00'"); } -%ENDCODE% +---JAVA--- 2. setting a default value on the field
**NOTE:**
Primitive types have an implicit default value of *0* or *false*. -%BEGINCODE% +---JAVA--- Date myDate = new Date(100, 0, 1); int myId; -%ENDCODE% +---JAVA--- ### Example Interface Model -%BEGINCODE% +---JAVA--- import com.iciql.Iciql; import com.iciql.Iciql.IQIgnore; @@ -276,8 +328,7 @@ public class Product implements Iciql { com.iciql.Define.index(productName, category); } } -%ENDCODE% - +---JAVA--- ## POJO (Plain Old Java Object) Configuration @@ -309,14 +360,14 @@ You may specify a default value on the field. **NOTE:**
Primitive types have an implicit default value of *0* or *false*. -%BEGINCODE% +---JAVA--- Date myDate = new Date(100, 0, 1); int myId; -%ENDCODE% +---JAVA--- ### Example POJO Model -%BEGINCODE% +---JAVA--- import com.iciql.Iciql.IQIgnore; public class Product { @@ -332,4 +383,4 @@ public class Product { public Product() { } } -%ENDCODE% \ No newline at end of file +---JAVA--- \ No newline at end of file diff --git a/src/site/tools.mkd b/src/site/tools.mkd index 6d8c348..8c02a12 100644 --- a/src/site/tools.mkd +++ b/src/site/tools.mkd @@ -20,16 +20,16 @@ If you do not have or do not want to annotate your existing model classes, you c Iciql can validate your model classes against your database to ensure that your models are optimally defined and are consistent with the current table and index definitions. Each `com.iciql.ValidationRemark` returned by the validation has an associated level from the following enum: -%BEGINCODE% +---JAVA--- public static enum Level { CONSIDER, WARN, ERROR; } -%ENDCODE% +---JAVA--- A typical validation may output recommendations for adjusting a model field annotation such as setting the *maxLength* of a string to match the length of its linked VARCHAR column. ### Sample Model Validation using JUnit 4 -%BEGINCODE% +---JAVA--- import static org.junit.Assert.assertTrue; import java.sql.SQLException; @@ -92,4 +92,4 @@ public class ValidateModels { System.out.println(message); } } -%ENDCODE% \ No newline at end of file +---JAVA--- \ No newline at end of file diff --git a/src/site/usage.mkd b/src/site/usage.mkd index 7b9d89d..b1f4c48 100644 --- a/src/site/usage.mkd +++ b/src/site/usage.mkd @@ -15,7 +15,7 @@ Use one of the static utility methods to instantiate a Db instance: You compose your statements using the builder pattern where each method call returns an object that is used to specify the next part of the statement. Through clever usage of generics, pioneered by the original JaQu project, compile-time safety flows through the statement. -%BEGINCODE% +---JAVA--- Db db = Db.open("jdbc:h2:mem:", "sa", "sa"); db.insertAll(Product.getList()); db.insertAll(Customer.getList()); @@ -31,7 +31,7 @@ for (Product product : restock) { where(p.productId).is(product.productId).update(); } db.close(); -%ENDCODE% +---JAVA--- Please see the [examples](examples.html) page for more code samples. @@ -41,9 +41,9 @@ Iciql gives you compile-time type-safety, but it becomes inconvenient if your de #### Where String Fragment Approach This approach is a mixture of iciql and jdbc. It uses the traditional prepared statement *field=?* tokens with iciql compile-time model class type checking. There is no field token type-safety checking. -%BEGINCODE% +---JAVA--- List restock = db.from(p).where("unitsInStock=? and productName like ? order by productId", 0, "Chef%").select(); -%ENDCODE% +---JAVA--- #### Db.executeQuery Approaches There may be times when the hybrid approach is still too restrictive and you'd prefer to write straight SQL. You can do that too and use iciql to build objects from your ResultSet, but be careful: @@ -51,24 +51,62 @@ There may be times when the hybrid approach is still too restrictive and you'd p 1. Make sure to _select *_ in your query otherwise db.buildObjects() will throw a RuntimeException 2. There is no model class type checking nor field type checking. -%BEGINCODE% +---JAVA--- List allProducts = db.executeQuery(Product.class, "select * from products"); List restock = db.executeQuery(Product.class, "select * from products where unitsInStock=?", 0); // parameterized query which can be cached and re-used later String q = db.from(p).where(p.unitsInStock).isParameter().toSQL(); List restock = db.executeQuery(Product.class, q, 0); - -%ENDCODE% +---JAVA--- Or if you want access to the raw *ResultSet* before building your model object instances... -%BEGINCODE% +---JAVA--- ResultSet rs = db.executeQuery("select * from products"); List allProducts = db.buildObjects(Product.class, rs); // This method ensures the creating statement is closed JdbcUtils.closeSilently(rs, true); -%ENDCODE% +---JAVA--- + +### Read-only Views + +View model classes can inherit their field definitions from a parent table model class. + +---JAVA--- +@IQView(name = "AnnotatedProductViewInherited", inheritColumns = true) +public class ProductViewFromQuery extends ProductAnnotationOnly { + + public String unmappedField; + + @IQColumn(name = "id") + public Long productId; + + public String toString() { + return productName + " (" + productId + ")"; + } +} +---JAVA--- + +You can then create or replace the VIEW in the database using a fluent syntax. + +---JAVA--- +// create view from query +ProductAnnotationOnly product = new ProductAnnotationOnly(); +db.from(product).where(product.productId).exceeds(2L) + .and(product.productId).atMost(7L).createView(ProductViewFromQuery.class); + +// select from the created view +ProductViewFromQuery view = new ProductViewFromQuery(); +List products = db.from(view).select(); + +// replace the view +db.from(product).where(product.productId).exceeds(3L) + .and(product.productId).atMost(8L).replaceView(ProductViewFromQuery.class); + +// select from the replaced view +products = db.from(view).select(); +---JAVA--- ### Natural Syntax @@ -80,7 +118,7 @@ This works by decompiling a Java expression, at runtime, to an SQL condition. T A proof-of-concept decompiler is included, but is incomplete. The proposed syntax is: -%BEGINCODE% +---JAVA--- long count = db.from(co). where(new Filter() { public boolean where() { return co.id == x @@ -92,7 +130,7 @@ long count = db.from(co). && co.time.before(java.sql.Time.valueOf("23:23:23")); } }).selectCount(); -%ENDCODE% +---JAVA--- ### JDBC Statements, ResultSets, and Exception Handling @@ -105,23 +143,23 @@ Iciql does not throw any [checked exceptions](http://en.wikipedia.org/wiki/Excep Iciql provides a mechanism to log generated statements and warnings to the console, to SLF4J, or to your own logging framework. Exceptions are not logged using this mechanism; exceptions are wrapped and rethrown as `IciqlException`, which is a RuntimeException. #### Console Logging -%BEGINCODE% +---JAVA--- IciqlLogger.activeConsoleLogger(); IciqlLogger.deactiveConsoleLogger(); -%ENDCODE% +---JAVA--- #### SLF4J Logging -%BEGINCODE% +---JAVA--- Slf4jIciqlListener slf4j = new Slf4jIciqlListener(); slf4j.setLevel(StatementType.CREATE, Level.WARN); slf4j.setLevel(StatementType.DELETE, Level.WARN); slf4j.setLevel(StatementType.MERGE, Level.OFF); IciqlLogger.registerListener(slf4j); IciqlLogger.unregisterListener(slf4j); -%ENDCODE% +---JAVA--- #### Custom Logging -%BEGINCODE% +---JAVA--- IciqlListener custom = new IciqlListener() { public void logIciql(StatementType type, String statement) { // do log @@ -129,14 +167,14 @@ IciqlListener custom = new IciqlListener() { }; IciqlLogger.registerListener(custom); IciqlLogger.unregisterListener(custom); -%ENDCODE% +---JAVA--- ## Understanding Aliases and Model Classes Consider the following example: -%BEGINCODE% +---JAVA--- Product p = new Product(); List restock = db.from(p).where(p.unitsInStock).is(0).select(); -%ENDCODE% +---JAVA--- The Product model class instance named **p** is an *alias* object. An *alias* is simply an instance of your model class that is only used to build the compile-time/runtime representation of your table. @@ -152,10 +190,10 @@ The _db.from(p)_ call reinstantiates each member field of p. Those reinstantiat Depending on your design, you might consider using a [ThreadLocal](http://download.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html) variable if you do not want to keep instantiating *alias* instances. A utility function is included for easily creating ThreadLocal variables. -%BEGINCODE% +---JAVA--- final ThreadLocal p = Utils.newThreadLocal(Product.class); db.from(p.get()).select(); -%ENDCODE% +---JAVA--- ## Best Practices @@ -166,7 +204,7 @@ db.from(p.get()).select();
Not Thread-SafeThread-Safe
-%BEGINCODE% +---JAVA--- final Product p = new Product(); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @@ -177,10 +215,10 @@ for (int i = 0; i < 5; i++) { }, "Thread-" + i); thread.start(); } -%ENDCODE% +---JAVA--- -%BEGINCODE% +---JAVA--- final ThreadLocal p = Utils.newThreadLocal(Product.class); for (int i = 0; i < 5; i++) { Thread thread = new Thread(new Runnable() { @@ -191,7 +229,7 @@ for (int i = 0; i < 5; i++) { }, "Thread-" + i); thread.start(); } -%ENDCODE% +---JAVA---
\ No newline at end of file -- 2.39.5