Java Easy Persistent Layer - JEPLayer
JEPLayer es una sencilla pero poderosa API encima de JDBC basada en listeners que controlan opcionalmente el ciclo de vida de un proceso JDBC en múltiples puntos.
http://jeplayer.googlecode.com
JEPLayer es de código abierto con licencia Apache v2
¿Por qué otra herramienta basada en JDBC?
JEPLayer nació para proporcionar
1. Una API sencilla para evitar las tareas tediosas de JDBC
2. Varios listeners opcionales para personalizar completamente el ciclo de vida de la persistencia de JDBC
3. Métodos para construir DAOs simples y complejos
4. Una manera muy sencilla, automática, configurable y libre de errores para demarcar las transacciones
5. No sustituye a JDBC, en lugar de ésto, los objetos JDBC están expuestos para cuando sea necesario
6. Siempre usando PreparedStatement, siempre seguro
7. Los objetos PreparedStatement son automáticamente almacenados en caché y reutilizados
¿Qué es diferente en JEPLayer?
JEPLayer es más sencillo que JdbcTemplate de Spring y tiene una potencia similar, el ciclo de vida persistente puede ser totalmente configurable, ofreciendo más puntos de intercepción, no trata de sustituir al JDBC y las transacciones JDBC están implícitas.
JEPLayer es programático con toda la libertad y capacidad de reutilización que eso supone en lugar del camino declarativo que ha tomado iBatis/MyBatis.
JEPLayer permite sacar el máximo partido de la base de datos sin pérdida de rendimiento y sin pérdida de control típico de los ORMs de persistencia transparente.
Ejemplo de llamadas sencillas (MySQL)
DataSource ds = ...;
JEPLDataSource jds = new JEPLBoot().createJEPLDataSource(ds);
JEPLDAL dal = jds.createJEPLDAL();
dal.executeUpdate("DROP TABLE IF EXISTS PERSON");
dal.executeUpdate("DROP TABLE IF EXISTS COMPANY");
dal.executeUpdate("DROP TABLE IF EXISTS CONTACT");
dal.executeUpdate(
"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"
);
Un sencillo DAO
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 ... */
}
public class ContactDAO extends JEPLDAOBase implements JEPLResultSetDAOListener
{
public ContactDAO(JEPLDataSource ds)
{
super(ds);
}
@Override
public void setupJEPLResultSet(JEPLResultSet jrs, JEPLTask task) throws SQLException
{
}
@Override
public Contact createObject(JEPLResultSet jrs) throws SQLException
{
return new Contact();
}
@Override
public void fillObject(Contact obj, JEPLResultSet jrs) throws SQLException
{
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 = insert("INSERT INTO CONTACT (EMAIL, NAME, PHONE) " + "VALUES (?, ?, ?)",
int.class, contact.getEmail(), contact.getName(), contact.getPhone());
contact.setId(key);
}
public void update(Contact contact)
{
executeUpdateOne("UPDATE CONTACT SET EMAIL = ?, NAME = ?, PHONE = ? WHERE ID = ?",
contact.getEmail(), contact.getName(), contact.getPhone(), contact.getId());
}
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 executeUpdateZeroOrOne("DELETE FROM CONTACT WHERE ID = ?", id);
}
public int deleteAll()
{ // Only if "inherited" tables are empty or declared ON DELETE CASCADE
return executeUpdate("DELETE FROM CONTACT");
}
public List selectAll()
{
return selectList("SELECT * FROM CONTACT");
}
public JEPLResultSetDAO selectAllResultSetDAO()
{
return selectResultSetDAO("SELECT * FROM CONTACT");
}
public Contact selectById(int id)
{
return selectOneById("SELECT * FROM CONTACT WHERE ID = ?", id);
}
public List selectByNameAndEMail(String name, String email)
{
return selectList("SELECT * FROM CONTACT WHERE NAME = ? AND EMAIL = ?", name, email);
}
public int selectCount()
{
return selectOneRowAndField("SELECT COUNT(*) FROM CONTACT", int.class);
}
}
Añadimos un método más complejo a este DAO para proporcional limitación "al vuelo" del número de resultados
public List selectAllStatementListenerMaxRows(final int maxRows)
{
JEPLPreparedStatementListener> listener = new JEPLPreparedStatementListener>()
{
public void setupJEPLPreparedStatement(JEPLPreparedStatement jstmt, JEPLTask> task) throws SQLException
{
PreparedStatement stmt = jstmt.getPreparedStatement();
int old = stmt.getMaxRows();
stmt.setMaxRows(maxRows);
try
{
List res = task.exec();
}
finally
{
stmt.setMaxRows(old); // Restore
}
}
};
return selectList("SELECT * FROM CONTACT",listener);
}
Otro método para cargar resultados entre dos filas especificadas por índices
public List selectAllExplicitResultSetListenerRange(final int from,final int to)
{
JEPLResultSetDAOListener listener = new JEPLResultSetDAOListener()
{
@Override
public void setupJEPLResultSet(JEPLResultSet jrs, JEPLTask task) throws SQLException
{
ResultSet rs = jrs.getResultSet();
rs.absolute(from);
}
@Override
public Contact createObject(JEPLResultSet jrs) throws SQLException
{
return new Contact();
}
@Override
public void fillObject(Contact obj, JEPLResultSet jrs) throws SQLException
{
ResultSet rs = jrs.getResultSet();
obj.setId(rs.getInt("ID"));
obj.setName(rs.getString("NAME"));
obj.setPhone(rs.getString("PHONE"));
obj.setEmail(rs.getString("EMAIL"));
int row = rs.getRow(); // Now returned value is the "next row"
if (row == to) jrs.stop();
}
};
return selectList("SELECT * FROM CONTACT",listener);
}
Agrupando acciones persistentes (el Connection es obtenido al principio a partir del DataSource y liberado al final).
final JEPLDataSource jds = ...;
JEPLTask task = new JEPLTask()
{
@Override
public Contact exec() throws SQLException
{
Contact contact = new Contact();
contact.setName("A Contact object");
contact.setPhone("9999999");
contact.setEmail("contact@world.com");
ContactDAO dao = new ContactDAO(jds);
dao.insert(contact);
Contact contact2 = dao.selectById(contact.getId());
return contact2;
}
};
Contact contact = jds.exec(task);
Por supuesto con un simple boolean (autoCommit=false) el mismo código es ejecutado dentro de una transacción JDBC
La transacción es automaticamente "commited" al final o "rollbacked" en caso de error.
final JEPLDataSource jds = ...;
JEPLTask task = new JEPLTask()
{
@Override
public Contact exec() throws SQLException
{
...
}
};
Contact contact = jds.exec(task, false );
Puede ser que necesites un mayor control del ciclo de vida de la transacción
JEPLDataSource jds = ...;
JEPLTask task = new JEPLTask() { ... };
jds.exec(task,new JEPLConnectionListener()
{
public void setupJEPLConnection(JEPLConnection con,JEPLTask task) throws SQLException
{
con.getConnection().setAutoCommit(false); // transaction
try
{
task.exec();
con.getConnection().commit();
}
catch(Exception ex)
{
con.getConnection().rollback();
throw new SQLException(ex);
}
}
}
);
A veces puede que necesites disponer de un objeto similar al ResultSet pero que devuelva objetos del modelo cargados bajo demanda de la base de datos a medida que iteramos
Añadiendo a ContactDAO este método:
public JEPLResultSetDAO selectAllResultSetDAO()
{
return selectResultSetDAO("SELECT * FROM CONTACT");
}
Ahora este código es posible (dentro de una JEPLTask):
ContactDAO dao = ...;
JEPLResultSetDAO resSetDAO = dao.selectAllResultSetDAO();
if (resSetDAO.isClosed()) throw new JEPLException("WRONG");
while(resSetDAO.next())
{
Contact contact = resSetDAO.getObject();
System.out.println("Contact: " + contact.getName());
}
// Now we know is closed
if (!resSetDAO.isClosed()) throw new JEPLException("WRONG");
Afortunadamente JEPLResultSetDAO es también un List que carga los objetos también bajo demanda
ContactDAO dao = ...;
List resSetDAO = dao.selectAllResultSetDAO();
if (((JEPLResultSetDAO)resSetDAO).isClosed()) throw new JEPLException("WRONG");
for(Contact contact : resSetDAO) // Uses Iterator
{
System.out.println("Contact: " + contact.getName());
}
// Now we know is closed
if (!((JEPLResultSetDAO)resSetDAO).isClosed()) throw new JEPLException("WRONG");
El Manual de Referencia contiene un ejemplo de herencia de tres clases basado en tres tablas sin atributo discriminador.
Aunque se me ocurren más ideas, la librería por ahora se quedará así, tengo cosas más importantes que hacer :)
Que la disfruteis.
Reader Comments