Introducción
Hablar acerca de persistencia de datos con Sqlite, es considerar que la mayoría de aplicaciones requieren almacenar información. El objetivo puede ser la toma de decisiones, como es el caso de programas empresariales; o para mantener persistente una configuración de la misma aplicación. Podríamos requerir que esta información se almacene al lado del cliente o del servidor. En este artículo veremos cómo persistir nuestro datos del lado del cliente utilizando SQLite con Java. Ahora que sabemos qué es SQLite y hemos dado nuestros primeros pasos con Maven, vamos a crear un pequeño proyecto en Java. A este proyecto lo nombraremos “jsqlite”. En él podremos hacer persistencia de datos con Sqlite implementando o utilizando JDBC puro.
El proyecto “jsqlite” estará desarrollado en Java y tendrá las siguientes características:
- Portable
- Lo podremos ejecutar desde una terminal
- Creará la base de datos en el caso que no existiera.
- Creará una tabla en el caso que no existiera.
- También podremos agregar, listar, borrar y actualizar registros.
- Está basado en patrones de diseño MVC y Singleton.
¿Qué es JDBC?
Java Database Connectivity es una API de Java con una colección de interfaces y clases. Nos ayuda a que los programas Java accedan a sistemas de gestión de bases de datos.
Las aplicaciones desarrolladas en Java, mediante las interfaces y clases estándar de JDBC, pueden hacer consultas escritas en el lenguaje de consulta estructurada (SQL). ¿Quieres saber más?
Desarrollo de jsqlite (Nuestro Proyecto).
Iniciamos el proyecto de persistencia de datos con Sqlite.
Iniciamos creando la estructura de directorios base. Utilizando Maven digitamos desde la terminal la siguiente instrucción:
mvn archetype:generate -DgroupId=ni.jug -DartifactId=jsqlite -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Nos quedará la siguiente estructura de directorios:
. ├── pom.xml └── src ├── main │ └── java │ └── ni │ └── jug │ └── App.java └── test └── java └── ni └── jug └── AppTest.java
Crearemos las carpetas controller, view y model dentro de la carpeta main/java/ni/jug/
mkdir main/java/ni/jug/util mkdir main/java/ni/jug/controller mkdir main/java/ni/jug/view mkdir main/java/ni/jug/model
Modificación el pom.xml
Modificamos el archivo pom.xml, agregamos la dependencia para el driver de SQLite. También agregamos el plugin maven-assembly-plugin para la construcción del jar y agregamos la propiedad para que al momento de compilar Maven utilice la versión de Java 11. Nos quedará de la siguiente manera:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>ni.jug</groupId> <artifactId>jsqlite</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>jsqlite</name> <url>http://maven.apache.org</url> <dependencies> <!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.28.0</version> </dependency> </dependencies> <build> <plugins> <!-- Configuracion del plugin para contruccion del jar --> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.1</version> <configuration> <!-- Le decimos a maven que agrege en el manifiesto cual es la clase que contiene el metodo main --> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>ni.jug.App</mainClass> </manifest> </archive> <!-- Le decimos a maven que incluya las dependencia en el jar --> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>assemble-all</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> </project>
Conexión con la base de datos.
Vamos a crear un clase con el nombre ConnectionBD y estará ubicada en main/java/ni/jug. Esta clase hará la conexión a la base de datos. Aunque SQLite soporta que múltiples procesos lean la base de datos, solo puede haber un proceso de escritura, por lo utilizaremos el patrón de diseño Singleton para evitar múltiples conexiones a la base de datos.
package ni.jug; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * Establece la conexion con la base de datos y nos devuelve la conexion por el metodo * @author gacs */ public class ConnectionBD { private static ConnectionBD connectionBD=null; //inilicializamos la clase de conexion private final String strUrl; //Almacena la direccion de la base de datos private final String strDriver ; //Almacena el driver de conexion private static Connection conn=null; private ConnectionBD() { this.strDriver="org.sqlite.JDBC"; //Driver de conexion strUrl="jdbc:sqlite:var/db/mybd.db"; //Parametros de conexion de conexion setConnection(); //Establece la conexion y la almacena en la propiedad conn } public static Connection getConnection(){ if(connectionBD==null) connectionBD=new ConnectionBD(); return conn; } /** * metodo de conexion con la base de datos * Almacena la conexion en la propiedad conn */ private void setConnection() { try { Class.forName(strDriver); conn=DriverManager.getConnection(strUrl); }catch(ClassNotFoundException | SQLException e){ System.err.println(e); } } }
Crearemos la carpeta ../var/db dentro la carpeta donde hemos creado el proyecto, al mismo nivel de la carpeta src y del archivo pom.xml. La estructura de directorio se verá así:
. ├── src │ ├── main │ │ └── java │ │ └── ni │ │ └── jug │ │ ├── controller │ │ ├── model │ │ ├── util │ │ └── view │ └── test │ └── java │ └── ni │ └── jug └── var └── db
Persistiendo datos con el patrón de diseño MVC.
Modelo
Crearemos una clase que nos servirá de modelo. Esta clase tendrá el nombre de Persona y estará ubicada en la carpeta main/java/ni/jug/model. A continuación el código que contiene la clase Persona:
package ni.jug.model; /** * Clase modelo Persona * @author gacs * */ public class Persona { private int id; private String nombre; private String apellido; private String sexo; public Persona() { } public Persona(int id, String nombre, String apellido, String sexo) { this.id = id; this.nombre = nombre; this.apellido = apellido; this.sexo = sexo; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNombre() { return nombre; } public String getNombreCompleto() { return nombre+" "+apellido; } public void setNombre(String nombre) { this.nombre = nombre; } public String getApellido() { return apellido; } public void setApellido(String apellido) { this.apellido = apellido; } public String getSexo() { return sexo; } public void setSexo(String sexo) { this.sexo = sexo; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Persona other = (Persona) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "[ "+id+" "+ nombre +" "+apellido + " "+ sexo + "]"; } }
Vistas
Crearemos ahora las clases que nos darán la vista de los datos, como también las vistas que interactúan con el usuario. Estas clases estarán ubicadas en la carpeta main/java/ni/jug/view. Las clases que utilizaremos como vista son:
- NewPersona: Esta clase se utiliza para que el usuario agregue un nuevo registro en la tabla persona.
- ListPersona: Lista los registro de la tabla persona.
- EditPersona: Con esta clase podremos editar y modificar un registro de la tabla persona.
- DeletePersona: Con esta clase podremos borrar un registro almacenado en la tabla persona.
NewPersona.
package ni.jug.view; import java.util.Scanner; import ni.jug.model.Persona; import util.Input; /** * Agrega registro a la tabla * @author gacs * */ public class NewPersona { private Persona pm; public NewPersona(Persona pm) { this.pm = pm; } /** * Este metodo es llamado por el controlador */ public void setRegister() { Scanner reg = Input.getInstance(); System.out.println( "Nombre:" ); pm.setNombre(reg.next()); System.out.println( "Apellido:" ); pm.setApellido(reg.next()); System.out.println( "Sexo:" ); pm.setSexo(reg.next()); } }
ListPersona.
package ni.jug.view; import java.io.IOException; import java.util.List; import ni.jug.model.Persona; /** * Lista los registro de la tabla * @author gacs * */ public class ListPersona { public void sayRegisters(List a) { System.out.println(" Id "+" -- Nombre -- "+" -- Apellido -- "+" - Sexo - "); a.forEach(p ->{ System.out.println(p.toString()); }); try { System.out.println("\n\n Presione ENTER.."); System.in.read(); } catch(IOException exe) { System.out.printf("Error?"); } } public void sayRegister(Persona a) { System.out.println(" Id "+" -- Nombre -- "+" -- Apellido -- "+" - Sexo - "); System.out.println(a.toString()); } }
EditPersona.
package ni.jug.view; import java.util.Scanner; import ni.jug.model.Persona; import util.Input; public class EditPersona { public int getRegister() { int register; Scanner r= Input.getInstance(); System.out.print("Digite el Id del registro a modificar :"); register=r.nextInt(); return register; } public void postRegister(Persona a) { Scanner r= Input.getInstance(); System.out.println("Registro a modificar"); System.out.println("Id :"+a.getId()); System.out.println("Apellido :"+a.getApellido()); a.setApellido(r.next()); System.out.println("Nombre :"+a.getNombre()); a.setNombre(r.next()); System.out.println("Sexo :"+a.getSexo()); a.setSexo(r.next()); } }
DeletePersona.
package ni.jug.view; import java.util.Scanner; import ni.jug.model.Persona; import util.Input; public class DeletePersona { public int getRegister() { int register; Scanner r= Input.getInstance(); System.out.println("Digite el registro a eliminar"); register=r.nextInt(); return register; } public boolean postRegister(Persona a) { Scanner r= Input.getInstance(); System.out.println("Se borra el registro"); System.out.println("Id :"+a.getId()); System.out.println("Nombre :"+a.getNombreCompleto()); System.out.println("Sexo :"+a.getSexo()); System.out.print("Desear eliminar este registro ?[S/N]"); String bln=r.next(); return bln.equalsIgnoreCase("S"); } }
Clase util Input.
Vamos a crear una clase llama Input, la cual estará en la carpeta main/java/ni/jug/util . Esta clase devuelve la clase Scanner, la cual es utilizada por las vistas para interactuar con el usuario. El contenido de la clase es el siguiente:
package ni.jug.util; import java.util.Scanner; /** * Clase de utilidad para las vistas * aplicado bajo el modelo singleton * @author gacs */ public class Input { private static Scanner input=null; private Input() { input=new Scanner(System.in); } public static Scanner getInstance() { if(input==null) new Input(); return input; } }
Controlador.
Ya tenemos el modelo y las vistas de de nuestro proyecto. Ahora vamos a crear nuestra clase controladora, la que nos hará la persistencia de los datos que suministró el usuario en las vista a través del modelo. Esta clase tendrá el nombre de PersonaController y estará ubicado en la carpeta main/java/ni/jug/controller, y tendrá el siguiente código:
package ni.jug.controller; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import ni.jug.ConnectionBD; import ni.jug.model.Persona; import ni.jug.view.DeletePersona; import ni.jug.view.EditPersona; import ni.jug.view.ListPersona; import ni.jug.view.NewPersona; /** * Clase de control para la tabla persona * @author gacs */ public class PersonaController { private final String SQLINSERT="INSERT INTO PERSONA VALUES(NULL,?,?,?)"; private final String SQLQUERY="SELECT Id,Nombre,Apellido,Sexo FROM PERSONA"; private final String SQLQUERYONE=SQLQUERY+" WHERE Id=?"; private final String SQLDELETE="DELETE FROM PERSONA WHERE Id=?"; private final String SQLUPDATE="UPDATE PERSONA SET Nombre=?, Apellido=?,Sexo=? WHERE Id=?"; private Connection conn; public PersonaController() { initController(); } /** * Instancia la conexion, crea la tabla si esta no existe */ private void initController() { conn=ConnectionBD.getConnection(); PreparedStatement pst=null; String sqlCreate="CREATE TABLE IF NOT EXISTS PERSONA (" + " Id integer PRIMARY KEY autoincrement," + " Nombre varchar ( 25 ) NOT NULL," + " Apellido varchar ( 25 ) NOT NULL," + " Sexo char NOT NULL" + " )"; try { pst=conn.prepareStatement(sqlCreate); pst.execute(); }catch( SQLException e){ System.err.println(e); }finally { try { if(pst!=null) pst.close(); }catch( SQLException e){ System.err.println(e); } } } /** * llama a la vista NewPersona para que el usuario agregue los valores a los campos * luego estos valores se agregan a la tabla */ public void addRegister() { Persona persona=new Persona(); NewPersona view =new NewPersona(persona); view.setRegister(); PreparedStatement pst=null; try { pst=conn.prepareStatement(SQLINSERT); pst.setString(1, persona.getNombre()); pst.setString(2, persona.getApellido()); pst.setString(3, persona.getSexo()); pst.executeUpdate(); }catch( SQLException e){ System.err.println(e); }finally { try { if(pst!=null) pst.close(); }catch( SQLException e){ System.err.println(e); } } } /** * Extrae los registro de la tabla y los visualiza en pantalla por medio de la vista * ListPersona */ public void getRegisters() { List personas=new ArrayList<>(); PreparedStatement pst=null; ResultSet rs=null; try { pst=conn.prepareStatement(SQLQUERY); rs=pst.executeQuery(); while(rs.next()) { personas.add(new Persona( rs.getInt("Id"), rs.getString("Nombre"), rs.getString("Apellido"), rs.getString("Sexo") )); } }catch( SQLException e){ System.err.println(e); }finally { try { if(pst!=null) pst.close(); if(rs!=null) rs.close(); }catch( SQLException e){ System.err.println(e); } } ListPersona view =new ListPersona(); view.sayRegisters(personas); } private Persona getRegister(int a) { PreparedStatement pst=null; ResultSet rs=null; Persona persona=null; try { pst=conn.prepareStatement(SQLQUERYONE); pst.setInt(1, a); rs=pst.executeQuery(); if(rs.next()) { persona=new Persona( rs.getInt("Id"), rs.getString("Nombre"), rs.getString("Apellido"), rs.getString("Sexo") ); } }catch( SQLException e){ System.err.println(e); }finally { //Nos aseguramos que se cierre el PreparedStatement y ResultSet al finalizar el metodo try { if(pst!=null) pst.close(); if(rs!=null) rs.close(); }catch( SQLException e){ System.err.println(e); } } return persona; } /** * Borra un registro * llama a la vista DeletePersona * con el metodo get determina el registro que quiere eliminar de la tabla * con el metodo post visualiza el registro a eliminar y pide confirmacion para borrar * */ public void deleteRegister() { DeletePersona dp=new DeletePersona(); Persona persona=null; int reg; PreparedStatement pst=null; do { reg=dp.getRegister(); persona=getRegister(reg); if(persona.getId()!=reg) { System.out.println("Registro no encontrado!!!"); } }while(persona.getId()!=reg); if(dp.postRegister(persona)) { try { pst=conn.prepareStatement(SQLDELETE); pst.setInt(1, persona.getId()); pst.execute(); }catch( SQLException e){ System.err.println(e); } finally { //Nos aseguramos que se cierre el PreparedStatement al finalizar el metodo try { if(pst!=null) pst.close(); }catch( SQLException e){ System.err.println(e); } } } } public void editRegister() { EditPersona edit =new EditPersona(); Persona persona=null; int reg; PreparedStatement pst=null; do { reg=edit.getRegister(); persona=getRegister(reg); if(persona.getId()!=reg) { System.out.println("Registro no encontrado!!!"); } }while(persona.getId()!=reg); //Envia el registro para ser modificado edit.postRegister(persona); try { pst=conn.prepareStatement(SQLUPDATE); pst.setString(1, persona.getNombre()); pst.setString(2, persona.getApellido()); pst.setString(3, persona.getSexo()); pst.setInt(4, persona.getId()); pst.executeUpdate(); }catch( SQLException e){ System.err.println(e); } //Nos aseguramos que se cierre el PreparedStatement al finalizar el metodo finally { try { if(pst!=null) pst.close(); }catch( SQLException e){ System.err.println(e); } } } }
Clase principal (main class)
Para terminar el desarrollo de nuestro proyecto vamos a modificar la clase App.java, la cual se encuentra en la carpeta main/java/ni/jug. Esta es la primera clase que se iniciará en nuestra aplicación, por lo que en su contenido tendrá el método main. El código de esta clase sea el siguiente:
package ni.jug; import java.sql.SQLException; import java.util.Scanner; import ni.jug.controller.PersonaController; import ni.jug.util.Input; /** * class main * @author gacs */ public class App { public App() { menus(); } public static void main( String[] args ) { App app = new App(); System.out.println("Termino"); } /** * Metodo que proporciona el menu de la aplicacion */ private void menus() { Scanner menu; menu=Input.getInstance(); PersonaController pc = new PersonaController(); byte op=0; do { System.out.println( "-- Persistencia con SQLite --" ); System.out.println( "1. Ingresar" ); System.out.println( "2. Listar" ); System.out.println( "3. Borrar" ); System.out.println( "4. Editar" ); System.out.println( "0. Salir de la aplicacion" ); op=menu.nextByte(); switch (op) { case 0: System.out.println( "Saliendo del sistema" ); break; case 1: pc.addRegister(); break; case 2: pc.getRegisters(); break; case 3: pc.deleteRegister(); break; case 4: pc.editRegister(); break; default: System.out.println( "Solo puede eligir una de las opciones del menu" ); break; } }while(op!=0); menu.close(); try { ConnectionBD.getConnection().close(); }catch( SQLException e){ System.err.println(e); } } }
Hasta aquí ya tenemos finalizado nuestro proyecto, cumpliendo lo básico para persistir nuestros datos en base de datos SQLite por medio de JDBC.
Compilación y ejecución.
Compilando el proyecto.
Para terminar con el desarrollo de nuestro pequeño proyecto de persistencia de datos en SQLite, procederemos con la compilación. Para ello, nos trasladamos a la carpeta que contiene nuestro proyecto y ejecutamos la siguiente sentencia.
mvn clean install
Una vez que finalice de compilar, Maven nos habrá creado una carpeta target y en ella unos archivos jar.
target/jsqlite-1.0-SNAPSHOT.jar target/jsqlite-1.0-SNAPSHOT-jar-with-dependencies.jar
Ejecución de jsqlite.
Para ejecutar nuestro aplicación, desde una terminal correremos el archivo jsqlite-1.0-SNAPSHOT-jar-with-dependencies.jar, con el siguiente sentencia
java -jar target/jsqlite-1.0-SNAPSHOT-jar-with-dependencies.jar
Nos presentará las siguientes pantallas:
Ingresamos datos con la opción 1.
Listamos los datos que ingresamos con la opción 2.
Salimos de la aplicación y volvemos a ingresar y listamos los registro que contiene la base de datos para comprobar si está persistiendo nuestra jsqlite.
Buenos, finalizamos y hemos alcanzado nuestro objetivo.
¿Qué les parece? ¿Dejé por fuera algo? ¿No entendiste algo? Déjamelo en los comentarios..