Whinstone use jkniv-sqlegance to handle the query language for your database. The philosophy behind jkniv-whinstone it’s keeping the java code simple, without SQL manipulation, all SQL is handler by XML files.
The jkniv-whinstone doesn’t have section or ORM to mapping your objects, so there is no magic, to handler the automatic binding for result set the columns or the alias are used to build the POJO. For example:
select b.id, b.isbn, b.name from Book b
Bind with POJO:
public class Book { private Long id; private String isbn; private String name; // getters and setters REQUIRED }
The attribute and columns name are the same, match! More important are the getters and the setters.
Note: jkniv-whinstone doesn’t broken the objects encapsulation, always the bind is through the getter and setters methods.
But, not always the column have the same attribute name, then columns alias must be used to bind with the attribute. That means that you use Snake Case (snake_case) to bind with Camel Case (camelCase).
select b.ID, b.PUBLISHER as publisher_name from Book b
Bind with POJO:
public class Book { private Long id; private String publisherName; // getters and setters REQUIRED }
A set method matching name and type is required to bind the column and alias names, like publisher_name:
setPublisherName(String publisherName)
The jkniv-whinstone supports bind nested attributes (One-to-One relationship), this example I have a Book with one Author and an Address.
public class Book { private Long id; private String name; private Author author; // getters and setters REQUIRED } public class Author { private Long id; private String name; private Address address // getters and setters REQUIRED } public class Address { private String street; // getters and setters REQUIRED }
To populate the Book with relationship One-to-One the alias must be between quotes using dot notation to inject yours values.
select b.id, b.name ,a.ID as "author.id" ,a.NAME as "author.name" ,ad.STREET as "author.address.street" from book b inner join author a on a.id = b.author_id left join address ad on ad.id = a.address_id
Note: all bind happen using JavaBeans Conventions get/set/is methods, encapsulation is safe. A default constructor is required too (the exception it’s for JPA native queries where the bind for native queries happen into Constructor).
Defines a association with one-to-many multiplicity, jkniv-whinstone can populate this relationship using a special tag <one-to-many> in select element.
For example, consider when a Author having zero or more Books:
public class Author { private Long id; private String name; private List<Book> books; // getters and setters REQUIRED }
The plain query result could be translated to one-to-many relationship using the attribute element groupBy and <one-to-many> tag element:
<select id="authors" returnType="org.acme.model.Author" groupBy="id"> <one-to-many property="books" typeOf="org.acme.model.Book"/> select a.ID, a.NAME, b.ID as "books.id", b.ISBN as "books.isbn", b.NAME as "books.name" from AUTHOR a inner join BOOK b on b.AUTHOR_ID = a.ID order by a.name, b.name </select>
Bellow we have a copy of the code using JDBC PreparedStatement
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update COFFEES set SALES = ? where COF_NAME = ?"; String updateStatement = "update COFFEES set TOTAL = TOTAL + ? where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales != null) { updateSales.close(); } if (updateTotal != null) { updateTotal.close(); } con.setAutoCommit(true); } }
The same functionality could be wrote like this using jkniv-whinstone-jdbc:
XML queries:
<!-- (1) --> <update id="updateSales"> update COFFEES <set> <if test="sales != null"> SALES = :sales </if> <if test="total != null"> TOTAL = TOTAL + :total </if> </set> where COF_NAME = :coffee </update>
Java code:
public void updateCoffeeSales(Collection<Map<String, Integer>> salesForWeek) { Queryable salesQuery = QueryFactory.of("updateSales", salesForWeek); // (2) try { repository.getTransaction().begin(); // (3) repository.update(salesQuery); // (4) repository.getTransaction().commit(); // (5) } catch (RepositoryException e ) { // (6) System.err.print("Transaction is being rolled back"); repository.getTransaction().rollback(); // (7) } }
OK, java looks like more readable. Of course Collection<Map> could be replace to Collection<Sale> for a better design.
The class ItemOrderByNameDesc order the items descending implementing the Comparator interface.
public class ItemOrderByNameDesc implements Comparator<Item> { @Override public int compare(Item o1, Item o2) { return o2.getName().compareTo(o1.getName()); } }
To use ItemOrderByNameDesc to order the result set after the database
Queryable queryable = QueryFactory.builder() .sorter(new ItemOrderByNameDesc()) .build("getAllItems"); List<Item> items = repositoryDerby.list(queryable); assertThat(items.get(9).getName(), is("A")); assertThat(items.get(8).getName(), is("B")); assertThat(items.get(7).getName(), is("C")); assertThat(items.get(6).getName(), is("D")); assertThat(items.get(5).getName(), is("E")); assertThat(items.get(4).getName(), is("F")); assertThat(items.get(3).getName(), is("G")); assertThat(items.get(2).getName(), is("H")); assertThat(items.get(1).getName(), is("I")); assertThat(items.get(0).getName(), is("J"));
This feature is most useful when we are using NoSQL database where some queries is hard.
The interface class Filter allow to remove data elements from result set. When the data doesn’t match with isEqual method it will be removed.
public interface Filter<T> { boolean isEqual(T item); } public class ItemFilter implements Filter<Item> { Float price; public ItemFilter(Float price) { this.price = price; } @Override public boolean isEqual(Item item) { return item.getPrice() >= 150; } }
To use Filter to remove the data element from result set:
@Test public void whenSelectItemsWithJavaOrderBy() { Queryable queryable = QueryFactory.builder() .filter(new ItemFilter(150F)) .build("getAllItems"); List<Item> items = repositoryDerby.list(queryable); Float MIN_PRICE = 150F; assertThat(items.get(0).getPrice(), greaterThanOrEqualTo(MIN_PRICE)); assertThat(items.get(1).getPrice(), greaterThanOrEqualTo(MIN_PRICE)); assertThat(items.get(2).getPrice(), greaterThanOrEqualTo(MIN_PRICE)); assertThat(items.get(3).getPrice(), greaterThanOrEqualTo(MIN_PRICE)); assertThat(items.get(4).getPrice(), greaterThanOrEqualTo(MIN_PRICE)); }
This feature is most useful when we are using NoSQL database where some queries is hard.