java - ¿Cómo implemento un administrador DAO utilizando JDBC y grupos de conexiones?
connection-pooling genericdao (2)
Creo que si quieres hacer un patrón DAO simple en JDBC simple, debes hacerlo simple:
public List<Customer> listCustomers() {
List<Customer> list = new ArrayList<>();
try (Connection conn = getConnection();
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery("select * from customers")) {
while (rs.next()) {
list.add(processRow(rs));
}
return list;
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e); //or your exceptions
}
}
Puede seguir este patrón en una clase llamada, por ejemplo, CustomersDao o CustomerManager, y puede llamarla con un simple
CustomersDao dao = new CustomersDao();
List<Customers> customers = dao.listCustomers();
Tenga en cuenta que estoy usando try with resources y este código es seguro para las conexiones filtradas, limpias y directas. Probablemente no quiera seguir el patrón completo de DAO con Factorys, interfaces y todas las conexiones que en muchos casos no lo hacen agregue valor real
No creo que sea una buena idea usar ThreadLocals, Bad usado como en la respuesta aceptada es una fuente de fugas del cargador de clases
Recuerde SIEMPRE cierre sus recursos (Declaraciones, ResultSets, Conexiones) en un intento final de bloqueo o use try with resources
Mi problema es el siguiente. Necesito una clase que funcione como un único punto para una conexión de base de datos en un sistema web, para evitar tener un usuario con dos conexiones abiertas. Necesito que sea lo más óptimo posible y debe gestionar cada transacción en el sistema. En otras palabras, solo esa clase debería ser capaz de crear instancias de DAO. Y para hacerlo mejor, ¡también debería usar la agrupación de conexiones! ¿Que debería hacer?
Tendrá que implementar un Administrador DAO . Tomé la idea principal de este sitio web , sin embargo, hice mi propia implementación que resuelve algunos problemas.
Paso 1: agrupación de conexiones
En primer lugar, deberá configurar un grupo de conexiones . Un grupo de conexiones es, bueno, un grupo de conexiones. Cuando su aplicación se ejecuta, el grupo de conexiones iniciará una cierta cantidad de conexiones, esto se hace para evitar crear conexiones en tiempo de ejecución ya que es una operación costosa. Esta guía no pretende explicar cómo configurar uno, así que ve a buscarlo.
Para el registro, usaré Java como mi lenguaje y Glassfish como mi servidor.
Paso 2: Conéctate a la base de datos
Comencemos creando una clase DAOManager
. Vamos a darle métodos para abrir y cerrar una conexión en tiempo de ejecución. Nada demasiado elegante.
public class DAOManager {
public DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL"); //The string should be the same name you''re giving to your JNDI in Glassfish.
}
catch(Exception e) { throw e; }
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
}
Esta no es una clase muy elegante, pero será la base de lo que vamos a hacer. Entonces, haciendo esto:
DAOManager mngr = new DAOManager();
mngr.open();
mngr.close();
debería abrir y cerrar su conexión a la base de datos en un objeto.
Paso 3: ¡Haz que sea un solo punto!
¿Qué, ahora, si hiciéramos esto?
DAOManager mngr1 = new DAOManager();
DAOManager mngr2 = new DAOManager();
mngr1.open();
mngr2.open();
Algunos podrían argumentar, "¿por qué en el mundo harías esto?" . Pero entonces nunca se sabe lo que hará un programador. Incluso entonces, el programador podría forjar el cierre de una conexión antes de abrir una nueva. Además, esto es un desperdicio de recursos para la aplicación. Detente aquí si realmente quieres tener dos o más conexiones abiertas, esto será una implementación para una conexión por usuario.
Para que sea un solo punto, tendremos que convertir esta clase en un singleton . Un singleton es un patrón de diseño que nos permite tener una y solo una instancia de cualquier objeto dado. Entonces, ¡hagámoslo singleton!
- Debemos convertir nuestro constructor
public
en uno privado. Solo debemos dar una instancia a quien lo llame. ¡ElDAOManager
se convierte en una fábrica! - También debemos agregar una nueva clase
private
que realmente almacenará un singleton. - Además de todo esto, también necesitamos un método
getInstance()
que nos proporcione una instancia única a la que podamos llamar.
Veamos cómo se implementa.
public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE;
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final DAOManager INSTANCE;
static
{
DAOManager dm;
try
{
dm = new DAOManager();
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
Cuando se inicia la aplicación, cada vez que alguien necesita un singleton, el sistema instanciará un DAOManager
. Muy bien, ¡hemos creado un único punto de acceso!
Pero singleton es un antipatrón porque ¡razones! Sé que a algunas personas no les gustará Singleton. Sin embargo, resuelve el problema (y lo ha resuelto) con bastante decencia. Esta es solo una forma de implementar esta solución, si tiene otras maneras, puede sugerirlo.
Paso 4: Pero hay algo mal ...
Sí, de hecho lo hay ¡Un singleton creará UNA SOLA instancia para toda la aplicación! ¡Y esto está mal en muchos niveles, especialmente si tenemos un sistema web donde nuestra aplicación será multiproceso ! ¿Cómo resolvemos esto, entonces?
Java proporciona una clase llamada ThreadLocal
. Una variable ThreadLocal
tendrá una instancia por hilo. Oye, ¡resuelve nuestro problema! Vea más sobre cómo funciona , tendrá que comprender su propósito para que podamos continuar.
Hagamos nuestra INSTANCE
ThreadLocal
entonces. Modificar la clase de esta manera:
public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE.get();
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final ThreadLocal<DAOManager> INSTANCE;
static
{
ThreadLocal<DAOManager> dm;
try
{
dm = new ThreadLocal<DAOManager>(){
@Override
protected DAOManager initialValue() {
try
{
return new DAOManager();
}
catch(Exception e)
{
return null;
}
}
};
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
Me encantaría no hacer esto
catch(Exception e)
{
return null;
}
pero initialValue()
no puede lanzar una excepción. Oh, initialValue()
te refieres? Este método nos dirá qué valor tendrá la variable ThreadLocal
. Básicamente lo estamos inicializando. Entonces, gracias a esto ahora podemos tener una instancia por hilo.
Paso 5: Crea un DAO
Un DAOManager
no es nada sin un DAO. Entonces, al menos deberíamos crear un par de ellos.
Un DAO, abreviatura de "Objeto de acceso a datos" es un patrón de diseño que le da la responsabilidad de administrar las operaciones de la base de datos a una clase que representa una determinada tabla.
Para usar nuestro DAOManager
más eficientemente, definiremos un GenericDAO
, que es un DAO abstracto que contendrá las operaciones comunes entre todos los DAO.
public abstract class GenericDAO<T> {
public abstract int count() throws SQLException;
//Protected
protected final String tableName;
protected Connection con;
protected GenericDAO(Connection con, String tableName) {
this.tableName = tableName;
this.con = con;
}
}
Por ahora, eso será suficiente. Vamos a crear algunos DAO. Supongamos que tenemos dos POJO: First
y Second
, ambos con solo un campo String
llamado data
y sus getters y setters.
public class FirstDAO extends GenericDAO<First> {
public FirstDAO(Connection con) {
super(con, TABLENAME);
}
@Override
public int count() throws SQLException {
String query = "SELECT COUNT(*) AS count FROM "+this.tableName;
PreparedStatement counter;
try
{
counter = this.con.PrepareStatement(query);
ResultSet res = counter.executeQuery();
res.next();
return res.getInt("count");
}
catch(SQLException e){ throw e; }
}
//Private
private final static String TABLENAME = "FIRST";
}
SecondDAO
tendrá más o menos la misma estructura, simplemente cambiando TABLENAME
a "SECOND"
.
Paso 6: hacer que el gerente sea una fábrica
DAOManager
no solo debe servir para servir como un único punto de conexión. En realidad, DAOManager
debería responder esta pregunta:
¿Quién es el responsable de gestionar las conexiones a la base de datos?
Los DAO individuales no deberían gestionarlos, sino DAOManager
. Hemos respondido parcialmente a la pregunta, pero ahora no debemos permitir que nadie administre otras conexiones a la base de datos, ni siquiera los DAO. ¡Pero, los DAO necesitan una conexión a la base de datos! ¿Quién debería proporcionarlo? DAOManager
hecho! Lo que debemos hacer es crear un método de fábrica dentro de DAOManager
. ¡No solo eso, sino que DAOManager
también les dará la conexión actual!
Factory es un patrón de diseño que nos permitirá crear instancias de cierta superclase, sin saber exactamente qué clase será devuelta.
Primero, enum
un enumerando nuestras tablas.
public enum Table { FIRST, SECOND }
Y ahora, el método de fábrica dentro de DAOManager
:
public GenericDAO getDAO(Table t) throws SQLException
{
try
{
if(this.con == null || this.con.isClosed()) //Let''s ensure our connection is open
this.open();
}
catch(SQLException e){ throw e; }
switch(t)
{
case FIRST:
return new FirstDAO(this.con);
case SECOND:
return new SecondDAO(this.con);
default:
throw new SQLException("Trying to link to an unexistant table.");
}
}
Paso 7: poner todo junto
Estamos listos para irnos ahora. Pruebe el siguiente código:
DAOManager dao = DAOManager.getInstance();
FirstDAO fDao = (FirstDAO)dao.getDAO(Table.FIRST);
SecondDAO sDao = (SecondDAO)dao.getDAO(Table.SECOND);
System.out.println(fDao.count());
System.out.println(sDao.count());
dao.close();
¿No es elegante y fácil de leer? No solo eso, sino que cuando llamas a close()
, cierras cada conexión que usan los DAO. ¡¿Pero cómo?! Bueno, comparten la misma conexión, así que es natural.
Paso 8: afinar nuestra clase
Podemos hacer varias cosas a partir de ahora. Para garantizar que las conexiones se cierren y se devuelvan al grupo, haga lo siguiente en DAOManager
:
@Override
protected void finalize()
{
try{ this.close(); }
finally{ super.finalize(); }
}
También puede implementar métodos que encapsulen setAutoCommit()
, commit()
y rollback()
desde Connection
para que pueda manejar mejor sus transacciones. Lo que también hice fue que, en lugar de solo tener una Connection
, DAOManager
también tiene un Estado PreparedStatement
y un DAOManager
. Por lo tanto, al llamar a close()
también cierra ambos. ¡Una manera rápida de cerrar declaraciones y conjuntos de resultados!
¡Espero que esta guía pueda serle útil en su próximo proyecto!