Rearrange readme

This commit is contained in:
Travis Burtrum 2018-09-05 23:16:36 -04:00
parent e896a66e0e
commit d85e5ce7f1

230
readme.md
View File

@ -24,6 +24,121 @@ Goals
4. Be runnable and testable inside or outside of containers easily 4. Be runnable and testable inside or outside of containers easily
5. No surprises, as little magic as possible 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<Person> getPeople(String lastName) throws SQLException; // all rows in any Collection<T> (like Set<T>, LinkedList<T> etc), T[], ResultSetIterable<T> or Stream<T> (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>(), 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<K,V>), 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<String, ?>, 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 Column to Object Mapping
------------------------ ------------------------
@ -229,121 +344,6 @@ String s = rs.getString(index);
return s == null ? null : ZoneOffset.of(s); 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<K,V>), 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<String, ?>, 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<Person> getPeople(String lastName) throws SQLException; // all rows in any Collection<T> (like Set<T>, LinkedList<T> etc), T[], ResultSetIterable<T> or Stream<T> (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>(), String.class, "Person")); // [Person{personNo=0,birthDate=null,firstName=First,lastName=Person}, Person{personNo=1,birthDate=null,firstName=Second,lastName=Person}]
}
```
TODO TODO
---- ----