JEPLayer is a simple persistent ORM API on top of JDBC and JTA

http://jeplayer.googlecode.com

JEPLayer v1.0 adds JTA transaction support for a single or multiple databases, fake JTA transactions based on JDBC, a fluid query API and automatic db field-bean attribute binding. 

JPELayer is open source Apache v2 licensed.

Why another ORM tool?

JEPLayer was born to provide

  1. A simple API to avoid the tedious tasks of JDBC and/or JTA
  2. Several optional listeners to fully customize the lifecycle of JDBC persistence (simple and non-invasive IoC)
  3. Methods to build simple and complex DAOs, automatic attribute-table field binding or fully customized.
  4. Does not replace JDBC, instead of this, JDBC objects are exposed when required
  5. Ever using PreparedStatement, ever secure
  6. PreparedStatement objects are automatically cached and reused
  7. Fluid API for queries similar to JPA
  8. Extremely simple, automatic, configurable, declarative and programmatic, non-invasive and error-free way to demarcate JDBC and JTA transactions
  9. JTA transaction declaration with the same semantics as JavaEE EJB beans
  10. Extremely simple and error-free two-phase commit JTA transactions for multiple databases
  11. False JTA transactions with pure JDBC infrastructure, change to real JTA with a simple configuration method.

What is different in JEPLayer

  • JEPLayer is simpler than Spring’s JdbcTemplate and transactions and has similar power, the persistent lifecycle can be fully configurable providing more interception points, it does not try to replace JDBC and JDBC/JTA transactions are built-in and tightly integrated. JTA transactions for multiple databases are built-in.
  • JEPLayer is programmatic instead of the declarative path of iBatis/MyBatis. Transactions can be optionally declared in methods.
  • JEPLayer allows getting the most of the database with no performance penalty and no waste of control typical of transparent persistence ORMs (like Hibernate or JPA), because JEPLayer is lower level.

Examples

Code is the best way to understand JEPLayer approach to persistence (MySQL is supposed).

Our data model:

public class Contact
{
    protected int id;
    protected String name;
    protected String phone;
    protected String email;

    public Contact(int id, String name, String phone, String email)
    {
        this.id = id;
        this.name = name;
        this.phone = phone;
        this.email = email;
    }

    public Contact()
    {
    }
  /* ... gets and sets ... */
}

This is an example of db table creation (DataSourceFactory is just a custom factory class to get the DataSource and is not part of JEPLayer):

public class CreateDBModel
{
    public static void main(String[] args)
    {
        DataSourceFactory dsFactory = ...;
        DataSource ds = dsFactory.getDataSource();
        JEPLBootNonJTA boot = JEPLBootRoot.get().createJEPLBootNonJTA();
        JEPLNonJTADataSource jds = boot.createJEPLNonJTADataSource(ds);
         try
        {
            JEPLDAL dal = jds.createJEPLDAL();
            dal.createJEPLDALQuery("DROP TABLE IF EXISTS CONTACT").executeUpdate();
            dal.createJEPLDALQuery(
                "CREATE TABLE  CONTACT (" +
                "  ID INT NOT NULL AUTO_INCREMENT," +
                "  EMAIL VARCHAR(255) NOT NULL," +
                "  NAME VARCHAR(255) NOT NULL," +
                "  PHONE VARCHAR(255) NOT NULL," +
                "  PRIMARY KEY (ID)" +
                ")" +
                "ENGINE=InnoDB"
                ).executeUpdate();
        }
        finally
        {
            dsFactory.destroy();
        }
    }
}

A DAO class based on JEPLayer (later we will see how db column-attribute can be automatically bound) :

public class ContactDAO implements JEPLResultSetDAOListener
{
    protected JEPLDAO dao;

    public ContactDAO(JEPLDataSource ds)
    {
        this.dao = ds.createJEPLDAO(Contact.class);
        dao.addJEPLListener(this);
    }

    public JEPLDAO getJEPLDAO()
    {
        return dao;
    }

    @Override
    public void setupJEPLResultSet(JEPLResultSet jrs,JEPLTask task)   throws Exception
    {
    }

    @Override
    public Contact createObject(JEPLResultSet jrs) throws Exception
    {
        return new Contact();
    }

    @Override
    public void fillObject(Contact obj,JEPLResultSet jrs) throws Exception
    {
        ResultSet rs = jrs.getResultSet();
        obj.setId(rs.getInt("ID"));
        obj.setName(rs.getString("NAME"));
        obj.setPhone(rs.getString("PHONE"));
        obj.setEmail(rs.getString("EMAIL"));
    }

    public void insert(Contact contact)
    {
        int key = dao.createJEPLDALQuery(
               "INSERT INTO CONTACT (EMAIL, NAME, PHONE) VALUES (?, ?, ?)")
                .addParameters(contact.getEmail(),
                       contact.getName(),contact.getPhone())
                .getGeneratedKey(int.class);
        contact.setId(key);
    }

    public void update(Contact contact)
    {
        dao.createJEPLDALQuery(
               "UPDATE CONTACT SET EMAIL = ?, NAME = ?, PHONE = ? WHERE ID = ?")
                .addParameters(contact.getEmail(),contact.getName(),
                       contact.getPhone(),contact.getId())
                .setStrictMinRows(1).setStrictMaxRows(1)
                .executeUpdate();
    }

    public boolean delete(Contact obj)
    {
        return deleteById(obj.getId());
    }

    public boolean deleteById(int id)
    {
        // Only if there is no "inherited" rows or declared ON DELETE CASCADE
        return dao.createJEPLDALQuery("DELETE FROM CONTACT WHERE ID = ?")
                    .setStrictMinRows(0).setStrictMaxRows(1)
                    .addParameter(id)
                    .executeUpdate() > 0;
    }

    public int deleteAll()
    {
        // Only if "inherited" tables are empty or declared ON DELETE CASCADE
        return dao.createJEPLDALQuery("DELETE FROM CONTACT").executeUpdate();
    }

    public List selectAll()
    {
        return dao.createJEPLDAOQuery("SELECT * FROM CONTACT").getResultList();
    }

    public JEPLResultSetDAO selectAllResultSetDAO()
    {
        return dao.createJEPLDAOQuery(
                       "SELECT * FROM CONTACT").getJEPLResultSetDAO();
    }

    public List selectJEPLDAOQueryRange(int from,int to)
    {
        return dao.createJEPLDAOQuery("SELECT * FROM CONTACT")
                .setFirstResult(from)
                .setMaxResults(to - from)
                .getResultList();
    }

    public Contact selectById(int id)
    {
        return dao.createJEPLDAOQuery("SELECT * FROM CONTACT WHERE ID = ?")
                .addParameter(id)
                .getSingleResult();
    }

     public List selectByNameAndEMail(String name,String email)
    {
        return dao.createJEPLDAOQuery(
               "SELECT * FROM CONTACT WHERE NAME = ? AND EMAIL = ?")
                .addParameters(name,email)
                .getResultList();
    }

    public int selectCount()
    {
        return dao.createJEPLDALQuery("SELECT COUNT(*) FROM CONTACT")
                    .getOneRowFromSingleField(int.class);
    }
}

In version 1.0 you can avoid manual binding implementing JEPLResultSetDAOListener, alternatively you can register the default implementation for automatic binding (JEPLResultSetDAOListenerDefault). In the following method added to our DAO, we use automatic binding on a concrete query (you could register this listener in global level of the DAO avoiding JEPLResultSetDAOListener custom methods).

public List selectAllExplicitResultSetDAOListenerBean()
{
    JEPLResultSetDAOListenerDefault listener =
            dao.getJEPLDataSource()
           .createJEPLResultSetDAOListenerDefault(Contact.class);
    return dao.createJEPLDAOQuery("SELECT * FROM CONTACT")
            .addJEPLListener(listener)
            .getResultList();
}

JEPLResultSetDAOListenerDefault  allows attribute interception which cannot be automatically mapped with db fields using JEPLRowBeanMapper. This example is just to show JEPLRowBeanMapper, automatic binding also works.

public List selectAllExplicitResultSetDAOListenerBeanWithMapper()
{
    JEPLRowBeanMapper rowMapper = new JEPLRowBeanMapper()
    {
        public boolean setColumnInBean(Contact obj,JEPLResultSet jrs,
                   int col, String columnName, Object value, Method setter)
        {
            if (columnName.equalsIgnoreCase("email"))
            {
                obj.setEmail((String)value);
                return true;
            }
            return false;
        }
    };

    JEPLResultSetDAOListenerDefault listener =
            dao.getJEPLDataSource()
            .createJEPLResultSetDAOListenerDefault(Contact.class,rowMapper);
    return dao.createJEPLDAOQuery("SELECT * FROM CONTACT")
            .addJEPLListener(listener)
            .getResultList();
}

Now parameters can be numbered:

int key = dao.createJEPLDALQuery(
            "INSERT INTO CONTACT (EMAIL, NAME, PHONE) VALUES (?1, ?2, ?3)")
        .setParameter(1,contact.getEmail())
        .setParameter(2,contact.getName())
        .setParameter(3,contact.getPhone())
        .getGeneratedKey(int.class);

Or with names (like JPA):

int key = dao.createJEPLDALQuery(
    "INSERT INTO CONTACT (EMAIL, NAME, PHONE) VALUES (:email,:name,:phone)")
        .setParameter("email",contact.getEmail())
        .setParameter("name",contact.getName())
        .setParameter("phone",contact.getPhone())
        .getGeneratedKey(int.class);

IoC listeners (interceptors) are still available like  JEPLConnectionListener, JEPLPreparedStatementListener, JEPLResultSetDALListener y JEPLResultSetDAOListener.

For instance registering JEPLPreparedStatementListener we can intercept when a PreparedStatement is going to be used, this example method to be added to our DAO shows an alternative to query a subset of rows:

    public List selectAllStatementListenerMaxRows(final int maxRows)
    {
        JEPLPreparedStatementListener> listener =
                new JEPLPreparedStatementListener>()
        {
            public void setupJEPLPreparedStatement(JEPLPreparedStatement jstmt,
                    JEPLTask> task) throws Exception
            {
                PreparedStatement stmt = jstmt.getPreparedStatement();
                int old = stmt.getMaxRows();
                stmt.setMaxRows(maxRows);
                try
                {
                    List res = task.exec();
                }
                finally
                {
                    stmt.setMaxRows(old); // Restore
                }
            }
        };

        return dao.createJEPLDAOQuery("SELECT * FROM CONTACT")
                .addJEPLListener(listener)
                .getResultList();
    }

Sometimes you may want an alive ResultSet-like object returning user defined data model objects loaded on demand from DB using a JEPLResultSetDAO:

public JEPLResultSetDAO selectAllResultSetDAO() // Method to be added to ContactDAO
{
    return dao.createJEPLDAOQuery(
           "SELECT * FROM CONTACT").getJEPLResultSetDAO();
}

...
ContactDAO dao = ...;
JEPLResultSetDAO resSetDAO = dao.selectAllResultSetDAO();
if (resSetDAO.isClosed()) throw new RuntimeException("UNEXPECTED");
while(resSetDAO.next())
{
    Contact contact = resSetDAO.getObject();
    System.out.println("Contact: " + contact.getName());
}

// Now we know is closed
if (!resSetDAO.isClosed()) throw new RuntimeException("UNEXPECTED");

Because returned object also implements java.util.List, we can see it like a List loading on demand objects from database:

ContactDAO dao = ...;
List resSetDAO = dao.selectAllResultSetDAO();
if (((JEPLResultSetDAO)resSetDAO).isClosed())
    throw new RuntimeException("UNEXPECTED");
for(Contact contact : resSetDAO) // Uses Iterator
{
    System.out.println("Contact: " + contact.getName());
}

// Now we know is closed
if (!((JEPLResultSetDAO)resSetDAO).isClosed())
    throw new RuntimeException("UNEXPECTED");

Maybe we don't need stuff like DAOs, beans etc and we just want to get custom data from a database as simple as possible avoiding the tedious tasks of JDBC. So we have JEPLCachedResultSet to get results and be used like a disconnected ResultSet (two rows are supposed):

JEPLDataSource jds = ...;
JEPLDAL dal = jds.createJEPLDAL();
JEPLCachedResultSet resSet = dal.createJEPLDALQuery(
            "SELECT COUNT(*) AS CO,AVG(ID) AS AV FROM CONTACT")
            .getJEPLCachedResultSet();

String[] colNames = resSet.getColumnLabels();
if (colNames.length != 2) throw new RuntimeException("UNEXPECTED");
if (!colNames[0].equals("CO")) throw new RuntimeException("UNEXPECTED");
if (!colNames[1].equals("AV")) throw new RuntimeException("UNEXPECTED");
if (resSet.size() != 2) throw new RuntimeException("UNEXPECTED");

int count = resSet.getValue(1, 1, int.class); // Row 1, column 1
count = resSet.getValue(1, "CO", int.class);

int avg = resSet.getValue(1, 2, int.class); // Row 1, column 2
avg = resSet.getValue(1, "AV", int.class);

Regarding transactions, we can group db actions in the same transaction "executing" a JEPLTask, Connection is got and released automatically from pool and transaction demarcation is automatic, the transaction is automatically commited in the end or rollbacked on exception.

    final JEPLDataSource jds = ...;
    final ContactDAO dao = new ContactDAO(jds);       
    ...
    JEPLTask task = new JEPLTask()
    {
        @Override
        public Contact exec() throws Exception
        {
            Contact contact = new Contact();
            contact.setName("A Contact object");
            contact.setPhone("9999999");
            contact.setEmail("contact at world dot com");
            dao.insert(contact);

            Contact contact2 = dao.selectById(contact.getId());
            return contact2;
        }
    };
    Contact contact = jds.exec(task);
    //...

But now there is specific API for JDBC (non-JTA) and JTA transactions. For instance for JDBC (non-JTA):

JEPLNonJTADataSource jds = boot.createJEPLNonJTADataSource(ds);
 jds.setDefaultAutoCommit(false);
 ...
 JEPLTask task = new JEPLTask()
 {
     @Override
     public Contact exec() throws Exception
     {
          ...
     }
 };

 Contact contact = jds.exec(task);

Now transaction (autocommit set to to false) alternatively can be declared with an annotation:

JEPLNonJTADataSource jds = ...;
...
JEPLTask task = new JEPLTask()
{
    @Override
    @JEPLTransactionalNonJTA
    public Contact exec() throws Exception
    {
        ...
    }
};
Contact contact = jds.exec(task);

Or with an autoCommit parameter on exec method (this example is not transactional because autoCommit is true):

JEPLNonJTADataSource jds = ...;
...
JEPLTask task = new JEPLTask()
{
    @Override
    public Contact exec() throws Exception
    {
        ...
    }
};
Contact contact = jds.exec(task,true);

In this version we can declare JTA transactions with the same semantics than Spring or JavaEE EJB:

UserTransaction txn = ...;
TransactionManager txnMgr = ...;
DataSource ds = ...;

JEPLBootJTA boot = JEPLBootRoot.get().createJEPLBootJTA();
boot.setUserTransaction(txn);
boot.setTransactionManager(txnMgr);

JEPLJTADataSource jds = boot.createJEPLJTADataSource(ds);
jds.setDefaultJEPLTransactionPropagation(
          JEPLTransactionPropagation.REQUIRED);
...
JEPLTask task = new JEPLTask()
{
    @Override
    public Contact exec() throws Exception
    {
        return ...; // Database actions
    }
};
Contact contact = jds.exec(task);

Again with an annotation:

JEPLTask task = new JEPLTask()
{
    @Override
    @JEPLTransactionalJTA
    public Contact exec() throws Exception
    {
        return ...; // Database actions
    }
};
Contact contact = jds.exec(task);

Or parameter:

JEPLTask task = new JEPLTask()
{
  @Override
  public Contact exec() throws Exception
  {
      return ...; // Database actions
  }
};
Contact contact = jds.exec(task,JEPLTransactionPropagation.REQUIRED);

This version introduces JTA transactions for multiple databases (then using two-phase commit). Transaction is automatically open and commited or rollbacked when some exception is thrown and one Connection per database is automatically got and released from pools.

UserTransaction txn = ...;
TransactionManager txnMgr = ...;
DataSource ds1 = ...;
DataSource ds2 = ...;

JEPLBootJTA boot = JEPLBootRoot.get().createJEPLBootJTA();
boot.setUserTransaction(txn);
boot.setTransactionManager(txnMgr);

JEPLJTAMultipleDataSource jdsMgr = boot.getJEPLJTAMultipleDataSource();
final JEPLJTADataSource jds1 = boot.createJEPLJTADataSource(ds1);
final JEPLJTADataSource jds2 = boot.createJEPLJTADataSource(ds2);

JEPLTask task = new JEPLTask()
{
    @Override
    public Contact exec() throws Exception
    {
        // Database actions using jds1 and jds2
        return ...;
    }
};
jdsMgr.exec(task);

The same transactional semantic is used in this case to all DataSource registered, again with global configuration, annotation or parameter for every call. For example:

JEPLJTAMultipleDataSource jdsMgr = boot.get JEPLJTAMultipleDataSource();
jdsMgr.setDefaultJEPLTransactionPropagation(
         JEPLTransactionPropagation.REQUIRED);

...

Enjoy!