Resumen Seminario Java Cliente-Servidor

SEMINARIO CLIENTE - SERVIDOR CON JAVA
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);

  List readAll();

  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 List readAll() {
        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.



SQL Server - Agrupando datos

SQL SERVER - AGRUPANDO DATOS
Introducción
Muchas veces tenemos la necesidad de presentar reportes con datos estadísticos simples, como por ejemplo con respecto al resultado de una evaluación:
  • Cuantos alumnos tomaron la evaluación
  • La nota mayor.
  • La nota menor.
  • La nota promedio.
  • La cantidad de aprobados
Estos cálculos se pueden ampliar a todas las evaluaciones de un curso, o tal vez aplicarlos a todas las evaluaciones de todos los cursos.
También se puede aplicar a otros campos, por ejemplo una tienda por departamentos podría necesitar información estadística sobre las preferencias de sus clientes para aplicar un marketing personalizado, el área de compras necesita cierta información para poder negociar con los proveedores, y así podemos encontrar muchos casos en los que se necesita información estadística para tomar mejores decisiones.
Para obtener esta información directamente de la base de datos debemos aplicar funciones agregadas y agrupamiento de datos en la sentencia SELECT.
Tabla de Muestra
Para los ejemplos utilizaremos la siguiente tabla:
TABLA: NOTAS
Curso Alumno Practica Nota
Java Pedro P1 16
Java Karla P1 12
Java Doris P1 13
Oracle Manuel P1 15
Oracle Pedro P1 18
Oracle Karla P1 10
Oracle Doris P1 13
Java Manuel P2 14
Java Pedro P2 17
Java Karla P2 15
Java Doris P2 16
Funciones Agregadas
Las funciones agregadas permiten realizar cálculos estadístico, se aplican sobre las columnas de una tabla o expresiones a través de todas las filas de una tabla.
Las más utilizadas son:
  • COUNT: Contar filas o valores no nulos.
  • MAX: Obtener el valor máximo de una columna.
  • MIN: Obtener el valor mínimo de una columna.
  • SUM: Obtener la suma de los valores de una columna, no considera los nulos.
  • AVG: Obtener el promedio de los valores de una columna, no considera los nulos
Ejemplo 1
Cuantos alumnos han rendido la práctica 1 de Java.
select COUNT(Alumno) Alumnos
from dbo.NOTAS
where Curso = 'Java' and Practica = 'P1';
GO
El resultado es el siguiente:
Alumnos
-----------
3
Ejemplo 2
Cuantos alumnos han aprobado la práctica 1 de Oracle.
select COUNT(Alumno) Alumnos
from dbo.NOTAS
where Curso = 'Oracle'
and Practica = 'P1'
and nota >= 12;
GO
El resultado es el siguiente:
Alumnos
-----------
3
GROUP BY
La cláusula GROUP BY permite agrupar datos y obtener datos estadísticos de cada grupo.
Cada grupo se resume en una sola fila.
Ejemplo 3
Obtener la cantidad de alumnos de cada curso.
select Curso, COUNT(distinct Alumno) Alumnos
from dbo.NOTAS
group by Curso;
GO
La cláusula DISTINCT se utiliza para que no cuente los valores duplicados. El resultado es el siguiente:
Curso           Alumnos
--------------- -----------
Java            4
Oracle          4
Ejemplo 4
Cuántos alumnos han rendido cada práctica por cada uno de los cursos.
select Curso, Practica, COUNT(Alumno) Alumnos
from dbo.NOTAS
group by Curso, Practica
order by 1, 2;
GO
La cláusula ORDER BY se utiliza para ordenar el resultado, en este se está ordenando por las dos primeras columnas. El resultado es el siguiente:
Curso           Practica Alumnos
--------------- -------- -----------
Java            P1       3
Java            P2       4
Oracle          P1       4
HAVING
La cláusula HAVING se utiliza para hacer filtro de grupos, por ejemplo, se necesita saber quiénes son los alumnos con promedio mayor o igual a 18, o tal vez, se necesita saber el cliente que más consume con tarjeta VISA.
Ejemplo 5
Que alumnos han rendido solo dos prácticas.
select Alumno, COUNT(Practica) Practicas
from dbo.NOTAS
group by Alumno
having COUNT(Practica) = 2;
GO
Las funciones que se utilicen en la cláusula SELECT son las que debemos utilizar en la cláusula HAVING. El resultado es el siguiente:
Alumno          Practicas
--------------- -----------
Manuel          2
Video
A continuación tienes un video tutorial donde te explico todos estos temas.
CÓDIGO 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.