El problema que viene a resolver este patrón es el de contar con diversas fuentes de datos (base de datos, archivos, servicios externos, etc). De tal forma que se encapsula la forma de acceder a la fuente de datos. Este patrón surge históricamente de la necesidad de gestionar una diversidad de fuentes de datos, aunque su uso se extiende al problema de ancapsular no sólo la fuente de datos, sino además ocultar la forma de acceder a los datos. Se trata de que el software cliente se centre en los datos que necesita y se olvide de cómo se realiza el acceso a los datos o de cual es la fuente de almacenamiento.
Las aplicaciones pueden utilizar el API JDBC para acceder a los datos de una base de datos relacional. Este API permite una forma estándar de acceder y manipular datos en una base de datos ralacional. El API JDBC permite a las aplicaciones J2EE utilizar sentencias SQL, que son el método estándar para acceder a tablas y vistas. La idea de este patrón es ocultar la fuente de datos y la complejidad del uso de JDBC a la capa de presentación o de negocio.
Un DAO define la relación entre la lógica de presentación y empresa por una parte y por otra los datos. El DAO tiene un interfaz común, sea cual sea el modo y fuente de acceso a datos.

Algunas características:
En el siguiente gráfico se muestran las interacciones entre los elementos del patrón. En este gráfico el TransferObject se denomina ValueObject. Puede observarse las llamadas que recibe y genera el DAO para una consulta y actualización de datos:

En el siguiente sencillo ejemplo tenemos dos tablas en nuestra base de datos. La tabla de clientes incluye:
En la tabla de ventas tenemos las ventas realizadas a cada cliente:
Vamos a representar estas tablas en clases que de manera informal se conocen como "beans de tabla". Antes de crear estas tablas vamos a ver su interface común (Bean.java):
package dao.bean;
public interface Bean {
//// Me indica si los objetos corresponden al mismo registro de base de datos (identidad de clave primaria)
public boolean esIgual( Bean bean );
}
A continuación escribimos los bean de tabla que implementan el interface anterior, empezamos por la calse Cliente.java:
/****************************************************************************************
* Bean de tabla "cliente"
***************************************************************************************/
public class Cliente implements Bean {
private String codigo = null;
private String nombre = null;
private String ape1 = null;
private String ape2 = null;
private Integer edad = null;
private Vector ventas = null;
public Cliente( String codigo, String nombre, String ape1, String ape2, Integer edad ) {
setCodigo( codigo );
setNombre( nombre );
setApe1( ape1 );
setApe2( ape2 );
setEdad( edad );
}
public Cliente(){}
public String getApe1() { return ape1; }
public void setApe1(String ape1) { this.ape1 = ape1; }
... Otros métodos set/get ...
//// Me indica si los objetos corresponden al mismo registro (identidad de clave primaria)
public boolean esIgual( Bean bean ) {
Cliente cli = (Cliente) bean;
if ( cli.getCodigo().equals( this.getCodigo()) )
return true;
return false;
}
public String toString() {
return (codigo + ", " + nombre + ", " + ape1 + ", " + ape2 + ", " + edad);
}
}
En Cliente.java se puede observar que uno de los atributos es un vector de ventas. La utilidad de este Vector es representar en modo "objetos" una relación 1:N de tablas (cliente y ventas) de la base de datos. Seguimos con la clase Venta.java:
****************************************************************************************
* Bean de tabla "venta"
***************************************************************************************/
public class Venta implements Bean {
Integer idVenta = null;
String codigo = null;
Float precio = null;
Float coste = null;
Cliente cliente = null;
public Venta(Integer idVenta, String codigo, Float precio, Float coste ) {
setIdVenta( idVenta );
setCodigo( codigo );
setPrecio( precio );
setCoste( coste );
}
public Venta() {}
public Float getCoste() { return coste; }
public void setCoste(Float coste) { this.coste = coste; }
... Otros métodos set/get ...
//// Me indica si los objetos corresponden al mismo registro (identidad de clave primaria)
public boolean esIgual( Bean bean ) {
Venta ven = (Venta) bean;
if ( ven.getIdVenta().intValue() == this.getIdVenta().intValue() )
return true;
return false;
}
public String toString() {
return (idVenta + ", " + codigo + ", " + precio + ", " + coste );
}
}
Hemos empezado por lo más sencillo, representar las tablas de nuestra base de datos. Dicho de otra forma proyectar el modelo relacional sobre un modelo de objetos. Ahora tenemos que implementar los DAOs, los componentes que encapsulan el acceso a la fuente de datos (la base de datos). Empezamos creando un interface (InterfaceDAO.java) que representa el comportamiento genérico de cualquier DAO:
package dao.accesoDatos;
import java.sql.SQLException;
import java.util.Vector;
import dao.bean.Bean;
public interface InterfaceDAO {
public int insert( Bean bean ) throws SQLException;
public int update( Bean bean, String condicion ) throws SQLException;
public Bean find( String codigo ) throws SQLException;
public Vector select( String condicion ) throws SQLException;
public int delete(String condicion) throws SQLException;
}
A continuación y a modo de resumen, una parte de DAOCliente.java. Se puede ver que implementa el interface anterior y que además hereda de DAOGeneral, una clase que contiene servicios comunes, como por ejemplo getConexion(), cerrarConexion(Connection), etc:
public class DAOCliente extends DAOGeneral implements InterfaceDAO {
/***********************************************************************************
* Inserta cliente (argumento bean)
***********************************************************************************/
public int insert( Bean bean ) throws SQLException {
int numFilas = 0;
Cliente cli = (Cliente) bean;
Connection con = getConexion();
String orden = "INSERT INTO CLIENTE VALUES (" +
(cli.getCodigo()==null? null: "'" + cli.getCodigo() + "' ") +
", " + (cli.getNombre()==null? null: "'" + cli.getNombre() + "' ") +
", " + (cli.getApe1()==null? null: "'" + cli.getApe1() + "' ") +
", " + (cli.getApe2()==null? null: "'" + cli.getApe2() + "' ") +
", " + cli.getEdad() + ")";
Statement sentencia = con.createStatement();
numFilas = sentencia.executeUpdate(orden);
sentencia.close();
cerrarConexion( con );
return numFilas;
}
....
El método insert() recibe el Transfer Object (bean) que vamos a insertar, devolviendo el número de registros insertados (uno si ha ido bien, 0 en caso de no inserción). En el método select() recibimos la condición (claúsula WHERE) y devuelve un vector cuyos elementos son los clientes que cumplen la condición. Podría también devolver un ArrayList, que resulta más eficiente, pero lo esencial es que este método, al igual que el anterior, oculta el uso de SQL y JDBC a la clase que la llama (presentación o BusinessObject):
public Vector select( String condicion ) throws SQLException {
Vector vecClientes = new Vector();
Cliente cli;
Connection con = getConexion();
//// Si la condición es null o vacia, no hay parte WHERE
String orden = "SELECT * FROM cliente c " +
(condicion==null || condicion.length()==0 ? "":"WHERE " + condicion) +
" ORDER BY c.ape1, c.ape2, c.nombre";
Statement sentencia = con.createStatement();
ResultSet rs = sentencia.executeQuery( orden );
while (rs.next()) {
cli = new Cliente( rs.getString("codigo"), rs.getString("nombre"),
rs.getString( "ape1" ), rs.getString( "ape2" ),
(rs.getString( "edad" )==null ? null : new Integer(rs.getInt( "edad" ))));
vecClientes.add( cli );
}
sentencia.close();
cerrarConexion( con );
return vecClientes;
}
En la siguiente figura se muestra la relación de las clases anteriores:

Aunque no resulta imprescindible, en proyectos de cierta envergadura necesitaremos una factoria de objetos DAO, que se responsabiliza de instanciar el DAO adecuado (en nuestro ejemplo DAOCliente.java o DAOVenta.java). Enlace al patrón factoria. En nuestro ejemplo hemos implementado una Factoria Simple (FactoriaDAO.java):
package dao.accesoDatos;
import java.io.*;
/***********************************************************************************
*
* Factoria que crea el DAO en función del argumento del método getDAO()
*
**********************************************************************************/
public class FactoriaDAO {
// true=Carga de Properties desde archivo
private static boolean propiedadesCargadas = false;
// Propiedades
private static java.util.Properties prop = new java.util.Properties();
/***********************************************************************************
* Crea y devuelve el DAO
**********************************************************************************/
public static InterfaceDAO getDAO( String nombre ) {
try {
Class clase = Class.forName( getClase( nombre ) ); // La clase se consigue leyendo del archivo properties
return (InterfaceDAO) clase.newInstance(); // Creo una instancia
}
catch (ClassNotFoundException e) { // No existe la clase
e.printStackTrace();
return null;
}
catch (Exception e) { // No puedo instanciar la clase
e.printStackTrace();
return null;
}
}
/***********************************************************************************
* Lee un archivo properties donde se indica la clase que debe ser instanciada
**********************************************************************************/
private static String getClase( String nombrePropiedad ) {
String nombreClase = null;
try {
//// Carga de propiedades desde archivo
if ( !propiedadesCargadas ) {
FileInputStream archivo = new FileInputStream( "src/dao/accesoDatos/propiedades.properties" );
prop.load( archivo ); // Cargo propiedades
propiedadesCargadas = true;
}
//// Lectura de propiedad
nombreClase = prop.getProperty( nombrePropiedad, "");
if ( nombreClase.length() == 0)
return null;
}
catch ( FileNotFoundException e) { // No se puede encontrar archivo
e.printStackTrace();
}
catch ( IOException e) { // Falla load()
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
return nombreClase;
}
}
Esta clase lee un archivo properties donde asociamos una clave con una clase DAO. Su contenido es sencillo:
cliente=dao.accesoDatos.DAOCliente venta=dao.accesoDatos.DAOVenta
El método getClase() recibe un String (la vlave: cliente o venta). Si no se ha cargado el archivo properties en el atributo 'Properties prop', entonces lee archivo y carga las propiedades en prop. getClase() devuelve el valor de la clave, por ejemplo, si recibe 'cliente' devuelve 'dao.accesoDatos.DAOCliente'. El método getDAO() llama a getClase() para saber la clase DAO que debe instanciar.
Un aspecto importante es lo que devuelve getDAO(): el tipo InterfaceDAO. Al devolver el interface, estamos actuando con bástante generalidad: instanciamos objetos concretos, un DAOCliente.java o un DAOVenta.java, pero lo esencial es que usamos como tipo de referencia el interface que ambas clases implementan. La factoria hace su trabajo de modo genérico o abstracto, ya que aunque tenga 15 clases de DAOs diferentes que instanciar, devuelve un tipo genérico, es decir, devuelve el interface que implementan todos los DAOs.
Vamos a ver un sencillo ejemplo de capa cliente, es decir, la capa (sea presentación o de negocio) que debe usar lo anterior (factoria, beans y DAOs). Este sencillo ejemplo destaca una de las ventajas de trabajar con los interface: obtenemos abstracción (y resuabilidad). Veamos el ejemplo:
condicion = Teclado.getCadena("Condición:"); // Obtengo condición por teclado
InterfaceDAO dao = FactoriaDAO.getDAO( nombreTabla ); // Obtengo el DAO de la factoria
Vector vec = dao.select( condicion ); // La Select devuelve Vector
Iterator it = vec.iterator();
while (it.hasNext()) {
bean = (Bean) it.next();
System.out.println( bean.toString());
}
La variable nombreTabla es un sencillo String que contiene la clave del archivo properties. De esta forma la factoria sabe la clase DAO que debe instanciar. Lo primero que interesa destacar es que el DAO instanciado y devuelto por la factoria lo manejamos por medio del tipo interface (InterfaceDAO): sea la que sea la clase instanciada usamos un tipo genérico (el interface que implemnetan los DAO). Esto nos permite actuar con un alto grado de generalidad: para cualquier tipo de DAO le mandamos el mismo mensaje: dao.select( condicion ). No nos vemos obligados a realizar un código para DAOCliente, otro para DAOVenta, etc; sino que con el mismo código manejamos diversos DAOs. Este es un ejemplo de patrón estrategia: el código es el mismo (unificado) y la estrategia (el comportamiento específico) depende de los objetos (DAOs) utilizados.
Un ejemplo final de abstracción es que los elementos del vector los manejamos con el tipo interface Bean (que implementan las clases 'bean'). Esto permite que les mande a todos el mismo mensaje, bean.toString(), y que cada bean (sea de la clase que sea) implemente su comportamiento específico.
Otro ejemplo de aplicación del patrón DAO con servlets. En este ejemplo las clases DAO han cambiado ligeramente para soportar un login y password diferentes en cada conexión.
Evidentemente los DAOs deben implementar los métodos del interface (InterfaceDAO) que declaran. Pero además pueden implementar otros métodos que no están en el interfaz (lo cual resta generalidad). En nuestro ejemplo hemos introducido un método que nos permite hacer un select de clientes con sus ventas correspondientes. Antes hemos visto que uno de los atributos del 'bean' Cliente.java es un vector de ventas:
public class Cliente implements Bean {
...
private Vector ventas = null;
...
Este vector nos permite representar en nuestro modelo de objetos la relación 1:N de nuestras tablas. El siguiente método de DAOCliente.java nos devuelve un vector de clientes y cada cliente contiene un vector de ventas:
/**********************************************************************************
* Consulta de clientes CON sus ventas
* Si condición es null o vacia, no se aplica en el WHERE
**********************************************************************************/
public Vector selectConVentas( String condicion ) throws SQLException {
Vector vecClientes = new Vector();
Venta ven;
Cliente cliAnterior = null, cliActual = null;
Connection con = getConexion();
//// Si la condición es null o vacia, no hay parte WHERE
String orden = "SELECT c.codigo, c.nombre, c.ape1, c.ape2, c.edad, v.id_venta, v.precio, v.coste FROM cliente c, venta v " +
" WHERE c.codigo = v.codigo " +
(condicion==null || condicion.length()==0 ? "":"AND " + condicion) +
" ORDER BY c.ape1, c.ape2, c.nombre";
Statement sentencia = con.createStatement();
ResultSet rs = sentencia.executeQuery( orden );
//// Recorremos el ResultSet y guardamos los clientes en un vector. Cada cliente tiene su vector de ventas
while (rs.next()) {
//// Obtengo cliente y venta
cliActual = new Cliente( rs.getString("c.codigo"), rs.getString("c.nombre"),
rs.getString( "c.ape1" ), rs.getString( "c.ape2" ),
(rs.getString( "c.edad" )==null ? null : new Integer(rs.getInt( "c.edad" ))));
ven = new Venta( rs.getInt("v.id_venta"), rs.getString("c.codigo"),
rs.getFloat("v.precio"), rs.getFloat("v.coste"));
//// SI ES NUEVO: Si es el primer cliente (cliAnterior==null) o es distinto que el anterior ...
if ( cliAnterior == null || !cliActual.esIgual( cliAnterior ) ) {
//// El anterior es el actual
cliAnterior = cliActual;
//// Inicializo vector de ventas del cliente actual y añado venta
cliActual.setVentas( new Vector());
cliActual.addVenta( ven );
//// Añado cliente actual al vector
vecClientes.add( cliActual );
}
//// SI NO ES NUEVO CLIENTE: simplemente añado venta al cliente
else {
//// Añado venta al cliente actual
cliAnterior.addVenta( ven );
}
}
sentencia.close();
cerrarConexion( con );
return vecClientes;
}