I'm the father of ItsNat AJAX Java web framework, JNIEasy, LAMEOnJ, JEPLayer and JEPLDroid libraries (and very old stuff like XPDOM), supporter of the Single Page Interface (SPI) paradigm, writer of the SPI manifesto and currently Android native guy. Jose Maria has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

JEPLayer ORM v1.0, JTA Transactions and Fluid Query API

12.18.2011
| 2034 views |
  • submit to reddit

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

http://jeplayer.googlecode.com

JPELayer is open source Apache v2 licensed.

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. 

 

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 method call.

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).

JEPLayer was presented in JavaLobby some time before, now the query API is fluid and JDBC and JTA transactions are distinguished.

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 (DataSourceFactoryis any custom factory class to get the DataSource):

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();
        }
    }
}

The DAO class based on JEPLayer:

public class ContactDAO implements JEPLResultSetDAOListener<Contact>
{
    protected JEPLDAO<Contact> dao;

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

    public JEPLDAO<Contact> 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<Contact> selectAll()
    {
        return dao.createJEPLDAOQuery("SELECT * FROM CONTACT").getResultList();
    }

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

    public List<Contact> 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<Contact> 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<Contact> selectAllExplicitResultSetDAOListenerBean()
    {
        JEPLResultSetDAOListenerDefault<Contact> 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<Contact> selectAllExplicitResultSetDAOListenerBeanWithMapper()
    {
        JEPLRowBeanMapper<Contact> rowMapper = new JEPLRowBeanMapper<Contact>()
        {
            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<Contact> 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 are still available like:  JEPLConnectionListener, JEPLPreparedStatementListener, JEPLResultSetDALListener y JEPLResultSetDAOListener  (take a look to the first article of JEPLayer at javaLobby or Reference Manual).

JEPLResultSetDAO is worth enough to show again, now is obtained calling the fluid query API:

    public JEPLResultSetDAO<Contact> selectAllResultSetDAO() // Method to add to ContactDAO
    {
        return dao.createJEPLDAOQuery(
               "SELECT * FROM CONTACT").getJEPLResultSetDAO();
    }


    ContactDAO dao = ...;
    JEPLResultSetDAO<Contact> 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 List, we can see it like a List loading on demand objects from database:

    ContactDAO dao = ...;
    List<Contact> resSetDAO = dao.selectAllResultSetDAO();
    if (((JEPLResultSetDAO)resSetDAO).isClosed())
        throw new RuntimeException("UNEXPECTED");
    for(Contact contact : resSetDAO) // Uses Iterator<Contact> 
    {
        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 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:
    final JEPLDataSource jds = ...;
 ContactDAO dao = new ContactDAO(jds);
...
    JEPLTask<Contact> task = new JEPLTask<Contact>()
    {
        @Override
        public Contact exec() throws Exception
        {
            Contact contact = new Contact();
            contact.setName("A Contact object");
            contact.setPhone("9999999");
            contact.setEmail("contact@world.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:

 

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

    Contact contact = jds.exec(task);

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

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

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

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

In this version we can declare JTA transactions:

    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<Contact> task = new JEPLTask<Contact>()
    {
        @Override
        public Contact exec() throws Exception
        {
            return ...; // Database actions 
        }
    };
    Contact contact = jds.exec(task);

Again with an annotation:

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

Or parameter:

   JEPLTask<Contact> task = new JEPLTask<Contact>()
   {
     @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) :

    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<Contact> task = new JEPLTask<Contact>()
    {
        @Override
        public Contact exec() throws Exception
        {
            // Database actions using jds1 and jds2 
            return ...;
        }
    };
    jdsMgr.exec(task);

The same transactional semmantic 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);

 

If you miss something important in JEPLayer tell me with your comments.

0
Published at DZone with permission of its author, Jose Maria Arranz.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)