Java Database Toolbox

Ant <sql> task

<macrodef name="run-sql">
  <attribute name="userid" default="${jdbc.userid}"/>
  <attribute name="password" default="${jdbc.password}"/>
  <attribute name="file"/>
  <sequential>
    <sql driver="${jdbc.driver}"
         url="${jdbc.url}"
         userid="@{userid}"
         password="@{password}"
         delimiter=";"
         classpathref="classpath.local"
         >
      <fileset dir="src/main/sql/ddl" includes="@{file}"/>
    </sql>
  </sequential>
</macrodef>

Ant <sql> task

<macrodef name="run-sql">
  <attribute name="userid" default="${jdbc.userid}"/>
  <attribute name="password" default="${jdbc.password}"/>
  <attribute name="file"/>
  <sequential>
    <sql driver="${jdbc.driver}"
         url="${jdbc.url}"
         userid="@{userid}"
         password="@{password}"
         delimiter=";"
         classpathref="classpath.local"
         >
      <fileset dir="src/main/sql/ddl" includes="@{file}"/>
    </sql>
  </sequential>
</macrodef>

<target name="create-test-schema" description="Create 'birt' user." >
  <run-sql file="create_schema.sql"
    userid="${jdbc.system.userid}"
    password="${jdbc.system.password}"/>
</target>

<target name="run-ddl" description="Create tables etc." >
  <run-sql file="create_classicmodels.sql"/>
</target>

Using custom Ant <sql> tasks

--PASS_1
CREATE TABLE Payments (
  customerNumber INTEGER NOT NULL,
  checkNumber VARCHAR2(50) NOT NULL,
  paymentDate DATE NOT NULL,
  amount NUMBER NOT NULL,
  PRIMARY KEY (customerNumber, checkNumber)
);

--PASS_2
ALTER TABLE Payments ADD CONSTRAINT Payments_Customer_fk
  FOREIGN KEY (CustomerNumber)
  REFERENCES Customers(CustomerNumber);

Writing custom Ant <sql> tasks

Subclass JDBCTask...

public class MyJDBCTask extends JDBCTask {
  // ...
  public void execute() throws BuildException {
    Connection connection = getConnection();
    try {
      // execute SQL on Connection ...
    }
    finally {
      // close connection
    }
  }
}

And define it in an antlib.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<antlib>
  <taskdef name="mytask" classname="nzoug.ant.MyJDBCTask"/>
  <!-- ... -->
</antlib>

Writing custom Ant <sql> tasks

And use your task in an Ant script...

<project name="..." basedir="." default="help"
  xmlns:nzoug="antlib:org.nzoug.ant">

  <target name="define-tasks">
    <taskdef resource="org/nzoug/ant/antlib.xml"
      uri="antlib:org.nzoug.ant"
      classpathref="classpath.compile"/>
  </target>
  ...
  <target name="mytask" depends="create-ddl, define-tasks">
    <nzoug:mytask driver="${jdbc.driver}"
      url="${jdbc.url}"
      userid="${jdbc.userid}"
      password="${jdbc.password}">
      <classpath refid="classpath.compile"/>
    </nzoug:mytask>
  </target>
</project>

Ant build with SQL DDL

SchemaSpy

Running SchemaSpy from Ant

<target name="schemaspy" description="Run SchemaSpy">
  <echo message="Running SchemaSpy..." />
  <java classname="net.sourceforge.schemaspy.Main" fork="true">
    <classpath refid="classpath.local"/>
    <arg line="-t ${schemaspy.type}"/>
    <arg line="-host ${jdbc.host}"/>
    <arg line="-port ${jdbc.port}"/>
    <arg line="-db ${jdbc.db}"/>
    <arg line="-u ${jdbc.userid}"/>
    <arg line="-p ${jdbc.password}"/>
    <arg line="-s ${jdbc.schema}"/>
    <arg line="-o dist"/>
    <arg line="-hq"/>
  </java>
</target>

SchemaSpy Options

Implementing Data Access

public interface CustomerDao {
  Customer get(long id);
  List<Customer> findByName(String name);
  List<Customer> findWithOrdersOrPaymentsInMonth(int year, int month);
  List<Customer> findForFilter(CustomerFilter filter);
}

DAOs: The Wrong Way

public Customer get(long id) {
  Connection connection = null;
  try {
    connection = dataSource.getConnection();
    PreparedStatement ps = null;
    try {
      ps = connection.prepareStatement("SELECT * FROM customers WHERE customernumber = ?");
      ps.setLong(1, id);
      ResultSet rs = null;
      try {
        rs = ps.executeQuery();
        if (rs.next()) {
          Customer result = new Customer();
          result.setCustomerName(rs.getString("customername"));
          result.setContactLastName(rs.getString("contactlastname"));
          result.setContactFirstName(rs.getString("contactfirstname"));
          result.setPhone(rs.getString("phone"));
          result.setAddressLine1(rs.getString("addressline1"));
          result.setAddressLine2(rs.getString("addressline2"));
          result.setCity(rs.getString("city"));
          result.setState(rs.getString("state"));
          result.setPostalCode(rs.getString("postalcode"));
          result.setCountry(rs.getString("country"));
          result.setSalesRep(null); // TODO
          result.setCreditLimit(rs.getBigDecimal("creditlimit"));
          return result;
        }
        return null;
      }
      catch (SQLException rsEx) {
        throw new RuntimeException("SQLException running query", rsEx);
      }
      finally {
        if (rs != null) {
          try {
            rs.close();
          }
          catch (SQLException ignore) {}
          }
      }
    }
    catch (SQLException psEx) {
      throw new RuntimeException("SQLException preparing statement", psEx);
    }
    finally {
      if (ps != null) {
        try {
          ps.close();
        }
        catch (SQLException ignore) {}
      }
    }
  }
  catch (SQLException conEx) {
    throw new RuntimeException("SQLException opening connection", conEx);
  }
  finally {
    if (connection != null) {
      try {
        connection.close();
      }
      catch (SQLException ignore) {}
    }
  }
}

Spring JDBC

public Customer get(long id) {
  return getJdbcTemplate().queryForObject(
    "SELECT * FROM customers WHERE customernumber = ?",
    new CustomerRowMapper(),
    id
  );
}
private static class CustomerRowMapper implements RowMapper<Customer> {
  public Customer mapRow(ResultSet rs, int i) throws SQLException {
    Customer result = new Customer();
    result.setCustomerNumber(rs.getLong("customernumber"));
    result.setCustomerName(rs.getString("customername"));
    result.setContactLastName(rs.getString("contactlastname"));
    result.setContactFirstName(rs.getString("contactfirstname"));
    result.setPhone(rs.getString("phone"));
    result.setAddressLine1(rs.getString("addressline1"));
    result.setAddressLine2(rs.getString("addressline2"));
    result.setCity(rs.getString("city"));
    result.setState(rs.getString("state"));
    result.setPostalCode(rs.getString("postalcode"));
    result.setCountry(rs.getString("country"));
    result.setCreditLimit(rs.getBigDecimal("creditlimit"));
    return result;
  }
}

Spring JDBC

Spring JDBC: Reuse RowMapper

public Customer get(long id) {
  return getJdbcTemplate().queryForObject(
    "SELECT * FROM customers WHERE customernumber = ?",
    new CustomerRowMapper(),
    id
  );
}
public List<Customer> findByName(String name) {
  return getJdbcTemplate().query(
    "SELECT * FROM customers WHERE UPPER(customername) LIKE ? " +
    "ORDER BY customernumber",
    new CustomerRowMapper(),
    "%" + name.toUpperCase() + "%"
  );
}

Spring JDBC: SimpleJdbcTemplate

public interface SimpleJdbcOperations {
  // ...
	<T> T queryForObject(String sql, Class<T> requiredType, Object... args)
	<T> T queryForObject(String sql, Class<T> requiredType, Map<String, ?> args)
	<T> T queryForObject(String sql, Class<T> requiredType, SqlParameterSource args)
	<T> T queryForObject(String sql, RowMapper<T> rm, Object... args)
	<T> T queryForObject(String sql, RowMapper<T> rm, Map<String, ?> args)
	<T> T queryForObject(String sql, RowMapper<T> rm, SqlParameterSource args)
	<T> List<T> query(String sql, RowMapper<T> rm, Object... args)
	<T> List<T> query(String sql, RowMapper<T> rm, Map<String, ?> args)
	<T> List<T> query(String sql, RowMapper<T> rm, SqlParameterSource args)
  // ...
}
public interface SqlParameterSource {
  boolean hasValue(String paramName);
  Object getValue(String paramName) throws IllegalArgumentException;
  int getSqlType(String paramName);
  String getTypeName(String paramName);
}

Example:

new MapSqlParameterSource("year", year).addValue("month", month)

Spring JDBC: Named Parameters

public List<Customer> findWithOrdersOrPaymentsInMonth(int year, int month) {
  return new NamedParameterJdbcTemplate(getDataSource()).query(
    "SELECT * " +
    "FROM customers c " +
    "WHERE EXISTS (" +
      "SELECT * " +
      "FROM orders o " +
      "WHERE o.customernumber = c.customernumber " +
        "AND EXTRACT(YEAR FROM orderdate) = :year " +
        "AND EXTRACT(MONTH FROM orderdate) = :month" +
    ") " +
      "OR  EXISTS (" +
      "SELECT * " +
      "FROM payments p " +
      "WHERE p.customernumber = c.customernumber " +
        "AND EXTRACT(YEAR FROM paymentdate) = :year " +
        "AND EXTRACT(MONTH FROM paymentdate) = :month" +
    ")" +
    "ORDER BY customernumber",
    new MapSqlParameterSource("year", year).addValue("month", month),
    new CustomerRowMapper()
  );
}

Spring JDBC: Strengths

Spring JDBC: Weaknesses

Java Persistence API

@Entity
@Table(name = "Customers")
public class Customer {
  private long customerNumber;
  private String customerName;

  @Id
  public long getCustomerNumber() {
    return customerNumber;
  }
  // ...
  public String getCustomerName() {
    return customerName;
  }
  // ...
  @ManyToOne
  @JoinColumn(name = "SalesRepEmployeeNumber")
  public Employee getSalesRep() {
    return salesRep;
  }
  // ...
}

Persistence Unit

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  version="1.0">
  <persistence-unit name="classicmodelsPU">
  <class>classicmodels.domain.Office</class>
  <class>classicmodels.domain.Employee</class>
  <class>classicmodels.domain.Customer</class>
  <!-- ... -->
  <properties>
    <property name="hibernate.show_sql" value="false"/>
    <property name="hibernate.dialect"
      value="org.hibernate.dialect.Oracle10gDialect"/>
  </properties>
  </persistence-unit>
</persistence>
EntityManagerFactory entityManagerFactory =
  Persistence.createEntityManagerFactory("classicmodelsPU");
EntityManager em = entityManagerFactory.createEntityManager();
public Customer get(long id) {
  return em.find(Customer.class, id);
}

JPA Criteria Queries

How do we add arbitrary conditions to a query?

public interface CustomerDao {
  Customer get(long id);
  List<Customer> findByName(String name);
  List<Customer> findWithOrdersOrPaymentsInMonth(int year, int month);
  List<Customer> findForFilter(CustomerFilter filter);
}

The CustomerFilter interface:

public interface CustomerFilter {
  String getName();
  String getPhone();
  String getAddress();
  BigDecimal getMinimumCreditLimit();
  BigDecimal getMaximumCreditLimit();
}
... to do this with JQL or SQL involves some really yucky string manipulation ...

JPA 2 Criteria Query API

public List<Customer> findForFilter(final CustomerFilter filter) {
  CriteriaBuilder builder = em.getCriteriaBuilder();
  CriteriaQuery<Customer> criteriaQuery = builder.createQuery(Customer.class);
  Root<Customer> customer = criteriaQuery.from(Customer.class);
  List<Predicate> conditions = new ArrayList<Predicate>();
  if (filter.getName() != null && !filter.getName().equals("")) {
    String name = "%" + filter.getName().toUpperCase() + "%";
    conditions.add(builder.or(
      builder.like(builder.upper(customer.get("customerName").as(String.class)), name),
      builder.like(builder.upper(customer.get("contactLastName").as(String.class)), name),
      builder.like(builder.upper(customer.get("contactFirstName").as(String.class)), name)
    ));
  }
  // similar for address and phone ...
  if (filter.getMinimumCreditLimit() != null) {
    BigDecimal minimumCreditLimit = filter.getMinimumCreditLimit();
    conditions.add(
      builder.gt(customer.get("creditLimit").as(Number.class), minimumCreditLimit)
    );
  }
  // similar for maximumCreditLimit ...
  Predicate predicate = builder.and(conditions.toArray(new Predicate[conditions.size()]));
  criteriaQuery.where(predicate);
  TypedQuery<Customer> query = em.createQuery(criteriaQuery);
  return query.getResultList();
}

MyBatis SQL Maps

public interface MyBatisCustomerDao extends CustomerDao {
  @Select(
    "SELECT customerNumber, customerName, contactLastName, " +
    "contactFirstName, phone, addressLine1, addressLine2, " +
    "city, state, postalCode, country, creditLimit " +
    "FROM customers " +
    "WHERE customerNumber = #{id}")
  public Customer get(long id);
  // ...
}
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment =
  new Environment("development", transactionFactory, getDataSource());
Configuration configuration = new Configuration(environment);
configuration.addMapper(MyBatisCustomerDao.class);
sessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession session = sessionFactory.openSession(false);
CustomerDao dao = session.getMapper(MyBatisCustomerDao.class);

MyBatis SQL Maps: Annotations

public interface MyBatisCustomerDao extends CustomerDao {
  @Select(
    "SELECT customerNumber, customerName, contactLastName, " +
    "contactFirstName, phone, addressLine1, addressLine2, " +
    "city, state, postalCode, country, creditLimit " +
    "FROM customers " +
    "WHERE customerNumber = #{id}")
  public Customer get(long id);

  @Select(
    "SELECT customerNumber, customerName, contactLastName, contactFirstName, phone, " +
    "addressLine1, addressLine2, city, state, postalCode, country, creditLimit " +
    "FROM customers " +
    "WHERE UPPER(customerName) LIKE UPPER('%' || #{name} || '%')" +
    "ORDER BY customerNumber")
  List<Customer> findByName(String name);
  // ...
}

MyBatis SQL Maps: XML Mapping File

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="classicmodels.dao.mybatis.MyBatisCustomerDao">

  <select id="get" parameterType="long"
    resultType="classicmodels.domain.Customer">
    SELECT * FROM Customers WHERE CustomerNumber = #{id}
  </select>

  <select id="findByName" parameterType="java.lang.String"
    resultType="classicmodels.domain.Customer">
    SELECT * FROM Customers
    WHERE  UPPER(CustomerName) LIKE UPPER('%' || #{name} || '%')
    ORDER BY CustomerNumber
  </select>

  <!-- ... -->
</mapper>

MyBatis SQL Maps: More Complex Queries

<select id="findWithOrdersOrPaymentsInMonth"
  resultType="classicmodels.domain.Customer">
  SELECT *
  FROM Customers c
  WHERE EXISTS (
    SELECT *
    FROM   Orders o
    WHERE  o.CustomerNumber = c.CustomerNumber
      AND  EXTRACT(YEAR FROM OrderDate) = #{0}
      AND  EXTRACT(MONTH FROM OrderDate) = #{1}
  )
  OR    EXISTS (
    SELECT *
    FROM   Payments p
    WHERE  p.CustomerNumber = c.CustomerNumber
      AND  EXTRACT(YEAR FROM PaymentDate) = #{0}
      AND  EXTRACT(MONTH FROM PaymentDate) = #{1}
  )
  ORDER BY CustomerNumber
</select>

(Compare with Spring)

public List<Customer> findWithOrdersOrPaymentsInMonth(int year, int month) {
  return new NamedParameterJdbcTemplate(getDataSource()).query(
    "SELECT * " +
    "FROM customers c " +
    "WHERE EXISTS (" +
      "SELECT * " +
      "FROM orders o " +
      "WHERE o.customernumber = c.customernumber " +
        "AND EXTRACT(YEAR FROM orderdate) = :year " +
        "AND EXTRACT(MONTH FROM orderdate) = :month" +
    ") " +
      "OR  EXISTS (" +
      "SELECT * " +
      "FROM payments p " +
      "WHERE p.customernumber = c.customernumber " +
        "AND EXTRACT(YEAR FROM paymentdate) = :year " +
        "AND EXTRACT(MONTH FROM paymentdate) = :month" +
    ")" +
    "ORDER BY customernumber",
    new MapSqlParameterSource("year", year).addValue("month", month),
    new CustomerRowMapper()
  );
}

MyBatis SQL Maps: Conditional Criteria

<select id="findForFilter" parameterType="classicmodels.dao.CustomerFilter"
  resultType="classicmodels.domain.Customer">
  SELECT *
  FROM   Customers c
  <where>
    <if test="name != null">
      AND (   UPPER(CustomerName) LIKE '%' || UPPER(#{name}) || '%'
           OR UPPER(ContactFirstName) LIKE '%' || UPPER(#{name}) || '%'
           OR UPPER(ContactLastName) LIKE '%' || UPPER(#{name}) || '%' )
    </if>
    <if test="phone != null">
      AND (   phone = #{phone} )
    </if>
    <!-- similar for address -->
    <if test="minimumCreditLimit != null">
      AND creditLimit > #{minimumCreditLimit}
    </if>
    <!-- similar for maximumCreditLimit -->
  </where>
  ORDER BY CustomerNumber
</select>

(Compare with JPA)

public List<Customer> findForFilter(final CustomerFilter filter) {
  CriteriaBuilder builder = em.getCriteriaBuilder();
  CriteriaQuery<Customer> criteriaQuery = builder.createQuery(Customer.class);
  Root<Customer> customer = criteriaQuery.from(Customer.class);
  List<Predicate> conditions = new ArrayList<Predicate>();
  if (filter.getName() != null && !filter.getName().equals("")) {
    String name = "%" + filter.getName().toUpperCase() + "%";
    conditions.add(builder.or(
      builder.like(builder.upper(customer.get("customerName").as(String.class)), name),
      builder.like(builder.upper(customer.get("contactLastName").as(String.class)), name),
      builder.like(builder.upper(customer.get("contactFirstName").as(String.class)), name)
    ));
  }
  // similar for address and phone ...
  if (filter.getMinimumCreditLimit() != null) {
    BigDecimal minimumCreditLimit = filter.getMinimumCreditLimit();
    conditions.add(
      builder.gt(customer.get("creditLimit").as(Number.class), minimumCreditLimit)
    );
  }
  // similar for maximumCreditLimit ...
  Predicate predicate = builder.and(conditions.toArray(new Predicate[conditions.size()]));
  criteriaQuery.where(predicate);
  TypedQuery<Customer> query = em.createQuery(criteriaQuery);
  return query.getResultList();
}

A gnarlier problem

@Entity
@Table(name = "Employees")
public class Employee implements Comparable<Employee> {
  // ...

  @OneToMany(mappedBy = "reportsTo")
  @Sort(type = SortType.NATURAL)
  public SortedSet<Employee> getDirectReports() {
    return directReports;
  }

  @Transient
  public SortedSet<Employee> getAllReports() {
    SortedSet<Employee> result = new TreeSet<Employee>();
    for (Employee directReport : directReports) {
      result.add(directReport);
      result.addAll(directReport.getAllReports());
    }
    return result;
  }
}

Using MyBatis SQL Maps

Define a DAO method for the query:

public interface MyBatisEmployeeDao extends EmployeeDao {

  List<Employee> getAllReports(long id);

}

And implement using SQL:

<select id="getAllReports" parameterType="long"
  resultType="classicmodels.domain.Employee">
  WITH r(Manager, EmployeeNumber) AS (
    SELECT ReportsTo, EmployeeNumber FROM Employees WHERE ReportsTo = #{id}
    UNION ALL
    SELECT r.EmployeeNumber, e.EmployeeNumber
    FROM r JOIN Employees e ON e.ReportsTo = r.EmployeeNumber
  )
  SELECT e.* FROM r JOIN Employees e ON e.EmployeeNumber = r.EmployeeNumber
</select>

Other MyBatis SQL Maps Features

DbUnit

DbUnit: A Simple Test

public class FlatXmlSelectOwnerTest extends DBTestCase {

  protected IDatabaseTester newDatabaseTester() throws Exception {
    return new DataSourceDatabaseTester(DataSourceUtils.getDataSource());
  }

  protected IDataSet getDataSet() throws Exception {
    return new FlatXmlDataSet(getClass().getResourceAsStream("FlatXmlSelectOwnerTest.xml"));
  }

  // ...

  public void testLoadOwner() {
    OwnerDao ownerDao = getOwnerDao();
    Owner joe = ownerDao.loadOwner(2);
    assertOwner(joe, 2, "Joe", "Jeffries", "25 Baywater Lane", "Northbrook", "555-2345678");
  }

  public void testFindOwners() {
    OwnerDao ownerDao = getOwnerDao();
    Iterator<Owner> owners = ownerDao.findOwners("smith").iterator();
    assertOwner(owners.next(), 1, "Mandy", "Smith", "12 Oxford Street", "Southfield", "555-1234567");
    assertOwner(owners.next(), 4, "Dave", "Smith-Jones", "12 Kent Way", "Southfield", "555-4567890");
    assertFalse(owners.hasNext());
  }
}

DbUnit: FlatXmlDataSet

<dataset>
  <owners id="1" first_name="Mandy" last_name="Smith"
    address="21 Oxford Street" city="Southfield" telephone="555-1234567"/>
  <owners id="2" first_name="Joe" last_name="Jeffries"
    address="25 Baywater Lane" city="Northbrook" telephone="555-2345678"/>
  <owners id="3" first_name="Herb" last_name="Dalton"
    address="2 Main St" city="Southfield" telephone="555-3456789"/>
  <owners id="4" first_name="Dave" last_name="Smith-Jones"
    address="12 Kent Way" city="Southfield" telephone="555-4567890"/>
  <pets/>
  <visits/>
</dataset>

DbUnit: Specifying NULLs by Omission

<dataset>
  <owners id="1" first_name="Mandy" last_name="Smith"
    address="12 Oxford Street" city="Southfield" telephone="555-1234567"/>
  <owners id="2" first_name="Joe" last_name="Jeffries"
    address="25 Baywater Lane" telephone="555-2345678"/>
  <owners id="3" first_name="Herb" last_name="Dalton"
    address="2 Main St" city="Southfield"/>
  <owners id="4" first_name="Dave" last_name="Smith-Jones"
    address="12 Kent Way" city="Southfield" telephone="555-4567890"/>
  <pets/>
  <visits/>
</dataset>

DbUnit: Verifying an Update

public void testUpdateOwner() throws Exception {
  OwnerDao ownerDao = getOwnerDao();
  Owner owner = ownerDao.loadOwner(1);
  owner.setFirstName("Mandy-Jane");
  owner.setLastName("Brown");
  owner.setAddress("21 Ocean Parade");
  owner.setCity("Westport");
  owner.setTelephone("555-9876543");
  ownerDao.storeOwner(owner);
  IDataSet expectedDataSet = new FlatXmlDataSet(
    getClass().getResourceAsStream("FlatXmlUpdateOwnerTest.xml")
  );
  ITable expectedTable = expectedDataSet.getTable("owners");
  ITable actualTable = getConnection().createDataSet().getTable("owners");
  Assertion.assertEquals(expectedTable, actualTable);
}

DbUnit Structure: TestCase classes

DbUnit Structure: IDatabaseConnection

DbUnit Structure: IDataSet

DbUnit Structure: ITable

DbUnit Structure: ITableMetaData

DbUnit: XLS DataSet

protected IDataSet getDataSet() throws Exception {
  return new XlsDataSet(getClass().getResourceAsStream("XlsSelectOwnerTest.xls"));
}

DbUnit: CSV DataSet

id,first_name,last_name,address,city,telephone
1,Mandy,Smith,12 Oxford Street,Southfield,555-1234567
2,Joe,Jeffries,25 Baywater Lane,Northbrook,555-2345678
3,Herb,Dalton,2 Main St,Southfield,555-3456789
4,Dave,Smith-Jones,12 Kent Way,Southfield,555-4567890
protected IDataSet getDataSet() throws Exception {
  return new CsvDataSet(
    new File("src/test/nzoug/petclinic/dao/test/csv/")
  );
}

DbUnit: Inline DataSet

public class InlineDataSet {
  public static IDataSet dataSet(ITable... tables) {
    return new DefaultDataSet(tables);
  }

  public static ITable table(String name, String[] cols, String[]... data)
    throws DataSetException {
    Column[] columns = new Column[cols.length];
    for (int i = 0; i < cols.length; i++) {
      columns[i] = new Column(cols[i], DataType.UNKNOWN);
    }
    DefaultTable result = new DefaultTable(name, columns);
    for (String[] row : data) {
      result.addRow(row);
    }
    return result;
  }

  public static String[] col(String... columns) {
    return columns;
  }

  public static String[] row(String... data) {
    return data;
  }
}

DbUnit: Inline DataSet Usage

protected IDataSet getDataSet() throws Exception {
  return dataSet(
    table("owners",
      col("id", "first_name", "last_name", "address", "city", "telephone"),
      row("1", "Mandy", "Smith"      , "12 Oxford Street", "Southfield", "555-1234567"),
      row("2", "Joe"  , "Jeffries"   , "25 Baywater Lane", "Northbrook", "555-2345678"),
      row("3", "Herb" , "Dalton"     , "2 Main St"       , "Southfield", "555-3456789"),
      row("4", "Dave" , "Smith-Jones", "12 Kent Way"     , "Southfield", "555-4567890")
    ),
    table("pets", col("id")),
    table("visits", col("id"))
  );
}

DbUnit: NULL with ReplacementDataSet

protected IDataSet getDataSet() throws Exception {
  IDataSet result = dataSet(
    table("owners",
      col("id", "first_name", "last_name", "address", "city", "telephone"),
      row("1", "Mandy", "Smith"      , "12 Oxford Street", "Southfield", "(NULL)"),
      row("2", "Joe"  , "Jeffries"   , "25 Baywater Lane", "(NULL)"    , "555-2345678"),
      row("3", "Herb" , "Dalton"     , "2 Main St"       , "Southfield", "(NULL)"),
      row("4", "Dave" , "Smith-Jones", "12 Kent Way"     , "(NULL)"    , "555-4567890")
    ),
    table("pets", col("id")),
    table("visits", col("id"))
  );
  Map objectMap = new HashMap(1);
  objectMap.put("(NULL)", null);
  return new ReplacementDataSet(result, objectMap, null);
}

JDBC Logging with jdbcdslog

SLF4J/logback.groovy configuration:

root(INFO, ["FILE"])
logger("org.jdbcdslog.ConnectionLogger", OFF)
logger("org.jdbcdslog.StatementLogger", INFO)
logger("org.jdbcdslog.ResultSetLogger", INFO)
logger("org.jdbcdslog.SlowQueryLogger", INFO)

DataSource configuration:

  OracleDataSource oracleDataSource = new OracleDataSource();
  oracleDataSource.setURL("jdbc:oracle:thin:@oracle11gr2xe:1521:XE");
  oracleDataSource.setUser("birt");
  oracleDataSource.setPassword("birt");
  ConnectionPoolDataSourceProxy loggedDataSource =
    new ConnectionPoolDataSourceProxy();
  loggedDataSource.setTargetDSDirect(oracleDataSource);
  dataSource = loggedDataSource;
  • jdbcdslog.slowQueryThreshold system property: threshold in milliseconds.
  • See also log4jdbc, also on Google Code.

jdbcdslog Sample Output

2011-10-29 17:35:30.319 INFO org.jdbcdslog.StatementLogger - SELECT * FROM customers WHERE customernumber = 114;
2011-10-29 17:35:30.320 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {114, 'Australian Collectors, Co.', 'Ferguson', 'Peter', '03 9520 4555', '636 St Kilda Road', 'Level 3', 'Melbourne', 'Victoria', '3004', 'Australia', 1611, 117300}
2011-10-29 17:35:44.429 INFO org.jdbcdslog.StatementLogger - SELECT * FROM customers WHERE UPPER(customername) LIKE '%IMPORTS%' ORDER BY customernumber;
2011-10-29 17:35:44.460 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {121, 'Baane Mini Imports', 'Bergulfsen', 'Jonas ', '07-98 9555', 'Erling Skakkes gate 78', null, 'Stavern', null, '4110', 'Norway', 1504, 81700}
2011-10-29 17:35:44.461 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {145, 'Danish Wholesale Imports', 'Petersen', 'Jytte ', '31 12 3555', 'Vinbæltet 34', null, 'Kobenhavn', null, '1734', 'Denmark', 1401, 83400}
2011-10-29 17:35:44.463 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {169, 'Porto Imports Co.', 'de Castro', 'Isabel ', '(1) 356-5555', 'Estrada da saúde n. 58', null, 'Lisboa', null, '1756', 'Portugal', null, 0}
2011-10-29 17:35:44.463 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {171, 'Daedalus Designs Imports', 'Rancé', 'Martine ', '20.16.1555', '184, chaussée de Tournai', null, 'Lille', null, '59000', 'France', 1370, 82900}
2011-10-29 17:35:44.464 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {303, 'Schuyler Imports', 'Schuyler', 'Bradley', '+31 20 491 9555', 'Kingsfordweg 151', null, 'Amsterdam', null, '1043 GR', 'Netherlands', null, 0}
2011-10-29 17:35:44.465 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {307, 'Der Hund Imports', 'Andersen', 'Mel', '030-0074555', 'Obere Str. 57', null, 'Berlin', null, '12209', 'Germany', null, 0}
2011-10-29 17:35:44.465 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {344, 'CAF Imports', 'Fernandez', 'Jesus', '+34 913 728 555', 'Merchants House', '27-30 Merchant''s Quay', 'Madrid', null, '28023', 'Spain', 1702, 59600}
2011-10-29 17:35:44.466 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {415, 'Bavarian Collectables Imports, Co.', 'Donnermeyer', 'Michael', ' +49 89 61 08 9555', 'Hansastr. 15', null, 'Munich', null, '80686', 'Germany', 1504, 77000}
2011-10-29 17:35:44.467 INFO org.jdbcdslog.ResultSetLogger - java.sql.ResultSet.next {484, 'Iberia Gift Imports, Corp.', 'Roel', 'José Pedro ', '(95) 555 82 82', 'C/ Romero, 33', null, 'Sevilla', null, '41101', 'Spain', 1702, 65700}

Java Database Toolbox

/