PARTE I - CONOCIENDO AL MONSTRUO
Cliente-Servidor: Aclarando Conceptos
Cliente-Servidor
Es un modelo basado en la cooperación e interacción de dos partes conocidas como servidor o back-end y cliente o front-end.
Aplicación Servidor
Es la aplicación que provee servicios, es la aplicación que provee los servicios; por ejemplo, los servidores de base de datos ofrecen servicios de persistencia de datos, podríamos mencionar a SQL Server, Oracle, MySQL, etc,
Aplicación Cliente
Es la aplicación que hace uso o consume los servicios de la Aplicación Servidor; por ejemplo, las aplicaciones comerciales como los sistemas de ventas y compras necesitan que sus datos persistan en el tiempo, para lo cual se recurren a los servidores de base de datos que ofrecen estos servicios.
Arquitectura Cliente-Servidor
El cliente se encuentra del lado del usuario y se define como un proceso consumidor de servicios, mientras que el servidor provee los servicios requeridos y se encuentra de manera remota al usuario y es transparente al cliente.
Los clientes deben estar en la red de la empresa, estas pueden ser redes locales, pero también es posible tener clientes remotos a través por ejemplo de una red VPN.
El API JDBC: Implementaciones
API JDBC
Esta API está compuesta por un conjunto de clases e interfaces que estandarizan el acceso a las bases de datos. Gracias a la utilización de JDBC, un programa Java puede acceder a cualquier base de datos sin necesidad de modificar la aplicación. Sin embargo, para que esto sea posible es necesario que el fabricante ofrezca un driver que cumpla la especificación JDBC.
Tipos de Driver
Tenemos cuatro tipos de driver:
- Tipo 1: JDBC-ODBC bridge driver
- Tipo 2: Native API/Partly Java Driver
- Tipo 3: Pure Java Driver
- Tipo 4: Native Protocol Java Driver
Tipo 1: JDBC-ODBC bridge driver
Permite utilizar drivers ODBC, su desventaja es que se debe instalar el driver ODBC en cada cliente.
Tipo 2: Native API/Partly Java Driver
Requiere que se instalen las librerías nativas en cada cliente.
Tipo 3: Pure Java Driver
Este tipo de driver traduce llamadas JDBC en un protocolo de red independiente del DBMS y luego, a un protocolo de DBMS.
Un Servidor JDBC, realiza la tarea de middleware, siendo capaz de conectar un cliente Java a diferentes bases de datos.
Tipo 4: Native Protocol Java Driver
El driver tipo 4 convierte llamadas JDBC directamente en el protocolo de red usado por el DBMS. Esto permite llamadas directas desde la máquina del cliente al servidor del DBMS, es una solución muy práctica y la más utilizada.
El API JDBC: Componentes Principales
Objeto: Connection
Establece la conexión con la base de datos y maneja las transacciones.
Objeto: Statement
Se utiliza para ejecutar sentencias sin parámetros.
Objeto: PreparedStatement
Se utiliza para ejecutar sentencias con parámetros.
Objeto: CallableStatement
Se utiliza para ejecutar procedimientos almacenados.
Objeto ResultSet
Se utiliza para procesar resultados.
Estrategias para programar transacciones
Controladas desde el cliente
En este caso desde la aplicación java programamos el inicio y fin de la transacción.
Controladas en la base de datos
En este caso programamos la transacción en el procedimiento almacenado.
El patrón DAO
Desarrollo Clásico
En el objeto de negocio se programa la lógica de negocio y la lógica de persistencia, haciendo difícil su mantenimiento.
Patrón DAO – División en Capas
El patrón DAO permite dividir en capas separadas la lógica de negocio y la lógica de persistencia.
Patrón DAO – Estructura
La capa de negocio hace uso de la capa de persistencia, y para que se comuniquen los datos se utilizan objetos Transfer Object (TO), también denominados Data Transfer Object (DTO).
Por ejemplo, si se consulta los datos de un cliente, el objeto DAO devolverá un objeto TO con los datos del cliente.
Si se consulta los artículos de una categoría, el objeto DAO devolverá una colección de objetos TO de tipo Artículo.
Patrón DAO – Diagrama se secuencia
En el diagrama de secuencia se puede apreciar la responsabilidad de cada capa, la capa Data Source puede ser un motor de base de datos, como por ejemplo Oracle, MySQL o SQL Server.
PARTE II - DOMINANDO AL MONSTRUO
Modelo de Datos
Creación del Esquema de Datos
El siguiente video ilustra la creación del esquema VENTAS.
Conexión con la Fuente de Datos
public class AccesoDB { private AccesoDB() { } public static Connection getConnection() throws SQLException{ Connection cn = null; try { // -------------------------------------------------- // Paso 1: Cargar el driver a memoria Class.forName("oracle.jdbc.OracleDriver").newInstance(); // Paso 2: Obtener el objeto Connection String url = "jdbc:oracle:thin:@localhost:1521:XE"; cn = DriverManager.getConnection(url, "ventas", "admin"); // -------------------------------------------------- } catch (SQLException e) { throw e; } catch(ClassNotFoundException e){ throw new SQLException("No se encontró el driver de la base de datos."); } catch(Exception e){ throw new SQLException("No se puede establecer la conexión con la base de datos."); } return cn; } }
Implementación de un CRUD
Para la demostración se implementa el CRUD para la tabla PRODUCTO.
Interfaz
public interface ProductoDao { /** * Transacción controlada desde el cliente * * @param prod */ void create1(Producto prod); /* * Transacción controlada en la Base de Datos */ void create2(Producto prod); ListreadAll(); Producto readForId(int id); List readForName(String name); void update(Producto prod); void delete(int id); }
Plantilla para las Consultas
Connection cn = null; try { cn = AccesoDB.getConnection(); // Consulta } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch (Exception e) { throw new RuntimeException("Error bla bla bla."); } finally { try { cn.close(); } catch (Exception e) { } }
Plantilla para Transacción Desde el Cliente
Connection cn = null; try { cn = AccesoDB.getConnection(); cn.setAutoCommit(false); // Inicia Tx // Proceso cn.commit(); // Confirmar Tx } catch (SQLException e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException(e.getMessage()); } catch (Exception e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException("Error bla bla bla."); } finally { try { cn.close(); } catch (Exception e) { } }
Plantilla para Transacción Desde la Base de Datos
Connection cn = null; try { cn = AccesoDB.getConnection(); cn.setAutoCommit( true ); // Ejecución de procedimiento almacenado } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch (Exception e) { throw new RuntimeException("Error bla bla bla."); } finally { try { cn.close(); } catch (Exception e) { } }
Implementación
public class OracleProductoDao implements ProductoDao { /** * Control de transacción desde el cliente * @param prod */ @Override public void create1(Producto prod) { Connection cn = null; try { // Variables Statement stm; PreparedStatement pstm; String query; int id; ResultSet rs; // Conexión cn = AccesoDB.getConnection(); // Iniciar la Tx cn.setAutoCommit(false); // Obtener el Id del producto query = "select sq_producto.NextVal id from dual"; stm = cn.createStatement(); rs = stm.executeQuery(query); rs.next(); id = rs.getInt("id"); // Insertar el producto query = "insert into producto" + "(idprod,idcat,nombre,precio,stock) " + "values(?,?,?,?,?) "; pstm = cn.prepareStatement(query); pstm.setInt(1, id); pstm.setInt(2, prod.getIdcat()); pstm.setString(3, prod.getNombre()); pstm.setDouble(4, prod.getPrecio()); pstm.setInt(5, prod.getStock()); pstm.executeUpdate(); // Retornar el id prod.setIdprod(id); // Confirmar Tx cn.commit(); // Cerrar objetos rs.close(); stm.close(); pstm.close(); } catch (SQLException e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException(e.getMessage()); } catch (Exception e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException("Error en el proceso crear producto."); } finally { try { cn.close(); } catch (Exception e) { } } } /** * Control de transacción en el procedimiento almacenado * @param prod */ @Override public void create2(Producto prod) { Connection cn = null; try { cn = AccesoDB.getConnection(); cn.setAutoCommit(true); String query = "{call usp_crea_producto(?,?,?,?,?)}"; CallableStatement cstm = cn.prepareCall(query); cstm.registerOutParameter(1, Types.INTEGER); cstm.setInt(2, prod.getIdcat()); cstm.setString(3, prod.getNombre()); cstm.setDouble(4, prod.getPrecio()); cstm.setInt(5, prod.getStock()); cstm.executeUpdate(); prod.setIdprod(cstm.getInt(1)); cstm.close(); } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch (Exception e) { throw new RuntimeException("No se puede crear el producto."); } finally { try { cn.close(); } catch (Exception e) { } } } @Override public ListreadAll() { List lista = new ArrayList (); Connection cn = null; try { cn = AccesoDB.getConnection(); String query = "select idprod, idcat, nombre, precio, stock " + "from producto "; Statement stm = cn.createStatement(); ResultSet rs = stm.executeQuery(query); while (rs.next()) { Producto o = rsToBean(rs); lista.add(o); } rs.close(); stm.close(); } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch(Exception e){ throw new RuntimeException("No se puede consultar la base de datos."); }finally { try { cn.close(); } catch (Exception e) { } } return lista; } @Override public Producto readForId(int id) { Producto o = null; Connection cn = null; try { cn = AccesoDB.getConnection(); String query = "select idprod, idcat, nombre, precio, stock " + "from producto " + "where idprod = ?"; PreparedStatement pstm = cn.prepareStatement(query); pstm.setInt(1, id); ResultSet rs = pstm.executeQuery(); if (rs.next()) { o = rsToBean(rs); } rs.close(); pstm.close(); } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch (Exception e) { throw new RuntimeException("Error en la lectura del producto."); } finally { try { cn.close(); } catch (Exception e) { } } return o; } @Override public List readForName(String name) { List lista = new ArrayList (); Connection cn = null; try { cn = AccesoDB.getConnection(); String query = "select idprod, idcat, nombre, precio, stock " + "from producto " + "where upper(nombre) like ?"; PreparedStatement pstm = cn.prepareStatement(query); name = "%" + name.toUpperCase() + "%"; pstm.setString(1, name); ResultSet rs = pstm.executeQuery(); while (rs.next()) { Producto o = rsToBean(rs); lista.add(o); } rs.close(); pstm.close(); } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } catch(Exception e){ throw new RuntimeException("No se puede consultar la base de datos."); } finally { try { cn.close(); } catch (Exception e) { } } return lista; } @Override public void update(Producto prod) { Connection cn = null; try { // Variables PreparedStatement pstm; String query; int filas; // Conexión cn = AccesoDB.getConnection(); // Iniciar la Tx cn.setAutoCommit(false); // Insertar el producto query = "update producto set idcat=?, nombre=?, precio=?, " + "stock=? where idprod = ? "; pstm = cn.prepareStatement(query); pstm.setInt(1, prod.getIdcat()); pstm.setString(2, prod.getNombre()); pstm.setDouble(3, prod.getPrecio()); pstm.setInt(4, prod.getStock()); pstm.setInt(5, prod.getIdprod()); filas = pstm.executeUpdate(); if(filas == 0){ throw new SQLException("Producto no existe."); } // Confirmar Tx cn.commit(); // Cerrar objetos pstm.close(); } catch (SQLException e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException(e.getMessage()); } catch (Exception e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException("Error en el proceso crear producto."); } finally { try { cn.close(); } catch (Exception e) { } } } @Override public void delete(int id) { Connection cn = null; String query; PreparedStatement pstm; ResultSet rs; int cont; try { cn = AccesoDB.getConnection(); cn.setAutoCommit(false); // Verificar si el producto rgistra ventas query = "select count(*) cont from detalle where idprod = ?"; pstm = cn.prepareStatement(query); pstm.setInt(1, id); rs = pstm.executeQuery(); rs.next(); cont = rs.getInt("cont"); rs.close(); pstm.close(); if (cont > 0) { throw new SQLException("El producto registra ventas, no se puede eliminar."); } // Eliminar el producto query = "delete from producto where idprod=?"; pstm = cn.prepareStatement(query); pstm.setInt(1, id); int filas = pstm.executeUpdate(); if (filas == 0) { throw new SQLException("Código de producto no existe, " + "posiblemente fue eliminado por otro usuario."); } cn.commit(); pstm.close(); } catch (SQLException e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException(e.getMessage()); } catch (Exception e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException("Error al tratar de eliminar el producto."); } finally { try { cn.close(); } catch (Exception e) { } } } private Producto rsToBean(ResultSet rs) throws SQLException { Producto o = new Producto(); o.setIdprod(rs.getInt("idprod")); o.setIdcat(rs.getInt("idcat")); o.setNombre(rs.getString("nombre")); o.setPrecio(rs.getDouble("precio")); o.setStock(rs.getInt("stock")); return o; } }
Video
El siguiente video muestra el funcionamiento del CRUD de la tabla PRODUCTO.
Programación de Procesos de Negocio
Aclarando Conceptos
- Esta compuesto por varias operaciones que deben trabajar como una sola unidad indivisible.
- Cada proceso corresponde a un servicio de la capa de negocio.
- La lógica de persistencia se debe manejar como una transacción y corresponde a un servicio de la capa DAO.
- Por ejemplo, grabar una venta, es un proceso que involucra varias operaciones que se deben ejecutar como una unidad indivisible.
Interfaz
public interface VentasDao { void grabarVenta( VentaDto ventaDto); }
Implementación
public class OracleVentasDao implements VentasDao{ @Override public void grabarVenta(VentaDto ventaDto) { Connection cn = null; try { cn = AccesoDB.getConnection(); // Inicindo la Tx cn.setAutoCommit(false); // Obtener el idventa String query = "select sq_venta.nextval id from dual"; Statement stm = cn.createStatement(); ResultSet rset = stm.executeQuery(query); rset.next(); int idventa = rset.getInt("id"); // Grabar venta query = "insert into venta(IDVENTA,CLIENTE,IDEMP," + "FECHA,IMPORTE) values(?,?,?,sysdate,?)"; PreparedStatement pstm = cn.prepareStatement(query); pstm.setInt(1, idventa); pstm.setString(2, ventaDto.getCliente()); pstm.setInt(3, ventaDto.getIdEmpl()); pstm.setDouble(4, ventaDto.getImporte()); pstm.executeUpdate(); // Grabar los detalles query = "insert into detalle(IDVENTA,IDPROD,CANT," + "PRECIO,SUBTOTAL) values(?,?,?,?,?)"; pstm = cn.prepareStatement(query); PreparedStatement pstmLeerStock = cn.prepareStatement("select stock from producto where idprod = ? for update"); PreparedStatement pstmActStock = cn.prepareStatement("update producto set stock = ? where idprod = ?"); int stock; for ( VentaItem item: ventaDto.getDetalle()) { // Verificar stock pstmLeerStock.setInt(1, item.getIdprod()); ResultSet rs = pstmLeerStock.executeQuery(); rs.next(); stock = rs.getInt("stock"); rs.close(); if(stock < item.getCant()){ throw new SQLException("No existe stock suficiente de " + item.getNombre() + "."); } // Actualizar stock pstmActStock.setInt(1, stock - item.getCant()); pstmActStock.setInt(2, item.getIdprod()); pstmActStock.executeUpdate(); // Grabar detalle pstm.setInt(1, idventa); pstm.setInt(2, item.getIdprod()); pstm.setInt(3, item.getCant()); pstm.setDouble(4, item.getPrecio()); pstm.setDouble(5, item.getSubtotal()); pstm.executeUpdate(); } // Grabar pagos /* Falta implementar */ // confirmar Tx cn.commit(); ventaDto.setIdventa(idventa); } catch (SQLException e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException(e.getMessage()); } catch (Exception e) { try { cn.rollback(); } catch (Exception e1) { } throw new RuntimeException("Error en el proceso Grabar Venta."); } finally { try { cn.close(); } catch (Exception e) { } } } }
Video
El siguiente video muestra el funcionamiento del proceso de venta.
DESCARGAR PROYECTO
CODIGO FUENTE
En esta sección te presento un video de una aplicación CLIENTE-SERVIDOR.
Tú tienes acceso al código fuente de esta aplicación, después del video tienes el enlace.