Microsoft Access en Java (2)

Conectar con una base de datos de Microsoft Access mediante el controlador JDBC UCanAccess, emitir sentencias SQL a la base de datos y mostrar los resultados, ejecutar el programa desde línea de comandos junto con las dependencias, crear archivos de comando para ejecutar el programa; ejercicio de Java para consola

Desde Java 8 la conexión JDBC-ODBC ya no se incluye en Java. Existen algunos controladores JDBC propietarios para conectar con MS Access pero el proyecto UCanAccess está activo actualmente, es open source y proporciona un controlador JDBC generado sobre código de Jackcess.
UCanAccess funciona bien y es sencillo de utilizar. El dato más remarcable es el de las dependencias que requiere que en la versión actual 4.0.1 son: ucanaccess-4.0.1, hsqldb-2.3.1, jackcess-2.1.6, commons-logging-1.1.1 y commons-lang-2.6.

Jackcess a diferencia de UCanAccess es una librería de código Java diseñada para leer y escribir bases de datos de MS Access que no es un controlador JDBC sino una implementación directa de las características disponibles para interactuar con bases de datos de MS Access. Su licencia es del tipo Apache License.

Base de datos

Se utiliza una base de datos de MS Access con nombre 50empresas.accdb, tiene una única tabla llamada Contactos y esta tabla tiene 3 campos: Id (autonumérico) / Nombre (texto) / Telefono (texto).

Dependencias

Se necesita ucanaccess.jdbc.UcanaccessDriver, el controlador que permite acceder a MS Access desde Java, y la cadena apropiada para conectar con la base de datos, además de los 5 archivos JAR que son requeridos como dependencias. Se pueden descargar desde el repositorio Maven y también se proporcionan al final del artículo junto con el código fuente y la base de datos. Estos archivos han de ser llamados al ejecutar el programa desde la línea de comandos.

El usuario Gord Thompson tiene una respuesta en un hilo de Stackoverflow titulado «Manipulating an Access database from Java without ODBC» en la que explica con detalle este asunto de las dependencias de UCanAccess, merece la pena leerlo.

Ejecutar el programa con las dependencias

En este ejercicio el código fuente AccessEnJava.java está en la carpeta del disco D:\Java\JDBC y los 5 archivos JAR y la base de datos 50empresas.accdb están en la carpeta D:\Java\JDBC\lib. Para ejecutar el programa desde consola hay que incluir la clase principal, como se hace habitualmente, y también las dependencias que en este caso son archivos JAR. Esto es así porque estamos trabajando de la forma más simple, con editor de texto y línea de comandos. Si se trabaja con algún entorno integrado la adición de dependencias se realiza desde las herramientas del propio entorno y el resultado de la compilación lleva incorporado lo necesario para ser ejecutado.

Para llamar a las dependencias se usa el ejecutable java con el modificador -cp seguido de: ruta a la carpeta actual (D:\Java\JDBC en este ejemplo) + ruta a cada uno de los archivos JAR requeridos + clase principal. El comando es diferente dependiendo del sistema operativo.
En Windows hay que utilizar (va todo en una sola línea):

java -cp D:/Java/JDBC;D:/Java/JDBC/lib/ucanaccess-4.0.1.jar;D:/Java/JDBC/lib/commons-lang-2.6.jar;D:/Java/JDBC/lib/commons-logging-1.1.1.jar;D:/Java/JDBC/lib/hsqldb-2.3.1.jar;D:/Java/JDBC/lib/jackcess-2.1.6.jar AccessEnJava

Observa que las rutas están separadas por ; y no hay espacios entre ellas pero sí entre las rutas y el nombre de la clase que va al final. Como es engorroso escribir o copiar todo el texto cada vez que se ejecuta el programa, existe la opción de crear un archivo de comandos con extensión .bat desde el que iniciarlo de forma cómoda con doble clic. En este caso el texto del archivo de comandos ha de ser:

@echo off
cd D:/Java\JDBC\
java -cp D:/Java/JDBC;D:/Java/JDBC/lib/ucanaccess-4.0.1.jar;D:/Java/JDBC/lib/commons-lang-2.6.jar;D:/Java/JDBC/lib/commons-logging-1.1.1.jar;D:/Java/JDBC/lib/hsqldb-2.3.1.jar;D:/Java/JDBC/lib/jackcess-2.1.6.jar AccessEnJava
REM pause
REM exit

La línea REM pause se puede dejar así o descomentarla quitando REM, lo que hace es parar el flujo pidiendo pulsar una tecla para seguir.

El comando equivalente de Terminal en macOS sería (va todo en una línea):

java -cp .:./lib/ucanaccess-4.0.1.jar:./lib/commons-lang-2.6.jar:./lib/commons-logging-1.1.1.jar:./lib/hsqldb-2.3.1.jar:./lib/jackcess-2.1.6.jar AccessEnJava

Observa que el punto refiere a la carpeta actual y que el separador de rutas es : en lugar de ; como en Windows. Se puede generar un archivo de comandos con extensión .sh para que sea más cómoda la ejecución del programa:

#!/bin/bash
java -cp .:./lib/ucanaccess-4.0.1.jar:./lib/commons-lang-2.6.jar:./lib/commons-logging-1.1.1.jar:./lib/hsqldb-2.3.1.jar:./lib/jackcess-2.1.6.jar AccessEnJava

En este caso en lugar de doble clic hay que ejecutar el archivo .sh directamente en Terminal precedido de ./ seguido del nombre del archivo.

Importación de paquetes

Hay que importar los paquetes para la conexión con MS Access y el envío de comandos SQL además de la clase Scanner que permite recibir desde teclado (para detener el flujo del programa hasta que el usuario pulsa una tecla).

/ paquetes para la conexion con MS Access y comandos SQL
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.io.*;
// paquete para usar la clase Scanner que permite recibir desde teclado
import java.util.Scanner;

Se utilizan 3 variables para procesar la tarea: la conexión con la BD, la sentencia SQL y el resultado obtenido con las filas devueltas.

// variables conexion con la BD, sentencias SQL y filas obtenidas
Connection conectar = null;
Statement sentencia = null;
ResultSet resultado = null;
// variable para detectar pulsaciones de teclas, se usa para detener
// el flujo del programa hasta que el usuario pulse una tecla
Scanner s = new Scanner(System.in);

Conexión con la base de datos

Primero hay que registrar el controlador UCanAccess.

// registrar la clase JDBC driver de Ucanaccess
Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");

La cadena de conexión se construye con jdbc:ucanaccess:// seguido de la ruta al archivo. Para no dejar la ruta configurada como una constante se recurre al método getAbsolutePath que devuelve la ruta a la carpeta de trabajo.

// obtener la ruta absoluta a la carpeta de trabajo del proyecto
// se emplea el truco de crear nuevo archivo y obtener su ruta
String ruta = new File(".").getAbsolutePath();
// getAbsolutePath devuelve la ruta a la carpeta pero agrega un punto al final
// quitamos ese ultimo caracter con substring y length
ruta = ruta.substring(0, ruta.length()-1);
// y se agrega el nombre de la BD de MS Access
// se usa File.separatorChar que coloca \ o / dependiendo del sistema operativo
ruta = ruta + "lib" + File.separatorChar + "50empresas.accdb";
// Mensaje informativo
System.out.println ("Ruta a la base de datos: " + ruta + " \nPulsa una tecla para continuar...");
//String ruta = "D:/JDBC/50empresas.accdb";
String dbURL = "jdbc:ucanaccess://" + ruta;

La conexión se crea con la clase DriverManager a la que se le pasa como parámetro la ruta al origen de los datos.

// crear la conexion usando la clase DriverManager
conectar = DriverManager.getConnection(dbURL);

Pulsa una tecla para seguir…

Para obtener este efecto se importa la clase Scanner, se declara con el nombre s y se utiliza cuando hace falta instanciando una cadena nueva, cada vez con un nombre diferente, que recoge la variable s (la pulsación desde teclado).

Scanner s = new Scanner(System.in);
// ...
// se para hasta pulsar una tecla
String una = s.nextLine();
// ...
// se para hasta pulsar una tecla
String dos = s.nextLine();

Objetos de comando SQL

Los objetos de comando SQL (statement) tienen distintos métodos de ejecución:

  • ResultSet executeQuery() ejecuta una sentencia SQL y devuelve el ResultSet (tabla con las filas devueltas por una consulta SQL) generado por la consulta, se usa para leer el contenido de la tabla y mostrar los registros (sentencias SELECT)
  • int executeUpdate() ejecuta una sentencia SQL que ha de ser INSERT, UPDATE o DELETE, se usa para modificar el contenido de la tabla y devuelve el número de filas afectadas
  • boolean execute() ejecuta una sentencia SQL, es true si la instrucción devuelve un conjunto de resultados y false si devuelve un recuento de actualizaciones o no hay ningún resultado.

Aquí se crea la sentencia con el método createStatement de la conexión y el comando devuelve un ResultSet, la sentencia es del tipo SELECT por lo que el comando es del tipo executeQuery. Los registros se muestran recorriendo con un bucle while el ResultSet con su método getInt() al que se pasa el índice de cada columna de la tabla y devuelve el valor de esa celda.

// MOSTRAR TODOS LOS REGISTROS
// crear la sentencia SQL
sentencia = conectar.createStatement();
resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos");
// formatear la presentacion de los registros de la tabla
System.out.println("Id\tTel\u00e9fono\tNombre");
System.out.println("==\t========\t======");
while(resultado.next()) {
System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
}

Lo que se obtiene por pantalla en consola con estas primeras líneas de código es esto:

Ruta a la base de datos: D:\Java\JDBC\lib\50empresas.accdb
Pulsa una tecla para continuar...

Conexión abierta.
Se van a mostrar los 50 registros de la tabla.
SELECT Id, Nombre, Telefono FROM Contactos
Pulsa una tecla para continuar...

Id      Teléfono        Nombre
==      ========        ======
1       966521455       AEK Goup Inc.
2       944444102       ACE Hardware CO.
3       963325874       ADI Systems Inc.
4       986523365       Administaff Inc.
5       923656987       ADVO Inc.
6       956231487       Aeroquip - Vickers Inc.
7       933659214       LST IND. CO
8       956231487       Agway Inc.
9       912548758       Air Products and Chemicals Inc.
10      965235898       Airborne Freight Corp.
...

El ejercicio continúa con nuevas sentencias SQL y la presentación de los resultados en pantalla. Se prueban sentencias SELECT con BETWEEN (comando executeQuery), sentencias INSERT y UPDATE (comandos executeUpdate), etc. Al final se vacían los recursos utilizados (statement, ResultSet) y se cierra la conexión.

Código completo del archivo Java

// paquetes para la conexion con MS Access y comandos SQL
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.io.*;
// paquete para usar la clase Scanner que permite recibir desde teclado
import java.util.Scanner;

public class AccessEnJava {

    public static void main(String[] args) {

    // variables conexion con la BD, sentencias SQL y filas obtenidas
    Connection conectar = null;
    Statement sentencia = null;
    ResultSet resultado = null;
		// variable para detectar pulsaciones de teclas, se usa para detener
		// el flujo del programa hasta que el usuario pulse una tecla
		Scanner s = new Scanner(System.in);

    // registrar la clase JDBC driver de Ucanaccess
    try {
        Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");
    }
    // si la clase no se puede utilizar
    catch(ClassNotFoundException ex) {
         ex.printStackTrace();
    }

      try {
			// obtener la ruta absoluta a la carpeta de trabajo del proyecto
			// se emplea el truco de crear nuevo archivo y obtener su ruta
			String ruta = new File(".").getAbsolutePath();
			// getAbsolutePath devuelve la ruta a la carpeta pero agrega un punto al final
			// quitamos ese ultimo caracter con substring y length
			ruta = ruta.substring(0, ruta.length()-1);
			// y se agrega el nombre de la BD de MS Access
			// se usa File.separatorChar que coloca \ o / dependiendo del sistema operativo
			ruta = ruta + "lib" + File.separatorChar + "50empresas.accdb";
			System.out.println();
			System.out.println();
			// Mensaje informativo
			System.out.println ("Ruta a la base de datos: " + ruta + " \nPulsa una tecla para continuar...");
			// se para hasta pulsar una tecla
			String una = s.nextLine();
      //String ruta = "D:/JDBC/50empresas.accdb";
      String dbURL = "jdbc:ucanaccess://" + ruta;
			// Mensaje informativo
			System.out.println("Conexi\u00f3n abierta.\nSe van a mostrar los 50 registros de la tabla.");
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos");
      System.out.println ("Pulsa una tecla para continuar...");
			// se para hasta pulsar una tecla
			String dos = s.nextLine();
			//
      // crear la conexion usando la clase DriverManager
      conectar = DriverManager.getConnection(dbURL);
			//
			// MOSTRAR TODOS LOS REGISTROS
      // crear la sentencia SQL
      sentencia = conectar.createStatement();
			// Los objetos de comando (statement) SQL tienen distintos metodos de ejecucion:
      // - boolean execute() ejecuta una sentencia SQL, es true si la instrucción devuelve un conjunto 
      // de resultados y false si devuelve un recuento de actualizaciones o no hay ningún resultado.
      // - ResultSet executeQuery() ejecuta una sentencia SQL y devuelve el ResultSet generado por la consulta
			// se usa para leer el contenido de la tabla y mostrar los registros
      // - int executeUpdate() ejecuta una sentencia SQL que ha de ser SQL INSERT, UPDATE o DELETE
			// se usa para modificar el contenido de la tabla y devuelve el numero de filas afectadas
      resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos");
			// formatear la presentacion de los registros de la tabla
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
       while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
      }
			// MOSTRAR SOLO REGISTROS CON ID DEL 12 AL 24
			System.out.println();
			System.out.println ("Se van a mostrar los registros con Id del 40 al 50.");
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos Where Id BETWEEN 40 AND 50");
      System.out.println ("Pulsa una tecla para continuar...");
			String tres = s.nextLine();
			resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos Where Id BETWEEN 40 AND 50");
			// mostrar los registros del 12 al 24
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
       while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
			}
			// REEMPLAZAR LOS VALORES DE NOMBRE Y TELEFONO DE  UN REGISTRO
			System.out.println();
			System.out.println ("Se va a reemplazar Bay Networks Inc. 986523365 por Colubi Corp. 935469214.\nSe van a mostrar los registros con Id a partir de 40.");
      System.out.println("UPDATE Contactos SET Nombre ='Colubi Corp.', Telefono='935469214' WHERE Nombre ='Bay Networks Inc.'");
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println ("Pulsa una tecla para continuar...");
			String nueve = s.nextLine();
			sentencia.executeUpdate("UPDATE Contactos SET Nombre ='Colubi Corp.', Telefono='935469214' WHERE Nombre ='Bay Networks Inc.'");
			// mostrar todos los registros
			resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
       while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
			}
			// REVERTIR EL CAMBIO DE NOMBRE Y TELEFONO REALIZADO
			System.out.println();
			System.out.println ("Se va a revertir el cambio realizado.\nSe van a mostrar los registros con Id a partir de 40.");
      System.out.println("UPDATE Contactos SET Nombre ='Bay Networks Inc.', Telefono='986523365' WHERE Nombre ='Colubi Corp.');");
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println ("Pulsa una tecla para continuar...");
			String DIEZ = s.nextLine();
			sentencia.executeUpdate("UPDATE Contactos SET Nombre ='Bay Networks Inc.', Telefono='986523365' WHERE Nombre ='Colubi Corp.'");
			// mostrar todos los registros
			resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
      while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
			}
			// INSERTAR 15 REGISTROS NUEVOS
			System.out.println();
			System.out.println ("Se van a insertar 15 registros nuevos.\nSe van a mostrar los registros con Id a partir de 40.");
      String insertar = "INSERT INTO Contactos (Nombre, Telefono) VALUES ('TOP Group INC', '923652547');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Orion Capital Corp.', '900125458');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('IBM', '956985447');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('SCE Hardware Corp.', '945896325');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Faxter International Inc.', '936521452');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Lockheed Martin Corp.', '903056898');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Ray Networks Inc.', '985654125');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('MCI Communications Corp.', '900326587');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('DO Seidman LLP', '978563214');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Katerpillar Inc.', '932655096');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Data General Corp.', '955561023');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Gateway Inc.', '993201145');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('Hewlett-Packard Co.', '975556912');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('ICF Kaiser International Inc.', '990023654');" +
      "\nINSERT INTO Contactos (Nombre, Telefono) VALUES ('LDI Systems Inc.', '922569687');";
      System.out.println(insertar);
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println ("Pulsa una tecla para continuar...");
			String cuatro = s.nextLine();
			// comandos de insercion (insertan 15 registros nuevos en la tabla)
      //sentencia.executeUpdate(insertar);
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('TOP Group INC', '923652547')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Orion Capital Corp.', '900125458')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('IBM', '956985447')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('SCE Hardware Corp.', '945896325')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Faxter International Inc.', '936521452')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Lockheed Martin Corp.', '903056898')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Ray Networks Inc.', '985654125')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('MCI Communications Corp.', '900326587')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('DO Seidman LLP', '978563214')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Katerpillar Inc.', '932655096')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Data General Corp.', '955561023')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Gateway Inc.', '993201145')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('Hewlett-Packard Co.', '975556912')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('ICF Kaiser International Inc.', '990023654')");
			sentencia.executeUpdate("INSERT INTO Contactos (Nombre, Telefono) VALUES ('LDI Systems Inc.', '922569687')");
			// mostrar todos los registros
			resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
      while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
			}
			// BORRAR LOS REGISTROS INSERTADOS
			System.out.println();
			System.out.println ("Se van a eliminar los registros insertados.\nSe van a mostrar los registros con Id a partir de 40.");
      System.out.println("DELETE FROM Contactos WHERE Id > 50");
      System.out.println("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println ("Pulsa una tecla para continuar...");
			String seis = s.nextLine();
			// eliminar registros con Id del 31 al 50
			sentencia.executeUpdate("DELETE FROM Contactos WHERE Id > 50");
			// mostrar todos los registros
			resultado = sentencia.executeQuery("SELECT Id, Nombre, Telefono FROM Contactos Where Id > 39");
      System.out.println("Id\tTel\u00e9fono\tNombre");
      System.out.println("==\t========\t======");
      while(resultado.next()) {
          System.out.println(resultado.getInt(1) + "\t" + resultado.getString(3) + "\t" + resultado.getString(2));
			}
		}

      catch(SQLException ex){
          ex.printStackTrace();
      }
      finally {
          try {
              if(null != conectar) {
              // limpiar los recursos relacionados con la conexion
              resultado.close();
              sentencia.close();
              // cerrar la conexion
              conectar.close();
              System.out.println();
              // Mensaje informativo
              System.out.println("Conexi\u00f3n cerrada. \nPulsa una tecla para salir...");
              // se para hasta pulsar una tecla
              String ocho = s.nextLine();
              }
          }
          catch (SQLException ex) {
              ex.printStackTrace();
          }
        }
    }
}

Descarga del código fuente

Puedes descargar el código junto con la base de datos y las dependencias desde aquí.

Deja un comentario

Tu dirección de correo electrónico no será publicada.