From d85e5ce7f1ea28f77cc6cb84d0b04d1aa2784fbb Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Wed, 5 Sep 2018 23:16:36 -0400 Subject: [PATCH] Rearrange readme --- readme.md | 230 +++++++++++++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/readme.md b/readme.md index 6ed77a4..48860b1 100644 --- a/readme.md +++ b/readme.md @@ -24,6 +24,121 @@ Goals 4. Be runnable and testable inside or outside of containers easily 5. No surprises, as little magic as possible +JdbcMapper +---------- + +Write an interface or abstract class with methods that make sense for accessing your database, annotate the methods with +SQL, and on compilation an annotation processor will generate the required java.sql API code to execute your query and +return what you wanted. This code is guaranteed to be the fastest code possible because hand written code would look +the same, just more error prone and harder to maintain. The annotation processor also checks that the SQL queries are +valid, have all the right bind parameters, and can bind the result columns to all the correct fields on the result object. +If anything is wrong it's a compile error pointing you to the exact problem. + +Example: + +```java +@JdbcMapper.Mapper(jndiName = "java:/comp/env/jdbc/testPool") // omit jndiName and you must send in a java.sql.Connection +public interface PersonDAO extends Closeable { // Closeable is optional but must have a 'void close()' method to use cachePreparedStatements or jndiName + + @JdbcMapper.SQL("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)") + void createTablePerson(); + + @JdbcMapper.SQL("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES ({personNo}, {birthDate}, {firstName}, {lastName})") + int insertPerson(long personNo, Date birthDate, String firstName, String lastName); + + @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") + int setFirstName(String firstName, long personNo); // returning int will return number of rows modified, can also return void + + @JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}") + String getFirstName(long personNo) throws SQLException; // can map directly to simple types + + @JdbcMapper.SQL("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = {personNo}") + Person getPerson(long personNo) throws SQLException; // or multiple fields, set methods, or constructor parameters on a POJO + + @JdbcMapper.SQL("SELECT person_no, first_name, last_name, birth_date FROM person WHERE last_name = {lastName}") + List getPeople(String lastName) throws SQLException; // all rows in any Collection (like Set, LinkedList etc), T[], ResultSetIterable or Stream (java8+) works too +} + +// code: +try(PersonDAO personDao = JdbcMapperFactory.create(PersonDAO.class)) { + personDao.createTablePerson(); + System.out.println(personDao.insertPerson(0, null, "First", "Person")); // 1 + System.out.println(personDao.insertPerson(1, null, "First", "Person")); // 1 + System.out.println(personDao.setFirstName("Second", 1)); // 1 + + System.out.println(personDao.getFirstName(0)); // First + System.out.println(personDao.getFirstName(1)); // Second + + System.out.println(personDao.getPerson(0)); // Person{personNo=0,birthDate=null,firstName=First,lastName=Person} + System.out.println(personDao.getPerson(1)); // Person{personNo=1,birthDate=null,firstName=Second,lastName=Person} + + System.out.println(personDao.getPeople("Person")); // [Person{personNo=0,birthDate=null,firstName=First,lastName=Person}, Person{personNo=1,birthDate=null,firstName=Second,lastName=Person}] +} +``` + +QueryMapper +----------- + +Need to generate SQL dynamically or just execute some queries quickly and easily? Mapping is done using reflection in +ResultSetMapper or code is dynamically generated, compiled, instantiated, and cached at runtime to do the mapping using +CompilingResultSetMapper. + +Example: + +```java +// CompilingResultSetMapper is an alternative to ResultSetMapper, default is ResultSetMapper +try(QueryMapper qm = new QueryMapper("java:/comp/env/jdbc/testPool", new ResultSetMapper())) { // or send in java.sql.Connection + // executeUpdate returns int + qm.executeUpdate("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)"); + System.out.println(qm.executeUpdate("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES (?, ?, ?, ?)", 0, null, "First", "Person")); // 1 + System.out.println(qm.executeUpdate("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES (?, ?, ?, ?)", 1, null, "First", "Person")); // 1 + System.out.println(qm.executeUpdate("UPDATE person SET first_name = ? WHERE person_no = ?", "Second", 1)); // 1 + + // can map directly to simple types + System.out.println(qm.toObject("SELECT first_name FROM person WHERE person_no = ?", String.class, 0)); // First + System.out.println(qm.toObject("SELECT first_name FROM person WHERE person_no = ?", String.class, 1)); // Second + + // or multiple fields, set methods, or constructor parameters on a POJO + System.out.println(qm.toObject("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = ?", String.class, 0)); // Person{personNo=0,birthDate=null,firstName=First,lastName=Person} + System.out.println(qm.toObject("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = ?", String.class, 1)); // Person{personNo=1,birthDate=null,firstName=Second,lastName=Person} + + // instead of toCollection can use toList, toArray, toResultSetIterable, toStream (java8+) + System.out.println(qm.toCollection("SELECT person_no, first_name, last_name, birth_date FROM person WHERE last_name = ?", new ArrayList(), String.class, "Person")); // [Person{personNo=0,birthDate=null,firstName=First,lastName=Person}, Person{personNo=1,birthDate=null,firstName=Second,lastName=Person}] +} +``` + +ResultSet (multiple rows) to Object/Collection Mapping +-------------------------------------- + +todo: document + +Row to Object Mapping +--------------------- + +In cases of only one column being returned from the query (or two in the case of Map), the same simple +column -> Object mapping described below will take place. If a more complex object is requested, column names or +indices are used to decide how to construct/map the object. + +A single row can be represented by 3 main Objects: + + 1. Array, where each column is mapped by index, starting at 0, array type of course determines the type returned + 2. Map, where each column is mapped by name as key, and column value as value, mapped according to type + * consider using the supplied com.moparisthebest.jdbc.util.CaseInsensitiveHashMap where case is ignored for keys + 3. Custom class Object, which attempts many different ways to map all returned columns to the class, if one of these + is not a perfect match, an exception is thrown at runtime with QueryMapper, and a compile-time error happens with + JdbcMapper. This is an ordered list of how rows are mapped to class objects: + 1. If the class has a public constructor that takes a single java.sql.ResultSet parameter and nothing else, each + row is sent in to create a new object, nothing else is done. + 2. If the class has a public constructor that takes the same number of arguments as columns returned, and all names + match (order does not matter), this constructor is used. This method has some requirements though: + * Java 8+ only + * requires -parameters argument to javac for runtime with QueryMapper, or compiling against classes without + source with JdbcMapper + * Beware Java 8 only Bug ID [JDK-8191074](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8191074), + fixed in Java 9+ but will not be backported to 8 + +todo: explain how rows are mapped to POJOs + Column to Object Mapping ------------------------ @@ -229,121 +344,6 @@ String s = rs.getString(index); return s == null ? null : ZoneOffset.of(s); ``` -Row to Object Mapping ---------------------- - -In cases of only one column being returned from the query (or two in the case of Map), the same simple -column -> Object mapping described above will take place. If a more complex object is requested, column names or -indices are used to decide how to construct/map the object. - -A single row can be represented by 3 main Objects: - - 1. Array, where each column is mapped by index, starting at 0, array type of course determines the type returned - 2. Map, where each column is mapped by name as key, and column value as value, mapped according to type - * consider using the supplied com.moparisthebest.jdbc.util.CaseInsensitiveHashMap where case is ignored for keys - 3. Custom class Object, which attempts many different ways to map all returned columns to the class, if one of these - is not a perfect match, an exception is thrown at runtime with QueryMapper, and a compile-time error happens with - JdbcMapper. This is an ordered list of how rows are mapped to class objects: - 1. If the class has a public constructor that takes a single java.sql.ResultSet parameter and nothing else, each - row is sent in to create a new object, nothing else is done. - 2. If the class has a public constructor that takes the same number of arguments as columns returned, and all names - match (order does not matter), this constructor is used. This method has some requirements though: - * Java 8+ only - * requires -parameters argument to javac for runtime with QueryMapper, or compiling against classes without - source with JdbcMapper - * Beware Java 8 only Bug ID [JDK-8191074](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8191074), - fixed in Java 9+ but will not be backported to 8 - -todo: explain how rows are mapped to POJOs - -ResultSet (multiple rows) to Object/Collection Mapping --------------------------------------- - -todo: document - -JdbcMapper ----------- - -Write an interface or abstract class with methods that make sense for accessing your database, annotate the methods with -SQL, and on compilation an annotation processor will generate the required java.sql API code to execute your query and -return what you wanted. This code is guaranteed to be the fastest code possible because hand written code would look -the same, just more error prone and harder to maintain. The annotation processor also checks that the SQL queries are -valid, have all the right bind parameters, and can bind the result columns to all the correct fields on the result object. -If anything is wrong it's a compile error pointing you to the exact problem. - -Example: - -``` -@JdbcMapper.Mapper(jndiName = "java:/comp/env/jdbc/testPool") // omit jndiName and you must send in a java.sql.Connection -public interface PersonDAO extends Closeable { // Closeable is optional but must have a 'void close()' method to use cachePreparedStatements or jndiName - - @JdbcMapper.SQL("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)") - void createTablePerson(); - - @JdbcMapper.SQL("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES ({personNo}, {birthDate}, {firstName}, {lastName})") - int insertPerson(long personNo, Date birthDate, String firstName, String lastName); - - @JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}") - int setFirstName(String firstName, long personNo); // returning int will return number of rows modified, can also return void - - @JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}") - String getFirstName(long personNo) throws SQLException; // can map directly to simple types - - @JdbcMapper.SQL("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = {personNo}") - Person getPerson(long personNo) throws SQLException; // or multiple fields, set methods, or constructor parameters on a POJO - - @JdbcMapper.SQL("SELECT person_no, first_name, last_name, birth_date FROM person WHERE last_name = {lastName}") - List getPeople(String lastName) throws SQLException; // all rows in any Collection (like Set, LinkedList etc), T[], ResultSetIterable or Stream (java8+) works too -} - -// code: -try(PersonDAO personDao = JdbcMapperFactory.create(PersonDAO.class)) { - personDao.createTablePerson(); - System.out.println(personDao.insertPerson(0, null, "First", "Person")); // 1 - System.out.println(personDao.insertPerson(1, null, "First", "Person")); // 1 - System.out.println(personDao.setFirstName("Second", 1)); // 1 - - System.out.println(personDao.getFirstName(0)); // First - System.out.println(personDao.getFirstName(1)); // Second - - System.out.println(personDao.getPerson(0)); // Person{personNo=0,birthDate=null,firstName=First,lastName=Person} - System.out.println(personDao.getPerson(1)); // Person{personNo=1,birthDate=null,firstName=Second,lastName=Person} - - System.out.println(personDao.getPeople("Person")); // [Person{personNo=0,birthDate=null,firstName=First,lastName=Person}, Person{personNo=1,birthDate=null,firstName=Second,lastName=Person}] -} -``` - -QueryMapper ------------ - -Need to generate SQL dynamically or just execute some queries quickly and easily? Mapping is done using reflection in -ResultSetMapper or code is dynamically generated, compiled, instantiated, and cached at runtime to do the mapping using -CompilingResultSetMapper. - -Example: - -``` -// CompilingResultSetMapper is an alternative to ResultSetMapper, default is ResultSetMapper -try(QueryMapper qm = new QueryMapper("java:/comp/env/jdbc/testPool", new ResultSetMapper())) { // or send in java.sql.Connection - // executeUpdate returns int - qm.executeUpdate("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)"); - System.out.println(qm.executeUpdate("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES (?, ?, ?, ?)", 0, null, "First", "Person")); // 1 - System.out.println(qm.executeUpdate("INSERT INTO person (person_no, birth_date, last_name, first_name) VALUES (?, ?, ?, ?)", 1, null, "First", "Person")); // 1 - System.out.println(qm.executeUpdate("UPDATE person SET first_name = ? WHERE person_no = ?", "Second", 1)); // 1 - - // can map directly to simple types - System.out.println(qm.toObject("SELECT first_name FROM person WHERE person_no = ?", String.class, 0)); // First - System.out.println(qm.toObject("SELECT first_name FROM person WHERE person_no = ?", String.class, 1)); // Second - - // or multiple fields, set methods, or constructor parameters on a POJO - System.out.println(qm.toObject("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = ?", String.class, 0)); // Person{personNo=0,birthDate=null,firstName=First,lastName=Person} - System.out.println(qm.toObject("SELECT person_no, first_name, last_name, birth_date FROM person WHERE person_no = ?", String.class, 1)); // Person{personNo=1,birthDate=null,firstName=Second,lastName=Person} - - // instead of toCollection can use toList, toArray, toResultSetIterable, toStream (java8+) - System.out.println(qm.toCollection("SELECT person_no, first_name, last_name, birth_date FROM person WHERE last_name = ?", new ArrayList(), String.class, "Person")); // [Person{personNo=0,birthDate=null,firstName=First,lastName=Person}, Person{personNo=1,birthDate=null,firstName=Second,lastName=Person}] -} -``` - TODO ----