0% encontró este documento útil (0 votos)
386 vistas

Java y Eclipse

Este documento describe un libro sobre el desarrollo de aplicaciones con Java y Eclipse. El libro guía al lector paso a paso en la construcción de una aplicación de gestión utilizando conceptos de POO, bases de datos MySQL, el patrón MVC, JUnit y la internacionalización. El entorno de desarrollo incluye Java 8, Eclipse, Xampp y JasperReports para informes y gráficos. La aplicación final incluirá funcionalidades como la gestión de clientes, pedidos, informes e informes.

Cargado por

48delcano
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
386 vistas

Java y Eclipse

Este documento describe un libro sobre el desarrollo de aplicaciones con Java y Eclipse. El libro guía al lector paso a paso en la construcción de una aplicación de gestión utilizando conceptos de POO, bases de datos MySQL, el patrón MVC, JUnit y la internacionalización. El entorno de desarrollo incluye Java 8, Eclipse, Xampp y JasperReports para informes y gráficos. La aplicación final incluirá funcionalidades como la gestión de clientes, pedidos, informes e informes.

Cargado por

48delcano
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 403

16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?

exporttype=1

Java y Eclipse
Desarrolle una aplicación con Java y Eclipse

Este libro sobre Java y Eclipse se dirige a los desarrolladores y estudiantes de informática. Enlaza los
conocimientos teóricos y prácticos apoyándose en el desarrollo de una aplicación de gestión.

Desde la comprensión de los conceptos de POO pasando por el análisis, se guía al lector paso a paso en la
construcción de la aplicación. Para la parte de desarrollo que constituye lo esencial de este libro, podemos resumir
los puntos fuertes en la explotación de una base de datos con varias tablas con MySQL y JPA, la codificación
de las principales clases después de un acercamiento mediante ingeniería software basada en UML, la
utilización del patrón MVC, la creación de los tests unitarios con JUnit y una especial atención a la
internacionalización de una aplicación.

A medida que avance en el libro y en la realización del proyecto, el lector descubrirá las principales novedades
de Java 8 como son las funciones lambdas, los streams y la gestión de fechas, los pasos prácticos y los
conocimientos teóricos indispensables al desarrollo de una aplicación profesional, y se le propondrá diferentes
alternativas para alcanzar mayor conocimiento de la programación Java.

El entorno de desarrollo se basa en Java 8, Eclipse IDE for Java Developpers, Xampp para el servidor,
WindowBuilder para realizar vistas bonitas y por fin JasperReports para la edición de los informes y
generación de gráficos. Aunque el libro se haya escrito con la versión 4.4 de Eclipse (llamada Luna), su contenido
así como el proyecto desarrollado son compatibles con la versión 4.5 de Eclipse (llamada Mars).

Al final, la aplicación de gestión incluye las funcionalidades esenciales en una aplicación profesional:
gestión de clientes, de artículos y de pedidos ­ edición y exportación de los informes a los principales formatos
(html, pdf,...) ­, creación de gráficos ­ vistas multi ventanas con actualización simultánea.

Existen elementos adicionales para su descarga en esta página.

Los capítulos del libro:


Prefacio – Entorno de desarrollo – Toma de contacto de Eclipse – Conceptos básicos de la POO – La caja de
herramientas de Java – La caja de herramientas de Eclipse – Presentación del proyecto – Análisis – Base de datos
MySQL – Maquetas – Conexión – Entidades – Modelo MVC – Aplicación multitabla – Aplicación final

Frédéric DÉLÉCHAMP ­ Henri LAUGIÉ


Ingeniero de formación, Frédéric DÉLÉCHAMP trabajó durante 15 años en el sector aeronáutico,
automovilístico, de la telefonía móvil, de la prensa, de las finanzas y de los juegos en entornos que van desde los
servidores hasta el diseño de interfaces gráficas, pasando por autómatas y sistemas embebidos. Desarrollo su
vida laboral en puestos de desarrollador, probador, integrador, arquitecto, responsable de metodologías y jefe de
proyecto. También formador, enseño Nuevas Tecnologías y particularmente el lenguaje Java a ingenieros y jefes
de proyectos que deseaban iniciarse o perfeccionarse en el mundo de la programación. En la nueva edición de
este libro, se ha esforzado en dar al lector el máximo de claves para ir más allá en la creación de un software
robusto y evolutivo.

A la vez formador, ingeniero y profesor de informática, Henri LAUGIÉ aúna conocimientos y experiencia tanto
técnicos como pedagógicos. En este libro, en el que se privilegia el aprendizaje mediante la práctica, el autor se
centra en lo esencial con cuidado de explicar lo más claramente posible todos los conceptos necesarios para el
desarrollo de aplicaciones, desde los más básicos hasta los más avanzados.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
Java 8 está disponible desde el 18 de Marzo de 2014. El reconocido lenguaje de programación orientado a objetos
de SUN, comprado por Oracle en Abril de 2009, es utilizado hoy en día por alrededor de nueve millones de
desarrolladores en el mundo e instalado en más del 95% de los ordenadores.

Para los desarrolladores, varias versiones están disponibles en el sitio web de Oracle (o los que dependen de Oracle:
openjdk.java.net, java.net) para cubrir las principales plataformas: Linux, Solaris y Windows. Apple tiene
disponible, en la actualidad, su propia implementación del JDK. Este libro utiliza el JDK 8 versión 64 bits para
Windows.

El programa utilizado para desarrollar con Java es Eclipse IDE for Java Developers. Esta versión incluye
WindowBuilder, que permite crear fácil y rápidamente interfaces HMI (interfaces hombre­máquina) bonitas. Para
crear informes (también llamados reportes) personalizados y gráficos, se ha elegido la herramienta de reporting
JasperReports. Aunque el libro haya sido escrito con la versión 4.4 de Eclipse (llamada Luna), su contenido así
como el proyecto desarrollado son compatibles con la versión 4.5 de Eclipse (llamada Mars).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Contenido del libro


El libro está dividido en dos grandes partes, agrupando la primera todas las etapas a recorrer antes de empezar el
desarrollo propiamente dicho.

Titulada Fases de preparación, esta primera parte empieza por la instalación y la configuración del entorno de
desarrollo, seguidas del aprendizaje del IDE (Integrated Development Environment). Se repasan los conceptos
básicos de la POO (Programación Orientada a Objetos) antes de usar y profundizar en la escritura del código. Se
presenta después el proyecto. El objetivo es entregar una aplicación de gestión de pedidos próxima a una solución
profesional que responda a un documento de requisitos determinado. Los puntos fuertes de la aplicación final, sin
tener en cuenta las acciones de introducción, eliminación, actualización y búsqueda de datos, son la edición y
exportación de informes a los formatos principales (PDF, HTML...), y la elaboración de estadísticas y gráficas. Las
fases de preparación finalizan con una presentación somera del análisis basado en UML (Unified Modeling
Language), y la instalación de la base de datos MySQL con el servidor XAMPP.

La parte dedicada al Desarrollo de la aplicación, que constituye la parte más importante de este libro, empieza
por la maquetación, la escritura de las entidades o clases con sus clases CRUD (Create­Read­Update­Delete) y la
gestión de la conexión con JPA (Java Persistence API). Continúa con la utilización del patrón de diseño MVC
(Model­View­Controller) a través de la gestión de los clientes. Concepto esencial en la creación de interfaces HMI
modernas, se detalla MVC en profundidad. A continuación es posible crear una aplicación multi ventanas con
actualización simultánea de diferentes vistas sobre los mismos modelos de datos. Las siguientes etapas del
desarrollo con la gestión de los artículos y las facturas permite abordar la dimensión multi tabla, presente en toda
aplicación de gestión.

Encontrará en la página Información el código de las clases del proyecto.

Enlaces URL del libro

Los enlaces URL referenciados en el libro se corresponden, esencialmente, con recursos open source. Pueden
cambiar rápidamente en el tiempo. Conociendo los recursos a utilizar, basta con buscar en su motor de búsqueda
favorito para obtener los nuevos enlaces para la descarga. En este caso, se trata a menudo de una nueva versión.

Los desarrolladores o los editores pueden también decidir dejar inaccesibles ciertos recursos (aplicaciones, plug­ins,
librerías,...). Esto tiene que ver habitualmente con versiones que ya no son actualizadas o que se han convertido
en versiones de pago. Deberá entonces buscar soluciones open source similares (a nivel lógico y conceptual) o
pagar la licencia. El lector puede, a continuación, probar sin gran dificultad los ejemplos propuestos. Recordamos
que el libro está destinado a informáticos, estudiantes de informática o verdaderos autodidactas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Instalación del JDK de Java 8


Compruebe primero si tiene instalado Java y su versión.

Bajo Windows, según las versiones, puede proceder de diferentes maneras. La más sencilla es utilizar
alguno de los sitios dependiente de Oracle, por ejemplo: http://java.com/es/download/installed.jsp

Si no dispone todavía de la versión 8 para desarrolladores, puede descargarla en la web de Oracle. El acceso a los
recursos Java no es demasiado práctico, le dejamos la URL de acceso directo actualmente:
http://www.oracle.com/technetwork/java/javase/downloads/jdk8­downloads­2133151.html

Acepte el acuerdo de licencia y elija la versión del JDK que corresponde a su sistema operativo: Windows,
Linux, Solaris o Mac OS.

Para desarrollar el proyecto bajo Windows 8 64 bits, la versión Windows x64 es la elegida.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Proceda con la instalación del JDK haciendo doble clic sobre el ejecutable.

Puede utilizar las opciones por defecto del instalador, están bien adaptadas para el uso de Eclipse.

Es indispensable dispones de la ayuda. Esta está accesible online en la web de Oracle:


http://download.oracle.com/javase/8/docs/api/

También puede descargarla actualmente en la dirección:


http://www.oracle.com/technetwork/java/javase/documentation/jdk8­doc­downloads­2133158.html

Proceda a su instalación y haga doble clic en el archivo index.html en la carpeta tutorial para consultarla en
modo desconectado (offline).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Instalación de Eclipse Luna


Acceda a la sección de descarga de Eclipse: http://www.eclipse.org/downloads/

Descargue la versión Eclipse IDE for Java Developers después de haber elegido la versión correspondiente
a su sistema operativo.

La versión Windows 64 bits es la que debe elegir para trabajar bajo Windows 8 64 bits.

Descomprima el archivo y ejecute Eclipse haciendo doble clic en el archivo eclipse.exe.

En la primera ejecución, Eclipse propone una carpeta por defecto llamada workspace (o espacio de
trabajo en español) en la que se guardan los proyectos. Puede crear y seleccionar otra carpeta más
personal.

Seleccione la opción Use this as the default and do not ask again para abrir este workspace por defecto.

Puede cambiar de workspace posteriormente con el menú File ­ Switch Workspace.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En la primera ejecución, Eclipse presenta también una página de bienvenida que permite obtener información
acerca del IDE, repartida en cuatro secciones.

Overview: permite acceder rápidamente a la sección de ayuda en línea correspondiente a la sección seleccionada.

Samples: permite abrir los ejemplos de aplicación a descargar de Internet.

Tutorials: permite acceder a asistentes que proponen, bajo la forma de tutoriales, realizar aplicaciones simples o
plug­ins.

What’s New: permite acceder rápidamente a la parte de ayuda en línea sobre las novedades de Eclipse.

Estas son las características de Eclipse Luna propuestas por la ventana About Eclipse.

Ahora hay que verificar que Eclipse está bien configurado para trabajar con el JDK 1.8.

Haga clic en Windows ­ Preferences, elija Java ­ Compiler y asegúrese de que se trata de la versión 1.8
en la lista desplegable.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Arriba a la izquierda de las preferencias de Eclipse, existe un campo de búsqueda a su disposición para acelerar su
elección entre dichas preferencias.

Después consulte el apartado Installed JREs. Verifique que la referencia al JDK 1.8 está realmente
presente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Descubriendo el IDE
Acceda sin más dilación al workbench o plan de trabajo.

Haga clic en el siguiente icono .

1. Perspectiva ­ Vista
Eclipse propone un entorno de desarrollo por defecto llamado perspectiva, compuesto de varias pestañas (vistas
en la terminología de Eclipse). Más precisamente, se trata de la perspectiva Java.

Existen otras perspectivas accesibles en el menú Windows ­ Open Perspective y se utilizan dependiendo de las
necesidades del desarrollador. Para no saturar el espacio de memoria del IDE, es mejor cerrar las que no se
utilizan.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cada perspectiva está constituida de un número determinado de elementos llamados vistas (views) que no tienen
por qué estar todas abiertas (visibles). Igual que para las perspectivas, abra únicamente las vistas que le resulten
realmente útiles, en caso contrario se arriesga a trabajar en una parte de la pantalla tan grande como un sello
postal.

Para añadir por ejemplo la vista Console a la perspectiva Java, elija la opción del menú Window ­ Show
View ­ Console.

Para mostrar una vista en todo el espacio disponible, haga doble clic en su pestaña. Vuelva a hacer doble clic
en la pestaña para restaurar la vista con su tamaño inicial.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para restaurar una perspectiva, haga clic con el botón derecho en el nombre de la pestaña situado arriba a la
derecha de la pantalla, y elija Reset.

2. Editor de código
Es posible elegir entre dos editores de código, Java Editor y Text Editor.

Por defecto, se abren las clases con el editor Java.

Una instrucción puede ser larga y sobrepasar el ancho de la zona de código de la pantalla. Para escribirla en varias
líneas, basta con efectuar un salto de línea en el lugar donde quiera cortar la línea.

Ejemplo:

laVentana.getJLab_Action().setIcon(new ImageIcon(
Ventana.class.getResource("/imagenes/gestion/modificar.png")));

Por el contrario, para cortar cadenas, debe obligatoriamente utilizar el operador +. Dicho esto, Eclipse se encarga
de hacerlo por usted en cuanto realiza un salto de línea.

Como ejercicio, sitúe el cursor en medio de una cadena de caracteres, y pulse sobre [Intro].

La cadena se dividirá en dos automáticamente, en dos líneas diferentes.

Ejemplo:

...
getResource("/imagenes/gestion" +
"/modificar.png")));

Para beneficiarse del autocompletado, teclee las primeras letras seguidas de la combinación de teclas [Ctrl]
[Espacio].

Eclipse completa lo tecleado o le propone una lista. Puede parametrizar el autocompletado modificando
las opciones disponibles aquí: Window ­ Preferences ­ Editor ­ Content Assist.

Para beneficiarse de una ayuda conceptual, sitúe por encima de una palabra del código con el cursor del
ratón.

Ejemplo:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Eclipse se encarga automáticamente de la indentación del código.

Para provocar voluntariamente la indentación, seleccione la o las líneas de código contiguas afectadas y
utilice el atajo de teclado [Ctrl] I o haga clic con el botón derecho y elija Source ­ Correct Indentation.

Notará que en el menú contextual los comandos disponibles están acompañados a su derecha de los atajos de
teclado asociados.

Eclipse no formatea automáticamente el código por defecto.

Para formatear el código (espacios, indentación...), utilice el atajo de teclado [Ctrl][Shift] F, o haga clic
derecho y seleccione Source ­ Format.

Para formatear solamente una parte del código, seleccione el código y efectué la operación anterior.

Para desactivar el corrector ortográfico que actúa sobre sus comentarios, elija Window ­ Preferences ­
General ­ Editors ­ Text Editors ­ Spelling y desactive la opción Enable spell checking.

Para añadir los números de las líneas, desarrollar o reducir el código, efectúe un clic derecho en la banda
vertical izquierda ligeramente gris.

3. Depurador
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El desarrollo necesita numerosos test. Para controlar la correcta ejecución de una parte de un programa que
contiene estructuras condicionales e iterativas, el uso del depurador resulta en muchas ocasiones indispensable.
El descubrimiento del depurador de Eclipse se hará a través de un programa que muestra las tablas de
multiplicación.

Aquí tiene una propuesta de código para un programa así:

Compruebe antes el programa pulsando las teclas [Ctrl][F11].

Haga un clic derecho en el margen gris delante del número de la línea 20 y elija Toggle Breakpoint.

Pruebe el programa pulsando esta vez únicamente en la tecla [F11] para lanzar el depurador.

Si la perspectiva Debug no se encuentra aún abierta, Eclipse mostrará una ventana.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione la opción Remember my decision.

Esto tiene como resultado abrir esta perspectiva; se añade un botón en la barra principal de los iconos del IDE
para permitir acceder a ella rápidamente.

Los valores de las variables se muestran en una vista.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El depurador permitirá verificar la exactitud de los datos controlando paso a paso los valores de las variables.

Haga clic en el icono o pulse la tecla [F5].

Estando el punto de interrupción en la línea 20, el recorrido se efectúa en el bucle for interno. A la siguiente
iteración, la variable fila debe contener el número 1 y las variables columna y numero deben permanecer con
su valor anterior.

Para seguir con las pruebas y leer los valores de las variables, presione de nuevo en [F5].

Para volver a la perspectiva Java, haga clic en el botón Java arriba a la derecha de la ventana principal del
IDE.

Si este no es visible, haga clic en los símbolos >> para mostrarlo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Primer proyecto
Para familiarizarse con el workbench, se crea una clase muy sencilla.

Primero debe crear un proyecto y después un package, toda clase de una aplicación que se considere profesional
debe pertenecer a un package.

Como recordatorio, un package es físicamente una carpeta, y simbólicamente un espacio de nombre que permite
reagrupar clases con la misma funcionalidad.

Si no reagrupa sus clases en un package, Eclipse la guardará en el package por defecto. Está desaconsejado usar el
package por defecto.

También es la ocasión de proponer una estructura para los archivos del proyecto, con el fin de mejorar la legibilidad
y testeabilidad de las aplicaciones.

Como preámbulo a este trabajo, y para facilitar la apertura de los archivos que provengan de sistemas operativos
diferentes, es conveniente indicar la codificación de los archivos.

En el menú, elija Window ­ Preferences. Diríjase al apartado General ­ Workspace y elija el valor Other
­ UTF­8 en la sección Text file encoding.

Este parámetro permite que los archivos con caracteres acentuados en Eclipse puedan leerse correctamente, ya
sea en Windows, Linux o Mac.

Los acentos así como los caracteres especiales están permitidos en los nombres de variables, pero no se recomienda
su uso. Lo entenderá el día en el que abra su proyecto en un ordenador Linux, por ejemplo, o cuando tenga que
trabajar en un proyecto con los nombres de variables en chino, japonés, hebreo o farsi.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En el menú elija File ­ New ­ Java Project, o haga un clic derecho en la vista y después New ­ Java
Project.

Aparece la ventana de diálogo de creación de proyecto. Permite dar un nombre al nuevo proyecto y elegir dónde
guardarlo en el disco duro donde almacenaremos los archivos del proyecto (por defecto, los proyectos se
almacenan en la carpeta del espacio de trabajo elegido durante la instalación de Eclipse).

Introduzca el nombre del proyecto y haga clic en el enlace Configure default junto a la opción Create
separate folders for sources and class files.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Esta acción permite definir las carpetas en las cuales guardaremos los archivos fuente y los archivos binarios
compilados. De manera general, estos dos tipos de archivos deben estar separados. Esto permite por ejemplo
copiar o eliminar fácilmente los binarios.

Los archivos fuente en un proyecto Java tienen muchas veces varios tipos: existen archivos fuente para las clases
de la aplicación, archivos fuente para las clases de test, archivos de recursos tales como imágenes o
configuraciones...

La siguiente acción permite definir las distintas carpetas para cada uno de estos tipos. Estas carpetas están
inspiradas de los proyectos Maven (http://maven.apache.org/) y Gradle (https://gradle.org/), que son potentes
herramientas para la gestión de proyectos informáticos, y permiten lanzar desde la línea de comandos o mediante
una interfaz gráfica la compilación de archivos fuente en binarios, la realización de test unitarios, la transformación
de clases en archivos jar, el empaquetado en un instalador y el despliegue en un servidor, por citar solamente
algunas de las funcionalidades disponibles.

Maven aplica el refrán «cada cosa en su sitio, y un sitio para cada cosa» y define la carpeta src/main/java como
la carpeta para los archivos fuente, src/main/resources para los archivos de configuración y las imágenes,
src/test/java para los archivos fuente de test y src/test/resources para los archivos de configuración de los
test. En cuanto a los archivos binarios, se guardan en la carpeta target/classes.

Modifique los nombres de las carpetas como en la siguiente imagen, y presione OK.

Eclipse vuelve a la ventana de diálogo de creación del proyecto.

Haga clic en Finish para terminar la creación del proyecto.

El proyecto y su contenido son ahora visibles en la vista Package Explorer.

Para renombrar un proyecto, haga clic con el botón derecho en su nombre y elija Refactor ­ Rename. El
procedimiento es el mismo para cualquier contenido incluido dentro de la vista Package Explorer del workbench.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Primer package
Seleccione su proyecto en la vista explorador de packages, y elija Package en el menú File ­ New.
Introduzca el nombre tomaDeContacto.primero utilizando una minúscula como primer carácter del
nombre respetando así las convenciones de escritura del lenguaje Java y haga clic en Finish.

Su package pertenece desde ahora a su proyecto.

Si mira las carpetas creadas en el disco duro (por ejemplo con el explorador de Windows o eligiendo Navigator en
el menú Navigate ­ Show In), obtendrá una estructura de carpetas similar a la que sigue:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Los archivos y carpetas que empiezan por un punto (como .classpath, .project y .settings) son gestionados por
Eclipse. Está fuertemente desaconsejado modificarlos o eliminarlos.

Con esta acción, se han creado dos packages: tomaDeContacto y primero, situado en el package
tomaDeContacto, ya que se ha utilizado el carácter punto como separador de packages. Un package puede contener
otros packages, al igual que las carpetas pueden contener subcarpetas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Primera clase
Seleccione el package creado.

Elija el menú File ­ New ­ Class o haga clic con el botón derecho y elija New ­ Class.

La ventana de diálogo se abre.

Introduzca el nombre para la clase MiPrimerPrograma empezando por una mayúscula (para respetar las
convenciones de nombres de Java) y seleccione la opción public static void main(String[] args). Esta
opción permite crear el método que sirve de punto de entrada a la ejecución del programa.

En las convenciones de Java, solo las clases empiezan por una mayúscula. Los packages, atributos y variables
empiezan por una minúscula.

Los caracteres especiales (%, ?,...) y los números no pueden utilizarse como primer carácter.

Haga clic en el botón Finish.

Se ha añadido la clase al package y se puede ver el código generado automáticamente por Eclipse en el
editor de código.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añada una propiedad (también llamada atributo) privada a la clase.

package tomaDeContacto.primero;

public class MiPrimerPrograma {

private String nombre;

public static void main(String[] args) {


// TODO Auto‐generated method stub

}
}

Una de las reglas de programación del autor de estas líneas es empezar siempre declarando las propiedades o
métodos como privados. Si es necesario acceder desde el exterior de la clase, se modifica la visibilidad. Esto permite
una mejor encapsulación de los datos y se puede resumir con la máxima de «primero prohibir, luego permitir».

Después debe añadir getters y setters para esta propiedad en la clase.

Elija en el menú contextual Source ­ Generate Getters and Setters.... Después seleccione la opción
nombre (las demás opciones debajo de la variable nombre son elegidas automáticamente) y valide las
elecciones.

Se han generado los getters y setters.

public String getNombre() {


return nombre;
}

public void setNombre(String nombre) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

this.nombre = nombre;
}

Añada un constructor para esta clase haciendo clic con el botón derecho en el editor de código y eligiendo la
opción Source ­ Generate Constructor using Fields....

Haga clic en el botón OK para validar. Se genera el constructor.

public MiPrimerPrograma(String nombre) {


super();
this.nombre = nombre;
}

La instrucción super() llamará al constructor de la súper clase, en este caso java.lang.Object.

Eclipse inicializa automáticamente la propiedad nombre ya que se seleccionó la opción de la variable en la ventana
de diálogo anterior. Los nombres de las propiedades y de la variable son las mismas: Para diferenciarlas, se prefija la
propiedad de la clase con this seguido de un punto.

Para añadir comentarios en la clase, añada dos barras (/) al inicio de la línea o desde donde quiera comentar el resto
de la línea. Todo lo que es comentario no será tomado en cuenta por el compilador.

Para crear comentarios de varias líneas, encuádrelas entre /* y */.

Si se escriben las palabras TODO o FIXME en un comentario, se indexará la línea en Eclipse. Es una manera práctica
de dejar indicaciones sobre lo que queda por hacer o corregir, al final de una dura jornada de trabajo.

Eclipse añade la línea TODO Auto­generated method stub cuando se genera un método automáticamente. Es mejor
quitarlo o substituirlo por sus propios comentarios.

Es posible añadir comentarios especiales que comienzan por /** y terminan por **/. Se usan estos comentarios
llamados javadoc para generar la documentación de la aplicación.

Teclee /** encima del método main y pulse [Intro].

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Eclipse crea automáticamente el patrón de comentario Javadoc.

Complete el método main().

public static void main(String[] args) {


MiPrimerPrograma programa = new MiPrimerPrograma("Yo");
System.out.println("¡Soy " +programa.nombre +"!");
}

Ahora toca probar lo que acabamos de programar.

Para probar este programa, se puede hacer de varias maneras:

Haga clic con el botón derecho y elija Run As ­ Java Application.

Estando la clase abierta en el editor, use las teclas [Ctrl][F11].

Haga clic en el icono de la barra de herramientas de Eclipse.

Se ejecuta entonces el programa y se muestra la salida en la vista Console.

Ya encontramos numerosas nociones propias a la programación orientada a objetos (POO) en estas pocas líneas:
package, propiedad, constructor, getter, setter,...

Haremos un recordatorio de estas nociones en el capítulo Conceptos básicos de la POO y en la realización del
proyecto.

Llegados a este punto, acuérdese de los siguientes puntos:

Un programa se compone al menos de una clase.

La clase debe llevar el mismo nombre que el archivo en el que se ha definido.

Se consideran las minúsculas y mayúsculas como caracteres diferentes: Java distingue las minúsculas de las
mayúsculas.

Toda clase debe estar dentro de un package. Un package puede contener otros packages.

En general, la case posee un constructor explícito. Este constructor permite crear instancias (hablamos de
instanciación) de la clase, siendo estas instancias llamadas también objetos.

Todos los métodos provienen obligatoriamente de una clase: ya sea una clase creada por un desarrollador, una
clase obtenida de la API Java, o una clase obtenida de una librería creada por otros desarrolladores.

El método principal main() permite ejecutar la clase. Es el punto de entrada de la aplicación: siempre se le llama el
primero.

Una clase empieza por una mayúscula, una variable por una minúscula.

Nada impide crear varias clases que dispongan del método main(). Entonces es necesario designar aquella cuyo
método main() se ejecuta, siendo por lo tanto las demás ignoradas.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Intente crear solamente un método main() por proyecto. Esto permite evitar las ambigüedades.

Para entender mejor la organización de los packages y de sus clases puede acceder a los archivos fuente de Java
navegando a la carpeta del JDK en el disco duro.

A continuación puede ver como ejemplo una parte de las clases del package java.lang:

Es posible visualizar la misma organización dentro de Eclipse, pero esta vez con los archivos binarios.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Primera ventana
Crearemos ahora una pequeña aplicación con una interfaz gráfica (GUI, del inglés Graphical User Interface, HMI
para interfaz hombre­máquina en español).

Una de las novedades de Eclipse desde la versión Indigo es poner a disposición un editor de interfaz gráfica
proveniente del mundo Google, WindowBuilder. Existen otros pero teniendo en cuenta la calidad de
WindowBuilder, utilizaremos este para el proyecto. En cuanto al plug­in Visual Editor, su desarrollo ha sido
abandonado.

WindowBuilder puede configurarse mediante el menú Window ­ Preferences. Para el proyecto usaremos las
opciones por defecto.

WindowBuilder es un plug­in de Eclipse que permite crear rápidamente interfaces gráficas WYSIWYG (What You
See Is What You Get: lo que ve es lo que obtiene).

Si desea que el código generado automáticamente tenga un formato distinto, puede parametrizar WindowBuilder
dirigiéndose al menú Window ­ Preferences ­ WindowBuilder.

Todos los editores de interfaces gráficas tiene la misma característica: permiten crear rápidamente componentes
gráficos. Por el contrario, no dan casi nunca directamente lo que desea: será por lo tanto necesario intervenir
después en el código generado.

1. Creación de la ventana
El objetivo es crear una ventana muy sencilla apoyándose en WindowBuilder.

Esta ventana contendrá un campo de introducción de texto en el que el usuario podrá introducir palabras. Con
un clic en el botón OK de la ventana, aparecerá lo que se ha introducido en otra ventana.

Haga clic con el botón derecho en el proyecto en el explorador de packages y elija New ­ Other.

Abra las carpetas WindowBuilder y Swing Designer y seleccione JFrame.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Un JFrame es una ventana gráfica autónoma, susceptible de contener otros elementos gráficos. Se trata del
contenedor gráfico de esta aplicación.

Es posible crear otros contenedores gráficos, en particular JDialog que utilizaremos para crear ventanas de diálogo
y JPanel que es un contenedor de elementos gráficos que no tiene representación en ventana.

Llame a la clase PrimeraVentana y elija el package tomaDeContacto.grafico como package de destino.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Al igual que para la clase PrimerPrograma, se genera automáticamente el código y aparece en la ventana del
editor de código.

Debajo del código del editor, aparecen dos pestañas: Source y Design. Source sirve para visualizar y editar el
código fuente en modo texto, Design sirve para visualizar y modificar la ventana en modo gráfico.

Si las pestañas no aparecen, puede forzar la apertura de la clase con el editor de WindowBuilder de la siguiente
manera:

Seleccione la clase PrimeraVentana en el explorador de packages, haga clic con el botón derecho y
seleccione Open With ­ WindowBuilder Editor.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en la pestaña Design.

Seguidamente puede ver una vista global del entorno de desarrollo:

La ventana creada contiene un componente particular de tipo JPanel, que es un contenedor en el cual es
posible situar otros componentes gráficos tales como botones, campos de introducción de datos, tablas, listas u
otros JPanel.

Este componente JPanel es la base gráfica de la aplicación, cargándose la ventana antes de incluirla en el sistema
de ventanas del sistema operativo y permitiendo a la aplicación disponer de un icono, un título, botones de
maximización, minimización, de cierre, y definiendo una posición y un tamaño en la pantalla del ordenador.

Haga clic en el centro de la pequeña ventana del editor. Esto selecciona el panel principal.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para acceder al código del componente seleccionado, haga clic en la pestaña Source.

Ejecute la ventana con [Ctrl][F11].

2. Añadir componentes
Una ventana vacía no resulta muy útil, ni atrayente. Vamos a añadirle componentes gráficos (llamados también
widgets), así como algunos pequeños elementos para vestir la ventana, como:

un título,

un icono,

una zona de introducción de datos,

un botón de validación.

Elija en su disco duro una imagen, si es posible que sea de 32 píxeles por 32 pixeles.

Cree una carpeta que contenga sus recursos gráficos, haciendo clic con el botón derecho y seleccionando
New ­ Source Folder. Introduzca src/main/resources como nombre de carpeta.

Eclipse (y Java) reconoce automáticamente el carácter / como separador del nombre de la carpeta, ¡y eso en todos
las plataformas!

Cree un package tomaDeContacto.grafico en esta nueva carpeta. Debe tener el mismo nombre que el
package de la clase gráfica.

Copie su imagen en el package tomaDeContacto.grafico de esta carpeta src/main/resources.

En el árbol de widgets, en la sección Components, haga clic en (javax.swing.JFrame).

La sección Properties situada debajo se actualiza para mostrar las propiedades de la ventana.

Haga clic en el pequeño botón con puntos suspensivos junto a la propiedad iconImage.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Aparece una ventana de diálogo que permite elegir la imagen.

Haga clic en el radio botón Classpath resources y abra la carpeta resources en la sección Parameters
hasta que aparezca su imagen. Haga doble clic en ella.

La ventana tiene ahora un icono en su barra de título.

En la sección Properties, deslice verticalmente las propiedades hasta ver la propiedad title. Haga clic junto
al nombre de la propiedad para cambiar su valor.

Ahora la ventana tiene un título.

La siguiente etapa consiste en interesarse en el interior de la ventana.

Haga clic en el componente JPanel en la sección Components, o en la visualización previa de la derecha.

La sección Properties muestra las propiedades del panel.

El panel es un contenedor gráfico, y todo contenedor debe saber cómo ubicar lo que contiene en su interior.

Java permite especificar las reglas de ubicación con la ayuda de un layout, o disposición. El layout por defecto de
un Panel es el BorderLayout.

Modifique este layout por Absolute layout con la ayuda de la propiedad del panel Properties.

Este layout permite ubicar los elementos del panel con precisión de pixel.

Este layout es útil pero debería estar reservado a algunos casos particulares, ya que no gestiona bien el
redimensionamiento de las ventanas.

En la paleta de componentes, haga clic en el componente JButton. Desplace después el ratón en la


visualización previa y sitúelo mejor en la parte baja de la ventana. Redimensiónelo y cambie la propiedad
text en la sección Properties.

De la misma manera, seleccione, sitúe y redimensione un componente JTextField para añadir un campo
de introducción de datos a la ventana.

Seleccione, sitúe y redimensione un componente JLabel. Este componente permite mostrar textos no
editables e imágenes.

Modifique sus propiedades: fuente, tamaño de carácter, alineación (por ejemplo centrado), color,...

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Acción de un botón
El objetivo de esta primera ventana es mostrar en un cuadro de diálogo el mensaje que el usuario de la aplicación
haya introducido.

De momento, la construcción de la ventana permite al usuario introducir este mensaje con la ayuda del
JTextField. El JLabel da indicaciones sobre lo que el usuario puede hacer. El JButton permite validar la
introducción y mostrar el cuadro de diálogo.

Sin embargo, si hace clic en el botón, no ocurre nada. En este punto, es totalmente lógico. Ahora debe codificar la
acción ejecutada al hacer clic en el botón.

En modo Design, seleccione y haga clic con el botón derecho sobre Validar en la visualización previa.
Después seleccione la siguiente opción Add event handler ­ action ­ actionPerformed.

WindowBuilder genera entonces automáticamente el siguiente código:

JButton btnValidar = new Jbutton("Validar");


btnValidar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En el bloque del método actionPerformed(ActionEvent e), por lo tanto dentro de las llaves, añada la
siguiente línea:

JOptionPane.showMessageDialog(btnValidar,
"Ha introducido \"" +textField.getText() +"\"");

¿Cómo añadir unas dobles comillas en una cadena de caracteres? Debe precederlas del carácter de escape backslash
(o barra oblicua inversa: \).

Aquí tiene el código obtenido después de lo añadido:

btnValidar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(btnValidar,
"Ha introducido \"" +textField.getText() +"\"");
}
});

Aparece un error en Eclipse, materializado por un pequeño icono rojo situado a la izquierda de la línea.

Esto indica que la clase PrimeraVentana no conoce de momento ninguna clase JOptionPane. JOptionPane es
una clase que permite crear fácilmente ventanas de diálogo.

Para corregir rápidamente el error, haga clic en el icono rojo. Aparece entonces un menú contextual que
propone soluciones. Cada solución muestra la visualización previa de su resultado por un tooltip con fondo
amarrillo.

Haga clic en la primera solución Import ’JOptionPane’ (javax.swing).

Los números de línea no tienen por qué coincidir en la clase que está modificando.

Guarde las modificaciones con el atajo de teclado [Ctrl] S y pruebe el programa con la combinación de teclas
[Ctrl][F11].

Con las modificaciones anteriores, se ha creado un listener para un evento de tipo Action. Este listener posee un
código que se ejecuta cada vez que se realiza una acción sobre el botón Validar, es decir cada vez que un usuario
hace clic en él.

Volveremos a ver este proceso con más detalle en los siguientes capítulos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Creación de un menú
Crearemos ahora un menú en esta primera ventana, muy rápidamente gracias a WindowBuilder.

En la paleta de WindowBuilder, despliegue la sección Menu.

El inicio de esta operación consiste en añadir una barra de menús.

Elija el componente JMenuBar y deposítelo en la barra de título de la visualización previa de la ventana.

Una inscripción verde debe aparecer para indicar dónde ubicar la barra de menú.

A partir de ahora es posible añadir menús a esta barra de menús.

Seleccione y deposite el componente JMenu desde la paleta de WindowBuilder hacia la barra de menús que
acaba de crear.

Se crea un nuevo menú, pidiendo WindowBuilder su nombre, por ejemplo Archivo.

Haga más grande la ventana para poder insertar los demás menús.

Deposite otro componente JMenu a la derecha del menú Archivo que acaba de crear.

Cambie el valor de su propiedad text por Cliente.

Se trata ahora de añadir elementos (también llamados ítems) para cada menú.

En la visualización previa, haga clic en el menú Archivo.

Seleccione y deposite un componente JMenuItem desde la paleta hacia la zona designada (Add items
here).

Llame este ítem Salir.

Pruebe la ventana. El ítem Salir está presente pero inactivo.

Para hacerlo sencillo, el menú Archivo contendrá un único ítem.

Queda terminar el menú completándolo de la siguiente manera:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione un componente JMenu en la paleta y deposítelo dentro del menú Cliente. El menú Cliente se
abre si pasa el ratón por encima, para permitir depositar el componente en la zona (Add items here).

Cambie el nombre del submenú que acaba de crear por Gestión, tecleando este nombre en la visualización
previa o modificando la propiedad text en la sección Properties.

Añada los ítems siguientes al submenú Gestión.

La línea gris entre Buscar y Eliminar se obtiene depositando un componente JSeparator como ítem del menú.

La próxima etapa es asociar una acción al ítem de menú Salir.

Seleccione el ítem Salir en el menú, haga clic con el botón derecho y seleccione la opción Set Action ­ New.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El texto del ítem cambia. Es perfectamente normal en este punto.

Haga clic en la pestaña Source para mostrar el código fuente de la clase Java.

WindowBuilder ha añadido automáticamente varias líneas:

private final Action action = new SwingAction();


...
JMenuItem mntmSalir = new JMenuItem("Salir");
mntmSalir.setAction(action);
mnArchivo.add(mntmSalir);
...
private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "SwingAction");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
}
}

Se ha añadido una acción al ítem del menú mediante la propiedad action.

La clase de esta acción se llama SwingAction (se trata de un nombre generado automáticamente), que deriva del
tipo AbstractAction.

Cuando el usuario hace clic sobre el ítem Salir, se ejecuta el código del método actionPerformed.

Modifique el método con el siguiente código:

public void actionPerformed(ActionEvent e) {


dispose();
}

El método dispose() permite liberar los recursos gráficos de la ventana y eliminarla de la pantalla. Como se trata
de la única ventana de nuestra primera aplicación, el programa finalizará después.

Existe un comando más rápido para terminar un programa Java: System.exit(0);. Este comando permite además
dar códigos de retorno de programa pero se puede abusar fácilmente de su uso. De una manera general, tenga un
punto de salida único para su aplicación, lo que permite cerrar limpiamente las conexiones a las bases de datos por
ejemplo.

Pruebe la ventana ejecutando el programa con [Ctrl][F11] y verifique que haciendo clic sobre SwingAction
se cierra bien la ventana.

Volviendo a la consola de Eclipse, debe ver la expresión <terminated>.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En las aplicaciones gráficas profesionales, los atajos de teclado son esenciales ya que permiten a los usuarios
ejecutar las acciones más habituales rápidamente, ganar tiempo y aumentar su productividad.

La siguiente etapa permite introducir estos atajos de teclado cambiando las propiedades de la acción.

Modifique el constructor de la clase SwingAction con las siguientes líneas:

public SwingAction() {
putValue(NAME, "Salir");
putValue(SHORT_DESCRIPTION, "Cierra la aplicación");
putValue(MNEMONIC_KEY, KeyEvent.VK_Q);
putValue(ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_Q,
InputEvent.CTRL_DOWN_MASK));
}

Rectifique los imports pulsando simultáneamente sobre las teclas [Ctrl][Shift] O (no es el cero, sino la letra O).
Es el atajo de teclado de Eclipse para reorganizar los imports de la clase y hacer que sepa quiénes son las
clases KeyStroke y InputEvent.

Pruebe la ventana ejecutando el programa, verificando que el texto del ítem ha cambiado, que la letra Q está
subrayada, y que se muestra Ctrl­Q junto a la acción.

Presione las teclas [Ctrl] Q. La ventana debe cerrarse.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Primer ejecutable
Los fuentes de una aplicación son esenciales para los desarrolladores. Para los usuarios, lo importante es el binario
(el ejecutable) sobre el que hacen doble clic para lanzar la aplicación.

La creación del ejecutable de la clase PrimeraVentana se realiza gracias a las siguientes etapas.

Elija la opción Export en el menú File de Eclipse. Se abre una ventana de diálogo.

Seleccione el destino Runnable JAR file dentro de la carpeta Java.

Haga clic sobre el botón Next.

Seleccione en la lista desplegable la configuración de ejecución de su aplicación, en nuestro caso


PrimeraVentana ­ MiPrimerProyecto.

Después elija el nombre del archivo que será generado, la única obligación es que el nombre del archivo tenga
la extensión .jar, haciendo clic en el botón Browse....

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en el botón Finish.

Puede ocurrir que aparezca una ventana de diálogo de alerta. No tiene por qué ser muy grave, pero acostúmbrese a
leer los mensajes haciendo clic en Details.

Para acabar, verifique que su aplicación funciona haciendo doble clic sobre el archivo .jar que acaba de
generar.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
La Programación Orientada a Objetos, o POO, corresponde a un paradigma ­ la representación de un sistema o un
dominio, para simplificar ­ particular de la programación informática donde el objeto es el concepto central. En este
capítulo se presentan los conceptos fundamentales de la POO enfocándose en lo esencial y apoyándose en el
lenguaje Java. La mayoría de ellos están realizados y profundizados en un proyecto Luna, facilitando así su
comprensión.

El interés de la programación orientada a objetos es proporcionar medios para modelizar diferentes dominios
necesarios para su aplicación.

Este paradigma permite también minimizar y racionalizar los impactos debidos a modificaciones más o menos
grandes de las aplicaciones durante su ciclo de vida, extender más fácilmente sus funcionalidades, aislar las
responsabilidades del código, manipular abstracciones sin conocer necesariamente sus tipos concretos, y por lo
tanto más generalmente proponer programas más robustos y abiertos al cambio.

Cuando creamos un programa con programación orientada a objetos, tratamos de modelizar informáticamente
distintas nociones para poder reutilizarlas en otros programas. No se trata de modelizar todo lo que ocurre en el
universo físico, a menos que se disponga de un plazo de desarrollo MUY MUY extenso y querer crear Matrix, sino
focalizarse sobre las nociones pertinentes e indispensables al funcionamiento robusto de esta modelización,
dejando a su vez abierta la aplicación a futuras modificaciones.

Los ejemplos presentados en este capítulo tienen vocación de permitir entender de manera más sencilla las nociones
subyacentes. Su adecuación a una aplicación realista necesitarían primero formalizar el objetivo y los límites de dicha
aplicación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Objeto
En el paradigma de programación orientada a objetos, todo es un objeto. Una ventana, un botón pero también
una factura, un cliente, etc. Más concretamente, un objeto puede considerarse como una entidad que posee
características y posibles acciones. Por ejemplo, un coche tiene una forma y un color y puede arrancar y parar.

Un gato también tiene varias características: el número de patas, un color determinado de pelaje, un nombre y
varios comportamientos: comer, ronronear, correr sobre las paredes, esconderse, tirar entre las piernas...

En la programación orientada a objetos, se representa Minino, vuestro gato, por un objeto, una instancia de la
clase particular Gato. Este objeto tiene por lo tanto propiedades específicas a su gato: su nombre, su color, su
edad, para empezar. También tiene propiedades comunes a todos los gatos, como las cuatro patas, los dos ojos, la
sangre caliente...

Un gato ­ y todos los gatos ­ es también un mamífero y más generalmente un animal, y más aun genéricamente
un ser vivo. La programación orientada a objetos intenta formalizar estas distintas categorizaciones y
características para obtener una abstracción, una modelización informática del objeto que nos interesa.

Cuando escuche la palabra Objeto, piense en un ejemplar determinado de este objeto: el gato Minino, el coche que
posee desde hace cuatro años, la factura que está editando...

Describiremos las características y comportamientos comunes de estos objetos en una clase.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clase
La creación espontánea no existe. El coche que conduce, la silla en la que está sentado, han sido fabricadas. Todos
estos objetos vienen de una «fábrica» o de una planta. Para una primera introducción en la noción de clase,
podemos retener este último término. También existen plantas de aviones, barcos, juguetes, etc.

Otra particularidad de las clases es el principio de agrupamiento. Evitamos fabricar en una misma planta coches,
barcos o juguetes y ordenadores al mismo tiempo. Los objetos fabricados están por lo tanto ligados a categorías que
son las clases en POO. Una clase es una especie de planta que fabrica categorías objetos que tienen características
comunes (los barcos tienen un casco, los coches una carrocería, los gatos un nombre).

Para definir una clase Gato en Java, basta con utilizar la palabra clase class.

class Gato {
// ... continuará
}

Observe las llaves abiertas y cerradas: todo lo que se encuentra entre estas llaves pertenece a la misma clase, y todo
lo que pertenece a la clase Gato debe encontrarse entre estas llaves. Se aplica el mismo principio para los métodos.

Para crear (también se dice instanciar) un nuevo objeto de tipo Gato que llamaremos miGato en la aplicación, use
el siguiente código con la palabra clave new:

Gato miGato = new Gato();

Si la palabra fábrica parece aún abstracta, una metáfora pastelera puede ser más apropiada: la clase es el molde
pastelero, el objeto es el pastel en sí. Una clase permite crear objetos, al igual que un molde permite crear pasteles
concretos, todos únicos y diferentes los unos de los otros. Sin embargo todos provienen del mismo molde.

En el universo Java, todo o casi todo es clase y objeto. Cada elemento unitario de su modelización debe estar
incluido en un archivo único, llamado con el mismo nombre que la clase descrita en su interior.

En su proyecto, habrá por lo tanto un archivo Gato.java, que contendrá el código ejemplo descrito más arriba, y
formalizará la existencia de una clase Gato en la aplicación.

Como todo es un objeto en el paradigma de la programación orientada a objetos, todos los objetos que son
utilizados en la aplicación tendrán una clase común como punto de partida de su jerarquía: la clase Object. Se
trata de una clase que describe los atributos y comportamientos mínimos para todos los objetos que modelizará.

Se desarrollará todo lo referente a los aspectos jerárquicos de las clases en la sección que versa sobre la herencia.

Y como en Java todo o casi todo es objeto y clase, ¡existe también una clase Class! No hablaremos más de esta
clase, ya que se trata de un concepto más avanzado que se refiere a lo que llamamos los mecanismos de
introspección (reflection en inglés).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Atributo
Volvamos a los que nos ocupa, es decir a nuestros gatos.

La primera versión de la clase Gato que se propuso no es muy interesante. Solo se ha formalizado el concepto de
un Gato. Para ir más allá, se completa la clase en función de las necesidades de cada uno. Suponiendo que se trata
de caracterizar los gatos por su nombre, color y edad, debe entonces añadir estas características (también llamadas
propiedades o atributos) a la clase.

class Gato {
nombre;
color;
fechaDeNacimiento;

En el estado actual de la clase, esta genera errores ya que las propiedades no están todavía tipadas.

Los atributos definen el aspecto estructural de una clase, sus datos.

Un gato tiene (posee) por lo tanto un nombre, un color y una edad, ya que es el objetivo de la modelización.

En realidad no. No tiene un atributo edad, solo una fecha de nacimiento.

¿Por qué?

Imagine que este programa se ejecuta sin problemas durante varios años. Si para cada vez que cumpla años
debemos actualizar la edad de todos los gatos afectados, se complica la cosa. Sin embargo una fecha de nacimiento
es fija y no será nunca modificada, salvo en casos excepcionales.

La edad es de hecho lo que llamamos un atributo derivado, que se puede obtener conociendo la fecha de
nacimiento y la fecha actual.

Abordaremos la formalización de este atributo derivado un poco más adelante.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Tipo de datos
Todo atributo debe imperativamente estar tipado: long para la fecha de nacimiento y cadena de caracteres para los
demás en nuestro ejemplo. El primer tipo es llamado simple. Hablamos de tipos elementales o primitivos. Tendrá
ocasión al abordar el desarrollo de utilizar tipos más enriquecidos, especialmente tipos de clases, como la clase
String.

class Gato {
String nombre;
String color;
long fechaDeNacimiento;
}

En las aplicaciones muchas veces aparece el problema de conversión (casting) de datos. Por ejemplo, las solicitudes
de introducción de datos mediante las ventanas de diálogo devuelven por defecto valores de tipo String. Para el
tratamiento de fechas o de números debe entonces convertir estos valores con el riesgo de provocar el
detenimiento repentino de la aplicación.

Quien puede con más, puede con menos. Considere el siguiente código:

int numeroEntero = 5;
double numeroDecimal = 0;
numeroDecimal = numeroEntero;

numeroDecimal = 5.5;
numeroEntero = (int) numeroDecimal;

A un double (que puede ser un número con signo muy grande con decimales) se le puede asignar un entero.
Hablamos de cast implícito. La operación inversa no es verdad. Debe antes convertir (hacer un cast explícito) el
double a entero, gracias al tipo indicado entre los paréntesis. Perderá entonces toda la información presente detrás
de la coma. Retendremos que para los números, las conversiones implícitas solo afectan a las que se hacen sin
pérdida de información.

Para conversiones de tipos totalmente diferentes, por ejemplo de un número a una cadena de caracteres, debe
hacer uso de clases particulares llamadas wrapper. Estas envuelven los datos tipados y los convierten gracias a
métodos apropiados a los tipos esperados.

Ejemplo:

// Número ‐‐> Cadena


String palabra = "";
int numeroEntero = 777_888;
palabra = Integer.toString(numeroEntero); // ‐‐> "777888"

// Cadena ‐‐> Número


numeroEntero = Integer.parseInt(palabra);
numeroEntero = numeroEntero + 1; // ‐‐> 777889

Java 8 permite escribir un número con un separador underscore (_) para una mejor legibilidad.

De la misma manera, es fácil convertir un booleano en cadena.

Ejemplo:

e inversamente:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

boolean eleccion = true;


String palabra = Boolean.toString(eleccion);

// Cadena ‐‐> Booleano


String palabra = "true";
boolean eleccion = Boolean.valueOf(palabra);
System.out.println("elección B.valueOf(palabra): " + eleccion);
if (eleccion == true) {
System.out.println("Soy un booleano.");
}

Observe que la condición if utilizada arriba no es óptima, para entender mejor su mecanismo. Una forma más
correcta sería:

if (eleccion) {
System.out.println("Soy una booleano.")
}

ya que la evaluación de la condición se hace evaluando datos booleanos.

Para las fechas, las conversiones son más complejas ya que hay que formatear y convertir las fechas en función de
la región. Java propone numerosas clases y métodos de las cuáles algunas serán utilizadas para nuestro proyecto.
Aquí tiene un ejemplo no óptimo de un método estático que convierte la fecha del día al formato día­mes­año.

public static String fechaDiaACadenaES() {


// Elección de la región por defecto
Locale locale = Locale.getDefault();
Date fechaDia = new Date();
// Definición del formato a utilizar
DateFormat fechaFormateada = new SimpleDateFormat("dd‐MM‐yyyy", locale);
String fecha = fechaFormateada.format(fechaDia);
return fecha;
}

Java 8 propone ahora una nueva gestión de fechas con las clases del package java.time. En el capítulo siguiente
dispone de una explicación más completa de su utilización. Si no utiliza Java 8 para el desarrollo de su aplicación,
tendrá que gestionar estas fechas «a la antigua» o utilizar librerías externas para simplificar esta gestión.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Método
Los métodos representan las acciones y los comportamientos que pueden llevar a cabo los objetos provenientes de
una clase. Definen el aspecto dinámico o de comportamiento de la clase. Los gatos pueden dormir, comer, maullar,
etc.

Presentamos algunos de estos métodos.

class Gato {
// propriedades
String nombre;
String color;
long fechaDeNacimiento;

// métodos
void dormir(){
return ;
}

void comer(Object comida){


}

String maullar(){
String elMaullido = "miau";
return elMaullido;
}
}

Los métodos que no devuelven ningún valor corresponden a procedimientos. Siempre van precedidos de la palabra
clave void.

Los métodos que devuelven un valor corresponden a funciones. Siempre van precedidas del tipo de datos devuelto.

Los métodos pueden evidentemente tener parámetros que deben estar tipados.

La palabra clave return permite salir del método y volver al método del que se ha llamado

Ejemplo:

double porcentaje(Double numerador, Double denominador) {


Double resultado = numerador/denominador * 100;
return resultado;
}

El código presentado aquí arriba conlleva acciones de autoboxing implícitas, noción que se detalla en el próximo
capítulo.

El nombre de un método y los tipos ordenados de sus parámetros constituye su firma (el tipo devuelto del método
es exclusivo de la firma, así como los nombres de los parámetros). Dentro de una clase dada, solo existe un único
método por firma.

Por ejemplo, el siguiente código provoca errores de compilación.

public class MalaFirma {


void calcular(int a, int b) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
// el compilador no acepta este método
int calcular(int c, int d) {
return c + d ;
}
}

Entre los métodos, algunos tienen la función de devolver los valores de las propiedades de la clase y otros
modificarlos. Son llamados accesores y mutadores (getters y setters).

Ejemplo:

int getEdad () {
// devuelve la edad calculada a partir de la
// fecha de nacimiento
}

long getFechaDeNacimiento () {
return fechaDeNacimiento ;
}

void setFechaDeNacimiento(long nuevaFecha) {


fechaDeNacimiento = nuevaFecha ;
}

Estos accesores y mutadores se codifican tradicionalmente según la forma getXXX/setXXX. La mezcla de


castellano e inglés no resulta ideal aquí, pero muchas herramientas se basan en esta notación para descubrir
dinámicamente las propiedades de una clase dada, como los Java Beans. Se aconseja conservar esta costumbre de
notación.

El término de servicios se utiliza a veces para designar los métodos y el de operación para la implementación de los
métodos.

Los métodos pueden prefijarse con la palabra clave static. En este caso ya no son específicos al objeto en sí sino a
su clase. Esto permite muchas veces crear servicios auxiliares, y más comúnmente el punto de entrada de una
aplicación.

Ejemplo:

static int calcularEdad(long fecha) {


Instant nacimiento = Instant.ofEpochMilli(fecha) ;
Instant ahora = Instant.now() ;
long anios = ChronoUnit.YEAR.between(nacimiento, ahora) ;
return anios ;
}

Los métodos estáticos son muy útiles pero tienen la desventaja de « romper » el paradigma de programación
orientada a objetos, y de hacer el código más complicado de modificar. Se desaconseja crearlos salvo que se tenga
una justificación para ello.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Accesibilidad
La POO añade el principio de accesibilidad (visibilidad) que consiste en controlar el acceso a las clases y a sus
miembros (propiedades y métodos).

El término de accesibilidad se utiliza también para designar las técnicas de uso de una aplicación para usuarios
minusválidos, por lo que en esta sección usaremos el término de visibilidad.

Los niveles de visibilidad en Java son los que siguen:

Clases :

public: las clases con visibilidad pública son accesibles por todo el mundo (todas las clases de todos los
packages).

<nada>: el hecho de no indicar la visibilidad de la clase hace a esta inaccesible fuera de las clases de su propio
package.

private: este nivel solo concierne las clases internas. Son clases definidas dentro de una clase y que solo son
utilizadas por esta.

Miembros :

public: los miembros (propiedades y métodos) son accesibles desde el exterior de la clase. Para proteger las
propiedades de modificaciones no autorizadas o equivocadas, se aconseja que nunca se declaren públicas.

protected: los miembros son accesibles por las clases hijas (ver la noción de herencia más adelante) pero
también con Java a las demás clases del mismo package.

<nada>: si no se específica la visibilidad, los atributos y métodos son accesibles únicamente para las clases
pertenecientes al mismo package.

private: es el nivel más elevado de restricción. Las propiedades y métodos solo son accesibles desde la propia
clase. En cuanto a las propiedades, pueden ser accedidas fuera de la clase pero únicamente mediante sus
mutadores y accesores declarados visibles.

Los miembros (atributos y métodos) de una clase pueden también calificarse con palabras claves especiales:

static: los miembros declarados static pertenecen a la clase y no a los objetos provenientes de la clase. No se
pueden heredar por las clases hijas.

final: los atributos declarados final no pueden ser modificados una vez inicializados. Deben también ser
inicializados en la construcción de los objetos. Los métodos declarados final no pueden redefinirse en las clases
hijas.

Otras palabras clave permiten caracterizar las clases y sus miembros en otros dominios diferentes al de la visibilidad
como la concurrencia o la optimización. Los lectores curiosos buscarán los usos de volatile, synchronized y
transient.

Aquí tiene la clase reescrita tomando en cuenta los principios enunciados.

public class Gato {


// propriedades
private String nombre;
private String color;
private long fechaDeNacimiento;

// accesores <=> getters


public String getNombreGato() {
return this.nombre;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public String getColor() {


return this.color;
}
public int getEdad() {
// calcula la edad en función de la fecha de nacimiento
return calcularEdad(fechaDeNacimiento);
}

public int getFechaDeNacimiento () {


return fechaDeNacimiento;
}

// mutadores <=> setters


public void setNombre(String nombre) {
this.nombre = nombre;
}
public void setColor(String color) {
this.color = color;
}
protected void setFechaDeNacimimento(long nuevaFecha) {
this.fechaDeNacimimento = nuevaFecha;
}

// otros métodos
public void dormir(){
}
public void comer(Object comida){
}
public String maullar(){
String elMaullido = "miau";
return elMaullido;
}

private static int calcularEdad(long fecha) {


Instant nacimiento = Instant.ofEpochMilli(fecha);
Instant ahora = Instant.now();
long anios = ChronoUnit.YEAR.between(nacimimento, ahora);
return anios ;
}
}

El atributo derivado edad, discutido anteriormente, se define con el uso del método getEdad(). No se corresponde
con ninguna propiedad almacenada en el objeto, sino a un cálculo donde interviene otro atributo.

Toda aplicación debe disponer de una clase con un método determinado encargado de su ejecución. Se trata del
método main() que hemos visto en el capítulo de Toma de contacto de Eclipse. Su sintaxis y estructura son
siempre las mismas:

public static void main(String[] args) {


// accion(es)
}

Por convención, se emplea el término args pero se puede elegir otro.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se trata de un método público y estático. Como hemos visto anteriormente, es por lo tanto accesible desde fuera
de la clase y pertenece a esta y no a sus instancias.

El método main() tiene un parámetro obligatorio llamado args de tipo String[].

Ejemplo:

public static void main(String[] args) {


System.out.print("¡ Java, es genial !");
}

En la única línea que constituye el cuerpo del programa, System designa una clase Java cuya madre es Object.
Consultando la ayuda, es posible ver la jerarquía de las clases.

Estas dos clases se guardan en el package lang, él mismo almacenado en el package java.

La palabra out designa una propiedad estática de la clase System, siendo out de tipo PrintStream cuya jerarquía
con sus clases madres se ve a continuación.

Consultando la clase PrintStream, se constata que println es un método de esta. Este método se puede usar en
la clase System con la propiedad estática out. El método println muestra el valor literal que se encuentra
delimitado por las comillas dobles y realiza un retorno de línea.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Encapsulación
El hecho de agrupar en el seno de una misma clase propiedades y métodos y definir el nivel de accesibilidad
constituye la encapsulación, uno de los conceptos esenciales de la POO.

Una clase va a actuar sobre los datos privados que le son propios, con la garantía que nada interferirá con el
tratamiento. Un desarrollador sabrá muy rápidamente qué parte del código es responsable (o infractora), ya que
una sola clase está habilitada para modificar el dato en cuestión. Solo son visibles los miembros públicos, lo que
refuerza la seguridad de los datos y el mantenimiento de los programas. Además, la implementación de los métodos
es enmascarada. Finalmente, con el concepto de encapsulación, la clase solo presenta un acceso compuesto por los
miembros públicos. Se esconde lo que es privado a los ojos del resto del mundo.

Retomando el ejemplo anterior, para modificar el nombre del gato, debe obligatoriamente usar el método
setNombre(), lo que permite efectuar eventuales controles y tratamientos adicionales. Tomando esto como
principio, puede aplicarlo a otros casos como el débito de una cuenta que no se puede realizar sin el método
apropiado y habilitado. Los riesgos de error se ven así reducidos y la seguridad aumenta. Sobre todo, la modificación
del código que se ocupa del maullido de un gato o del débito de una cuenta no tiene ninguna incidencia en el
programa, realizando este uso de un método mediante su firma.

Ejemplo:

// método modificado
public String maullar(){
// un gato que no para de maullar
String elMaullido = "miau miauuuu miauuuu ...";
return elMaullido;
}

// uso en un programa, como anteriormente


elGato.maullar();

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Constructor
Para poder instanciar objetos de una clase, hace falta un constructor. Es un método especial, con el mismo nombre
que la clase y escrito sin valor de retorno, que permite crear objetos. Si se omite un constructor, Java los insertará
al realizar la compilación del programa creando un constructor básico del tipo NombreDeLaClase(), sin
parámetros (se trata del constructor por defecto). Es posible declarar varios constructores en una clase, como en
el siguiente ejemplo.

public class Gato {


// propriedades
...
// constructores

private Gato() {
super();
}

private Gato(long laFechaDeNacimiento) {


this();
this.fechaDeNacimiento = laFechaDeNacimiento;
}

public Gato(String elNombre, String elColor,


long laFechaDeNacimiento){
this(laFechaDeNacimiento) ;
this.nombre = elNombre;
this.color = elColor;
}
// métodos
...
}

El primer constructor Gato() es privado. No puede utilizarse fuera de la clase Gato. Llama al constructor de la clase
madre con la ayuda de la instrucción super().

El segundo constructor Gato(long) es privado. Llama al primer constructor con la ayuda de la instrucción this().

El tercer constructor Gato(String, String, long) espera tres parámetros, lo que le permite crear un gato con un
nombre, un color y una fecha de nacimiento. Utiliza el segundo constructor privado, con la ayuda de la instrucción
this(laFechaDeNacimineto). Es público y puede por lo tanto ser utilizado en toda su aplicación y por programas
externos.

Ejemplo de uso de un constructor en un programa:

...
Gato elGato = new Gato("Minino", "pellirojo", 2);
...

La ausencia de alguno de los parámetros esperados provocará un error en la compilación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Herencia
Las costumbres del pensamiento humano utilizan habitualmente las nociones de jerarquía y de clasificación, como
por ejemplo para la zoología o para un árbol genealógico.

La POO intenta restituir esta manera de pensar gracias a la noción de herencia, con la que las clases «hijas»
recuperan las propiedades y comportamientos de su clase «madre», reservándose la posibilidad de poder
completarlas y/o modificarlas.

Retomando el ejemplo de Minino, un Mamífero es un concepto más general al de Felino que es un concepto más
general que Gato, del cual Minino es una instancia específica. En términos de programación orientada a objetos, es
posible crear una jerarquía de clases transponiendo esta manera de pensar. Existe por lo tanto un objeto Minino
construido desde una clase Gato que hereda de la clase Felino que hereda de la clase Mamífero.

De una manera más general, todos los felinos, cánidos y cetáceos heredan las características comunes de la clase
Mamífero: animal de sangre caliente, la hembra amamanta a sus crías, etc. Minino pertenece a una gran familia.
Va a heredar de todas las propiedades y métodos de sus clases ancestrales, dejando de lado los elementos
específicos de los cánidos y cetáceos. Para tener en cuenta esto, hay que revisar la definición de la clase Gato y
añadir las de las clases madres. Para hacerlo sencillo, aquí tiene un ejemplo limitado a las clases Felino y Gato.

public class Gato extends Felino {


private String nombre;
private long fechaDeNacimiento;
public Gato(String laEspecie, String elColor,
String elNombre, long laFechaDeNacimiento) {
super(laEspecie, elColor);
this.nombre = elNombre;
this.fechaDeNacimiento = laFechaDeNacimiento;
}
// los mutadores y accesores
...
// los demás métodos
...
}
public class Felino {
private String especie;
private String color;
public Felino(String especie, String color) {
this.especie = especie;
this.color = color;
}
// mutadores y accesores

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public String getColor() {


return this.color;
}
public void setColor(String color) {
this.color = color;
}
public String getEspecie() {
return this.especie;
}
}

Se deja la decisión de generalizar la propiedad fechaDeNacimiento a su libre entendimiento.

La palabra clave extends indica que la clase derivada (hija o subclase) Gato extiende de la clase base (madre o
super clase) Felina. La llamada del constructor de la clase madre se realiza con la palabra clave super().

Esto permite crear espacios en la aplicación para poder implementar características y componentes comunes a la
modelización. Cuando lo solicite la evolución de la aplicación, será posible crear una clase Tigre que heredará
también de la clase Felino y tendrá por lo tanto desde su creación todo los que caracterice a un felino: el código se
reutiliza, los comportamientos comunes son factorizados, y en términos globales la estructura de la aplicación
mejorada. Si se modifica el código de la clase Felino, todos los felinos de la aplicación se beneficiáran
inmediatamente de esta modificación indirectamente.

Resulta tentadora la sobre utilización en una aplicación, sobre todo para los desarrolladores que descubren esta
noción. Los maestros del pensamiento del Gang Of Four (Gamma, Helm, Johnson y Vlissides) recomiendan sin
embargo favorecer la composición de objetos (es decir combinar objetos simples para crear otros más complejos con la
ayuda de atributos) frente a la herencia de clase, ya que esta última técnica tiende a romper la encapsulación y crear
sistemas que son difíciles de modificar.

Aquí la clase Gato hereda los métodos getColor(), setColor() y getEspecie() de su clase madre sin necesidad de
reescribirlas.

El concepto de herencia está ligado a los comportamientos externos y no a una representación interna. Un pingüino
que no puede volar no debería heredar de una clase Pájaro donde se define el vuelo como un comportamiento.

A veces es útil indicar que una clase no puede ser instanciada directamente para crear objetos, cuando deseamos
crear una clase «sencilla» de base que será enriquecida por las clases hijas. En este caso, debe añadir la palabra
clave abstract. La clase Mamífero está definida a continuación como abstracta. No se podrá crear ninguna
instancia de esta clase, deberá extender la clase Mamífero para poder crear objetos.

public abstract class Mamifero {


}

Esto es coherente con la modelización. Aunque todos los gatos sean mamíferos, « un mamífero como tal » no
existe, y no debería tener la posibilidad de existir en la aplicación.

Para afinar el concepto de herencia, es posible precisar que algunas propiedades y métodos se pueden declarar
como protected.

public abstract class Mamifero {


protected boolean sangreCaliente = true;
protected void amamantar() {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

Ahora, solo las clases hijas de Mamifero pueden heredar de sus miembros y utilizarlos. Solo las clases
pertenecientes al mismo package pueden utilizar estos mismos miembros.

public class Felino extends Mamifero {


protected String especie;
...
protected String getColor() {
return this.color;
}
protected void setColor(String color) {
this.color = color;
}
...
}

Solo las clases hijas de Felino pueden heredar estos miembros definidos como protected.

También será posible declarar abstract la clase Felino.

La clase Gato no sufre, por su parte, ninguna modificación.

public class Gato extends Felino {


...
}

Aquí tiene un ejemplo de uso:

public class EjecutaHerencia {


public static void main(String[] args) {
Mamifero unGato = new Gato("siamés","gris","espinete",1);
unGato.amamantar();
System.out.println(unGato.sangreCaliente);
}
}

Para que funcione, esta clase debe encontrarse en el mismo package de las demás clases.

Todo esto resulta muy interesante para la modelización, pero este código no es necesariamente muy robusto. Si se
modifica la clase anterior ligeramente por:

public class EjecutaHerencia {


public static void main(String[] args) {
Mamifero unGato = new Gato("siamés","gris","espinete",1);
unGato.sangreCaliente = false ;
System.out.println(unGato.sangreCaliente);
}
}

¡Se ha obtenido un mamífero de sangre fría! Lo cual no se corresponde con lo que queríamos obtener...

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Con el fin de securizar más la modelización con respecto a este problema, podemos utilizar la palabra clave final.
Esto impedirá cualquier modificación del valor del atributo sangreCaliente, pudiendo utilizarlo en todo momento.

public abstract class Mamifero {


protected final boolean sangreCaliente = true;
}
}

La palabra clave final puede también utilizarse con las clases. Suponiendo que la clase Gato no se puede derivar y
con el fin de formalizar este hecho, se añade la palabra clave final a la definición de la clase como sigue:

public final class Gato extends Felino {


...
}

Se podrá como siempre usar la clase Gato como anteriormente, pero ahora es un punto final dentro de la jerarquía
de clases: no se puede formalizar el concepto de raza de Gato en la aplicación.

Se obtiene un ventaja más con el uso de la palabra clave final: sabiendo el compilador que no se puede cambiar este
valor o clase, el bytecode generado estará (un poco) más optimizado.

A diferencia de otros lenguajes de POO, la noción de herencia múltiple no existe en Java: una clase hija no puede
extender varias clases madres. Para paliar esta limitación, el lenguaje Java introduce la noción de interfaz.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Interfaces
Una interfaz es un medio para crear un contrato para las clases y por lo tanto los objetos, contrato que permitirá
definir los comportamientos obligatorios para todas las clases implementando esta interfaz.

El principal interés de una interfaz es factorizar comportamientos comunes para una utilización estándar. Si,
durante el ciclo de desarrollo de una aplicación se percata de que alguno de sus objetos puede tener una mejor
implementación (como un coche con mejor motor), y si usa una interfaz para comunicarse con este objeto, puede
modificar su código con más facilidad substituyendo su antigua clase con la nueva que integrará exactamente la
misma interfaz.

Volvamos al ejemplo del coche: cada modelo de coche es único. Se construye cada modelo siguiendo un esquema
diferente, con tecnologías diferentes. Sin embargo, la interfaz de cada modelo de coche es siempre más o menos la
misma: un volante, pedales para el freno y el acelerador, las palancas para los intermitentes... El volante siempre
está delante suyo, los pedales se encuentran en el mismo sitio sea cual sea el coche que conduzca, como las
palancas de intermitentes... ¡Felicidades! Conoce bien la interfaz para conducir un coche y no tiene que volver a
examinarse del permiso de conducir cada vez que se sienta en un coche diferente del suyo, como un coche inglés.

Por lo tanto ya ha aprendido a dominar una interfaz y no una implementación determinada, y eso en el mundo
real. Es posible transponer este dominio en una aplicación Java con la ayuda de interfaces que se declaran de la
siguiente manera:

interface Animal {
void comer(Object comida);
}

Una interfaz solo puede contener generalmente firmas de métodos públicos y abstractos, por lo tanto sin ninguna
implementación (sin llaves), y propiedades públicas, estáticas o finales.

Se ha subrayado anteriormente que la herencia múltiple no existe en Java. Gato no puede por lo tanto heredar
simultáneamente los comportamientos y características de varias clases.

Por el contrario, Gato puede implementar tantas interfaces como necesite.

public final class Gato extends Felino implements Animal,


SerVivo, Movible {
}

Una interfaz puede extenderse (derivarse) con otras interfaces mediante la palabra clave extends, como con una
clase. La diferencia principal respecto a una clase reside en el hecho de que una interfaz puede ella misma heredar
de varias interfaces. Las interfaces permiten ente otras cosas estructurar el código de manera más segura. En
efecto, cuando una clase implementa una interfaz con la palabra clave implements, debe obligatoriamente
redefinir todos los métodos de estas. Esto puede parecer restrictivo pero la aplicación tiene así la certeza de que los
servicios o contratos esperados por la interfaz implementada serán respetados.

interface Animal extends SerVivo, Movible {


void comer(Object comida);
}

Aquí, cualquier clase que desee implementar la interfaz Animal deberá obligatoriamente crear un método público
void comer(Object comida), además de los métodos presentes en las interfaces SerVivo y Movible.

En el interior de Java, se utiliza a menudo el mecanismo de interfaces. Permite crear modularidad y facilidades de
substitución de código. ¡Y Java permite usar y crear esta modularidad y esta facilidad en su aplicación!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se aconseja evitar crear interfaces con demasiados métodos y por lo tanto contratos. Esto perjudica globalmente a la
modelización y al ciclo de vida de la aplicación.

Para terminar, Java 8 permite definir implementaciones por defecto en las interfaces, con la ayuda de la palabra
clave default.

interface Animal {
default void comer(Object comida) {
System.out.println(this + " come " + comida);
}
}

Si un método de una interfaz tiene una implementación por defecto, no es necesario implementar este método: se
utilizará la implementación por defecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Polimorfismo
El polimorfismo es una noción importante en POO, y significa etimológicamente «que puede presentarse en
diferentes formas».

El polimorfismo es la capacidad del código para elegir dinámicamente en la ejecución el método a ejecutar, e implica
que una misma llamada a un método pueda tener comportamientos diferentes, ya sea en función de los
parámetros de esta llamada, o del objeto con el que se efectúa la llamada.

Java conlleva dos tipos de polimorfismo principales: la sobrecarga y la redefinición.

El mecanismo de polimorfismo puede también obtenerse con la ayuda de interfaces.

Desde Java 1.5, también existe un polimorfismo llamado paramétrico, usando los genéricos, que se estudiarán en el
siguiente capítulo.

1. Por sobrecarga
El polimorfismo de sobrecarga corresponde a la capacidad de un objeto de elegir en tiempo de ejecución
(dinámicamente) el método que corresponde mejor a sus necesidades entre sus propios métodos o los de la clase
madre.

Otra particularidad de este polimorfismo es que todos los métodos afectados llevan el mismo nombre. En el seno
de una clase, se diferencian obligatoriamente y únicamente por el nombre de los parámetros o el tipo de estos
últimos.

Recordatorio: una firma de un método solo se puede implementar una única vez en una misma clase.

Se modifica la clase Gato para evidenciar este mecanismo de sobrecarga.

public class Gato {


...
public void comer(String comida){
System.out.println("Como " + comida);
}

public void comer() {


System.out.println("Voy a cazar mi comida");
}

public void comer(List<Object> comida){


System.out.println("Me como todo esto: " + comida);
}
}

La notación List<Object> define un conjunto de datos de tipo Object sobre los cuales se puede iterar (recorrer
todos los elementos). Estas nociones serán desarrolladas en el próximo capítulo en las secciones Colecciones y
Genéricos.

Es posible entonces modificar el programa que hace uso de la clase:

elGato.comer("la comida para gato");


elGato.comer();
elGato.comer(Arrays.asList("croquetas", "comida para gato"));

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cuando se ejecuta, se obtiene el siguiente resultado:

Como la comida para gato


Voy a cazar mi comida
Me como todo esto: [croquetas, comida para gato]

Se puede aplicar también este mecanismo de sobrecarga a los constructores.

public class Gato {


private Gato() {
}

Gato(String nombre, String color) {


// crear un gato que acaba de nacer
this(nombre, color, new Date());
}
Gato(String nombre, String color,
Date fechaDeNacimiento){
this(nombre, color, fechaDeNacimiento.getTime());
}
Chat(String nombre, String color,
long fechaDeNacimiento) {
this();
...
}
}

2. Por redefinición
Los métodos de la clase madre pueden redefinirse en las clases hijas o subclases con una operación de
subtipado. Se trata en realidad de especialización. En el sentido inverso, es una generalización que permite
factorizar los comportamientos comunes.

Desde un punto de vista práctico, los métodos de las clases hijas se reescriben para obtener el comportamiento
deseado. Al usarlo, se tiene la impresión de llamar el método de la clase madre, pero en realidad el código que se
ejecuta es el de la clase hija.

Un ejemplo clásico es el método toString() de la clase Object, presente en TODAS las clases de la aplicación.
Permite proveer una descripción en texto del objeto instanciado. Se puede redefinir este método en las clases de
la aplicación para dar más información contextual sobre los objetos.

Retomando el caso de los animales, todos los mamíferos se alimentan pero los gatos domésticos comen
croquetas, lo cual no ocurre necesariamente en el caso de los tigres.

Con las clases ya creadas, obtenemos:

public class Felino {


...
public void comer(Object comida){
}
}

public class Gato extends Felino {


...
@Override

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void comer(Object comida) {


System.out.println("Como " + comida);
}
}
public class Tigre extends Felino {
...
@Override
public void comer(Object comida){
if( "croquetas".equals(comida)) {
System.out.println("Un tigre no come esto");
}
...
}
}

La línea @Override es una anotación, que se explicará en el próximo capítulo. Significa que el método anotado (el
situado justo debajo) debe redefinirse obligatoriamente y llevará a un error en caso contrario.

Ejemplo en un programa:

elGato.comer("croquetas") ;
elTigre.comer("croquetas") ;

Se obtiene el siguiente resultado al ejecutarlo:

Como croquetas
Un tigre no come esto

Para detener este mecanismo de redefinición, se puede utilizar la palabra clave final. En este caso, los métodos
no podrán redefinirse (reescribirse).

Ejemplo:

public class Felino {


...
public final void comer(Object comida){
System.out.println("Como " + comida);
}
}

public class Gato extends Felino {


...
// error en la compilación
public void comer(Object comida){
}
}

3. Por interfaz
El polimorfismo por interfaz permite tener métodos con el mismo nombre y tipos (hablamos de firmas
idénticas) en clases diferentes (sin ningún enlace de herencia entre ellas), cuya implementación puede también
ser diferente.

Para conservar un enlace semántico entre estos métodos, es importante crear una interfaz que incluya este
método, e implementar esta interfaz en las distintas clases. Esto permite llamar a este método a nivel de interfaz

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

sin conocer a priori la clase que concretiza este método.

Ejemplo de código:

public interface Oyente {


void algoHaSucedido(Object algo) ;
}

class PrimerOyente implements Oyente {


public void algoHaSucedido(Object algo) {
System.out.println("acción 1");
}
}
class SegundoOyente implements Oyente {
public void algoHaSucedido(Object algo) {
System.out.println("acción 2") ;
}
}

Se obtiene el siguiente resultado al ejecutar el código:

Oyente oyente = null ;

oyente = new PrimerOyente();


oyente.algoHaSucedido(null); // acción 1

oyente = new SegundoOyente() ;


oyente.algoHaSucedido(null) ; // acción 2

El código anterior crea objetos con las clases concretas PrimerOyente y SegundoOyente, y los almacena en
una variable de tipo Oyente. El tipo concreto del objeto desaparece por lo tanto en este momento. Sin embargo,
llamando al mismo método algoHaSucedido(), los comportamientos de ambos objetos difieren.

No es polimorfismo de redefinición, ya que las clases no tienen un jerarquía común. Se basa aquí el polimorfismo
en el contrato que deben respetar las clases que implementan la interfaz.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Principios SOLID
Para obtener una modelización y un desarrollo más fiables y robustos en programación orientada a objetos, es
posible utilizar cinco principios básicos agrupados bajo el acrónimo SOLID (en inglés). Estos principios vinculados
entre sí permiten arrancar un desarrollo sobre bases sanas que permitirán simplificar modificaciones posteriores.

1. Single Responsibility
El principio de responsabilidad única define que todas las clases de una aplicación deben responsabilizarse de
una de las funciones de la modelización, y que esta funcionalidad debe definirse en la clase. De esta forma, cada
clase solo tiene una única razón para modificarse, lo que permite guardar el control de su complejidad, mejorar la
legibilidad y posibilitar un mejor trabajo en equipo como ventajas inmediatas.

Un contra ejemplo de este principio es una clase que agrupa varias funcionalidades, como una clase Comando que
modeliza los datos, permite el acceso directo a la base de datos y gestiona ella misma la impresión. Esto crea una
clase compleja, cuyas distintas funcionalidades se encuentran enlazadas entre ellas, y que será sensible a
cualquier cambio, ya que no se puede garantizar que una modificación de la parte de impresión no impactará al
acceso a las bases de datos.

En vez de utilizar una herramienta multifunción cuyos elementos resultan mediocres en cuanto a su uso, es
preferible disponer de una caja con diferentes herramientas, cada una dedicada a un uso específico.

2. Open/Closed
El principio de abierto/cerrado enuncia que un programa debe ser abierto a extenderse pero cerrado a la
modificación. El objetivo de este principio es conseguir una modelización donde podrá añadir nuevos
comportamientos sin modificar la mecánica interna.

Un contra ejemplo de este principio es un módulo de generación de informes con distintos formatos: PDF,
imagen,… Suponiendo que un método de impresión tenga la forma:

if (formato == PDF) {
// generar un PDF
}
if (formato == JPEG) {
// generar una imagen JPEG
}

El día que los usuarios pidan un nuevo formato de impresión, tendrá que modificar este método para añadir una
nueva condición sobre la variable de formato (y es probable que tenga que añadir nuevas condiciones en otros
métodos).

Una de las posibilidades que respeta el principio abierto/cerrado es crear una clase que modelice el formato, que
contenga un método generar(). El día que deba crear un nuevo formato, basta con extender la clase, sin
modificar la mecánica interna de generación.

No es necesario pasar por cirugía el día en el que nos ponemos una nueva camisa.

3. Liskov Substitution

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El principio de substitución de Liskov postula que todo código que manipule una clase madre debe poder usar
una de sus clases derivadas sin conocer su uso: la clase base define un contrato que toda subclase debe
obligatoriamente respetar. El principio recíproco es que las clases derivadas deben poder sustituirse por su clase
base.

Un ejemplo clásico de violación de este principio es un método que evalúe una condición sobre el tipo (la clase) de
un objeto para efectuar operaciones sobre él, como en el seudocódigo que sigue:

si el archivo es de la clase ArchivoPDF


entonces manipulamos un archivo PDF
si el archivo es de la clase Archivo
entonces manipulamos un archivo de texto
...

Una de las claves para comprender este principio es que la relación de herencia (A es una especie de B) está
ligada a un comportamiento externo y no a un comportamiento o atributos internos.

Por ejemplo, una primera aproximación para modelizar un cuadrado es expresar esta noción como un caso
particular de un rectángulo con un ancho y un alto iguales: crea entonces una clase Cuadrado, que hereda de
una clase Rectangulo. Se modifican los métodos mutadores para expresar la restricción adicional.

Si se crea una condición genérica de los rectángulos para verificar que los atributos ancho y alto se modifican
correctamente, ¡la condición no será verdad para la clase Cuadrado! Contrariamente a la aceptación popular, en
términos de modelización un cuadrado no se comporta como un rectángulo.

Si esto se parece a un pato, cloquea como un pato, pero necesita pilas para funciones, no es un pato.

4. Interface Segregation
El principio de segregación de interfaces enuncia que un programa no puede depender de los métodos que no
usa. En una modelización por contrato, este contrato debe poder separarse en pequeñas interfaces.

Este principio permite también contribuir al primer principio de responsabilidad única.

A la inversa, si una funcionalidad define muchos métodos en su contrato, como generar(), imprimir(),
enviarFax(),..., aporta demasiados conocimientos. ¿Realmente necesita un usuario de la función de impresión
conocer la función de fax? Además, cuando tenga que crear un nuevo sistema de impresión, ¿es necesario que
tenga que recrear el mecanismo de fax?

Es menos costoso reemplazar un destornillador que volver a comprar una herramienta multifunción de la que solo
utilizamos el destornillador.

5. Dependency Inversion
El principio de inversión de dependencias postula que un módulo de alto nivel no debe depender de la
implementación de un módulo de bajo nivel pero que los dos niveles deben estar ligados por una abstracción.
Además, una abstracción no debe depender de los detalles, pero los detalles deben depender de las abstracciones,
pudiendo materializarse la abstracción en una clase base o una interfaz.

Un contraejemplo es un cliente dependiente de clases de un servidor para el tratamiento de la comunicación


entre ambos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El día en el que el protocolo de datos del servidor cambie, deberá modificar el cliente con todos los riesgos de
incompatibilidad que acontezcan.

Un mejor diseño siguiendo el principio de inversión de dependencias consistiría en intercalar entre ambos una
noción de protocolo (mediante una interfaz o una clase abstracta), protocolo implementado en una clase distinta.

Este cambio de diseño permite minimizar el impacto derivado del cambio de protocolo, y permite potencialmente
al sistema generar varios protocolos a la vez.

Soldar una lámpara a un enchufe eléctrico no resulta generalmente una buena idea.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Algunos principios útiles


La literatura es abundante en cuanto a principios aplicables a la POO. Detallamos algunos de ellos en esta sección.

1. DRY (Don’t Repeat Yourself)


El principio DRY («No se repita» en español) tiene como finalidad limitar las repeticiones de información y
procesamientos en una aplicación y postula que cada elemento de información en un sistema debe tener una
representación única, no ambigua y con autoridad.

Considera el copia­pega excesivo de código como un indicador de inestabilidad en la aplicación.

El credo personal del autor en relación a este principio es: cree una vez, cópielo la segunda, factorice la tercera.

2. KISS (Keep It Simple, Stupid)


El principio KISS («Sea simple y estúpido») privilegia la simplicidad en el diseño para conseguir lo más
rápidamente posible el objetivo a alcanzar.

Aquí debe evitar la complejidad innecesaria del código, sobre todo en cuanto a optimizaciones que solo deberían
llevarse a cabo cuando una versión sencilla de la aplicación es operacional.

Una aplicación simple resulta más sencilla de comprender y mantener, pero no necesariamente más sencilla de
desarrollar. O citando a Leonardo da Vinci: «La simplicidad es la sofisticación suprema».

3. YAGNI (You Aren’t Gonna Need It)


El principio YAGNI («No lo va a necesitar») previene de la creación de funcionalidades que no son
inmediatamente necesarias para la aplicación.

Siempre es tentador crear código que no se va a usar directamente porque «podríamos necesitarlo algún día».
Sin embargo, siempre que este código no esté implementado, no está sistemáticamente probado, su escritura ha
consumido tiempo que podría dedicarse a otras funcionalidades inmediatamente indispensables, y añade
complejidad al sistema.

¿Necesita ahora de una lógica ternaria (Sí, No, Tal vez) en su código?

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Genéricos
Java introdujo otro tipo de polimorfismo desde Java 1.5, llamado paramétrico, que permite tipar y restringir las
clases. Estas restricciones se expresan mediante un notación entre los símbolos < y >.

Retomando el ejemplo de los animales, y suponiendo que la aplicación concierne a un zoo, la modelización incluirá
corrales que agrupan diferentes animales. En estos corrales, se debe evitar a toda costa mezclar carnívoros con
herbívoros. Los genéricos permiten establecer este tipo de seguridad.

Ejemplo:

public class Corral<T extends Animal> {


private T[] animales;
...
public anadir(T animal) {
...
}
}

El ejemplo anterior define una noción de corral para cualquier tipo de animales, gracias a la notación entre los
símbolos < y >. Esta notación define un tipo particular, con el alias T, que se usará para todos los objetos que
extienden la clase Animal. Se puede entonces usar este alias en el interior de la clase en substitución del tipo.

La clase Corral puede después instanciarse con el operador diamante desde Java 1.7 como sigue:

Ejemplo de utilización con Java 1.8:

Corral<Tigre> corralDeTigres = new Corral<>();

O en Java 1.5 como:

Ejemplo de utilización con Java 1.5:

Corral<Mono> corralDeMonos = new Corral<Mono>();

Se pueden crear corrales más específicos:

class CorralHerbivoro<T extends Herbivoro> extends Corral<T> {


...
}

Los genéricos ofrecen una protección a la compilación. Si una parte del código intenta enlazar un objeto que no
tiene el tipo adecuado, un error aparece.

Corral<Tigre> corralDeTigres = new Corral<>();

corralDeTrigres.add(new Mono()); // error en la compilación

Los métodos pueden también declararse como genéricos:

<T extends Animal, P> T resultado(P parametro) {


return new Tigre();
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Colecciones
Cuando se modeliza un programa con la ayuda de clases, es necesario crear abstracciones de datos. Sin embargo,
igual de importante es poder utilizar esos datos como conjunto.

Es posible utilizar vectores para ello, como:

Animal[] animales = new Animal[42];

Pero los vectores no son muy flexibles: no se pueden redimensionar (se dice que tienen un tamaño fijo), no tienen
métodos, etc. Para aprovechar todas estas características, una colección es una herramienta muy potente.

El concepto de colección es simple. Corresponde a las colecciones de sellos, conchas, etc. Los objetos de la misma
naturaleza se almacenan en el interior de una colección. Java creó desde su primera versión en el package
java.util clases e interfaces que dan una ayuda para modelizar estos conjuntos de datos, y pone a disposición del
programador varios tipos de colecciones que difieren en sus características: ordenadas o no, datos repetidos o no,
valores null aceptados o no, ordenación por defecto o no, etc. Todas tienen la capacidad de redimensionarse
automáticamente al añadir o suprimir objetos.

Una instancia de una colección es un objeto como toda instancia de una clase.

Java define varias interfaces que permiten especificar el contrato y el comportamiento de estos conjuntos de datos.
Contamos muchos, pero tradicionalmente existen dos que sirven de base:

La interfaz Map representa el contrato de una estructura donde cada dato es referenciado por una clave.

La interfaz Collection representa el contrato base para todas las demás estructuras de datos, entre las cuales
encontramos especializaciones derivadas:

La interfaz List representa el contrato funcional para todas las estructuras de datos ordenados (en las que se
puede encontrar un elemento determinado gracias a su orden de inserción, es decir su índice en la lista).

La interfaz Set representa el contrato de una estructura donde cada elemento es único en ella. Estos datos no
tienen por qué estar ordenados.

La interfaz Queue representa el contrato de una estructura de datos ordenados, pero en la que la adición se hace
al final de la estructura y la extracción al principio. Un poco como una fila de espera en un supermercado, en el
peaje de la autopista, etc.

Estos contratos base se especializan después con interfaces que los extienden, como SortedSet, Deque,
NavigableMap, etc.

Java proporciona varias implementacione base de estas interfaces, que se pueden elegir en función de las
necesidades y objetivos de la modelización.

Se implementa la interfaz List con las clases ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack,...

Se implementa la interfaz Map con las clases HashMap, ConcurrentHashMap, Hashtable,...

Se implementa la interfaz Set con HashSet, TreeSet, EnumSet, etc.

Se implementa la interfaz Queue con LinkedList, PriorityQueue, PriorityBlockingQueue,...

Cada estructura de datos es genérica, lo que significa que es posible crear conjuntos de datos que tengan todos un
contrato común en referencia a su contenido.

List<Animal> animales = new ArrayList<>();


List<Tigre> tigres = new LinkedList<>();
animales.add(new Tigre());
animales.add(new Mono());

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

...

tigres.add(new Mono()); // provoca un error en la compilación

Es interesante solamente enlazar, en la ejecución de un programa, a la interfaz de la estructura de datos, y no a su


implementación. Si las necesidades evolucionan, es posible entonces cambiar el tipo de la estructura sin modificar el
resto del programa.

List<Animal> animales = new LinkedList<>() ;


List<Tigre> tigres = new CopyOnWriteArrayList<>() ;

// el resto del programa queda sin cambios

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de los errores


Lo cotidiano para un programa y sus desarrolladores es gestionar los errores. Incluso es correcto decir que en un
contexto industrial, la gestión de los errores representa más tiempo y código que el funcionamiento normal.

Por lo tanto debe en todos los programas aprender a gestionar los datos incorrectos introducidos por un usuario
(introduce letras en vez de números para un precio), a gestionar los errores técnicos (la conexión a la base de datos
está desconectada o la red se ha caído), a crear estrategias de compensación (reintentar una connexión al menos
tres veces antes de abandonar, etc.).

Para ganar en claridad, Java proporciona un mecanismo de gestión de los errores por excepción. Se trata de
ejecutar una parte del código susceptible de generar excepciones (por lo tanto errores) englobándola y proporcionar
instrucciones alternativas si se produce alguna excepción. Una conexión se puede entonces reintentar, se pueden
indicar valores por defecto, ...

Una excepción es por lo tanto un evento que interrumpe la secuencia normal de la aplicación y que permite
ejecutar un código alternativo.

Esto se hace con la ayuda de bloques try­catch­finally, como en el siguiente ejemplo:

try {
// acceder a la red
// tratar los datos recuperados
} catch (IOException e) {
// el acceso a la red no se ha podido realizar
// el tratamiento de los datos no será efectuado

// proponer un tratamiento alternativo


} finally {
// se ejecutará este código en el caso normal
// Y en caso de error
}

Uno de los intereses de la gestión de los errores por excepción es que si alguna sección de código produce un error,
el resto del código en el bloque try no se ejecutará. Otro de sus intereses es claramente separar el funcionamiento
normal del código y el tratamiento de los errores.

Se proponen las excepciones en muchos lenguajes de programación diferentes. Antes de su creación, los
desarrolladores se basaban en los códigos de retorno, de los cuales se tenían que vigilar todos sus valores posibles y
actuar en función de ellos. Esto provocaba la creación de pirámides de if ... else, para los que su lectura era... laboriosa.
Y si un desarrollador olvidaba un valor de retorno, aparecían agujeros en la gestión de errores.

Java define una jerarquía de objetos que describen los posibles errores que aparecen en un programa, de los cuales
la clase base es Throwable.

Se especializa esta clase en dos subclases: Error y Exception. Error se refiere a problemas muy raros, como una
clase que no puede ser leída, o menos raros como un desbordamiento de pila (en inglés: stack overflow), mientras
que Exception se refiere a problemas más típicos de la ejecución de un programa, como la falta de una clase, la
falta de red o una variable usada cuando no ha sido inicializada.

Las excepciones se subdividen a continuación en dos categorías: las excepciones verificadas (checked) y no
verificadas (unchecked). Estas últimas heredan todas de la clase RuntimeException.

La principal diferencia entre estos dos tipos de excepciones está ligada a su declaración:

Si un método es susceptible de lanzar una excepción verificada, solo existen dos maneras de tratarlas:

O con un bloque try­catch­finally en el método,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

O declarando el método como susceptible de lanzar esta excepción con la palabra clave throws en su firma.
Traslada la responsabilidad del tratamiento de la excepción a los métodos situados más arriba en la pila de
llamada.

void metodoConExcepcion() throws MiExcepcion {

void llamadaConExcepcion() throws MiExcepcion {


metodoConExcepcion();
}

void llamada() {
try {
metodoConExcepcion();
} catch (MiExcepcion e) {
// ... gestionar la excepción
}
}

Es tentador tratar de inmediato una excepción. Muchas veces resulta más interesante dejar que lo haga el código que
invoca, sobre todo en el caso de la creación de una librería.

Si un método puede lanzar una excepción no verificada, no es obligatorio tratarla. Sin embargo puede aparecer,
como muchos desarrolladores verifican cada día. Ejemplos de este tipo de excepciones son las
NullPointerException, UnsupportedOperationException, ArithmeticException...

El compilador Java considera Error y sus subclases como no verificadas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Boxing/Unboxing
Dentro de Java, todo son objetos... o casi: existe lo que se llaman los tipos primitivos, como int, double, float, char,
boolean.

Cada uno de estos tipos primitivos tiene su equivalente en objeto: Integer, Double, Float, Character, Boolean.

Para facilitar la codificación, el lenguaje Java puede realizar una conversión automática del tipo primitivo a su
equivalente en objeto: se trata del mecanismo de boxing.

private Integer transformar(int entero) {


return entero; // convierte el tipo primitivo int
// en objeto de tipo Integer
}

Para hacerlo explícitamente, utilice el método estático valueOf() de las clases Boolean, Integer, Double, Float o
Character.

private Boolean transformar(boolean booleano) {


// convierte el tipo primitivo boolean
// en objeto de tipo Boolean
return Boolean.valueOf(booleano);
}

La operación inversa, llamada unboxing, permite por su parte transformar objetos Java en su tipo primitivo.

private double convertir(Double decimal) {


return decimal; // convierte el objeto de tipo Double
// en valor de tipo primitivo.
}

Para hacerlo explícitamente, utilice los métodos de instancia xxxValue():

Boolean objetoBooleano = Boolean.TRUE ;


boolean booleano = objetoBooleano.booleanValue();
System.out.println(booleano);

int entero = Integer.valueOf(1).intValue();


double decimal = Integer.valueOf(2).doubleValue();
float comaFlotante = Integer.valueOf(3).floatValue();

System.out.println(entero);
System.out.println(decimal);
System.out.println(comaFlotante);

// resultado
true
1
2.0
3.0

Existe una trampa con esta facilidad del lenguaje Java: si se usa el mecanismo de unboxing con un valor no
inicializado (la célebre palabra clave null), se produce una excepción, que no será muy explícita, y que puede ser
dificil de localizar.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Double cuidado = null;


double conversion = convertir(cuidado);
...
private double convertir(Double decimal) {
return decimal; // ¡error aquí!
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Enums
Un enum es un tipo especial de datos que proporciona un juego de constantes predefinidas

Es perfectamente posible crear un enum personalizado: se define como una clase o una interfaz en su propio
archivo, con la palabra clave enum.

Por ejemplo:

public enum Direccion {


NORTE, SUR, ESTE, OESTE
}

Puede también darle atributos, métodos y un constructor. La restricción principal es entonces que el constructor
debe ser privado o pertenecer a un package privado: no es posible crear un valor enum directamente, estos valores
solo pueden ser los declarados en el enum. Esta restricción prohibe también cualquier herencia de enum.

public enum Direccion {


NORTE("septentrional"),
SUR("sur"),
ESTE("levante"),
OESTE("poniente"); // el punto y coma es ahora necesario
// ya que existen miembros en el enum
private String antiguoNombre;

private Direccion(String nombre) {


this.antiguoNombre = nombre;
}

// public Direccion(String nombre) { // no compilará


// this.antiguoNombre = nombre;
// }

public String getAntiguoNombre() {


return this.antiguoNombre;
}
}

Esto permite limitar los valores posibles de una variable con un vocabulario controlado y poder usar esta variable en
un switch­case, por ejemplo.

Direccion direccion = ...

for(Direccion unaDireccion : Direccion.values()) {


System.out.println(unaDireccion +" "
+unaDireccion.getAntiguoNombre());
}
switch (direccion) {
case NORTE:
...
break;

default:
break;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Java proporciona varios enums como por ejemplo DayOfWeek y Month del package java.time. Estos enums
permiten formalizar los conceptos Lunes, Martes, etc. y los de Enero, Febrero, etc. y evitar tener que realizar un
ejercicio inútil (quitar 1 al número del mes...).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión del tiempo y de las fechas


La gestión de fechas ha sido históricamente un punto débil de Java.

Antes de la aparición de Java 8, crear una fecha, es decir un instante preciso de una línea temporal, se hacía con la
ayuda de la clase java.util.Date. Por ejemplo:

Date ahora = new Date();

El problema era ante todo conceptual: un objeto Date representa realmente un instante con una precisión de
milisegundos, podríamos decir un tiempo máquina, calculado por el número de milisegundos transcurridos desde el
epoch Java: el 1 de Enero de 1970 a la medianoche UTC (Coordinated Universal Time). Sin embargo, si utilizamos
el método toString() del objeto Date:

String descripcion = new Date().toString();


System.out.println(descripcion); // Mon Jun 01 14:09:38 CEST 2015

obtenemos una descripción de la fecha realizada para ser leída por humanos, sobre todo por la presencia del huso
horario (CEST significa Central European Summer Time, o sea la hora de verano en el huso horario de Madrid).

Estas dos nociones de un tiempo máquina y de un tiempo humano son conceptualmente diferentes, ya que un
tiempo humano utiliza siempre el concepto de huso horario y de horario de verano/invierno mientras que un
tiempo máquina es esencialmente un sello de tiempo (un timestamp en inglés). De la misma manera, un niño
nacido en Madrid el 1 de Julio de 2016 a las 15h55 y un niño nacido el mismo día a la misma hora y mismo minuto
en Katmandú no tienen exactamente la misma edad: tienen algunas horas de diferencia (3 horas y 45 minutos
para ser precisos).

La clase Date era tan errónea que casi todos sus métodos se han marcado como deprecados en la siguiente versión
de Java, con la introducción de una clase Calendar, que también sufría de problemas conceptuales.

Para obtener el año desde un objeto Date, hay que añadirle 1900, ¡y el mes 0 es el mes de Enero!

Java 8 introduce una revisión completa de la gestión del tiempo, y proporciona las clases del package java.time.
Sus principales clases son: Instant, LocalDate, LocalTime, LocalDateTime, ZoneId y ZonedDateTime. Las
clases Date y Calendar son también compatibles con estas nuevas clases.

Si crea un nuevo programa con Java 8, priorice siempre las clases del package java.time. La librería Joda
(http://www.joda.org/joda­time/) resulta de una ayuda preciosa para cualquier desarrollo con una versión menos reciente
de Java.

Instant representa una instante preciso en el tiempo. Esta clase no tiene ninguna noción de huso horario. Se
trata del equivalente más próximo a la clase java.util.Date.

La clase LocalDate representa un día preciso, sin indicación de hora y sin noción de huso horario: se trata de la
representación Java del día de un calendario de pared.

La clase LocalTime representa una hora precisa, sin noción de huso horario: se trata de la representación Java de
un reloj de pared.

La clase LocalDateTime es una combinación de las dos anteriores clases y representa por lo tanto un día y una
hora precisa sin noción de huso horario.

ZoneId representa un huso horario, con su eventual diferencia horaria de verano/invierno. ZoneDataTime
representa una transcripción de un instante en el tiempo en ese huso horario en particular.

Se ilustra su funcionamiento con un pequeño programa que permite mostrar sus diferencias.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree un pequeño programa con un método main() que permita ejecutarlo.

Dentro de este método main(), escriba el siguiente código y ejecutelo con [Ctrl][F11]:

public static void main(String[] args) {


Date ahora = new Date();
System.out.println("Fecha: " + ahora);
System.out.println("Instante: " + ahora.toInstant());
}

// resultado
Fecha: Mon Jun 01 14:09:38 CEST 2015
Instante: 2015‐06‐01T12:09:38.321Z

La fecha muestra una descripción del instante preciso cuando se ejecuta el método main() ¡en inglés! El instante
Java 8 que corresponde con el método toInstant() se muestra de forma normalizada con números, una T para
indicar la hora y la letra Z para describir el huso horario Zulu, utilizado por los militares y la aviación.

Ambos resultados tienen una diferencia horaria de dos horas, que corresponden a la diferencia de la hora de verano
en Madrid: un instante siempre se muestra en el huso horario militar.

Es posible con Java 8 conocer el instante preciso del inicio de un día en particular en el calendario, en distintas
zonas del mundo.

Complete el método main() con la ayuda del siguiente código y ejecutándolo:

LocalDate hoy = LocalDate.now();

LocalDateTime mediaNoche = hoy.atStartOfDay();


System.out.println("Medianoche:" + mediaNoche);

ZonedDateTime enMadrid = mediaNoche.atZone(ZoneId.systemDefault());


System.out.println("Medianoche en Madrid:" + enMadrid);

System.out.println("Un instante en Madrid:" +enMadrid.toInstant());

ZonedDateTime enKtm = mediaNoche.atZone(ZoneId.of("Asia/Katmandu"));


System.out.println("Medianoche en Katmandú:" + enKtm);
System.out.println("Un instante en Katmandú:" + enKtm.toInstant());

// resultado
Hoy:2015‐06‐01
Medianoche:2015‐06‐01T00:00
Medianoche en Madrid:2015‐06‐01T00:00+02:00[Europe/Paris]
Un instante en Madrid:2015‐05‐31T22:00:00Z
Medianoche en Katmandú:2015‐06‐01T00:00+05:45[Asia/Katmandu]
Un instante en Katmandú:2015‐05‐31T18:15:00Z

Para obtener un instante a partir de un objeto LocalDate, es necesario añadirle una hora: esto se hace aquí
gracias al método atStartOfDay(), que devuelve un objeto de tipo LocalDateTime. Esto se podría hacer también
con el método atTime(hora, minuto), u otro de los métodos atTime(...).

Esta forma de trabajar permite evaluar medianoche en Madrid y en Katmandú, estando estas dos medianoches
situadas de distinta manera en el tiempo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En el ejemplo, el 1 de Junio de 2015 a medianoche, que únicamente representa una fecha en un calendario con
una hora determinada de un reloj, corresponde en Madrid con el instante situado el 31 de Mayo de 2015 a las 22
horas UTC (o GMT, aunque sea una amalgama confundir UTC y GMT), y en Katmandú con el instante del 31 de
Mayo de 2015 a las 18 horas y 15 minutos UTC.

Los objetos del package java.time son inmutables: ¡una vez creados no pueden ser modificados! Es posible por el
contrario crear nuevos objetos a partir de estos con la ayuda de los distintos métodos, empezando generalmente por
«at» o «with».

Examinemos ahora cómo, a partir de un instante preciso y único en el tiempo, es posible obtener la hora de las
distintas partes del mundo.

Agregue las siguientes líneas de código y ejecute el programa:

LocalDateTime fechaHora = null;

Instant ahora = Instant.now();


System.out.println("Instante:" + ahora);

ZoneId aqui = ZoneId.systemDefault();


fechaHora = LocalDateTime.ofInstant(ahora, aqui);
ZonedDateTime enMadrid = ahora.atZone(aqui);
System.out.println("Fecha local Aquí: " + fechaHora);
System.out.println("Fecha en la zona Aquí: " + fechaHora.atZone(aqui));
System.out.println("Ahora Aquí: " + enMadrid);
System.out.println("Un instante Aquí: " + enMadrid.toInstant());
System.out.println();

ZoneId ktm = ZoneId.of("Asia/Katmandu");

fechaHora = LocalDateTime.ofInstant(ahora, ktm);


System.out.println("Fecha local en Katmandú: " + fechaHora);
System.out.println();
System.out.println("Fecha en la zona de Katmandú: "
+ fechaHora.atZone(ktm));
System.out.println("Fecha en la zona Aquí: " + fechaHora.atZone(aqui));
System.out.println();

ZonedDateTime enKtm = ahora.atZone(ktm);


System.out.println("Ahora en Katmandú: " + enKtm);
System.out.println("Un instante en Katmandú: " +enKtm.toInstant());
System.out.println();

ZoneId bsas = ZoneId.of("America/Buenos_Aires");

fechaHora = LocalDateTime.ofInstant(ahora, bsas);


System.out.println("Fecha local en Buenos Aires: " + fechaHora);
System.out.println("Fecha en la zona de Buenos Aires: "
+fechaHora.atZone(bsas));
System.out.println("Fecha en la zona Aquí: " +fechaHora.atZone(aqui));

ZonedDateTime aBsAs = ahora.atZone(bsas);


System.out.println("Ahora en Buenos Aires : " + aBsAs);
System.out.println("Un instante en Buenos Aires: "

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

+ aBsAs.toInstant());

// resultado
Instante: 2015‐06‐01T19:42:14Z
Fecha local Aquí: 2015‐06‐01T21:42:14
Fecha en la zona Aquí: 2015‐06‐01T21:42:14+02:00[Europe/Paris]
Ahora Aquí: 2015‐06‐01T21:42:14+02:00[Europe/Paris]
Un instante Aquí: 2015‐06‐01T19:42:14Z

Fecha local en Katmandú: 2015‐06‐02T01:27:14

Fecha en la zona de Katmandú : 2015‐06‐02T01:27:14+05:45[Asia/Katmandu]


Fecha en la zona Aquí: 2015‐06‐02T01:27:14+02:00[Europe/Paris]

Ahora en Katmandú: 2015‐06‐02T01:27:14+05:45[Asia/Katmandu]


Un instante en Katmandú : 2015‐06‐01T19:42:14Z

Fecha local en Buenos Aires: 2015‐06‐01T16:42:14


Fecha en la zona de Buenos Aires:
2015‐06‐01T16:42:14‐03:00[America/Buenos_Aires]
Fecha en la zona Aquí: 2015‐06‐01T16:42:14+02:00[Europe/Paris]
Ahora en Buenos Aires:
2015‐06‐01T16:42:14‐03:00[America/Buenos_Aires]
Un instante en Buenos Aires: 2015‐06‐01T19:42:14Z

Este ejemplo muestra que el 1 de Junio de 2015 a las 19 horas y 42 minutos UTC, que es un instante único en el
tiempo, corresponde al 1 de Junio de 2015 a las 21 horas y 42 minutos en el huso horario de Madrid (se dice más
habitualmente de Madrid hora local), al 2 de Junio de 2015 a la 1 y 27 de la mañana en el huso horario de
Katmandú, y al 1 de Junio de 2015 a las 16 horas y 42 minutos en Buenos Aires.

Estas pocas líneas ilustran la diferencia entre los conceptos de tiempo máquina y tiempo humano: el tiempo
humano incluye además del tiempo una noción de espacio (el huso horario) y una noción de compensación (la
diferencia horaria de verano/invierno).

Si se incluye una gestión del tiempo en su aplicación, siempre intente hacerlo con los objetos de tipo Instant. Esta
clase es la piedra angular en base a la cual se articulan las demás clases del package java.time.

Además existen maneras simples y elegantes para calcular el número de minutos o de semanas entre dos
instantes dados, para añadir dos días al inicio del próximo mes,... El package java.time es una de las grandes
novedades de Java 8.

Por ejemplo, ¿cómo calcular la hora local de llegada de un avión volando de Madrid a Katmandú? Aquí tiene una
propuesta de cálculo:

DateTimeFormatter formato = DateTimeFormatter


.ofLocalizedDateTime(
FormatStyle.LONG)
.withLocale(new Locale(”es”));

LocalDateTime enElBillete = LocalDateTime.of(2015, Month.JULY, 01,


19, 42);
ZoneId husoSalida = ZoneId.systemDefault();
ZonedDateTime salida = ZonedDateTime.of(enElBillete, husoSalida);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

String strSalida = salida.format(formato);


System.out.printf("Salida de aquí: %s (%s)%n", strSalida,
husoSalida);

// El vuelo dura 15 horas y 5 minutos, o sea 905 minutos


ZonedDateTime llegadaAqui = salida.plusMinutes(905);

ZoneId husoLlegada = ZoneId.of("Asia/Katmandu");


ZonedDateTime llegadaHoraLocal = llegadaAqui
.withZoneSameInstant(husoLlegada);

String strLlegada = llegadaHoraLocal.format(formato);


System.out.printf("Llegada hora local: %s (%s)%n", strLlegada,
husoLlegada);
strLlegada = llegadaAqui.format(formato);
System.out.printf("Llegada hora de aquí: %s (%s)%n", strLlegada,
husoSalida);

Duration duracion = Duration.between(salida, llegadaHoraLocal);


Duration duracion2 = Duration.between(salida, llegadaAqui);
System.out.printf("Duración del vuelo: %s %s %n", duracion, duracion2);

// resultado
Salida de aquí: 1 de julio de 2015 19:42:00 CEST (Europe/Madrid)
Llegada hora local: 2 de julio de 2015 13:47:00 NPT (Asia/Katmandu)
Llegada hora de aquí: 2 de julio de 2015 10:02:00 CEST (Europe/Madrid)
Duración del vuelo: PT15H05M PT15H05M

Aquí tiene una manera para formatear una fecha al formato español usando este nuevo package:

String matriz = "dd/MM/yyyy";


Instant laFecha = ...

DateTimeFormatter formato = DateTimeFormatter.ofPattern(matriz);


ZoneId huso = ZoneId.systemDefault();

ZonedDateTime fechaEnHuso = laFecha.atZone(huso);

String fecha = formato.format(fechaEnHuso);

Esta secuencia se descompone de la siguiente manera:

La matriz es el formato de la fecha, dd significa las dos cifras del día, MM las dos cifras del mes, yyyy el año. La
sintaxis del formato es amplia, consulte la documentación de la clase DateTimeFormatter para una explicación
extensa.

La variable laFecha es el instante que debe convertirse en cadena de caracteres.

El código obtiene un objeto que formateará la fecha en función de la matriz gracias al método estático
ofPattern(...) de la clase DateTimeFormatter.

El huso horario actual (como está configurado en el ordenador) se obtiene del método estático systemDefault()
de la clase ZoneId.

El método atZone() de la clase Instant convierte un instante en un objeto temporal de zona, es decir que se
convierte el tiempo según el huso horario en parámetro.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se formatea después este objeto temporal de zona como cadena de carácteres gracias al método format.

También es posible realizar la operación inversa, que consiste en encontrar un instante partiendo de una cadena de
caracteres que describe un día determinado: una efeméride.

DateTimeFormatter formato =
DateTimeFormatter.ofPattern("dd/MM/yyyy");

LocalDate fecha = LocalDate.parse(laFechaCadena, formato);


LocalDateTime mediaNoche = fecha.atStartOfDay();
ZoneId huso = ZoneId.systemDefault();
ZonedDateTime aqui = mediaNoche.atZone(huso);

Instant laFecha = aqui.toInstant();

El código se inicia de la misma manera, obteniendo un formateador de fecha en función de la matriz.

Como se trata de un día, sin indicación de la hora, se utiliza el método estático parse de la clase LocalDate. El
método devuelve un objeto de tipo LocalDate.

Después se trata de añadir una hora a este objeto. Para ello, el método de instancia atStartOfDay() devuelve un
objeto temporal del día concreto a medianoche, o sea al primer segundo del inicio del día.

Técnicamente se trata del primer nanosegundo del inicio del día.

Se obtiene ahora un tiempo situado en un huso horario, que permite obtener su instante preciso.

Antes de Java 8, la clase SimpleDateFormat permitía convertir una fecha en cadena, pero su uso presentaba
problemas sobre todo de acceso concurrente (ver la sección Threads).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Eventos
Toda aplicación que incluya interfaces gráficas debe ser capaz de gestionar eventos: se trata de un concepto
esencial de las interfaces hombre­máquina (o HMI).

Un evento es una señal que se envía desde las capas base del sistema operativo o de Java para notificar al
programa de que algo notable acaba de suceder: el usuario ha hecho clic sobre un botón, teclea con el teclado, pasa
por encima de una determinada zona de la interfaz gráfica con el ratón,...

Desde el punto de vista del desarrollador, los componentes gráficos generarán eventos de varios tipos, y tendrá que
programar secuencias de código para reaccionar a la aparición de estos eventos.

Existen realmente muchos tipos de eventos. Aquí tiene, por ejemplo, una parte de los que están disponibles para
un simple componente JButton (un botón en el que se puede hacer clic).

Solo para el ratón, existen cinco eventos posibles de base, más dos para los desplazamientos del ratón, y uno para
la ruedecilla.

Para programar acciones en respuesta a estos eventos, debe añadir lo que se llama un escuchador de eventos, un
listener en terminología Java.

Como recordatorio, en el capítulo Toma de contacto de Eclipse, codificó lo siguiente para responder al clic del botón:

JButton btnValidar = new Jbutton("Validar");


btnValidar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("¡Acción!");
}
});

Aquí la acción es diferente ya que no es el punto más importante de momento.

Se creó un escuchador de acción para el botón, es decir un código que se ejecuta cuando el usuario hace clic
encima con el ratón, o presiona la tecla [Espacio] o [Intro] cuando el botón tiene el foco (cuando está subrayado en
la interfaz gráfica).

En una aplicación gráfica, se selecciona un solo componente en un instante T. Es el principio de focalización y se dice
que el componente tiene el foco. Por ejemplo un cuadrado aparece alrededor de un botón que tiene el foco, y el
cursor parpadea en una zona de introducción de datos que a su vez tiene el foco.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Depués se añade este accionador, que hereda de la interfaz ActionListener, como escuchador sobre el botón con
la ayuda del método addActionListener(). Es posible desasignarlo de los escuchadores con el método
removeActionListener(). El mecanismo para añadir y suprimir escuchadores se basa en Java en los métodos
addXXXListener y removeXXXListener, donde XXX es el nombre del evento a escuchar.

Detallaremos más adelante el concepto de eventos en el capítulo Modelo MVC.

El escuchador de una acción es una clase interna anónima, es decir una clase sin nombre definida dentro de otra
clase.

El código del método actionPerformed es capaz de usar variables locales definidas en el código que realiza la
llamada, con la condición de que estas variables estén anotadas con final o no sean modificadas.

boolean test = false;


// o
// final boolean test = false;
rdbtnCodigo.addActionListener((e) ‐> {
System.out.println(test);
if (test) {
// hacer algo
}
});
// descomentar la línea produce un error de compilación
// test = true;

Antes de Java 8, era necesario marcar estas variables como final. Java 8 suprime esta necesidad, pero sin embargo
sigue siendo necesario que no se modifiquen estas variables.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Lambdas
Las expresiones lambda son otra de las novedades de Java 8.

En una aplicación, particularmente en su parte gráfica, es muy habitual programar pequeñas clases llamadas
internas anónimas (ya que son tan pequeñas que no tienen nombre, y están codificadas dentro de una clase).

Retomando el ejemplo del escuchador de eventos:

JButton btnValidar = new Jbutton("Validar");


btnValidar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("¡Acción!");
}
});

En realidad creó una clase anónima que hereda de la interfaz ActionListener y codificó la acción resultante del clic
en el botón dentro del método actionPerformed.

Esta interfaz ActionListener es muy sencilla: solo tiene un método que implementar. Es lo que llamamos una
interfaz funcional.

La escritura puede entonces simplificarse gracias a las expresiones lambda de la siguiente manera:

btnValidar.addActionListener(
(ActionEvent e) ‐> {
System.out.println("¡Acción!");
}
);

Las expresiones lambda permiten una simplificación de escritura y lectura de estas interfaces funcionales.

Como el método actionPerformed() solo tiene un parámetro, la escritura puede por lo tanto aligerarse omitiendo
el tipo del parámetro y sus paréntesis:

btnValidar.addActionListener(
e ‐> {
System.out.println("¡Acción!");
}
);

Y como el código de la acción solo contiene una única línea, es posible continuar con esta simplificación de la
sintaxis suprimiendo las llaves:

btnValidar.addActionListener(
e ‐> System.out.println("¡Acción!") // ¡sin punto y coma!
);

WindowBuilder no tiene (todavía) en cuenta las expresiones lambda al generar código de la interfaz gráfica.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clases gráficas
Java proporciona varios componentes gráficos básicos, gracias a los packages java.awt y javax.swing. Los
componentes AWT son históricamente los primeros en aparecer y han sido enriquecidos más tarde con los
componentes Swing.

De una manera más general, utilice los componentes Swing. Disponen de más posibilidades de interacción y de
riqueza gráfica.

Explicamos estos componentes en su versión por defecto: tal vez la apariencia gráfica no es la más agradable, pero
Java proporciona un mecanismo de modificación gráfica con el sistema Look and Feel, que se explicará un poco más
adelante.

Los siguientes apartados realizan una pequeña visita guiada de los componentes más habituales: los que se
encuentran casi en cada aplicación.

Sepa primero que todo componente gráfico presentado aquí tiene un estado activado o no (en inglés: enabled). No
se puede interaccionar con un componente desactivado.

1. Botones
Los botones permiten al usuario realizar acciones o elegir entre opciones.

Cada tipo de botón dispone de un estado: seleccionado o no (en inglés: selected).

a. JButton

La clase JButton permite mostrar un botón muy sencillo con un texto y eventualmente un ícono.

Se trata de uno de los tipos más sencillos de componente gráfico de interacción con el usuario: hace clic en el
botón (es decir que lo selecciona), se ejecuta una acción, el botón vuelve a su estado inicial (no seleccionado).

b. JCheckBox

La clase JCheckBox permite al usuario realizar una elección binaria, es decir una elección entre dos opciones
con exclusión mutua: sí o no, verdadero o falso, caliente o frío,...

La casilla a marcar se queda en el estado en el que el usuario la ha dejado. Tiene un efecto de memoria, como
los botones radio.

c. JRadioButton

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La clase JRadioButton permite al usuario elegir entre varias opciones (por lo tanto potencialmente más de dos)
con exclusión mutua: elegir un color entre rojo, verde o azul, elegir entre el lector de CD o la radio de Internet,
elegir entre una tarta, un pastel o una crema catalana,...

El nombre de estos botones proviene de las antiguas radios: cuando se apretaba un botón, los demás botones
saltaban. De la misma manera, en un grupo de JRadioButton, cuando se selecciona uno, los demás son
deseleccionados.

Un botón radio solo tiene interés si está asociado a otros botones radio, con la ayuda de un grupo formalizado
con la clase ButtonGroup.

d. JToggleButton

La clase JToggleButton es una combinación de las clases JButton y JCheckBox. Se utiliza con muy poca
frecuencia.

2. Introducción de texto
Estos componentes gráficos permiten al usuario introducir texto.

El texto puede introducirse en cualquier idioma y escritura, al utilizar Java Unicode para todas las cadenas de
caracteres. Es también posible modificar el sentido de lectura y escritura de estos componentes para adaptarse al
farsi (y los idiomas árabes en general), al japonés,...

a. JTextField

La clase JTextField permite al usuario introducir una única línea de texto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

b. JPasswordField

La clase JPasswordField permite al usuario introducir texto ocultando su valor. Un uso típico de esta clase es
la introducción de una contraseña.

c. JFormattedTextField

La clase JFormattedTextField permite al usuario introducir texto y a la aplicación mostrar datos complejos de
manera textual.

Esta clase se encarga por ejemplo de convertir una cadena de caracteres en fecha y de convertir una fecha en
cadena de caracteres, o también de formatear números con tres decimales exactos.

Java no proporciona componentes gráficos para introducir y mostrar fechas por defecto. Otros desarrolladores
proponen librerías para disponer de su propio componente gráfico. Pruebe JCalendar (http://toedter.com/jcalendar/),
JDatePicker (http://jdatepicker.org/), JXDatePicker (https://swingx.java.net/), ¡o cualquier otra librería que le parezca
atractiva!

La apariencia gráfica es la misma que la de la clase JTextField.

d. JTextArea

La clase JTextArea permite introducir varias líneas de texto bruto (sin decoraciones, ni estilos de caracteres: no
hay negrita, ni itálica por ejemplo).

e. JEditorPane y JTextPane

La clase JEditorPane permite mostrar texto con estilos: es decir caracteres en negrita, subrayados en amarillo,
etc.

¡Esta clase permite hasta mostrar páginas web con formato HTML, o documentos con formato RTF (Rich Text
Format)! También es posible crear sus propios formatos con la ayuda de un objeto EditorKit.

JTextPane es una especialización de la clase JEditorPane que permite crear y mostrar sus propios
documentos con estilo.

Estas dos clases son relativamente complejas de usar, y por lo tanto no serán explicadas con más detalles en
este libro.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

3. Introducción de números
En vez de utilizar campos de texto simple para introducir números enteros, Java proporciona dos componentes
gráficos dedicados.

a. JSpinner

La clase JSpinner permite introducir números enteros, ya sea por introducción directa mediante el teclado en
un campo de texto, o manipulando pequeñas flechas situadas junto al campo de texto para disminuir o
aumentar el valor introducido.

b. JSlider

La clase JSlider provee un cursor gráfico que permite introducir un número. Este componente gráfico no posee
una zona para mostrar el valor introducido.

Sin embargo es capaz de mostrar los valores mínimo y máximo posibles, así como los valores intermedios (los
ticks).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

4. Visualización de información
Es importante en una aplicación gráfica poder mostrar información no editable por el usuario, por ejemplo el total
de un pedido, un pequeño texto junto a un campo de texto que describa lo que debe introducir o un porcentaje.

a. JLabel

La clase JLabel permite mostrar un texto y/o una imagen.

Es posible elegir la posición relativa del texto y de la imagen, así como el color del texto.

Un JLabel también puede utilizarse para mostrar una imagen sin texto asociado.

Alineando un JLabel junto a un JTextField y modificando la fuente de caracteres a negrita, se puede crear una
entrada de formulario como la que sigue:

b. JProgressBar

La clase JProgressBar permite mostrar un porcentaje en forma gráfica, con una barra que se llena en función
del porcentaje.

Esta barra puede mostrar el porcentaje, con orientación horizontal o vertical. Esta barra también puede estar
indeterminada: aparece entonces una animación.

Este componente resulta muy útil para indicar al usuario el avance de una operación potencialmente larga.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

c. JSeparator

La clase JSeparator permite trazar líneas finas horizontales o verticales en la aplicación, para separar
visualmente los diferentes componentes gráficos.

Un separador permite airear la pantalla.

Si se sitúa un separador entre un botón de Buscar y otro de Eliminar, se crea una división lógica y gráfica
entre las dos funcionalidades: el usuario será un poco menos propenso a confundirlos y a hacer clic en el botón
erróneo.

Un separador puede ser vertical u horizontal. Se utiliza con mayor frecuencia en los menús o en las barras de
herramientas en su versión horizontal.

5. Visualización y selección de objetos


Los componentes gráficos vistos anteriormente permiten mostrar e introducir texto o números. Es habitual en
una aplicación poder mostrar e introducir objetos más complejos ofreciendo un conjunto restringido de elecciones
al usuario.

Los componentes que se van a presentar en esta sección son más complejos en su codificación: necesitan objetos
adicionales de los cuales uno de ellos proveerá datos que mostrar (se le llama modelo) y otro el aspecto gráfico
(se le llama renderer).

Estos componentes están basados en una variante de la arquitectura MVC (Modelo­Vista­Controlador), que se
explicará en el capítulo Modelo MVC.

a. JComboBox

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La clase JComboBox permite mostrar una lista desplegable de posibles opciones. El usuario puede entonces
modificar su elección, que se mostrará en consecuencia.

Es posible hacer esta lista desplegable editable, lo que permite entonces al usuario poder introducir un dato
libremente, que no está restringido a las opciones del combo box, lo que permite entonces orientar y facilitar la
introducción de datos.

b. JList

Al igual que la clase JComboBox, la clase JList permite mostrar una lista de opciones o información y poder
elegir dentro de los valores de esta lista.

Sin embargo, un JList permite realizar selecciones múltiples, lo que resulta imposible con un JComboBox.
Además, un JComboBox necesita dos clics para realizar la selección: un clic para desplegar la lista, un clic para
seleccionar. Un JList permite realizar esta selección con un solo clic.

6. Datos estructurados
Muchas veces es importante en una aplicación mostrar datos estructurados con sus detalles.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Tome por ejemplo el explorador de archivos de su sistema operativo (favorito o no). Se ven clásicamente dos tipos
de datos: la lista de carpetas y de sus subcarpetas que son datos de tipo árbol y la lista de archivos de una
carpeta con su nombre, su tipo, su fecha de modificación... que son datos tabulados (se le llama tabla).

Java proporciona componentes gráficos que permiten visualizar e interactuar con estos dos grandes tipos de datos
estructurados.

De este modo, resulta perfectamente viable construir su propio explorador de archivos multiplataforma, como
ejercicio.

a. JTree

La clase JTree permite mostrar datos en forma de árbol.

Cada elemento del árbol se llama nodo (node en Java), y cada elemento terminal (que no tenga subelementos)
se llama hoja (leaf en Java).

b. JTable

La clase JTable permite mostrar datos tabulados, en líneas y en columnas: cada línea representa un dato, cada
columna representa un detalle de este dato.

Se puede especificar un encabezado de la tabla, que proveerá los nombres de las columnas por ejemplo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

7. Contenedores
Casi ha terminado el recorrido por los componentes elementales. Ahora es tiempo de interesarse en el
emplazamiento de estos componentes.

En efecto, una aplicación nunca se compone de un único elemento gráfico, sino más bien de una multitud de
componentes. Estos componentes deben organizarse para proporcionar un uso intuitivo de la aplicación.

Para ello, puede utilizar paneles, que permiten agrupar gráficamente componentes.

a. JPanel

Un JPanel es uno de los contenedores más sencillo de Java. ¡Es tan sencillo que generalmente es invisible al
usuario!

Sin embargo es posible proveer un borde o un color de fondo a este panel, para ponerlo de relieve.

Un panel va por lo tanto a acoger componentes gráficos. Para saber dónde mostrarlos, este panel necesita de un
sistema que permita especificar la disposición entre los distintos componentes. Se llama a esta disposición
layout, y un JPanel necesita de un gestor de disposición, el LayoutManager.

La primera ventana que se creó usaba un JPanel con un layout absoluto, es decir que se disponían los
componentes con una precisión de pixel. El único problema con esta manera de trabajar aparece si se
redimensiona la ventana: los componentes se quedan en su sitio y no se adaptan al espacio disponible.

Se presentarán los diferentes layouts disponibles un poco más adelante en este capítulo.

b. JScrollPane

Puede ocurrir que un componente gráfico no tenga el espacio necesario para ser mostrado en su totalidad
dentro del espacio que le ha sido asignado por el LayoutManager.

Entonces es necesario dar al usuario un medio gráfico para poder recorrer el contenido y acceder a la totalidad
del componente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La clase JScrollPane permite realizar este recorrido. Puede ser horizontal, vertical o de ambos tipos a la vez, lo
que representa el caso más frecuente.

Gráficamente, aparecen dos barras situadas a la derecha y debajo del componente, con ascensores que
permiten realizar este recorrido.

c. JSplitPane

La clase JSplitPane permite mostrar dos componentes gráficos uno al lado del otro.

Estos dos componentes pueden presentarse horizontal o verticalmente. Una barra de separación permite variar
sus respectivas dimensiones.

Observe las dos pequeñas flechas de la barra de separación. Es posible configurar botones que permitan ocultar y
mostrar uno de los componentes con un solo clic de ratón.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

d. JTabbedPane

La clase JTabbedPane permite mostrar varios componentes separados, cada uno en una pestaña dedicada.
Cuando el usuario hace clic en una pestaña, se muestra el componente asociado, los demás componentes se
ocultan.

e. JToolbar

La clase JToolbar permite mostrar componentes (generalmente botones) en una barra de herramientas para
permitir un acceso rápido a acciones habituales, por ejemplo.

Se puede reposicionar esta barra de herramientas en la aplicación o desanclarla en una pequeña ventana
adjunta.

8. Ventanas
Todos estos componentes gráficos deben obligatoriamente insertarse en el sistema de ventanas del sistema
operativo. Una ventana está compuesta por un borde, un título, un icono y pequeños botones para minimizar,
maximizar y cerrar. Este borde y la fuente de los caracteres del título (hablamos de la decoración de la ventana)
son generalmente definidos por el sistema operativo: es sin embargo posible modificarlos desde Java.

Cada ventana posee también un tamaño y una posición en pantalla. Estos parámetros pueden modificarse desde
Java. Es posible también impedir el redimensionamiento de una ventana, aunque no sea generalmente la mejor
idea.

a. JFrame

Un JFrame es una ventana de alto nivel, que constituye habitualmente el punto de entrada de una aplicación
clásica.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Es posible especificar la acción correspondiente al botón cerrar (arriba a la derecha del borde en Windows): no
hacer nada, minimizar la ventana, finalizar la aplicación o liberar los recursos gráficos del JFrame (se elimina
entonces la ventana de la pantalla).

¡La liberación de los recursos gráficos de un JFrame no significa necesariamente que la aplicación va a finalizar!

b. JDialog

Un cuadro de diálogo, o más comúnmente diálogo, es una ventana independiente, ligada generalmente a una
ventana principal. La mayoría de los cuadros de diálogo consisten en la presentación de mensajes de error, de
información o de alerta, pero también pueden mostrar imágenes o componentes gráficos.

Un cuadro de diálogo no incluye botones de maximizar o minimizar en el borde.

Un cuadro de diálogo puede declararse modal: en este caso, se suspende la ejecución del código del thread
gráfico hasta que se cierre el cuadro de diálogo.

JDialog dialog = new JDialog();


dialog.setTitle("Diálogo vacío");
dialog.setIconImage(new ImageIcon(
getClass().getResource("Moon‐32.png")).getImage());

dialog.setModalityType(ModalityType.APPLICATION_MODAL);

// se muestra el diálogo en la siguiente línea


dialog.setVisible(true);
// se ejecuta el resto del código cuando se cierra el diálogo

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

c. JOptionPane

La clase JOptionPane es un medio práctico para crear cuadros de diálogo estándar y simples.

Aquí tiene el código para crear estos cuadros de diálogo:

JOptionPane.showMessageDialog(null,
"Mensaje destinado al usuario");

JOptionPane.showMessageDialog(null,
"Alerta destinada al usuario",
"¡Cuidado!",
JOptionPane.WARNING_MESSAGE);

JOptionPane.showMessageDialog(null,
"Ha ocurrido un error",
"¡¡Error!!",
JOptionPane.ERROR_MESSAGE);

JOptionPane.showMessageDialog(null,
"¿Puede contestar?",
"¿Pregunta?",
JOptionPane.QUESTION_MESSAGE);

JOptionPane.showMessageDialog(null,
"Un diálogo muy estándar",
"Estándar",
JOptionPane.PLAIN_MESSAGE);

JOptionPane permite también crear cuadros de diálogo de confirmación con un código de retorno que permite
conocer la decisión del usuario.

int confirmacion = JOptionPane.showConfirmDialog(null,


"Confirme la acción",
"Confirmación requerida",
JOptionPane.YES_NO_OPTION);
if( confirmacion == JOptionPane.YES_OPTION) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
if( confirmacion == JOptionPane.NO_OPTION) {
}

confirmacion = JOptionPane.showConfirmDialog(null,
"Confirme la acción",
"Confirmación requerida",
JOptionPane.YES_NO_CANCEL_OPTION);

if( confirmacion == JOptionPane.CANCEL_OPTION) {


}
confirmacion = JOptionPane.showConfirmDialog(null,
"Confirme la acción",
"Confirmación requerida",
JOptionPane.OK_CANCEL_OPTION);

Este código permite crear los siguientes cuadros de diálogo:

d. JFileChooser

La clase JFileChooser provee un cuadro de diálogo para seleccionar archivos o carpetas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JFileChooser chooser = new JFileChooser();


int resultado = chooser.showOpenDialog(null);
if( resultado == JFileChooser.APPROVE_OPTION) {
File archivoSeleccionado = chooser.getSelectedFile();
...
}

e. JColorChooser

La clase JColorChooser permite al usuario seleccionar colores.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Color color = JColorChooser.showDialog(null,


"Elegir un color",
Color.BLUE);

9. Menús
Los menús son elementos omnipresentes en una aplicación moderna y permiten acceder a diferentes partes de la
aplicación, ejecutar diversas acciones...

Todo empieza con la barra de menús.

En una aplicación, se presenta la barra de menús en la zona superior de la ventana principal. Observe en su
ordenador, por ejemplo en Eclipse. Puede ver las palabras File, Edit, Source... Project, Run, Window, Help. Estas
palabras corresponden a menús enlazados a la barra de menús principal.

Si selecciona la palabra Help por ejemplo, el menú específico a la ayuda aparece.

Este menú consiste en ítems de menú, como Welcome, Help Contents, Install New Software...

Si hace clic en alguno de estos ítems provoca una acción, lo más habitual la apertura de una ventana o un cuadro
de diálogo que permite realizar acciones, ajustar parámetros, etc.

a. JMenuBar

La clase JMenuBar es el componente gráfico correspondiente a la barra de menús. Si esta barra está vacía, no
existe ninguna representación gráfica.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 16/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

b. JMenu

La clase JMenu es el componente gráfico del menú y del botón que permite abrir este menú.

Un menú sin elementos

Un menú con un ítem

Se pueblan los menús con submenús (que son JMenu) e ítems de menú, que ejecutarán la acción asociada.

Un menú con su submenú

c. JMenuItem

La clase JMenuItem corresponde a un elemento final en el menú. Si hacemos clic en este ítem, se ejecuta una
acción, como la visualización de otra ventana, la apertura de un navegador web, el almacenamiento de un
archivo...

En el ejemplo presentado arriba, se ha creado un ítem para el menú, que se llama una «acción».

Si examina atentamente podrá ver que la letra a está subrayada. Se trata de un elemento mnemotécnico que
permite seleccionar rápidamente el ítem del menú asociado.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 17/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En Eclipse, pulse en la tecla [Alt] y en la tecla F. Así se crea la combinación [Alt] F.

Esta combinación abre el menú de primer nivel asociado con la F, es decir File. La asociación es visible ya que la
letra F está subrayada en la palabra File presente en el menú.

Presione la tecla P.

Se abre el cuadro de diálogo de impresión.

Volviendo al anterior ejemplo, otro elemento característico es la presencia del texto «Mayús­A». Se trata de un
acelerador.

Un acelerador es una combinación de teclas que permite efectuar rápidamente una acción.

Por ejemplo [Ctrl] S sirve clásicamente para guardar algo en prácticamente todas las aplicaciones. [Ctrl] C sirve
para copiar, [Ctrl] V para pegar, ...

Java permite asignar estos aceleradores a acciones de la aplicación.

Las combinaciones [Ctrl] +... no son habituales para los usuarios de Mac. Java permite tener en cuenta estas
especificidades.

d. JPopupMenu

Existe otro tipo de menú que puede encontrar en casi todas las aplicaciones. Se trata del menú contextual, que
obtenemos haciendo clic con el botón derecho. También se le llama menú pop­up.

La clase JPopupMenu permite crear un menú contextual. Basta después con añadirle JMenu y JMenuItem.

El ejemplo anterior ilustra el hecho de que es posible insertar casillas de verificación y botones radio en los menús
con la ayuda de las clases JCheckBoxMenuItem y JRadioButtonMenuItem.

10. Layouts
Los layouts permiten organizar los componentes gráficos en el interior de un JPanel. Aquí tiene los más habituales
provistos por Java.

a. FlowLayout

La clase FlowLayout permite disponer los componentes en una línea dimensionada según el tamaño preferido.
Si el panel es demasiado pequeño para mostrarlos en una única línea, este layout utiliza varias líneas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 18/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se trata de uno de los layouts más sencillos.

b. BoxLayout

El BoxLayout sitúa los componentes en una única línea o una única columna. Los componentes están
dimensionados según su tamaño preferido.

c. BorderLayout

El BorderLayout solo permite situar cinco componentes a la vez: uno en el centro, los demás en los
alrededores.

Los componentes ocupan todo el espacio disponible.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 19/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

d. GridLayout

El GridLayout sitúa los componentes en una rejilla. Se organizan, por lo tanto, en filas y en columnas. Todas
las filas tienen la misma altura, todas las columnas tienen la misma altura y los componentes ocupan todo el
espacio disponible en su celda.

e. GridBagLayout

El GridBagLayout es uno de los layouts más sofisticados. Los componentes se sitúan en filas y columnas, pero
algunos pueden ocupar varias filas o columnas. Pueden ocupar todo el espacio disponible o redimensionarse con
su tamaño preferido.

Cada componente puede además tener un margen (un espacio vacío) a su alrededor.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 20/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

11. Look and Feel


El sistema Look and Feel de Java permite modificar la apariencia de los componentes gráficos (el Look) y la
manera en la que se comportan (el Feel).

Desde este punto de vista, se divide cada tipo de componente gráfico en dos categorías: los J (JButton, JList,...) y
los UI (ButtonUI, ListUI,...). Los UI son objetos a los que los J delegan las operaciones de apariencia gráfica.

Java propone varios Look and Feel básicos: en primer lugar el Look and Feel del sistema que corresponde a la
apariencia gráfica del sistema operativo (con algunas variantes), después el Look and Feel multiplataforma
(comúnmente llamado Metal) que da un aspecto uniforme en Windows, Apple o Linux. Existe también el Look
and Feel Synth que permite a los desarrolladores crear su propia apariencia mediante archivos de configuración
XML o Nimbus, que se introdujeron con la aparición de la versión Java 6.

Para especificar un Look and Feel determinado, es posible utilizar alguna de las siguientes líneas de código:

// Look and Feel multiplataforma


UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName());

// Look and Feel sistema


UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());

También puede usar la versión que utiliza cadenas de caracteres:

// Look and Feel multiplataforma


UIManager.setLookAndFeel(
"javax.swing.plaf.metal.MetalLookAndFeel");

// Look and Feel Motif (no muy bonito)


UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.motif.MotifLookAndFeel");

// Look and Feel Nimbus


UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 21/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Estas líneas deben utilizarse antes de que se muestre la primera ventana gráfica.

Si su aplicación gráfica ya es visible, es necesario usar la siguiente línea de código:

SwingUtilities.updateComponentTreeUI(ventanaOComponente);

Para estar seguro de disponer de decoraciones bonitas en la ventana, utilice el código:

JFrame.setDefaultLookAndFeelDecorated(true);

Menos mal que los Look and Feel no se limitan a los de Java. Como puede crear el suyo propio, varios
desarrolladores han seguido este camino y proponen gratuitamente el suyo. ¡Incluso algunos los venden!

Pruebe por ejemplo:

Synthetica, disponible en la dirección: http://www.javasoft.de/synthetica/screenshots/ incorporando el .jar descargado


en la línea:

UIManager.setLookAndFeel(
"de.javasoft.plaf.synthetica.SyntheticaStandardLookAndFeel");

Alloy (de pago), disponible aquí: http://lookandfeel.incors.com/

JGoodies Looks (http://www.jgoodies.com/freeware/libraries/looks/), con la línea:

UIManager.setLookAndFeel(
new com.jgoodies.looks.plastic.Plastic3DLookAndFeel());

UIManager.setLookAndFeel(
new com.jgoodies.looks.plastic.PlasticLookAndFeel());

Existen multitud de Look and Feel. Elegir uno es materia de gustos, colores y compatibilidad gráfica con el sistema
operativo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 22/22
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Threads
Se codifica un programa para que las instrucciones se ejecuten secuencialmente.

Reproduzca el siguiente código:

public class Secuencial {


public static void main(String[] args) {
System.out.println("1era línea");
System.out.println("2nda línea");
System.out.println("3era línea");
}
}

En este código, la primera línea aparecerá siempre antes que la segunda. La tercera línea se ejecutará siempre la
última.

Esto lo garantiza Java ya que ejecuta estas instrucciones en un hilo de ejecución, un Thread.

Coloque un punto de interrupción (un breakpoint) haciendo doble clic en el margen izquierdo del editor en la
segunda línea del método main(). Aparecerá un punto azul. Si no, sitúe el cursor sobre esta línea y utilice la
combinación de teclas [Ctrl][Shift] B.

Ejecute el programa en modo depurador presionando la tecla [F11]. Se cambia a la perspectiva de depurador.

Se muestra el hilo de ejecución principal de este programa en la vista Debug. Este hilo, o thread, tiene un nombre:
main.

Dentro de este hilo, las instrucciones se ejecutan secuencialmente, una después de las otras.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Sin embargo, generalmente no es lo que un usuario espera de una aplicación: muchas veces quiere poder
descargar archivos al mismo tiempo que imprime un documento mientras escucha su canción favorita...

En Java, es posible crear tales aplicaciones: cada parte de la aplicación tendrá un thread dedicado, fuera del hilo de
ejecución principal, que ejecutará sus propias instrucciones secuencialmente. Esto dará al usuario la posibilidad de
realizar tareas en paralelo.

Existen varias maneras de ejecutar hilos paralelos. Implica en la mayor parte de los casos encapsular el código a
ejecutar dentro de un objeto que implemente la interfaz Runnable.

Runnable run = new Runnable() {

@Override
public void run() {
System.out.println("línea independiente");
}
};
Thread hilo = new Thread(run, "paralelo");
hilo.start();

Las cosas se complican entonces...

Para que el código funcione correctamente, es necesario asegurarse de una buena sincronización de estos hilos de
ejecución: la impresión solo se ejecuta cuando el archivo se ha descargado…

Por ejemplo, el código anterior que ilustra el concepto de Thread paralelo tiene pocas posibilidades de ejecutarse si
lo añade en un método main de la clase Secuencial: ¡muchas veces se interrumpirá el programa antes de que se
ejecute el thread secundario!

Para conseguir un comportamiento correcto de la ejecución completa de los dos hilos de ejecución, deberá esperar a
que el thread paralelo se termine antes de que termine el propio programa: esto se consigue con la llamada a la
función join().

public class Secuencial {


public static void main(String[] args) {
System.out.println("1era línea");
System.out.println("2nda línea");
System.out.println("3era línea");

Runnable run = new Runnable() {

@Override
public void run() {
System.out.println("línea independiente ");

}
};
Thread hilo = new Thread(run, "paralelo");
hilo.start();
try {
hilo.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

Cuando un desarrollador empieza a codificar aplicaciones con threads, se obtiene la excepción


InterruptedException en muchas ocasiones. Nunca debe ignorar el tratamiento de esta excepción, y como mínimo
debe utilizar la instrucción Thread.currentThread().interrupt() para dar una oportunidad al hilo de ejecución para que
finalice de manera limpia. Los libros y artículos de Brian Goetz representan lecturas esenciales para estos desarrolladores.

Crear demasiados threads puede ralentizar la ejecución global del programa, dando la impresión (¡y no solo la
impresión!) de lentitud.

Una versión más sofisticada consistiría en usar un ExecutorService del package java.util.concurrent.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

...

ExecutorService executor = Executors.newSingleThreadExecutor();


// ExecutorService executor = Executors.newCachedThreadPool();
// ExecutorService executor = Executors.newScheduledThreadPool(4);
executor.execute(run);
...
executor.shutdown();

Los threads tienen la capacidad de acceder al espacio de memoria de la aplicación. Por lo tanto es posible compartir
variables, a diferencia de los procesos donde cada uno posee su propio espacio de memoria.

Si se ejecutan varios threads y estos manipulan los mismos objetos, aparecen problemas de sincronización: un
thread puede modificar el valor de un dato entre dos lecturas de este dato por otro thread, lo que conlleva
comportamientos ridículos y aleatorios.

Se trata de uno de los peores bugs de una aplicación: el bug aparece... de vez en cuando.

Para sincronizar varios threads, existen varias técnicas, entre ellas:

Utilizar la palabra clave synchronized.

Utilizar clases del package java.util.concurrent.

Las problemáticas de sincronización y de concurrencia son aspectos avanzados de Java que no se tratarán en este
libro.

Se debe gestionar un aspecto específico de los threads en las aplicaciones gráficas.

Cuando se ejecuta una aplicación gráfica, la máquina virtual de Java crea un thread específico llamado Event
Dispatch Thread (abreviado como EDT), cuya vocación es ejecutar todas las instrucciones ligadas a la visualización
gráfica de una aplicación, como los eventos de ratón, la visualización de un botón...

Por este motivo WindowBuilder crea un método main de la siguiente manera:

public static void main(String[] args) {


EventQueue.invokeLater(new Runnable() {
public void run() {
try {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

PrimeraVentana frame = new PrimeraVentana();


frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

La ventana gráfica no debería mostrarse nunca desde un thread no gráfico. El método estático invokeLater de la
clase EventQueue permite ejecutar un código arbitrario en el EDT.

El método estático invokeLater de la clase SwingUtilities permite hacer exactamente lo mismo.

¿Ha utilizado ya una aplicación que parece estar colgada cuando hace clic en algún botón? ¿La ventana no se
refresca si se minimiza o se muestra un color gris si se posiciona otra ventana por encima y de repente todo vuelve
a la normalidad?

Esto es debido a que los desarrolladores de la aplicación crearon un código largo en cuanto a ejecución dentro del
hilo del EDT. Mientras este código no se termina, las demás operaciones gráficas están puestas en espera.

Para programar una aplicación gráfica que produzca una impresión de rapidez, ¡es esencial no ejecutar jamás un
código largo en la EDT!

Para simplificar el trabajo de los desarrolladores, Java proporciona una clase llamada SwingWorker, que permite
crear un código de este estilo como tareas en segundo plano y, cuando el código finaliza, ejecutar otro código en el
EDT.

Resulta habitual, por ejemplo, cuando un usuario hace clic sobre un botón de impresión, deshabilitarlo (para evitar
42 impresiones de la misma página, por ejemplo), ejecutar la impresión en segundo plano y reactivar el botón
cuando la impresión ha finalizado.

private void impresion(JButton botón) {


// en el thread gráfico
// inicializar una barra de progreso

// desactivar el botón para evitar impresiones múltiples


boton.setEnabled(false);
SwingWorker<?,?> worker
= new SwingWorker<List<Object>, Integer>() {

@Override
protected List<Object> doInBackground()
throws Exception {
// en el thread de segundo plano

// ... operación larga...


// ... operación muy larga...

// se ha imprimido una página,


// alertar al thread gráfico
publish(Integer.valueOf(1));

// ... operación larga...

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

return new ArrayList<Object>();


}

@Override
protected void done() {
// la tarea en segundo plano ha finalizado,
// de vuelta en el thread gráfico

try {
// recuperar la lista de páginas impresas
List<Object> list = get();
// mostrar algo de esta lista
} catch (InterruptedException e) {
// interrupción : hay que gestionar
e.printStackTrace();
} catch (ExecutionException e) {
// error durante la ejecución
// hay que gestionar
Throwable causa = e.getCause();
e.printStackTrace();
}
// reactivar el botón
boton.setEnabled(true);
}

@Override
protected void process(List<Integer> chunks) {
// se recuperan los valores que provienen de la
// llamada al método publish()
// en el thread gráfico

// actualizar la barra de progreso


}
}
worker.execute();
}

En resumen:

¡Todas las operaciones sobre los componentes gráficos deben hacerse en el EDT!

¡Todas las operaciones hechas en el EDT deben acabarse rápidamente!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Anotaciones
Las anotaciones son metadatos que se pueden añadir directamente en el código de una aplicación Java, en casi
todos los niveles: en los packages, las clases, los métodos, los atributos, los parámetros y las variables locales.

Siempre se declaran las anotaciones empezando por el carácter @.

Ya se han visto ejemplos de anotaciones, sobre todo @Override en algunos métodos.

@Override es una anotación de ayuda al compilador para indicar que el método anotado sobrecarga un método de
la súper clase o de una interfaz. Si el desarrollador comete un error de tipografía en el nombre de la clase, el
compilador señalará este error.

Para anotar un código, debe escribir la anotación inmediatamente antes de la sección de código afectada.

Por ejemplo, si desea anotar un método como @Deprecated (aparecerá entonces tachado en todo código que lo
utilice), haga lo siguiente:

@Deprecated
public void metodoNoMuyAcertado() {
...
}

Las anotaciones son útiles en dos fases distintas del programa: en su compilación y en su ejecución.

En su compilación, el compilador puede emitir avisos o directamente parar la compilación. Es posible también para
herramientas externas usar las anotaciones para generar automáticamente otras clases. Se trata de una técnica
utilizada en muchas ocasiones en ciertos frameworks, por ejemplo RESTX (http://restx.io ) o AndroidAnnotations
(http://androidannotations.org/).

Cuando se utiliza, librerías externas pueden «recuperar» las clases anotadas para permitir comportamientos no
previstos por Java. Es el caso por ejemplo de JPA (Java Persistence API) que permite crear objetos del dominio
almacenados en base de datos, o de JUnit que permite crear métodos para probar algunas partes de la aplicación.

Las anotaciones pueden parametrizarse con objetos como cadenas de caracteres o mediante otras anotaciones,
como se verá en el capítulo Entidades.

Puede escribir sus propias anotaciones así como utilizar herramientas de generación de clases adicionales. Este
aspecto no se detallará en este libro.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Otras nociones
¡El recorrido de las clases disponibles en Java no ha acabado, lejos de eso!

Queda explorar las posibilidades y sutilidades de los packages:

java.net

java.io

java.nio

java.util.concurrent

java.util.logging

java.util.regex

java.util.zip

javax.accessibility

javax.print

javax.imageio

¡por citar unos pocos!

Java proporciona a los desarrolladores un entorno rico y muy potente que permite hacer muchas cosas. Además,
existen muchas librerías (libres o de pago) disponibles para completar y enriquecer aún más las posibilidades de
programación.

El proyecto que sirve de hilo conductor a lo largo de este libro no utiliza las clases de estos packages. Por lo tanto,
no se profundizará en estas nociones.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Propiedades del proyecto


Eclipse permite personalizar las propiedades de cada proyecto.

Haga clic con el botón derecho en el nombre del proyecto y elija la opción Properties.

En la ventana de propiedades, están disponibles numerosas opciones de personalización. Esta ventana es


particularmente útil cuando desea asegurarse del nivel de compatibilidad con una versión del JDK.

Para acelerar la búsqueda de la opción deseada, y siempre que sepa su nombre, una caja de texto le permite filtrar
las propiedades. Se encuentra arriba a la izquierda del cuadro de diálogo de propiedades, con un pequeño mensaje
type filter text.

Para cambiar el compilador, seleccione Java Compiler, marque la opción Enable project specific settings,
desmarque la opción Use compliance from execution environment, y elija la versión deseada en la lista
desplegable.

Para personalizar el código, acceda a la opción Java Code Style.

Si por ejemplo tiene la costumbre de prefijar el acceso a las propiedades de una clase con la palabra clave
this, marque la opción Qualify all generated field accesses with ’this’.

Para realizar acciones automáticas cada vez que guarde un archivo, acceda a la opción Java Editor ­ Save
Actions, y marque las opciones que le interesen.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cada vez que se guarde el código con esta configuración, se formateará y se añadirán las anotaciones habituales si
no están presentes.

También es posible hacer clic en el enlace Formatter presente en la página de acciones al salvaguardar para
especificar cómo se presentará el código fuente. Una vez que esté satisfecho con el formateo, es posible exportarlo
para compartirlo entre los miembros de un equipo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Tests unitarios
En todo desarrollo de software, una etapa que no debe descuidar nunca es la fase de pruebas: ningún desarrollador,
hasta los más expertos, está exento de incurrir en errores en su desarrollo.

¿Qué diferencia a estos desarrolladores expertos de los demás? Han construido su confianza alrededor de su trabajo
probando una y otra vez su código.

No se trata de revisar manualmente todos los test de una aplicación. En una aplicación de cierta entidad, esto es
simplemente imposible.

Es mucho mejor confiar estas pruebas a un sistema automático que las realizará todos los días.

Se ha creado toda una cultura alrededor de esta manera de desarrollar que consiste en situar los test en el centro
de la actividad de creación de software, o dicho de otro modo el desarrollo está gobernado por los test (Test­Driven
Development o TDD en inglés).

Este libro no es una introducción a esta cultura, pero algunos principios básicos pueden desde ahora utilizarse: por
ejemplo el Red/Green/Refactor o en español Rojo, Verde, Rehacer.

La idea es empezar primero por crear los test, que no se pasarán, para trabajar en la implementación y la
codificación hasta que estos test se pasen sin errores. Una vez conseguido, debe después seguir codificando el
producto hasta que tenga una forma correcta.

Eclipse propone herramientas para crear los test de bajo nivel, llamados unitarios.

Volvamos al proyecto MiPrimerPrograma haciendo clic con el botón derecho encima del explorador de
packages y elija la opción New ­ Source Folder.

Llame a la nueva carpeta src/test/java. Haga clic en Finish.

Se guardarán las clases de test en esta carpeta. Crear esta carpeta permite separar claramente entre las
clases de los test unitarios y las clases que se utilizarán para el desarrollo de la aplicación.

Vuelva a hacer clic con el botón derecho en el explorador de packages y elija la opción New ­ JUnit Test
Case.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se abre un cuadro de diálogo que permite configurar el test.

Elija las siguientes opciones:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Esta clase prueba la clase MiPrimerPrograma. Está guardada en la carpeta src/main/java, y pertenece al
mismo package que la clase sobre la que se realiza la prueba, es decir tomaDeContacto.primero.

Una buena práctica es terminar el nombre de las clases de test con Test. Como por ejemplo MiPrimerProgramaTest.

Haga clic en Next.

Se abre un cuadro de diálogo adicional que permite elegir los métodos a probar.

Seleccione por ejemplo el constructor de la clase MiPrimerPrograma y haga clic en Finish.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Eclipse propone después añadir la librería JUnit en el path de librerías del proyecto para que pueda compilar la
clase.

Haga clic en OK.

Se abre una nueva clase en el editor.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

package tomaDeContacto.primero;

import static org.junit.Assert.*;

import org.junit.Test;

public class MiPrimerProgramaTest {

@Test
public void testMiPrimerPrograma() {
fail("Not yet implemented");
}
}

Esto genera el esqueleto en el que construir los test. Los métodos públicos anotados con @Test se ejecutarán en la
fase de tests.

Suprima la línea que empieza por fail(...) y añada la siguiente línea:

@Test
public void testMiPrimerPrograma() {
MiPrimerPrograma.main(null);
}

Después debe lanzar este test.

Haga clic con el botón derecho en el editor o el explorador de packages teniendo la precaución de elegir la clase
MiPrimerPrograma antes. Seleccione después la opción Run As ­ JUnit Test.

Debería obtener la siguiente pantalla con su test en verde.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Una buena regla básica consiste en limitar el número de métodos main() en una aplicación.

Renombre el método main() por yo() y suprima la palabra clave static.

public void yo() {


MiPrimerPrograma programa = new MiPrimerPrograma("Yo");
System.out.println("¡Soy " +programa.nombre +"!");
}

El programa de test ya no compila: aparecen iconos rojos en distintos lugares, sobre todo allí donde está el error. Es
normal en este punto.

Modifique el test con el siguiente código:

import org.junit.Assert;
import org.junit.Test;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public class MiPrimerProgramaTest {

@Test
public void testMiPrimerPrograma() {
MiPrimerPrograma test = new MiPrimerPrograma("Pepe");

test.yo();

Assert.assertEquals("¡No es Pepe!",
"Pepe", test.getNombre());
}
}

El método estático assertEquals de la clase org.junit.Assert verifica que el nombre de la clase a probar es
efectivamente igual a la cadena de caracteres ”Pepe”. Si no es el caso, devuelve un mensaje de error con la cadena
del primer parámetro.

El package a utilizar es efectivamente org.junit. Existe una clase con el mismo nombre en el package
junit.framework pero está descatalogada y no debe utilizarse.

Vuelva a ejecutar el test, por ejemplo haciendo clic en el botón Rerun Test.

Se pasa correctamente el test, pero sigue mostrando «¡Soy Yo!». Debería con toda lógica mostrar «¡Soy Pepe!».

No parece normal, pero es lo que hace realmente el código del método yo():

public void yo() {


MiPrimerPrograma programa = new MiPrimerPrograma("Yo");
System.out.println("¡Soy " +programa.nombre +"!");
}

Este código instancia un nuevo objeto MiPrimerPrograma y lo ejecuta en vez de tomar en cuenta los atributos
del objeto.

Modifique el método yo() con el código:

public void yo() {


System.out.println("¡Soy " + nombre +"!");
}

Vuelva a ejecutar el test. El programa muestra el resultado esperado.

El trabajo no ha acabado aún. El método yo() no se puede realmente probar: hace falta que alguien humano lea
su salida. Todavía no se puede confiar a un sistema automatizado.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Debe por lo tanto modificar el método añadiéndole por ejemplo un valor de retorno que será la cadena a mostrar, y
un método para mostrarlo en la salida de la consola.

Modifique la clase MiPrimerPrograma de la siguiente manera:

public String yo() {


return "¡Soy " + nombre +"!";
}

public void mostrar() {


System.out.println(yo());
}

@Test
public void testMiPrimerPrograma() {
MiPrimerPrograma test = new MiPrimerPrograma("Pepe");

String visualizacion = test.yo();

Assert.assertEquals("¡No es Pepe!",
"Pepe", test.getNombre());

Assert.assertEquals("La visualización es incorrecta",


"¡Soy Pepe!", visualizacion);

test.mostrar();
}

Vuelva a ejecutar el test.

La visualización sigue siendo correcta y se ha comprobado la salida unitariamente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Internacionalización
La internacionalización de un programa consiste en adaptarlo a varios países.

Desde el punto de vista de un desarrollador de un aplicación, los puntos importantes a tener en cuenta cuando
quiera distribuir su software en varios países son:

El idioma: no todo el mundo entiende el español o el inglés.

Las unidades de medida: esto abarca desde el peso (para una aplicación que gestiona artículos al peso) a la
moneda.

El sentido de lectura: si una aplicación se distribuye en países del mundo árabe, en Israel o en Japón tendrá usuario
con otros hábitos de lectura. Por ejemplo el mundo hebreo y árabe leen horizontalmente pero de derecha a
izquierda (salvo para las palabra inglesas, españolas,...).

El sentido de lectura tendrá también impacto sobre la disposición gráfica de los componentes de una aplicación.

1. Traducción de las cadenas de caracteres


Volvamos a la clase MiPrimerPrograma.

Existen dos cadenas de caracteres a traducir de momento.

Estas cadenas plantean un problema: tienen como objetivo crear un mensaje a partir de una variable, que es el
nombre. Nada permite afirmar que la estructura de la traducción será siempre la misma. Además, el traductor
deberá saber que las palabras a traducir forman parte de una frase más amplia.

Para facilitar la tarea de traducción, se transforma la técnica de concatenación de cadenas de caracteres usando
el método estático format() de la clase String para obtener el efecto deseado.

Modifique el código del método yo() por:

public String yo() {


return String.format("¡Soy %s!", nombre);
}

El código %s permite realizar la substitución textual de un objeto (se llama al método toString() del objeto y se
reemplaza %s por el valor devuelto de la llamada a este método, aquí el valor del nombre).

El método String.format es muy potente y acepta un parámetro con una sintaxis muy rica:
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax

Ahora tan solo hace falta una cadena de caracteres a traducir.

Eclipse propone un mecanismo de traducción de cadenas de caracteres basada en las API de Java.

Seleccione la clase MiPrimerPrograma en el explorador de packages (en inglés Package Explorer) y haga
clic con el botón derecho para elegir la opción Source ­ Externalize Strings.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Aparece un cuadro de diálogo que permite elegir las cadenas de caracteres a traducir.

Verifique que la parte Accessor class apunta correctamente hacia tomaDeContacto.primero.Messages.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en Next.

Aparece cierta información: se van a crear archivos.

Haga clic en Next.

Aparece una confirmación de las acciones realizadas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en Finish.

Eclipse ha creado dos archivos:

Un archivo de texto messages.properties, que contiene las traducciones. Es un archivo que podrá enviar a los
traductores.

Una clase Messages. Esta clase agrupa la mecánica usada para leer el archivo de traducción.

Eclipse ha modificado también la clase MiPrimerPrograma para acceder a la traducción.

public String yo() {


return String.format(
Messages.getString("MiPrimerPrograma.0"),//$NON‐NLS‐1$
nombre);
}

2. Test de las traducciones


Es momento de probar estos cambios.

Vuelva a ejecutar los test JUnit. El test unitario debe seguir mostrándose en verde.

Hasta aquí, las modificaciones no tienen ningún impacto sobre el código. Todo sigue funcionando como antes.

Cree una carpeta fuente con la opción New ­ Source Folder y elija src/main/resources como nombre
de carpeta.

Todos los recursos, es decir las imágenes y archivos de traducción se guardarán en esta carpeta.

Esta organización de proyecto permitirá encontrar fácilmente los pocos archivos que deba enviar a los traductores.

Cree después un nuevo package llamado tomaDeContacto.primero en esta nueva carpeta y desplace el
archivo messages.properties hasta este package.

Copie el archivo messages.properties y péguelo en la misma carpeta con el nombre


messages_en.properties.

Debería obtener el siguiente resultado:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Esto permite crear un archivo de traducción específico para la lengua inglesa. La clase Messages encontrará las
traducciones en función de los archivos messages_<algo>.properties.

Este algo corresponde al código internacional de dos letras de los idiomas.

Si quiere añadir traducciones específicas para Inglaterra, cree el archivo messages_en_GB.properties. Si desea
crear una traducción para los belgas francófonos, cree el archivo messages_fr_BE.properties. Para los suizos,
cree el archivo messages_fr_CH.properties. Según el javadoc de la clase Locale
(http://docs.oracle.com/javase/8/docs/api/java/util/Locale.html ), las primeras letras que designan el idioma provienen de la
norma ISO 639, las letras que designan el país de la norma ISO 3166.

Abra el archivo messages_en.properties y modifique la traducción por:

MiPrimerPrograma.0=I’m %s\!

Añada el siguiente método a la clase de test:

@Test
public void testMiPrimerProgramaIngles() {
Locale.setDefault(Locale.ENGLISH);
MiPrimerPrograma test = new MiPrimerPrograma("John");

String visualizacion = test.yo();

Assert.assertEquals("No es John!",
"John", test.getNombre());
Assert.assertEquals("La visualización es incorrecta",
"It’s John!", visualizacion);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

test.mostrar();
}

La llamada al método estático setDefault de la clase java.util.Locale permite forzar el programa para que use el
inglés.

Vuelva a ejecutar el test.

Uno de los dos test fallará. Esto es debido a que los mensajes (las traducciones) han cambiado en el inicio de la
aplicación y no pueden cambiar después.

La implementación de las traducciones en Eclipse no proporciona mecanismos dinámicos de cambio de idioma.


Tendrá que crearla usted mismo o mirar otros mecanismos de traducción.

Se transformará este mecanismo para que pueda soportar el cambio dinámico de idioma. Aquí tiene la clase
Messages:

package tomaDeContacto.primero;

import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class Messages {


private static final String BUNDLE_NAME =
"tomaDeContacto.primero.messages"; //$NON‐NLS‐1$

private static final ResourceBundle


RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);

private Messages() {
}

public static String getString(String key) {


try {
return RESOURCE_BUNDLE.getString(key);
} catch (MissingResourceException e) {
return ’!’ + key + ’!’;
}
}
}

Para encontrar una traducción vinculada a una clave, la clase Messages interroga un objeto de tipo
ResourceBundle gracias al atributo llamado RESOURCE_BUNDLE.

Este atributo se define estáticamente y solo se inicializa cuando la JVM carga la clase Messages: es la principal
causa del problema.

Modifique la clase Messages eliminando el atributo RESOURCE_BUNDLE y añadiendo el código:

private static final Map<Locale, ResourceBundle> BUNDLES


= new HashMap<>();
static {
charge(new Locale("es", "ES"));
charge(Locale.ENGLISH);
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private static void charge(Locale locale) {


BUNDLES.put(locale,
ResourceBundle.getBundle(BUNDLE_NAME, locale));
}

Se ha creado una propiedad estática de tipo Map<Locale, ResourceBundle> que almacenará los bundles de
traducción cada uno con la clave del idioma asociado. El tipo Map es una inferfaz, y se inicializa la propiedad con
un objeto de tipo HashMap, que es una clase que implementa la interfaz Map.

Se inicializa el contenido de esta propiedad con el bloque static { ... }, que llama al método charge(),
guardando este último las traducciones en el Map.

Modifique después el método getString() por:

public static String getString(String key) {


try {
return getString(key, Locale.getDefault());
} catch (MissingResourceException e) {
return ’!’ + key + ’!’;
}
}

Este método llama a otro método que se va a crear ahora.

Añada en la clase Messages el método getString(String, Locale).

public static String getString(String key, Locale locale) {

ResourceBundle bundle = BUNDLES.get(locale);


Optional<ResourceBundle> opt = Optional.ofNullable(bundle);
bundle = opt.orElse(BUNDLES.get(Locale.SPANISH));
Stream<ResourceBundle> flujo = Stream.of(bundle);
Stream<String> trad = flujo.map(rsc ‐> rsc.getString(key));
return trad.findFirst().get();
}

Este método utiliza dos novedades de Java 8: los Optional y los Stream.

Optional es una clase que permite tener en cuenta el potencial de una variable nula.

Las siguientes líneas:

Optional<ResourceBundle> opt = Optional.ofNullable(bundle);


bundle = opt.orElse(BUNDLES.get(Locale.SPANISH));

Son equivalentes a:

if (bundle == null) {
bundle = BUNDLES.get(Locale.SPANISH);
}

La complejidad de un método se cifra en función del número de if, else, for que lo compone. Un método demasiado
complejo es difícilmente mantenible y poco legible. La clase Optional permite reducir esta complejidad.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Una vez obtenido el bundle, se trata de recuperar la traducción.

Para ello, se utilizan los streams. Un stream es un flujo de datos que puede transformarse en otros flujos con
filtrados intermedios.

El flujo se corresponde aquí con un solo dato, el bundle. Se crea con el método estático of() de la interfaz
Stream.

Stream<ResourceBundle> flujo = Stream.of(bundle);

Este flujo se transforma (mapea) después en una cadena de caracteres.

Stream<String> trad = flujo.map(rsc ‐> rsc.getString(key));

Lo que es equivalente a:

String traduccion = bundle.getString(key);

La primera traducción se recupera con el método findFirst(), que devuelve un Optional de esta cadena de
caracteres. Después se recupera el valor del opcional llamando a get().

Esta manera de codificar permite disminuir la complejidad de un método: ya no existen if para probar valores, se
suprimen los bucles for o while,... Uno de los objetivos es ganar en legibilidad.

Pruebe la clase.

Todavían existen errores. Esto es debido a que la clase ResourceBundle no encuentra traducción para el idioma
español (no hay archivo messages_es.properties), y propone como alternativa las traducciones por defecto, es
decir el resultado de llamar a Locale.getDefault().

Cree un archivo messages_es.properties, a partir de messages.properties, y vuelva a ejecutar los test.

La siguiente línea:

bundle = opt.orElse(BUNDLES.get(Locale.SPANISH));

permite proveer la traducción española si se desconoce el idioma en el sistema, por ejemplo el italiano.

Añada el siguiente método en la clase de tests:

@Test
public void testMessages() {
String string = null;
try {
string = Messages.getString("inexistente",
Locale.SPANISH);
Assert.fail("Mensaje desconocido recuperado ");
} catch(MissingResourceException e) {
Assert.assertNotNull(e);
}
string = Messages.getString("MiPrimerPrograma.0",
Locale.SPANISH);
Assert.assertEquals("Formato incorrecto en Español ",
"¡Soy %s!", string);

string = Messages.getString("MiPrimerPrograma.0",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Locale.ENGLISH);
Assert.assertEquals("Formato incorrecto en Inglés ",
"It’s %s!", string);

string = Messages.getString("MiPrimerPrograma.0",
Locale.ITALIAN);
Assert.assertEquals("Formato incorrecto en Italiano ",
"¡Soy %s!", string);
}

Es posible utilizar otras versiones del método getString(String, Locale), como por ejemplo:

public static String getString(String key, Locale locale) {


return Stream.of(BUNDLES)
.map(bundles ‐> bundles.get(locale))
.map(Optional::ofNullable)
.map(o ‐> o.orElse(BUNDLES.get(Locale.SPANISH)))
.map(rsc ‐> rsc.getString(key))
.findFirst()
.orElse("?" + key + "?");
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añadir plug­ins
Existen dos maneras de añadir plug­ins a Eclipse. O realiza usted mismo las operaciones, o indica a Eclipse la URL
del sitio web que proporciona el plug­in para su descarga y le deja hacer el trabajo.

En el primer caso, una vez descargado el plug­in, descomprímalo (un plug­in está muchas veces disponible como
un archivo comprimido: un archivo zip) y copie su contenido en la subcarpeta dropins de la carpeta de instalación
de Eclipse. Eclipse supervisa esta carpeta y se instalarán los plug­ins tras el próximo inicio de Eclipse.

En el segundo caso, basta con ir al menú Help ­ Install New Software... y seleccionar o copiar la dirección del
sitio web en la sección Work with.

Eclipse proporciona también un conjunto de plug­ins directamente accesibles mediante su Marketplace.

Vaya al menú Help ­ Eclipse Marketplace... para obtener e instalar un conjunto de plug­ins seleccionados
por Eclipse.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Por defecto, Eclipse está en inglés. Existen plug­ins de traducción en diferentes lenguajes. Llegando en muchas
ocasiones las traducciones con cierto retraso, se arriesga a tener dificultades para encontrar o disponer de una
interfaz gráfica que mezcle el español y el inglés. Además, una vez Eclipse traducido, no podrá acceder fácilmente a
las traducciones inglesas. Y en cuanto busque ayuda o información por Internet, el proceso será más difícil.

Dicho esto, Java está en inglés y a menos de que seamos totalmente herméticos al idioma de Shakespeare, se
recomienda trabajar con el idioma por defecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añadir librerías selectivamente


Al descargar recursos adicionales, puede necesitar solamente algunas librerías entre las presentes.

Para añadirlas selectivamente, haga clic con el botón derecho en el proyecto, seleccione Properties y elija
Java Build Path.

Haga clic después en el botón Add External JARs....

No se recomienda utilizar los jars externos (que no se encuentran en la carpeta del proyecto), si trabaja en equipo. E
incluso trabajando solo, si cambia de ordenador y solo ha comprimido el proyecto y no los jars externos, se arriesga
a perder tiempo para volver a inicializar todo. Lo mejor es copiar estos jars en el proyecto y referenciarlos con el botón
Add JARs....

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestionar las versiones de un proyecto


Con el transcurso del ciclo de vida de una aplicación se modifican, borran, mueven, ... los archivos.

Es interesante para un desarrollador o un equipo de desarrollo guardar una traza de las diferentes modificaciones,
también llamadas versiones.

El método más básico de realizar esto es conservar en el disco duro cada modificación en una carpeta distinta, en
el mejor de los casos con la fecha incluida en el nombre.

Este método permite compartir el código entre varias personas, cada una trabajando de su lado de una a tres
semanas, y después... permite reintegrar cada una de las modificaciones transcurridas de una a tres semanas
teniendo el riesgo de olvidar algunas.

Por suerte, existen otras maneras de realizar esto. Para facilitar este trabajo necesario, existen programas de
gestión de versiones como Git (https://git­scm.com/), Mercurial (https://mercurial­scm.org/), Subversion
(https://subversion.apache.org/) o incluso CVS (http://www.nongnu.org/cvs/) para los que tienen alma de
paleontólogo.

Estas distintas aplicaciones de gestión de versiones permiten archivar todas las modificaciones del código fuente de
un programa, compartir el código entre varios miembros de un equipo, integrar fácilmente sus modificaciones,
probar diferentes soluciones, encontrar qué modificación provocó bugs así como el responsable,... y más
vulgarmente recuperar un estado estable del código fuente si alguien modificó o borró sin querer archivos.

Estos sistemas de gestión de versiones no solo sirven para los equipos de desarrollo: aunque un único desarrollador
trabaje sobre un proyecto, podrá aprovechar este paracaídas en caso de error de codificación.

Estando Git integrado de manera nativa en Eclipse Luna, explicamos brevemente a continuación este sistema
descentralizado de gestión de versiones.

Seleccione el proyecto MiPrimerPrograma en el explorador de packages, haga clic con el botón derecho y
elija Team ­ Share Project.

Se abre un cuadro de diálogo para parametrizar la gestión de versiones del proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione Git como tipo de gestión de versiones y haga clic en Next.

Después haga clic en el botón Create junto al campo Repository.

Se abre un nuevo cuadro de diálogo para crear un espacio de almacenamiento para todas las versiones
del proyecto. Esta etapa solo es necesario realizarla la primera vez.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Elija la ubicación en el disco duro donde crear este espacio de almacenamiento (llamado repository o
repositorio), y haga clic en Finish.

Seguidamente haga clic en Finish.

El proyecto está desde este momento gestionado por el sistema de control de versiones pero los archivos
existentes no se consideran aún como versionados: están decorados con un signo de interrogación en la vista
Package Explorer.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione el proyecto y haga clic con el botón derecho para elegir la opción Team ­ Add to Index.

Los archivos están ahora marcados con un asterisco sobre fondo negro: se han detectado como pertenecientes a
una modificación por Git.

Seleccione después la opción Team ­ Commit tras hacer clic con el botón derecho en el proyecto.

Aparece un cuadro de diálogo que permite confirmar las modificaciones en el repositorio.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añada un mensaje para el commit de las modificaciones (el mensaje es obligatorio en el sistema Git) y haga
clic en Commit.

Acaba de versionar (commit) los archivos: ahora están decorados con un pequeño cilindro amarrillo.

Si más adelante se modifica algún archivo en el proyecto, aparecerá con un símbolo > delante del nombre.

Existen numerosas posibilidades con Git en Eclipse. Aquí tiene una breve descripción de las más utilizadas,
enumeradas según su ubicación en el menú que aparece al hacer clic con el botón derecho:

Team ­ Commit: guarda todas las modificaciones en el repositorio de gestión de versiones.

Team ­ Add to Index: añade un archivo en el sistema de gestión de versiones. Necesitará hacer un commit para
que se tome en cuenta el nuevo archivo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Compare With ­ Head Revision: compara un archivo con la última versión del mismo y abre un editor gráfico
para mostrar las modificaciones.

Replace With ­ Head Revision: borra las modificaciones actuales y vuelve a la última versión.

Team ­ Pull: recupera las últimas modificaciones guardadas en el repositorio del equipo.

Team ­ Synchronize Workspace: enumera los archivos modificados localmente y los modificados por el resto del
equipo.

También es posible crear ramas, es decir versiones alternativas del proyecto que tendrán su vida propia, poner en
espera modificaciones con el comando llamado Stash, etc.

Eclipse proporciona únicamente un subconjunto de todas las funcionalidades de Git. Existen interfaces gráficas que
permiten utilizar todas las funcionalidades: https://git­scm.com/download/gui/linux

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Aplicación Luna

La aplicación Luna es una aplicación clásica de gestión de pedidos.

En el ámbito de este libro, el objetivo es triple: en primer lugar revestir y profundizar concretamente los conceptos
principales de la programación orientada a objetos vistos anteriormente, en segundo lugar aprender a elaborar
código estructurado adoptando un método de desarrollo y para finalizar comprender las dificultades de realizar un
proyecto para al final entregar una aplicación que incluya todas las funcionalidades que se pueden presentar en
una aplicación profesional.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Requisitos del proyecto


Se presentan los requisitos del proyecto en grandes líneas.

El acceso a la aplicación necesita un usuario y una contraseña.

La aplicación incluye la gestión de los siguientes módulos:

clientes,

artículos,

pedidos.

Para cada módulo, las funcionalidades esperadas son las siguientes:

añadir,

eliminar,

modificar,

buscar,

imprimir,

exportar.

Se pedirá confirmación en todas las supresiones de datos.

Se podrá ver una vista preliminar antes de la impresión.

La exportación puede hacerse en los siguientes formatos: pdf, html, docx, odt, ods.

La aplicación propondrá los módulos adicionales siguientes:

estadísticas,

gráficos.

La gestión de errores tendrá en cuenta:

Los errores de introducción de datos,

Los problemas de conexión,

El envío de mensajes.

Por la preocupación ergonómica, los siguientes puntos deberán tenerse en cuenta:

Uso del teclado y/o del ratón en función de las acciones a realizar,

Ubicación del cursor en el primer campo de introducción de datos tras la apertura de la ventana,

Presentación de los datos en modo de ficha después de un doble clic en una línea de una tabla,

Vistas multiventanas con actualización simultánea.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Persistencia de los datos


El guardado de los datos de una aplicación Java puede realizarse de distintas maneras. La más sencilla es la que
consiste en guardarlos en archivos de tipo texto con delimitadores, pero es una solución obsoleta e inadaptada a
las aplicaciones de gestión. La serialización y el almacenado en bases de datos relacionales representan opciones
más serias.

La serialización es una solución elegante que permite obtener la persistencia de los objetos pero conlleva varias
variantes que presentan cada una ventajas e inconvenientes. Según el tipo de serialización elegida, existen
limitaciones en cuanto a velocidad, número de clases o también su complejidad.

Las bases de datos relacionales son inevitables en el mundo informático y más particularmente en el de la gestión.
Están basadas en una tecnología probada desde hace decenas de años y han sabido integrar los datos de tipo
objeto. Esta opción es la elegida para el proyecto Java, opción que es por otra parte la más utilizada hoy en día.

Entre los numerosos SGBD relacionales, se elige MySQL por sus cualidades además de que, al igual que Eclipse, es
un producto open source. Se describen su puesta en marcha con el servidor XAMPP, su utilización con Java y el
mapping objeto­relacional JPA en el capítulo Base de datos MySQL.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Utilización de patrones de diseño


La aplicación Luna pone sobre la mesa patrones de diseño (design patterns en inglés) que son soluciones
estándares que dan respuesta a problemáticas de diseño de software. Estos patrones permiten crear diferentes
organizaciones de clases para mejorar la mantenibilidad y la comprensión de una aplicación.

El Gang Of Four (Gamma, Helm, Johnson y Vlissides: la banda de los cuatro) formuló 23 patrones de diseño en
1994 en un libro titulado Design Patterns ­ Elements of Reusable Object­Oriented Software. Existen otros patrones
de diseño disponibles en la literatura como por ejemplo los patrones Support de inicialización al requerimiento,
Reactor y Active Record.

No se deben considerar como soluciones definitivas grabadas en piedra sino más bien como guías de buenas
prácticas que describen las grandes líneas de un diseño más modular y mantenible.

Un design pattern no es un dogma.

En un proyecto, los patrones están la mayor parte de las veces asociados entre ellos para proveer un código que
responda a las necesidades de los usuarios al mismo tiempo que dan un rumbo para las futuras evoluciones de la
aplicación.

La asociación de diferentes patrones de diseño lleva a patrones de arquitectura como MVC, que será el utilizado en
el proyecto Luna.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Metodología
Para realizar el análisis del proyecto, se utiliza UML (Unified Modeling Language).

UML nació de la fusión de las tres principales metodologías de modelización de objetos a mediados de los años 90:
OMT (Object Modeling Technique) de James Rumbaugh, Booch de Grady Booch y OOSE (Object­Oriented Software
Engineering) de Ivar Jacobson. No se trata sin embargo de una metodología pura sino de un lenguaje gráfico de
modelización de sistemas de información, dicho de otro modo de notación o representación gráfica del dominio que
se desea modelizar. Esto es tan cierto como que UML no precisa las etapas a seguir, sino cómo realizarlas para
pasar de las necesidades de los usuarios al código fuente.

La modelización UML se elabora con un formalismo gráfico y se apoya actualmente en catorce diagramas UML
versión 2.4.1. Estos últimos no se utilizan, generalmente, en su totalidad. Su empleo depende del tamaño, la
naturaleza y la complejidad del proyecto. Usaremos un número restringido de diagramas para construir la
aplicación Luna que es un proyecto pequeño clásico de gestión tal y como se definió en el capítulo Presentación del
proyecto.

Generalmente, se considera el diagrama de clases como el más importante. Es por lo tanto habitualmente el
primer diagrama que los creadores de aplicaciones realizan. Se elige un enfoque de Ingeniería del Software en este
libro inspirándose en métodos basados en UML como RUP (Rational Unified Process) y/o XP (eXtreme
Programming).

La metodología adoptada depende de las necesidades de los usuarios: no se modelizan por lo tanto en primer lugar
las clases técnicas, como las de conexión, que no son directamente perceptibles por el usuario final.

Por el contrario, teniendo en cuenta la situación central del usuario, se crean las maquetas representativas de la
aplicación final en primer lugar para que después sean sometidas a aprobación.

Se supone en el ámbito de este libro que estas maquetas y los prototipos posteriores serán validados por estos
usuarios.

Se resume la metodología elegida para este proyecto. Partiendo de las necesidades, se realizan maquetas y en
paralelo los diagramas de casos de uso.

Los diagramas de secuencia y de comunicación se crean después, para finalmente construir el diagrama de clases
de la aplicación. Todo esto se realiza utilizando ciclos iterativos e incrementales.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Al final la idea es simple: para modelizar (comprender y representar) un sistema complejo, es necesario volver a
empezar varias veces afinando el análisis en etapas y autorizando la marcha atrás hacia las etapas anteriores. Esta
metodología se aplica al ciclo de desarrollo en su conjunto. Una o varias iteraciones permiten así un avance
significativo que se llama incremento. Se reitera el proceso en cada incremento.

La metodología iterativa e incremental puede también aplicarse a los ciclos de vida más clásicos (en cascada, en
V,...).

Cuando se llegue al diagrama de clases, se puede optar por dos opciones. O bien la producción de código empieza
de inmediato o bien se difiere esta para antes agrupas las clases. Se realiza este esfuerzo adicional por la voluntad
de racionalizar el código para facilitar el mantenimiento correctivo y evolutivo.

Se determinan las clases en función de tres tipos:

diálogo,

control,

entidad.

El tipo diálogo agrupa las clases que constituyen la interfaz hombre­máquina: el HMI (en inglés GUI por Graphical
User Interface). Son en su mayoría clases gráficas que surgen de las maquetas. No realizan ningún tratamiento
excepto los que permiten interactuar con el usuario y son responsables de la visualización de los datos.

El tipo entidad corresponde a las clases propias del dominio que se quiere modelizar. Cada instancia de estas
clases corresponderá en general a una línea de datos almacenada en alguna de las tablas de la base de datos
MySQL. Cada clase dispondrá de métodos llamados CRUD (del inglés Create, Read, Update, Delete), que permiten
realizar operaciones elementales sobre los datos.

Las clases de tipo control establecen la comunicación entre los dos tipos de clases anteriormente descritos. Por
ejemplo, se trata la solicitud de creación de un cliente por una clase de control que la transmite a la clase CRUD
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

que posee este método y que es responsable de la gestión de clientes. También es posible hacerlas responsables de
los controles relativos a las reglas de negocio.

Las clases técnicas son también de este tipo.

Desde el punto de vista del código, estas clases se almacenan en packages.

Una arquitectura de tres capas florece para la aplicación Luna.

UML es un formalismo que permite describir gráficamente un sistema informático. ¡Es perfectamente posible hacer
la modelización UML con un bolígrafo y un papel!

Algunos programas permiten también realizar esta modelización de manera informática, con el plus de por ejemplo
la generación automática de código...

Lo más importante en una modelización UML es que un participante en el proyecto puede comprender la aplicación
sin necesidad de tener un bagaje técnico adicional.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añadir el plug­in UML en Eclipse


La modelización de la aplicación Luna se realiza con un programa integrado en Eclipse.

Existen varios plug­ins UML para Eclipse, algunos gratuitos y otros de pago. La elección de un plug­in se hace por
razones de costumbre, coste y hasta de gusto... Para seguir con un espíritu open source, se utiliza el plug­in
Papyrus que está totalmente integrado en Eclipse.

Abra el sistema de instalación de plug­ins de Eclipse accediendo al menú Help y después a Install New
Software.

Añada el sitio web de actualización de Papyrus en el campo Work With:


http://download.eclipse.org/modeling/mdt/papyrus/updates/releases/luna

Seleccione después los plug­ins de Papyrus a instalar y haga clic en Next.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en Next y acepte el acuerdo de licencia. Haga clic en Finish. Al acabar la instalación, reinicialice
Eclipse.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Diagrama de casos de uso


Se describen brevemente los diagramas elegidos para el proyecto. Para un estudio más profundo, consulte los
libros de tratan sobre UML. Aquí se explica la realización de los mismos con el plug­in Papyrus.

Se proponen los diagramas elaborados según la metodología descrita anteriormente a título indicativo.

Como se ha subrayado en la sección Metodología, el diagrama de casos de uso es a la vez uno de los más sencillos
y uno de los menos utilizados. Numerosos desarrolladores le dan todavía poca importancia. Esto es quizás debido a
la herencia de Merise y de la mayor parte de las primeras metodologías de modelización de objetos (antes de la
fusión de OMT, OOSE y Booch) para los cuales no existe un equivalente y cuyo elemento principal en cuanto a
Merise es el modelo conceptual de los datos, modelo que presenta similitudes con el diagrama de clases.

Se trata con el diagrama de casos de uso de definir las funcionalidades y el comportamiento de la futura aplicación
de cara al usuario o de su entorno (otros usuarios, sistemas informáticos externos a la aplicación, autómatas,...).
Permite una comprensión común entre el conocimiento del dominio y el conocimiento de realización, dicho de otra
forma entre el cliente, ya sea simple usuario o experto, y el desarrollador.

Los casos de uso describen únicamente las interactuaciones o las funcionalidades esperadas del sistema en
desarrollo. No describen la manera de conseguirlas. Durante el desarrollo sirven de referencia para verificar si las
necesidades expresadas por el cliente están realmente satisfechas.

Cree un nuevo proyecto UML_Luna de tipo Papyrus Project con el menú File ­ New ­ Other y eligiendo
Papyrus Project. Introduzca el nombre del proyecto y haga clic en Next.

Elija el lenguaje UML y haga clic en Next.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree al mismo tiempo un nuevo diagrama de casos de uso seleccionando UseCase Diagram y llamándole
CasoDeUso. Haga clic en Finish.

Se crea un nuevo proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Ahora abra la perspectiva de modelización Papyrus con el menú Window ­ Open Perspective ­ Other.

Las vistas de Eclipse se reordenan para adaptarse al contexto de modelización UML según Papyrus, y aparece el
modelo en la zona de edición con el primer diagrama. Este diagrama está evidentemente vacío.

El diagrama de casos de uso propuesto aquí presenta las interacciones posibles del usuario con el sistema, limitado
a la gestión de clientes. Este debe poder, después de identificarse, consultar, añadir modificar y suprimir un cliente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para realizar este diagrama, utilice los elementos gráficos presentes en la paleta situada a la derecha del
diagrama.

Para añadir un usuario, deslice el elemento Actor en el diagrama. Este elemento se encuentra en la sección
Nodes de la paleta.

Para enlazar elementos, seleccione los enlaces en la sección Links de la paleta, y apunte sucesivamente con
el ratón a los elementos del diagrama que desea enlazar.

Después de conversar con los usuarios, se da cuenta de que olvidó una función importante: ¡cuando se quiere
modificar o suprimir un cliente, debe primero realizar una búsqueda entre ellos! Por lo tanto se modifica el
diagrama en la siguiente iteración de esta manera:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Diagrama de secuencia
El diagrama de secuencia persigue modelizar el aspecto dinámico del sistema. Se le puede comparar a una
storyboard que evidencia las interacciones existentes entre los objetos del sistema. Se pone el acento sobre el
orden cronológico de los mensajes emitidos, siendo el eje de tiempo el eje vertical de este diagrama y siendo los
primeros mensajes emitidos los situados gráficamente más arriba en el diagrama.

Para cada acción del usuario definida en el diagrama de casos de uso, es conveniente establecer un diagrama de
secuencia. Utilizamos la función de lectura de todos los clientes como ejemplo.

En la vista Model Explorer, seleccione el módulo raíz haciendo clic con el botón derecho y elija la opción
New Diagram ­ Create a new UML Sequence Diagram.

Elija un nombre para este diagrama, como por ejemplo SecuenciaLecturaClientesGlobal.

Se genera el diagrama con otra paleta.

Haga clic con el botón derecho en el elemento modelo de la vista Model Explorer y añada un package con la
opción New Child ­ Package. Llame a este elemento dialogo.

Repita la operación para crear un package control.

Haga clic con el botón derecho en este package en la misma vista y añada una clase con la opción New Child
­ Class. Llame a este elemento VistaClientes.

Repita la operación para las clases que desea introducir en el modelo.

Utilice los elementos presentes en la vista Model Explorer para construir el diagrama deslizando elementos
que interactúen entre ellos.

Utilice la sección Edges de la paleta para crear flechas que representen las interacciones entre los elementos.

Las flechas solo pueden enlazar componentes de sistema sobre un rectángulo presente en su línea de
vida.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para añadir este rectángulo, seleccione el elemento Action Execution Specification en la paleta de la
sección Nodes, y deslícelo sobre el elemento deseado.

El primer diagrama presentado aquí representa una posible primera iteración.

Un diseño del sistema en un lenguaje orientado a objetos como Java necesita para una mayor claridad y
mantenibilidad de la aplicación confiar solamente en una responsabilidad por clase. El control de cliente tiene aquí
demasiadas responsabilidades a gestionar, algunas de estas responsabilidades son comunes al futuro control de
artículo. Por lo tanto es más ventajoso descomponerlas en clases más pequeñas.

Una segunda iteración en la construcción del diagrama permite aproximar mejor los diferentes elementos
necesarios en las interacciones entre el control cliente y la base de datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Diagrama de comunicación
El diagrama de comunicación es equivalente desde un punto de vista semántico al diagrama de secuencia. Sin
embargo pone el acento sobre la organización estructural de los objetos de cara a la emisión de mensajes.

Es muy sencillo realizar el diagrama de comunicación partiendo del diagrama de secuencia.

Cree un nuevo diagrama a partir del modelo de la vista Model Explorer, seleccionándolo y eligiendo la opción
New Diagram ­ Create a new UML Communication Diagram. Llámelo por ejemplo
ComunicacionLecturaClientes.

Deslice en este nuevo diagrama los elementos presentes en el diagrama de secuencia desde la vista Model
Explorer.

Para cada uno de los mensajes emitidos presentes en el diagrama de secuencia, cree un nuevo mensaje
equivalente entre los mismos elementos en el diagrama de comunicación.

Es habitual prefijar estos mensajes con números indicando el orden de aparición en el diagrama de
secuencia.

Algunas otras herramientas de modelización UML proponen la generación automática de este diagrama.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Diagrama de clases
Se utiliza el diagrama de clases para modelizar el aspecto estático de un sistema. Pone en evidencia las clases y
las relaciones que estas tienen entre ellas: dependencia, asociación, generalización,... Según la importancia del
sistema a modelizar, se pueden utilizar diagramas de clases intermediarios, particularmente los diagramas de
clases participantes que describen para cada caso de uso las tres principales clases de análisis y sus relaciones
(consulte la sección Metodología).

Según la metodología elegida, es (por fin) posible llegados a este punto construir el diagrama de clases. Debe
contener las clases puestas en evidencia por los objetos presentes en los diagrama de secuencia y de comunicación
así como las que eventualmente faltan, necesarias para el funcionamiento de las maquetas (consulte el capítulo
Maquetas).

Haga clic con el botón derecho en la vista Model Explorer y elija la opción Create a new UML Class
Diagram.

Llame al diagrama ClasesClientes.

En función de las necesidades, otras clases pueden aparecer desde el análisis y/o al realizar el primer juego de
maquetas. Por ejemplo se puede añadir una clase gráfica adicional surgida de las maquetas si se desea mostrar el
conjunto de registros a guardar de una consulta SQL en una nueva ventana. Maquetas y diagramas pueden así
interactuar para llegar lo antes posible a lo esencial de las clases del proyecto.

El diagrama de clases propuesto es consecuencia de los diagramas de secuencia y de comunicación del caso de uso
Leer todos los clientes.

En este diagrama, la clase VistaClientes accede directamente a la clase ControlCliente, e indirectamente a la


clase ModeloClientes. Se trata del patrón de arquitectura MVC que se estudia e implementa en el capítulo Modelo
MVC.

Este diagrama basado en los tres tipos de clases de análisis presentados anteriormente propone por lo tanto una
división semejante a la descrita por el modelo MVC (Modelo ­ Vista ­ Controlador). MVC, creado en 1980 por Xerox
Parc para el lenguaje Smalltalk, es un patrón de arquitectura o design pattern que introduce entre otros el
concepto de sincronización. La idea es actualizar automáticamente todas las vistas que presentan los mismos
datos en función de las modificaciones realizadas en la base de datos y recíprocamente. Esta actualización puede
afectar solamente a vistas debido a las acciones del usuario. Es lo que diferencia fundamentalmente MVC del
enfoque escogido que busca esencialmente la separación de los datos de la interfaz hombre­máquina mediante
una capa encargada de la transmisión de las consultas y los resultados.

MVC no presenta solamente ventajas. Para llevar a cabo la sincronización, debe crear nuevas interfaces y clases de
tipo listener y gestionar eventos para añadirlos al modelo para que pueda escuchar y notificar los cambios que le
afectan. Los controladores deben escribirse de manera que puedan ser informados de los cambios para realizar la
sincronización y actualización de las vistas y del modelo. Esta arquitectura aporta por lo tanto un nivel de

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

complejidad añadido que implica un importante trabajo de diseño y el correspondiente incremento del código. Por
otra parte, la sincronización puede depender de numerosas condiciones, lo que hace aún más pesada su
realización. Se detallan estos elementos en el capítulo Modelo MVC.

El rombo blanco en la relación VistaClientes ­ ControlClientes representa una relación de agregación: las
instancias de la clase VistaClientes contienen una instancia (y solamente una ya que se especifica con el número al
lado de la relación) de la clase ControlClientes.

El rombo negro en la relación ControlClientes ­ ClienteCrud es una relación de composición, que es equivalente a
una relación de agregación con restricciones adicionales. Representa sobre todo que una instancia de ClienteCrud
solo puede estar asociada con una única instancia de la clase ControlClientes, y que si se elimina una instancia de
la clase ControlClientes del sistema, se eliminará también la instancia ligada de ClienteCrud.

También se observa una relación de herencia entre la clase VistaClientes y la clase JPanel.

A continuación se ve cómo construir este diagrama.

Seleccione el package dialogo desde la vista Model Explorer y deslícelo en el diagrama.

Haga lo mismo con la clase VistaClientes.

Repita la operación con los demás packages y clases.

Para crear la clase Direccion, seleccione el elemento Class de la paleta en la sección Nodes y deslícelo en el
diagrama en el interior del package entidad. Llame a esta nueva clase Direccion.

La clase aparece también en la vista Model Explorer.

Repita la operación para las clases VentanaClientes y JPanel y deslícelas en el package dialogo.

Materialice la relación de herencia entre JPanel y VistaClientes eligiendo el enlace Generalization en la


sección Edges de la paleta.

La flecha simboliza esta relación de herencia en una dirección. Tenga cuidado de no invertir el sentido de esta
relación.

Materialice la relación entre Cliente y Direccion eligiendo el enlace Association en la sección Edges de la
paleta.

Seleccione esta nueva relación en la vista Properties de la sección UML, y en el detalle Member end
llamado direccion. Seleccione composite en la propiedad Aggregation. La propiedad Multiplicity debe valer
0..1.

La relación VistaClientes ­ ControlClientes posee una propiedad Aggregation con el valor shared en el Member
end del ControlClientes.

Las etapas seguidas para el análisis de las entidades Articulo y Pedido siguen una metodología similar a la de la
entidad Cliente. Evidentemente es posible afinar el análisis para tratamientos particulares de la aplicación siendo la
idea cubrir primero las necesidades genéricas antes de perder tiempo en los detalles.

El plug­in Papyrus no permite generar automáticamente el código. En este punto, es inútil mejorar la modelización
añadiendo atributos a las clases.

Otros plug­ins o programas permiten esta generación automática. Usted deberá decidir si esto vale la pena para su
proyecto, sabiendo que a veces este proceso resulta (muy) consumidor de tiempo para generar un proyecto entero a
partir de la modelización UML. Se puede realizar esta elección si por ejemplo la aplicación o ciertas partes de la misma
tendrán que escribirse en diferentes lenguajes orientados a objetos, como Java o C++.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
Este capítulo necesita conocimientos generales sobre las bases de datos y el lenguaje SQL (Structured Query
Language).

Se utiliza la aplicación XAMPP, acrónimo de X Apache MySQL Perl PHP. Así se dispone de un servidor, una base de
datos relacional y una interfaz gráfica de administración. Los lenguajes Perl y PHP no son útiles en el ámbito del
proyecto Luna. Existen versiones de XAMPP para Windows, Linux, Mac OS y Solaris.

También puede elegir Wamp.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

SQL y las bases de datos relacionales

1. Las bases de datos relacionales


Una base de datos permite almacenar información de manera persistente en estructuras dedicadas a este efecto.
Estas estructuras se llaman tablas. Cada tabla de una base de datos tiene por vocación almacenar información
similar entre sí, como por ejemplo la lista de clientes de una empresa. Los pedidos estarán, por su parte,
almacenados en una tabla diferente.

Una tabla define un número determinado de columnas, una por cada información a guardar. Por ejemplo la tabla
de clientes contendrá una columna con los apellidos del cliente, otra para almacenar su nombre, otra para su e­
mail, otra para la fecha de creación del cliente en el sistema, ... Estas columnas tienen todas un nombre único
dentro de la tabla.

Cada tabla contendrá datos. Se almacenarán estos datos en una fila. Por ejemplo la tabla de clientes contendrá
tantas filas como clientes existan, cada fila almacenará los datos del cliente en la columna apropiada.

Con el fin de identificar de manera única a un cliente, se dedicará una columna para almacenar este identificador.
Se habla de este identificador como una clave primaria. Una clave primaria debe obligatoriamente ser única
dentro de los valores de esta columna para un tabla dada.

Habitualmente, existen varias tablas en una base de datos, ligadas entre ellas: se debe poder en un sistema
encontrar todos los pedidos de un cliente dado. Se habla comúnmente de una relación entre los datos (de donde
proviene el término de bases de datos relacionales). Esta relación se crea añadiendo una columna adicional en
una de estas tablas donde se almacena la clave primaria de una fila de otra tabla. Se llama a esta columna
habitualmente clave foránea. Así la tabla de facturas tendrá una columna llamada ”clave_cliente”, representando
cada fila un pedido que almacena en esta columna la clave primaria del cliente asociado.

Una relación así se denomina Many To One: un cliente puede estar ligado a varios pedidos, pero solo puede existir
un cliente para un pedido dado. Existen otras relaciones del tipo One To One o Many To Many.

Este sistema de almacenamiento permite obtener datos estructurados y organizados entre ellos. Otro aspecto
importante es que si se debe modificar el nombre del cliente, esta modificación se llevará a cabo en un único lugar
ya que una base de datos bien concebida minimiza la información almacenada.

Un sistema de gestión de bases de datos relacionales (SGBD) es un programa que permite acceder y manipular
todos sus datos desde y hacia diferentes bases de datos. MySQL es un SGBD, como lo pueden ser Oracle,
PostgreSQL, SQLite,...

Existen otros tipos de SGBD, por ejemplo del tipo NoSQL como MongoDB. MongoDB no almacena los datos en
tablas sino en colecciones, no en filas sino en documentos, no en columnas sino en campos,...

Organizar una base de datos y tener un conjunto de datos coherente está muy bien, pero es necesario poder
«jugar» con ellos, consultar la base de datos o suprimir datos... Para alcanzar este objetivo, es necesario un
lenguaje de consultas: se trata de SQL.

2. SQL
SQL es un lenguaje, por lo tanto con una sintaxis, que permite interrogar una base de datos para encontrar
datos, y manipularlos (almacenarlos, modificarlos, suprimirlos). Permite efectuar lo que se llama operaciones
CRUD (del inglés Create, Read, Update, Delete) y muchas otras operaciones (contar, ...).

Para insertar (crear) una fila de datos en la tabla cliente, podrá escribir:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

INSERT INTO cliente(id, apellido, nombre, email)


VALUES (1, "Fernández", "Pedro", "[email protected]")

Para modificar un cliente existente, podrá utilizar la siguiente sintaxis.

UPDATE cliente SET email = "[email protected]" WHERE id = 1

Para encontrar todos los clientes, podrá usar:

SELECT * FROM cliente

Para encontrar los pedidos de un cliente, deberá realizar un enlace entre dos tablas. Este enlace (llamado join en
inglés) consiste generalmente en asociar las líneas de dos tablas con una restricción entre los valores de una
columna de la primera tabla y los valores de una columna de la segunda tabla.

SELECT * FROM pedido


JOIN cliente ON cliente.id = pedido.clave_cliente
WHERE cliente.apellido = "Fernández"

Este join devuelve las filas que representan los pedidos realizados por los clientes cuyo apellido es «Fernández»,
estando cada fila completada con los detalles del cliente.

Existen varios tipos de join: join interno (el más extendido, como el que se ve en el ejemplo anterior), join
externo a la izquierda, join externo a la derecha, join externo completo,... No se hablará más de estos tipos
diferentes en este libro.

La instrucción EXPLAIN situada delante de la consulta permite evaluar la complejidad de esta consulta y buscar
así posibles optimizaciones.

EXPLAIN SELECT * FROM pedido


JOIN cliente ON cliente.id = pedido.clave_cliente
WHERE cliente.apellido = "Fernández"

3. Transacciones
Las manipulaciones (las escrituras) de datos en una base de datos se hacen dentro de una transacción.

Suponga que está al cargo de una base de datos que gestiona las cuentas bancarias de diferentes clientes y que
una operación de gestión consiste en trasferir dinero de una cuenta a otra. Concretamente se trata de debitar de
una cuenta y añadir (abonar) la misma cantidad en la otra cuenta. Técnicamente, esto consiste en realizar dos
operaciones de UPDATE, una para cada cuenta.

Suponga ahora que se produce un error justo entre estas dos operaciones de UPDATE (alguien desenchufa la
toma de corriente del servidor, la conexión a la red cae,...). Es mala suerte pero esto significa que el dinero ha
desaparecido: ¡es un drama para uno de los clientes y para el banco!

Una transacción permite agrupas operaciones elementales en una única operación más grande. Si todas las
operaciones elementales tienen éxito, la transacción se valida (se habla de commit). Si alguna de las operaciones
elementales fracasa, la transacción considera que la operación global no se ha completado y se anularán todas las
operaciones elementales (se habla de rollback).

MySQL propone dos motores de almacenamiento: MyISAM e InnoDB. Solo InnoDB gestiona las transacciones (y el
mecanismo de claves foráneas). En contrapartida, InnoDB es más lento y goloso en espacio de almacenamiento.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Una transacción presenta tradicionalmente propiedades, agrupadas bajo el acrónimo ACID. Según este acrónimo,
una transacción debe ser:

Atómica: una transacción se realiza en su totalidad o bien no se realiza. Si alguna de las operaciones de la
transacción fracasa, se eliminará la ejecución del conjunto de las operaciones anteriores y los datos modificados
recuperarán sus valores antes del inicio de la transacción.

Coherente: cada transacción transforma datos válidos en datos válidos. La noción de dato válido se define
mediante reglas funcionales propias al dominio de la aplicación que muchas veces se implementan como
restricciones o triggers en la base de datos.

Aislada: cada transacción se ejecuta como si fuese la única en curso: si se ejecutan más transacciones
simultáneamente, cada una permanece independiente de las demás.

Permanente: cuando se efectúa una transacción con éxito, los resultados de la transacción se almacenan de
manera persistente: si una avería cualquiera ocurriera justo después de la ejecución de la transacción, los datos
modificados se podrán recuperar después de reiniciar la base de datos.

Existe un conjunto de propiedades alternativas a las propiedades ACID: se trata de los propiedades BASE (Basically
Available, Soft state, Eventual consistency) que permiten crear sistemas distribuidos de almacenamiento de datos.
Los químicos medirán la fuerza o debilidad del juego de palabras vinculado.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Instalación y configuración del servidor XAMPP


Para una instalación o actualización, puede ir a los distintos sitios web de descarga. Existen varios kits de
instalación. El más sencillo es el que conlleva una instalación. La versión utilizada durante la escritura de este libro
es XAMPP 5.6.8­0­VC11 para win32.

Las siguientes explicaciones y capturas de pantalla se corresponden con esta versión. Las operaciones a llevar a
cabo para versiones más recientes pueden ser diferentes.

Haga doble clic en el icono. La instalación se realiza automáticamente.

En Windows 8, aparece una alerta. Haga clic en OK.

Después puede elegir los componentes a instalar. Deje las opciones por defecto. Si no dispone de suficiente
espacio en su disco duro, elija solamente Apache, MySQL y phpMyAdmin.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La carpeta por defecto para la instalación es C:\xampp. Puede elegir otra carpeta. Haga clic después en el
botón Next.

Prosiga hasta llegar a la pantalla siguiente. Haga clic en Next para arrancar el proceso de instalación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Si todo se ha ejecutado bien, debería llegar a la siguiente pantalla. Haga clic en el botón Finish.

Acceda al panel de control situado debajo a la derecha de su pantalla y haga clic en los botones Start para
arrancar Apache y MySQL.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic después en el botón Admin de la opción MySQL.

Se abre una página web, empezando la dirección por http://localhost/phpmyadmin. Se trata de la página de
administración de las bases de datos MySQL gestionadas por phpMyAdmin.

1. Gestión de acceso al servidor


Al utilizarlo por primera vez, el acceso a XAMPP no tiene definida ninguna contraseña. No muestra ningún
mensaje para advertirnos de los riesgos. Un acceso no securizado es inaceptable para una aplicación y sus datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El primer trabajo a realizar es por lo tanto dotar de seguridad al acceso al servidor.

Haga clic en el icono de bienvenida y después en el botón Usuarios.

Haga clic en el enlace Editar los privilegios.

Haga clic después en el botón Cambio de contraseña.

Introduzca una contraseña y haga clic en el botón Continuar. La que usará para el proyecto Luna es
«tempo» (no introduzca las comillas dobles).

Después de crear la contraseña, aparece un mensaje.

Pare todos los servicios haciendo clic en los botones Stop en el panel de control y salga haciendo clic en el
botón Exit.

Se han modificado los parámetros del usuario root. Ahora debe actualizar estos datos en el archivo
config.inc.php para que se tome en cuenta en la próxima ejecución del servidor.

Abra el archivo config.inc.php. Se encuentra habitualmente en la siguiente carpeta:


C:\xampp\phpMyAdmin.

Busque la línea $cfg[’Servers’][$i][’password’] y efectúe las modificaciones:

config.inc.php antes: $cfg[’Servers’][$i][’password’] = ’’;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

config.inc.php después: $cfg[’Servers’][$i][’password’] = ’tempo’;

O indique su propia contraseña en vez de ”tempo”.

Reinicie el servidor como se ha visto anteriormente.

2. Creación de la base de datos MySQL


La base de datos de la aplicación Luna se compone de varias tablas, entre las cuales tenemos las tablas cliente,
articulo, pedido, lineaspedidos, mododecobro y categoria.

Antes, era necesario crear estas tablas manualmente en la base de datos. Con JPA, el proceso de creación puede
realizarse de manera completamente automática. Sigue sin embargo siendo necesario crear la base de datos en
sí misma.

Ejecute phpMyAdmin.

En el menú de Bienvenida, haga clic en Nueva.

En el campo Nombre de la base de datos, introduzca «luna» y haga clic en Crear.

Acaba de crear su base de datos.

Se securizó el acceso al servidor MySQL creando una contraseña para el súper usuario root. En vez de
acceder a la base de datos con el súper usuario, lo que está fuertemente desaconsejado, se creará un
usuario que servirá de punto de acceso a la base de datos «luna».

Haga clic en Usuarios como vimos antes y a continuación en Agregar usuario.

Introduzca después un nombre de usuario, por ejemplo «eni» (sin las comillas dobles). Después introduzca
dos veces su contraseña, «java» (siempre sin las comillas dobles). Estos valores son arbitrarios. Solo es
importante recordarlos bien ya que servirán en la aplicación más tarde.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Asegúrese de desmarcar todos privilegios globales. Haga clic por último en Ejecutar. Se ha creado el
usuario.

Ahora debe darle permisos específicos de acceso a la base de datos «luna» y únicamente a esta.

Haga clic en el enlace Base de datos y seleccione la base de datos «luna».

Después haga clic en Continuar. Se muestra la página de permisos específicos a esta base de datos. Por
rapidez, haga clic en Seleccionar todo y en Ejecutar.

Ya solo queda una última etapa para permitir a la aplicación acceder a su base de datos. En efecto,
MySQL gestiona los permisos de acceso con la ayuda de diferentes reglas. Seleccione la primera regla,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

que corresponde al permiso de acceso, para garantizarlo. Sin embargo, por defecto en la instalación, dos
reglas prohíben este acceso: se tratan de las reglas «anonymous» que debe suprimir.

En la pestaña Usuarios, seleccione las dos primeras reglas. Después haga clic en Ejecutar en la sección
Eliminar los usuarios seleccionados.

El usuario «eni» puede desde entonces acceder a la base de datos «luna», lo que podrá verificar un poco más
adelante con el driver ODBC.

Las operaciones de configuración de una base de datos en producción están generalmente delegadas a un
administrador de bases de datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JDBC
JDBC (Java DataBase Connectivity) permite que las aplicaciones clientes desarrolladas en Java accedan a bases de
datos relaciones. JDBC provee todas las clases útiles para gestionar estas operaciones.

JDBC es una API para la cual existen cuatro tipos de drivers que difieren en rendimiento y portabilidad.

Tipo 1: el primer tipo está basado en ODBC. Las llamadas JDBC se convierten en este caso en llamadas ODBC. Este
las transmite al SGBD que ejecuta las consultas recibidas. De la misma manera, el conjunto de resultados devueltos
utilizan la pasarela o puente JDBC/ODBC entre la aplicación Java y la base de datos.

Las principales operaciones realizadas son las que siguen:

1. Carga del driver

2. Establecimiento de la conexión mediante la pasarela JDBC/ODBC con la base de datos

3. Ejecución de las consultas SQL por el SGBD MySQL

4. Recuperación de los resultados (para consultas de selección)

5. Cierre de los resultados (si existen)

6. Cierre de la conexión

Tipo 2: se trata de un driver del una parte está escrita en Java y la otra en el lenguaje del SGBD. Las llamadas JDBC
se convierten en llamadas nativas. Su utilización necesita la instalación de la parte nativa en el cliente. Otros
inconvenientes: la pérdida de portabilidad del código, lo que excluye su uso con applets.

Tipo 3: se trata de un driver totalmente escrito en Java que se comunica con el SGBD mediante una aplicación
middleware. La portabilidad y el uso con los applets están por lo tanto asegurados.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Tipo 4: se trata de un driver totalmente escrito en Java con acceso directo al SGBD. Como para el tipo anterior, la
portabilidad y el uso con applets están asegurados. Son los editores del SGBD quienes proveen este tipo de driver.

Para el proyecto Luna se utilizará el driver de tipo 4 proporcionado por MySQL.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JPA
Java Persistence API o JPA es una interfaz de programación de aplicaciones que permite facilitar el acceso y la
codificación a datos persistentes por ejemplo en una base de datos.

JPA permite crear un modelo del dominio (clases) que servirá de interfaz entre la aplicación y los datos en la base de
datos. Se escribirán las consultas hacia la base de datos en lenguaje JPQL (JPA Query Language) que permite
escribir consultas SQL en forma de objeto. Sin embargo sigue siendo posible escribir las consultas en SQL.

Gracias a anotaciones del package javax.persistence es posible establece correspondencias entre las propiedades
de estos objetos y las tablas y columnas de la base de datos.

Incluso es posible con este modelo de dominio crear completamente la estructura de la base de datos: las tablas y
las columnas.

JPA es por lo tanto una API de alto nivel que permite concentrarse en el modelo del dominio y enmascarar los
detalles técnicos de la persistencia en una base de datos.

Ejemplo de anotación

package entidad;

import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Articulo {

@Id
private String code;

@ManyToOne(cascade = { CascadeType.PERSIST })
private Categoria categoria;

@Basic
private String designacion;

@Basic
private int cantidad;

@Basic
private double precio_unitario;

@Temporal(TemporalType.TIMESTAMP)
private Date fecha;
...
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Instalación de los drivers

1. Connector/ODBC
ODBC (Open Database Connectivity) gestiona numerosos drivers que permiten establecer la comunicación entre
aplicaciones cliente y SGBD. No es la solución con mejor rendimiento pero presenta la ventaja de la simplicidad.
Por otra parte está disponible gratuitamente en prácticamente todas las plataformas.

El proceso de instalación es muy sencillo.

Descargue el driver mysql­connector­odbc del sitio web de MySQL:


http://dev.mysql.com/downloads/connector/odbc/

Haga doble clic en el archivo y siga las instrucciones de instalación.

Ejecute el programa ODBC de Windows (Panel de control ­ Herramientas administrativas).

Haga clic en la pestaña Origenes de datos ODBC y después en el botón Añadir...

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Elija el driver para MySQL que acaba de instalar.

Configure los parámetros de la fuente de datos:

El nombre dado al Data Source Name debe ser explícito y no llevar espacios. Por comodidad para el
desarrollo Java que se lleva a cabo, conserve el mismo nombre propuesto a continuación.

Para el servidor, introduzca 127.0.0.1 o localhost en el campo TCP/IP Server. El DNS establece la
correspondencia entre el nombre y la dirección IP del servidor.

Para el User y el Password, consulte lo que indicó en la instalación de XAMPP: eni y java.

Asegúrese de que XAMPP está activo y pruebe la conexión.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

2. Connector/J
Como se indica en el sitio web de MySQL, se trata del tipo de driver JDBC oficial para las bases de datos MySQL. Es
un driver JDBC de tipo 4.

Se usará para el proyecto Luna.

En el sitio web de MySQL, solo está disponible la instalación mediante un archivo ejecutable para la versión
5.1.35. Descárguela desde la dirección https://dev.mysql.com/downloads/connector/j, e instálela.

Vaya a la carpeta C:\Program Files (x86)\MySQL\MySQL Connector J para obtener el driver JDBC.

La documentación y las fuentes de las clases del driver están también disponibles en esta carpeta.

Los test con este driver se realizarán desde la aplicación Java.

Para evitar tener enlaces rotos, puede copiar el driver en una carpeta del proyecto (por ejemplo lib/).

Aquí tiene el procedimiento de instalación bajo Eclipse:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En Eclipse, haga clic con el botón derecho en su proyecto y elija Properties.

Seleccione después Java Build Path y la pestaña Libraries.

Haga clic en el botón Add External JARs, acceda a la carpeta que contiene el driver y añádalo.

Haga clic en la pestaña Order and Export, marque la opción correspondiente al driver y valide.

Drivers de tipo 2 y 3

Para instalar un driver de tipo 2 o 3, descargue y descomprima el archivo jar. Siga después los mismos pasos que
para el driver de tipo 4.

3. EclipseLink
EclipseLink es una de las implementaciones disponibles de JPA. Puede descargar los archivos binarios en la
dirección http://eclipse.org/eclipselink/downloads/.

A la hora de escribir este libro, la versión 2.6.0 era la última disponible.

Descargue el instalador zip desde el sitio web.

Descomprima el archivo y copie el contenido de la carpeta eclipselink/jlib dentro de lib/eclipselink del


proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Obtiene un conjunto de archivos como sigue.

Agregue los archivos eclipselink.jar y jpa/javax.persistence_2.1.0.v201304241213.jar en el Build


Path del proyecto, como se ha visto anteriormente. Añada también estos archivos jar en la pestaña Order
and Export.

El proyecto contiene ahora todos los bloques necesarios para acceder a la base de datos.

Queda sin embargo configurar el proyecto para que sepa a qué base de datos acceder.

Cree una carpeta META­INF en src/main/resources haciendo clic con el botón derecho después de seleccionar
la carpeta src/main/resources, y seleccione New ­ Folder. En Folder Name, teclee «META­INF» (sin
comillas dobles). Haga clic en Finish.

En esta carpeta, cree un nuevo archivo llamado «persistence.xml» (sin comillas dobles), seleccionándolo en
el explorador. Haga después un clic derecho y seleccione New ­ File. Después teclee «persistence.xml» (sin
comillas dobles) en File Name. Haga clic después en Finish.

El archivo se abre en la zona de código.

En esta zona, teclee el siguiente contenido y guarde el archivo pulsando [Ctrl] S:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En esta configuración, JPA utiliza el driver JDBC que acaba de instalar.

La propiedad eclipselink.ddl­generation configura JPA para suprimir y volver a crear las tablas en cada
ejecución. Es una propiedad muy útil en fase de desarrollo. ¡Sin embargo es primordial quitarla cuando la
aplicación está en producción!

Para probar el acceso a la base de datos con JPA, queda crear una clase TestJPA en el package test.

Cree una clase llamada TestJPA en el package test. Esta clase tendrá el siguiente contenido:

package test;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.Persistence;

@Entity
public class TestJPA {
@Id
private long clavePrimaria;

@Basic
private String mensaje;

public static void main(String[] args) {


EntityManagerFactory fabrica =
Persistence.createEntityManagerFactory("eni‐acces");

EntityManager em = fabrica.createEntityManager();
System.out.println(em.getProperties());
}
}

Ejecute después esta clase seleccionándola y usando el atajo de teclado [Ctrl][F11].

Debe obtener el siguiente resultado en la consola:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

[EL Info]: server: 2015‐05‐15 15:14:03.537—ServerSession(451111351)‐‐


Detected server platform:
org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: server: 2015‐05‐15 15:14:03.74—ServerSession(451111351)‐‐
Detected server platform:
org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: 2015‐05‐15 15:14:04.14‐‐ServerSession(451111351)‐‐EclipseLink,
version: Eclipse Persistence Services ‐ 2.6.0.v20150309‐bf26070
[EL Info]: connection: 2015‐05‐15 15:14:04.421‐‐
ServerSession(451111351)‐‐/file:/D:/workspace‐luna/git/luna/target/classes/_eni‐acces login successful
{eclipselink.ddl‐generation=drop‐and‐create‐tables,
eclipselink.logging.level=INFO,
javax.persistence.jdbc.url=jdbc:mysql://localhost:3306/luna,
javax.persistence.jdbc.user=eni,
javax.persistence.jdbc.driver=com.mysql.jdbc.Driver,
javax.persistence.jdbc.password=java}

Abra un navegador web en la dirección http://localhost/phpmyadmin. Puede ver en la base de datos «luna»:

Haciendo clic en el enlace Estructura de la tabla testjpa, puede ver que JPA ha creado correctamente las
columnas:

¡En resumen, JPA le ha permitido crear una tabla con sus columnas sin haber realizado directamente ninguna
consulta SQL!

Aunque si bien JPA permite evitar la codificación de la mayor parte de las consultas INSERT, UPDATE o DELETE,
será sin embargo necesario codificar las consultas de tipo SELECT.

Este comportamiento de JPA es interesante en el caso de la creación desde cero de una aplicación. En el caso del
acceso a una base de datos existente, JPA permite enlazar las propiedades de un objeto de dominio a columnas
precisas, pero no es el caso que interesa en este libro.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/7
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Creación de los formularios


Se han realizado las tareas previas: análisis del proyecto con la elaboración de los principales diagramas UML,
parametrización del servidor XAMPP. El desarrollo puede ahora empezar.

Como se anunció en el capítulo Análisis, se realizan las maquetas de la interfaz gráfica en primer lugar. Después se
efectuarán varias iteraciones sobre el proyecto para la entrega de la aplicación final.

Se contabilizan siete formularios principales:

Conexión: introducción del usuario y la contraseña.

Bienvenida: formulario de bienvenida que permite elegir entre los principales módulos.

Clientes: acceso a la gestión completa de clientes.

Artículos: acceso a la gestión completa de artículos.

Pedidos: acceso a la gestión completa de pedidos.

Estadísticas: visualización y producción de estadísticas sobre los datos de la aplicación.

Parámetros: acceso a los parámetros de la aplicación.

Cree el proyecto luna siguiendo la siguiente estructura:

La decoración «git maquetación» significa que se utiliza el sistema de gestión de versiones git y que la rama activa se
llama «maquetación». Se ha introducido brevemente la gestión de las versiones de los archivos de código fuente en el
capítulo La caja de herramientas de Eclipse. Se recomienda encarecidamente su uso si desea tener el control sobre sus
desarrollos y así poder gestionar el histórico de sus archivos de código fuente. Se trata también de una ayuda formidable
para trabajar en equipo.

Acceda a su espacio de trabajo con el explorador de archivos. Verá la siguiente arborescencia en su disco duro:

Descargue la carpeta imagenes desde la página Información y cópielo en la carpeta src/main/resources.

Ahora debería tener la siguiente arborescencia:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Vuelva a Eclipse y seleccione la carpeta del proyecto luna. Presione sobre la tecla [F5] para refrescar los
recursos.

La estructura del proyecto queda de la siguiente manera:

A continuación se presenta una vista global de los seis primeros formularios más importantes del proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Empiece creando las maquetas de estos formularios. Después se abordará la gestión de su visualización y cierre, sin
preocuparse en este punto de los tratamientos de los datos.

Se explicó en el capítulo Toma de contacto de Eclipse cómo crear formularios con WindowBuilder. Siendo el objetivo
del proyecto completamente didáctico, no se proporciona en este libro el código fuente completo (ya que el objetivo
es que se familiarice con la herramienta WindowBuilder) pero se incidirá sobre los puntos importantes respecto a la
construcción de la interfaz gráfica y el tratamiento de las acciones de los usuarios. También abordaremos los
bloques de código donde se realizan las llamadas a los métodos.

Intente crear las interfaces gráficas tomando como ejemplo las capturas de pantallas expuestas.

De vez en cuando, WindowBuilder no consigue volver a leer los archivos de código fuente. No es muy grave. Basta
con indicar a WindowBuilder que debe releer los archivos de código fuente con la ayuda del botón Reparse. Haga clic
varias veces en el botón si es necesario.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

1. Formulario de conexión

Todas las imágenes usadas para este formulario se encuentran en la subcarpeta conexion de la carpeta
imagenes.

Cree una nueva clase FConexion que herede de JFrame apoyándose en WindowBuilder. Para ello, utilice la
combinación de teclas [Ctrl] N, y seleccione la opción Window Builder ­ Swing Designer ­ JFrame en el
cuadro de diálogo que se abre. Sitúe esta clase en el package dialogo de la carpeta maquetas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La visualización es ligeramente distinta en WindowBuilder, ya que no tiene en cuenta todavía las pequeñas
mejoras que se aportarán a la interfaz gráfica.

El contentPane principal utiliza el layout por defecto, es decir un BorderLayout.

Al norte de este layout (por lo tanto arriba) se encuentra un JLabel que indica la función del formulario.

Al sur de este layout (por lo tanto abajo) se encuentra un JPanel que contiene los botones que sirven para realizar
las acciones estándar.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El panel de acciones tiene como layout un GridBagLayout. Se trata de uno de los layouts más potentes que
permite utilizar varios tipos de ubicaciones.

En particular, el botón de los parámetros tiene restricciones adicionales: toma el espacio disponible en el panel y
está anclado al inicio de la línea, es decir a la izquierda para los occidentales.

El botón Validar presenta algunas características: a diferencia de sus homólogos, la imagen está situada a la
derecha del texto y este texto está escrito en blanco.

WindowBuilder no proporciona ninguna acción gráfica para cambiar la posición de la imagen en relación al texto.
Debe por lo tanto codificarlo en la pestaña Source.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Haga clic en la pestaña Source del editor. Inserte la siguiente línea en el código de la declaración del botón:

btnValidar.setHorizontalTextPosition(SwingConstants.LEADING);

Los nombres de sus variables pueden ser diferentes de los del libro.

En el centro del contentPane encontramos los campos a introducir para la conexión. Está compuesto de un
primer JPanel. Este panel no tiene ningún interés: proporciona un borde vacío a su contenido, que consiste en un
único panel.

Este borde se compone de 14 píxeles vacíos a su alrededor.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Dentro de este panel global se encuentra un panel embebido en su centro.

Este panel tiene un borde compuesto por dos bordes: un borde en forma de línea en su exterior y un borde vacío
de 5 píxeles en su interior para airear un poco su contenido.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En este último panel se encuentran los componentes gráficos que sirven para la conexión: el nombre de usuario
que se introduce mediante un JTextField y la contraseña mediante un JPasswordField.

Para airear un poco más este formulario, cada componente tiene en sus parámetros de ubicación un objeto de
tipo Insets que permite configurar unos márgenes de 5 píxeles a su alrededor.

Al final, un componente JTextPane propone un pequeño mensaje en caso de que el usuario necesite ayuda
para rellenar los campos. Este componente no es editable y contiene un texto en varias líneas que podemos

rellenar haciendo clic en el pequeño botón junto a la propiedad text.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Es hora de aplicar un aspecto más agradable de este formulario.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Existen varias maneras de modificar el aspecto de un componente gráfico. Todas implican modificar el código
generado por WindowBuilder. Las más destacadas son la gestión manual (que será tratada aquí) y la creación de un
Look and Feel específico.

Cree una clase UI en el package dialogo.

package dialogo;

import java.awt.Color;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.SwingConstants;

public class UI {

private static final String LOGO = "/imagenes/Moon‐32.png";

public static Image getLogo() {


return Toolkit.getDefaultToolkit().getImage(
UI.class.getResource(LOGO));
}

private static ImageIcon getIcono(String ruta) {


return new ImageIcon(UI.class.getResource(ruta));
}

private static void quitarAspectoBoton(JButton boton) {


boton.setOpaque(false);
boton.setContentAreaFilled(false);
boton.setBorderPainted(false);
}

public static void quitarAspectoBoton(JButton boton,


String icono) {
quitarAspectoBoton(boton, "gestión", icono);
boton.setForeground(Color.WHITE);
}

public static void quitarAspectoBoton(JButton boton,


String carpeta,
String icono) {
quitarAspectoBoton(boton, carpeta, icono, 48);
}

public static void quitarAspectoBoton(JButton boton,


String carpeta,
String icono,
int size) {
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

quitarAspectoBoton(boton);
String nombre = "/imagenes/" + carpeta + "/" + icono
+ "‐" + size;
boton.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
boton.setIcon(getIcono(nombre
+ "‐activo.png"));
}

@Override
public void mouseExited(MouseEvent e) {
boton.setIcon(getIcono(nombre + ".png"));
}
});
boton.setHorizontalAlignment(SwingConstants.LEFT);
boton.setIcon(getIcono(nombre +".png"));
boton.setFont(boton.getFont().deriveFont(14f));
}

public static void darAspecto(JComponent componente) {


componente.setFont(componente.getFont().deriveFont(17f));
}
}

Esta clase contiene algunos métodos estáticos de los cuales los más interesantes para la creación de la interfaz
son:

UI.getLogo() devuelve la imagen del logo de la aplicación.

UI.darAspecto() permite cambiar la tipografía de todos los componentes gráficos fácilmente (la fuente por
defecto de Java no resulta muy agradable).

UI.quitarAspecto() y sus variantes permiten cambiar el look por defecto de los botones para aplicarles algunas
modificaciones: el texto será de color blanco, no tendrá aspecto de botón clásico y las imágenes cambiarán
automáticamente cuando el ratón pase por encima del botón.

Esta clase funciona bien porque los nombres de las imágenes de los botones están normalizados.

Después use esta clase en el código de la ventana de conexión:

setIconImage(UI.getLogo());
setTitle("Luna SA");
...
UI.darAspecto(lblContrasena);
...
UI.darAspecto(pwdContrasena);
...
UI.darAspecto(txtpnInfo);

UI.quitarAspectoBoton(btnParametros, "conexión", "Customize‐01");


...
UI.quitarAspectoBoton(btnSalir, "conexión", "Stop");
...
UI.quitarAspectoBoton(btnValidar, "conexión", "Power");

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

2. Formulario de bienvenida de la aplicación

Cree una nueva clase FBienvenida que herede de JFrame con WindowBuilder.

Este formulario contiene un JPanel como contenido (un contentPane). Este panel tiene un BorderLayout como
layout.

En este panel, se crea otro JPanel a la izquierda con la restricción WEST.

Este panel enumera las acciones posibles, por el momento Salir.

Contiene también un JLabel que muestra el nombre de la empresa, así como un pequeño JLabel que indica la
acción que se efectuará cuando el puntero pasa por encima de los diferentes botones del formulario.

También dispone de una barra de menú para tener acceso estandarizado a los diferentes módulos.

En el centro del panel principal (restricción CENTER), un panel contiene todos los botones que permiten acceder a
los diferentes módulos de la aplicación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Esta disposición se consigue con un GridBagLayout para los diferentes JPanel.

Intente crear esta disposición con ayuda de un GroupLayout para el panel izquierdo. WindowBuilder
permite cambiar muy fácilmente la disposición de manera gráfica.

Los puntos importantes en el GroupLayout son la disposición relativa de los elementos y el espacio existente entre
ellos.

Use un layout MigLayout para la disposición del panel central.

MigLayout no es una clase disponible de manera estándar en Java. Se basa en una descripción textual de líneas y
columnas. Su sintaxis es relativamente compleja pero WindowBuilder enmascara al máximo esta complejidad
permitiendo realizar pantallas espectaculares.

Ejecute el código de este formulario e intente redimensionarlo. El contenido debe adaptarse en casi todos los
tamaños excepto los más extremos.

Añada una barra de menú con ítems que permitan acceder rápidamente a los módulos.

Aquí tiene el código de la clase de gestión de los botones.

package dialogo;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

import net.miginfocom.swing.MigLayout;
import dialogo.articulo.FArticulos;
import dialogo.cliente.PClientes;
import dialogo.pedido.FPedidos;
import dialogo.estadistica.FTablaBorde;

public class FBienvenida extends JFrame {

private static final long serialVersionUID = 1L;

private JPanel contentPane;

private final Action accionSalir = new AccionSalir();


private final Action accionClientes = new AccionClientes();
private final Action accionArticulos = new AccionArticulos();

/**
* Create the frame.
*/
public FBienvenida() {

setIconImage(UI.getLogo());
setTitle("Bienvenida");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 926, 686);
setLocationRelativeTo(null);

JMenuBar menuBar = new JMenuBar();


setJMenuBar(menuBar);
UI.darAspecto(menuBar);

JMenu mnFichero = new JMenu("Fichero");


menuBar.add(mnFichero);
UI.darAspecto(mnFichero);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JMenuItem mntmParametros = new JMenuItem("Parámetros");


mnFichero.add(mntmParametros);
UI.darAspecto(mntmParametros);

JSeparator separator = new JSeparator();


mnFichero.add(separator);

JMenuItem mntmSalir = new JMenuItem("Salir");


mnFichero.add(mntmSalir);
UI.darAspecto(mntmSalir);

JMenu mnVistas = new Jmenu("Vistas");


mnVistas.setMnemonic(KeyEvent.VK_V);
menuBar.add(mnVistas);
UI.darAspecto(mnVistas);

JMenuItem mntmClientes = new JMenuItem("Clientes");


mnVistas.add(mntmClientes);
UI.darAspecto(mntmClientes);

JMenuItem mntmArticulos = new JMenuItem("Artículos");


mnVistas.add(mntmArticulos);
UI.darAspecto(mntmArticulos);

JMenuItem mntmPedidos = new JMenuItem("Pedidos");


mnVistas.add(mntmPedidos);

contentPane = new JPanel();


contentPane.setBorder(null);
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);

JPanel panel_menu = new JPanel();


panel_menu.setBorder(new EmptyBorder(5, 5, 5, 5));
panel_menu.setBackground(SystemColor.controlShadow);
panel_menu.setBounds(0, 0, 200, 300);
contentPane.add(panel_menu, BorderLayout.WEST);

JLabel lblTitulo = new JLabel("Luna SA");


lblTitulo.setHorizontalAlignment(SwingConstants.CENTER);
lblTitulo.setFont(lblTitulo.getFont().deriveFont(33f));

JLabel lblInfos = new JLabel("Mostrar opción");


lblInfos.setForeground(SystemColor.control);
lblInfos.setBackground(SystemColor.controlShadow);
lblInfos.setOpaque(true);
lblInfos.setHorizontalAlignment(SwingConstants.CENTER);
lblInfos.setFont(lblInfos.getFont().deriveFont(21f));

JButton btnSalir = new JButton("Salir");


btnSalir.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/conexion/Stop‐48.png")));
UI.quitarAspectoBoton(btnSalir, "conexión", "Stop");

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 16/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

btnSalir.setForeground(SystemColor.control);

GroupLayout glmenu = new GroupLayout(panel_menu);


glmenu.setHorizontalGroup(
glmenu.createParallelGroup(Alignment.LEADING)
.addGroup(glmenu.createSequentialGroup()
.addContainerGap()
.addComponent(lblInfos,
GroupLayout.DEFAULT_SIZE,
160, Short.MAX_VALUE)
.addContainerGap())
.addComponent(btnSalir,
GroupLayout.DEFAULT_SIZE,
190, Short.MAX_VALUE)
.addComponent(lblTitulo,
GroupLayout.DEFAULT_SIZE,
190, Short.MAX_VALUE)
);
glmenu.setVerticalGroup(
glmenu.createParallelGroup(Alignment.LEADING)
.addGroup(glmenu.createSequentialGroup()
.addGap(7)
.addComponent(lblTitulo)
.addGap(30)
.addComponent(lblInfos)
.addGap(348)
.addComponent(btnSalir)
.addContainerGap(81,
Short.MAX_VALUE))
);
panel_menu.setLayout(glmenu);

JPanel panel_principal = new JPanel();


panel_principal.setBorder(new EmptyBorder(5, 0, 5, 5));
contentPane.add(panel_principal, BorderLayout.CENTER);
panel_principal.setLayout(new MigLayout("",
"[33%][33%][33%]", "[33%][33%][33%]"));

JButton btnArticulos = new JButton();


btnArticulos.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/bienvenida/Product‐128.png")));
UI.quitarAspectoBoton(btnArticulos, "bienvenida",
"Producto", 128);

btnArticulos.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Articulos");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 17/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

});
btnArticulos.setCursor(new Cursor(Cursor.HAND_CURSOR));

panel_principal.add(btnArticulos,
"cell 1 0,alignx center");

JButton btnClientes = new JButton();


btnClientes.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/bienvenida/People‐128‐activo.png")));
UI.quitarAspectoBoton(btnClientes, "bienvenida",
"People", 128);

btnClientes.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Clientes");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}
});
btnClientes.setCursor(new Cursor(Cursor.HAND_CURSOR));

panel_principal.add(btnClientes,
"cell 0 1,alignx center");

JButton btnStats = new JButton("");


UI.quitarAspectoBoton(btnStats, "bienvenida",
"Diagram", 128);

btnStats.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Estadísticas");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}
});
btnStats.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/bienvenida/Diagram‐128.png")));
btnStats.setCursor(new Cursor(Cursor.HAND_CURSOR));

panel_principal.add(btnStats,
"cell 1 1,alignx center");

JButton btnPedidos = new JButton("");


UI.quitarAspectoBoton(btnPedidos, "bienvenida",
"Shopping‐Bag", 128);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 18/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

btnPedidos.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Pedidos");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}
});
btnPedidos.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/bienvenida/Shopping‐Bag‐128.png")));
btnPedidos.setCursor(new Cursor(Cursor.HAND_CURSOR));

panel_principal.add(btnPedidos,
"cell 2 1,alignx center");

JButton btnParametros = new JButton ();


btnParametros.setIcon(new ImageIcon(
FBienvenida.class.getResource(
"/imagenes/bienvenida/Settings‐02‐128.png")));
UI.quitarAspectoBoton(btnParametros, "bienvenida",
"Settings‐02", 128);
btnPedidos.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Parámetros");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}
});
btnParametros.setCursor(new Cursor(Cursor.HAND_CURSOR));

panel_principal.add(btnParametros,
"cell 1 2,alignx center");

SwingUtilities.invokeLater(
() ‐> btnSalir.requestFocusInWindow()
);
}

protected void mostrarParametros() {


// realizarlo como ejercicio
}

protected void mostrarPedidos() {


// realizado más adelante
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 19/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

protected void mostrarStats() {


// realizado más adelante
}

protected void mostrarArticulos() {


// realizado más adelante
}

protected void mostrarClientes() {


// realizado más adelante
}
}

Si lee atentamente el código, observará la siguiente línea:

SwingUtilities.invokeLater(
() ‐> btnSalir.requestFocusInWindow()
);

La clase SwingUtilities permite ejecutar el código en el thread gráfico.

Se trata de pedir a Java que ponga por defecto el foco en el botón Salir utilizando las expresiones lambdas de
Java 8.

3. Formulario de gestión de clientes

Este formulario posee acciones que abren otros componentes gráficos: los botones Añadir, Modificar y Buscar
mostrarán cada uno una interfaz gráfica distinta.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 20/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se podría mostrar entonces un nuevo formulario para cada una de estas acciones, pero el aspecto no sería muy
agradable: la apertura de un formulario puede ser perturbadora para un usuario y es preferible reservar este
comportamiento para los principales módulos únicamente.

Para conservar un único formulario para los clientes, se crean distintos JPanel con WindowBuilder. Estos paneles
se utilizan como panel de contenido en el formulario de clientes.

a. Panel principal

Cree un nuevo JPanel llamado PClientes con la ayuda de WindowBuilder.

Cree el contenido de este panel con la ayuda de WindowBuilder inspirándose en la siguiente captura de
pantalla:

El gran espacio gris de este panel se hace con un JTable. Idealmente este mismo JTable debe estar ubicado en
un JScrollPane para gestionar el caso en el que el espacio no sea suficiente para mostrar todos los clientes al
mismo tiempo.

Añada un JScrollPane en la ubicación deseada.

Añada un JTable dentro de un JScrollPane en la ubicación del Viewport.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 21/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Los JTextField usados para la visualización de un cliente seleccionado en la tabla de abajo no están activados: su
propiedad enabled debe valer false. Lo mismo es válido para la casilla a marcar arriba a la derecha.

b. Paneles para añadir y modificar

Cada una de las grandes categorías de información correspondiente al cliente se sitúa en un JPanel con bordes y
un título (Titled Border).

El campo que muestra la fecha de creación del cliente no es un JTextField sino un JFormattedTextField.

Para este último componente gráfico, es necesario crear una clase anexa que se encargue de dar formato y
convertir un objeto de tipo Instant en una cadena de caracteres y a la inversa. Este punto se abordará más
adelante en este libro.

c. Panel para buscar

La búsqueda de clientes existentes se desplegará en esta ventana.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 22/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

4. Formulario de gestión de artículos


Los diferentes artículos del sistema se gestionan en este formulario.

Esta ventana integra un componente JToolBar que propone acciones disponibles relativas al artículo.

Para gestionar los clics en los botones radio y para crear la lógica de deselección de uno de estos botones cuando
el otro está seleccionado, hay que crear un ButtonGroup que los contenga con ayuda del siguiente código:

Es posible modificar el valor mostrado para la cantidad cuando se utiliza el slider con la ayuda del siguiente código:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 23/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

ButtonGroup buttonGroup = new ButtonGroup();


buttonGroup.add(rdbtnCodigo);
buttonGroup.add(rdbtnCategoria);

rdbtnCodigo.addActionListener((ActionEvent e) ‐> {
System.out.println("Código seleccionado ? "
+ rdbtnCodigo.isSelected());
});
rdbtnCategoria.addActionListener((ActionEvent e) ‐> {
System.out.println("Categoría seleccionada ? "
+ rdbtnCategoria.isSelected());
});

sliderCantidad.addChangeListener((ChangeEvent e) ‐> {
String valor =
Integer.toString(sliderCantidad.getValue());
txtCantidad.setText(valor);
});

Los códigos anteriores implican transformar las variables gráficas locales en atributos de la clase FArticulos.

El código de la clase FArticulos está disponible para su descarga en el sitio web de Ediciones ENI.

5. Formulario de gestión de pedidos

a. Introducción de nuevos pedidos

Este formulario permite realizar la introducción de los pedidos. La validación provoca automáticamente la
impresión del pedido en curso.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 24/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El código de la clase FPedidos está disponible en las descargas de este libro.

b. Visualización de los pedidos existentes

Esta ventana permite generar un archivo con los pedidos realizados.

El código de la clase FPedidosExistentes está disponible en las descargas de este libro.

Para estos tres últimos formularios, el código es similar al del formulario de gestión de clientes.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 25/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El código completo de todas las maquetas del proyecto está disponible para su descarga en el sitio web de
Ediciones ENI.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 26/26
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión avanzada de los eventos


Se han creado las maquetas de los principales formularios de la aplicación. En esta sección, a estas maquetas se les
integra la parte operacional teniendo en cuenta las acciones que el usuario puede realizar desde el teclado o el
ratón.

Antes hay que profundizar en el concepto de evento visto en el capítulo La caja de herramientas de Java.

En Java, excepto los tipos primitivos, todo es un objeto. Los eventos también lo son. Más precisamente, son
instancias de clases cuyos nombres acaban por Event, por ejemplo ActionEvent, FocusEvent, HyperlinkEvent,
MenuEvent, etc. Java se encarga de numerosos eventos, repartidos por razones históricas entre el package
java.awt.event y javax.swing.event.

¿Pero quién crea el objeto evento? Dicho de otra manera, ¿cuál es la fuente que crea este objeto evento? Una
primera respuesta sería el usuario, que lo crea mediante un clic con el ratón o pulsando en una tecla del teclado. En
parte es verdad en el sentido en que el evento necesita de esta acción del usuario para ser creado.

Desde un punto de vista de programación, es en efecto el propio componente gráfico quien crea el objeto evento a
partir de una solicitud externa. Una vez el evento creado, se envía a uno o varios objetos especializados que están a
la escucha de este evento en particular. Por esta razón, se llama a estos objetos escuchadores o listeners en la
terminología Java.

Cada componente puede generar diferentes tipos de eventos, pero no todos los eventos se generan por todos los
componentes. Por ejemplo, las ventanas como Window, JFrame o JDialog podrán generar objetos
WindowEvent (para la apertura, el cierre,...). Un JButton no tiene ninguna utilidad en este tipo de evento, por el
contrario podrá generar eventos de acción, de tipo ActionEvent, cuando se presione. Estos ActionEvent serán
también generados por las instancias de clase JList o JMenuItem.

Para cada tipo de evento, existe una interfaz de escuchador (que incluye al menos un método y con frecuencia
varios). Java estandariza los nombres de estos escuchadores y son por lo tanto fácilmente identificables. Por
ejemplo, el nombre de la interfaz del escuchador de un evento ActionEvent se llama ActionListener; el listener
de un WindowEvent se llama WindowListener.

Concretamente, un escuchador es por lo tanto una instancia de una clase que implemente la interfaz deseada.
Estos escuchadores y sus clases debe crearlos usted mismo.

Queda añadir los escuchadores a los componentes gráficos. Por esta razón, estos poseen métodos llamados
addXXXListener y removeXXXListener. Así, un JButton tiene métodos addActionListener(ActionListener) y
removeActionListener(ActionListener) para permitir añadir y suprimir un escuchador en los eventos de tipo
ActionEvent. Ya los ha utilizado de manera implícita al crear interfaces gráficas con WindowBuilder.

Pasemos ahora al tratamiento de estos eventos. A más bajo nivel, debe crear por lo tanto una clase que
implemente la interfaz deseada, pero también necesita redefinir todos sus métodos (no todos serán
necesariamente útiles para la aplicación).

Aquí tiene un ejemplo de una clase que implementa la interfaz WindowListener, interfaz que posee siete
métodos.

class EscuchadorFormulario implements WindowListener {


@Override
public void windowOpened(WindowEvent evt) {
System.out.println("Formulario abierto");
}

@Override
public void windowIconified(WindowEvent evt) {
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

@Override
public void windowDeiconified(WindowEvent evt) {
}

@Override
public void windowDeactivated(WindowEvent evt) {
}

@Override
public void windowClosing(WindowEvent evt) {
}

@Override
public void windowClosed(WindowEvent evt) {
}

@Override
public void windowActivated(WindowEvent evt) {
}
}

Para no implementar los métodos que no tienen ningún interés para la aplicación, es posible recurrir a los
adaptadores. Son clases particulares llamadas XXXAdapter, que ya redefinen con una definición vacía todos los
métodos de las interfaces XXXListener. Así, a las interfaces ActionListener y WindowListener les corresponden
las clases adaptadas ActionAdapter y WindowAdapter.

Retomando el ejemplo anterior, de este modo hay un único método a redefinir.

class EscuchadorFormulario extends WindowAdapter {


@Override
public void windowOpened(WindowEvent evt) {
System.out.println("Formulario abierto");
}
}

En vez de crear una clase en un archivo aparte, también es posible crear lo que se llama una clase interna anónima
que extiende la interfaz deseada y proceder a su almacenamiento cerca del componente gráfico.

boton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Botón accionado");
}
});

La última posibilidad es utilizar expresiones lambda para aligerar la sintaxis de creación de estas clases internas
anónimas.

boton.addActionListener(evt ‐> {
System.out.println("Boton accionado");
});

Para acabar con esta introducción a los eventos, es importante recordar que:

Los componentes gráficos son las fuentes de los eventos.

Los eventos se transfieren a los escuchadores.


http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Los tratamientos se realizan en los escuchadores.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Activación de los formularios


Acabada la parte de construcción de la interfaz gráfica, es tiempo de empezar la parte interactiva, donde el código
responderá a las acciones del usuario, sobre todo los clics en los botones. Ahora se pasa a esta puesta en marcha
con Eclipse y WindowBuilder empezando por el formulario de conexión.

1. Conexión
Abra la clase FConexion.

Seleccione el botón Parámetros, haga un clic derecho y elija la opción Add event handler ­ mouse ­
mouseClicked.

En el código creado automáticamente por WindowBuilder, añada la llamada a un método como sigue:

btnParametros.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
parametros();
}
});

El compilador señala un error; es normal, el método parametros() no existe todavía.

Existen dos maneras rápidas de crear este método:

Sitúese encima de la línea con el error: un mensaje con fondo amarrillo aparece entonces proponiéndole
diferentes soluciones. Elija la última opción Create method ’parametros()’ in type ’FConexion’.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Ponga el cursor sobre la línea con el error y presione las teclas [Ctrl] 1. Se trata de un Quick­Fix de Eclipse
que propone las mismas correcciones. Elija la opción Create method ’parametros()’ in type
’FConexion’.

Repita el procedimiento con el botón Salir, esta vez creando un método llamado salir().

El botón Validar tiene un procedimiento distinto para gestionar el clic.

Selecciónelo, haga clic con el botón derecho y elija la opción Set Action ­ New.

El texto del botón cambia. También es normal. Todo volverá a su sitio en poco tiempo.

WindowBuilder ha modificado el código:

Ha creado una clase interna en la clase FConexion que se llama SwingAction.

Ha creado un atributo llamado action del tipo de esta clase.

Ha añadido la siguiente línea en el tratamiento de la acción.

btnValidar.setAction(action);

Todo esto permite crear un sistema que reaccione al clic del botón.

Renombre la clase SwingAction por ActionValidar con la ayuda de la combinación de teclas [Ctrl] 1
después de seleccionarla en el código fuente. Elija la opción Rename in File.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Modifique entonces esta clase con el siguiente código:

private class ActionValidar extends AbstractAction {

private static final long serialVersionUID = 1L;

public ActionValidar() {
putValue(NAME, "Validar");
putValue(SHORT_DESCRIPTION,
"Conectarse a la aplicación");
}
public void actionPerformed(ActionEvent e) {
validar();
}
}

Por último, cree el método validar() en la clase FConexion.

Traduzca los textos de la interfaz con la ayuda de Eclipse. Seleccione la clase en el explorador de packages,
haga un clic derecho y elija la opción Source ­ Externalize Strings. Haga después lo mismo que se
describe en el capítulo La caja de herramientas de Eclipse para poner las traducciones en un archivo
messages.properties.

Es preferible gestionar las acciones de botones con clases derivadas de AbstractAction que añadir un
ActionListener sobre estos mismos botones.

Elimine el método main() de la clase para evitar la proliferación de estos métodos.

Cree una clase Main en el package inicio, con un método main(). Será el punto de entrada principal de la
aplicación.

Modifique el código como sigue:

package inicio;

import java.awt.EventQueue;

import dialogo.FConexion;

/**
* Punto de partida de la aplicación.
*/
public class Main implements Runnable {

public static void main(String[] args) {

System.out.println("Ejecución del programa Luna");

EventQueue.invokeLater(new Main());
}

@Override
public void run() {
FConexion frame = new FConnexion();
frame.setLocationRelativeTo(null);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

frame.setVisible(true);

System.out.println("Formulario de Conexión visible");


}
}

Queda escribir el código de los métodos salir() y validar() en la clase FConexion.

Escriba el siguiente código:

private void salir() {


dispose();
}

El método dispose() de un JFrame libera los recursos gráficos. Como se trata de un único formulario mostrado
en este momento, la aplicación se termina.

Codificaremos un doble control a la vez de introducción y de conexión en el método validar(). Se advertirá así al
usuario de un eventual problema de acceso a la base de datos. Si la introducción es incorrecta, aparece un nuevo
mensaje desde la clase FConexion.

Complete el método validar() con ayuda del siguiente código.

private void validar() {


boolean valido = true; // se reemplazará este código más adelante
if (valido) {
FConexion.this.dispose();
FBienvenida elFormularioMenu = new FBienvenida();
elFormularioMenu.setVisible(true);
} else {
JOptionPane.showMessageDialog(
btnValidar,
"Mensaje de aviso a rellenar"),
"Título del aviso a modificar",
JOptionPane.ERROR_MESSAGE);
}
}

Este código realiza las siguientes operaciones: si la combinación usuario/contraseña es válida, cierra la ventana de
conexión (FConnexion.this.dispose() es equivalente a dispose()), crea un formulario de bienvenida y lo
muestra con setVisible(true).

La validación del usuario se abordará en el capítulo Conexión.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En caso contrario, se muestra un cuadro de diálogo de error al usuario y la ventana de conexión queda en
pantalla para realizar un nuevo intento.

NombreDeLaClase.this es una notación Java para encontrar la instancia de la clase principal en el caso de una
clase interna.

2. Clientes
Abra la clase PClientes.

Ponga en marcha las acciones para todos los botones del panel con la ayuda de la opción Set Action ­ New
del menú contextual.

Añada los métodos siguientes:

public void setFormulario(JDialog formulario) {


this.formulario = formulario;
}

private JDialog getFormulario() {


return formulario;
}

private void cambiarPanel(JPanel panel, String titulo) {


formulario.setContentPane(panel);
formulario.setTitle(titulo);
formulario.revalidate();
}

Estos métodos permiten respectivamente fijar y encontrar el formulario activo en el panel y modificar el panel del
contenido principal.

Añada una acción que servirá a los paneles de añadir y buscar para que puedan volver a mostrar el panel
principal.

private final Action accionCancelar = new ActionCancelar();

private class ActionCancelar extends AbstractAction {


private static final long serialVersionUID = 1L;
public ActionCancelar() {
putValue(NAME, "Cancelar");
putValue(SHORT_DESCRIPTION,
"Cancelar la acción en curso");
}

public void actionPerformed(ActionEvent e) {


cambiarPanel(PClientes.this, "Gestión de Clientes");
}
}

Complete el método actionPerformed del escuchador del botón Añadir con el siguiente código:

Este código permite cambiar el panel del contenido por el panel para añadir un cliente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

PCliente añadir = new PCliente();


añadir.setActionCancelar(actionCancelar);

btnAñadir.setIcon(new ImageIcon(
PClientes.class.getResource(
"/imagenes/gestion/Add‐New‐48.png")));
cambiarPanel(añadir ,"Añadir un nuevo cliente");

Cree el método setActionCancelar en las clases PCliente y PClienteBusqueda y asigne el parámetro al


botón Cancelar.

void setActionCancelar(Action action) {


btnBienvenida.setAction(action);
UI.quitarAspectoBoton(btnBienvenida, "Cancelar");
}

Complete el método actionPerformed del escuchador del botón Buscar con el siguiente método:

public void actionPerformed(ActionEvent e) {

PClienteBusqueda busqueda = new PClienteBusqueda();


busqueda.setActionCancelar(actionCancelar);

btnBuscar.setIcon(new ImageIcon(
PClientes.class.getResource(
"/imagenes/gestion/Search‐48.png")));
cambiarPanel(busqueda, "Búsqueda de cliente(s)");
}

3. Bienvenida
Abra la clase FBienvenida.

Añada los escuchadores de eventos ActionListener en los diferentes botones para responder a los clics del
usuario, como en el formulario de conexión.

Para cada uno de los botones, añada la gestión de eventos (event handler) para mostrar el nombre de la
acción cuando pasa por encima con el ratón.

Por ejemplo, el código para el botón de clientes quedaría de la siguiente forma:

JButton btnClientes = new JButton();


btnClientes.setIcon(new ImageIcon(

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

FBienvenida.class.getResource(
"/imagenes/bienvenida/People‐128‐activo.png"
)));
UI.quitarAspectoBoton(btnClientes, "bienvenida", "People", 128);
btnClientes.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
mostrarClientes();
}
});
btnClientes.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
lblInfos.setText("Clientes");
}

@Override
public void mouseExited(MouseEvent e) {
lblInfos.setText(" ");
}
});
btnClientes.setCursor(new Cursor(Cursor.HAND_CURSOR));
panel_principal.add(btnClientes, "cell 0 1,alignx center");

lblInfos es el nombre en el panel de la izquierda que muestra la acción que se efectuará. Este nombre puede ser
diferente en su código.

La línea siguiente:

btnClientes.setCursor(new Cursor(Cursor.HAND_CURSOR));

permite mostrar un cursor con forma de mano cuando el ratón pasa por encima del botón.

Gestione las acciones desde los ítems de la barra de menús.

Esto implica crear acciones que gestionen los clics en estos ítems.

Cree en estas acciones aceleradores y nemotécnicos para facilitar su uso.

A continuación, puede ver el código de la acción que gestiona la visualización del módulo de artículos.

private class ActionArticulos extends AbstractAction {


private static final long serialVersionUID = 1L;

public ActionArticulos() {
putValue(NAME, "Articulos");
putValue(SHORT_DESCRIPTION,
"Mostrar la lista de artículos");
putValue(ACCELERATOR_KEY,
KeyStroke.getKeyStroke(
KeyEvent.VK_A,
ActionEvent.ALT_MASK));
putValue(MNEMONIC_KEY, KeyEvent.VK_A);
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void actionPerformed(ActionEvent e) {


mostrarAticulos();
}
}
}

Cree los métodos mostrarClientes(), mostrarArticulos(), mostrarPedidos() y


mostrarParametros() llamados en las acciones o escuchadores correspondientes.

Estos métodos están de momento vacíos.

Complete el método mostrarArticulos() con el siguiente código:

protected void mostrarArticulos() {


FArticulos elFormulario = new FArticulos(this);
elFormulario.setVisible(true);
}

Complete el método mostrarPedidos() con el siguiente código:

protected void mostrarPedidos () {


FPedidos elFormulario = new FPedidos(this);
elFormulario.setVisible(true);
}

Cree un método setFormulario() en el archivo PClientes que tenga como parámetro un diálogo:

public void setFormulario(JDialog formulario) {


this.formulario = formulario;
}

Este método implica crear también una propiedad formulario en la clase.

Complete el método mostrarClientes() con el siguiente código:

protected void mostrarClientes () {


JDialog dialogo = new JDialog(this);
PClientes clientes = new PClientes();
clientes.setFormulario(dialogo);
dialogo.setContentPane(clientes);
dialogo.setIconImage(UI.getLogo());
dialogo.setTitle("Gestión de Clientes");
dialogo.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
dialogo.setBounds(100, 100, 1000, 700);
dialogo.setLocationRelativeTo(null);
dialogo.setVisible(true);
}

Este código crea un nuevo cuadro de diálogo cuyo padre es el formulario actual, sitúa el panel de clientes en él,
almacena el diálogo en el panel de clientes, inicializa algunas propiedades como el título y el icono y muestra este
cuadro de diálogo.

Seleccione el botón Salir y añada una nueva acción haciendo clic con el botón derecho y la seleccionando la
opción Set Action ­ New.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Renombre la clase de la acción por ActionSalir.

Complete la acción con el siguiente código:

private class ActionSalir extends AbstractAction {


private static final long serialVersionUID = 1L;

public ActionSalir() {
putValue(NAME, "Salir");
putValue(SHORT_DESCRIPTION, "Salir de la aplicación");
}

public void actionPerformed(ActionEvent e) {


FBienvenida.this.dispose();
}
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/9
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
Se han creado las principales maquetas entre las cuales encontramos el formulario de conexión y se ha tratado la
gestión de eventos en el capítulo Maquetas. En este capítulo se trata de poner en marcha todos los controles
necesarios antes de autorizar el acceso a la aplicación. Simultáneamente, hay que gestionar el acceso al servidor y
a la base de datos teniendo en cuenta los posibles problemas de conexión.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Parámetros de conexión
JPA provee un mecanismo general de configuración de la conexión a la base de datos. Este mecanismo se apoya en
la presencia de un archivo particular en un sitio determinado. Se ha realizado la primera etapa, vista en el capítulo
Base de datos MySQL, creando este archivo y rellenándolo con los datos de conexión a la base de datos. Se
completará a medida que se avance en el proyecto.

Repita la creación del archivo «persistence.xml» en la carpeta src/main/resources/META­INF del proyecto


Luna.

Añada el driver JDBC MySQL en las propiedades del proyecto.

Añada los archivos jar de la librería EclipseLink.

Pruebe el acceso a la base de datos con la clase TestJPA que se codificó en el capítulo Base de datos MySQL,
importándola en el proyecto Luna.

A continuación es momento de interesarse en el modo de acceso a los datos de la aplicación.

Para hacerlo sencillo y rápido, el nombre del usuario y la contraseña son únicos para todos los usuarios de la
aplicación. Los parámetros correspondientes al driver, el servidor y la base de datos vienen de la instalación
realizada para el SGBD MySQL (consulte el capítulo Base de datos MySQL), así como los datos de identificación.

En una aplicación más realista, sería posible crear tablas de usuarios y configurar para cada uno una combinación
usuario/contraseña única y un identificador de acceso único a la base de datos. Otra posibilidad sería gestionar los
usuarios mediante la propia base de datos, encargándose la aplicación de inyectar en la configuración los datos de
conexión provistos por el usuario. También es posible conectarse a un directorio LDAP o usar cualquier otro método.

Para una explotación real, nunca escriba en claro la contraseña.

Se crea ahora una clase que permite centralizar el acceso a base de datos. Esta clase contiene también los métodos
útiles para el resto de la aplicación.

Complete la estructura del proyecto añadiendo los packages llamados ”entidad” y ”control” a la carpeta
src/main/java:

Para conservar una organización clara y estructurada de nuestras clases, añada un subpackage llamado
«connection» al package control (no olvide el punto de separación).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Después cree una clase Conexion con la ayuda del menú File ­ New ­ Class, o seleccionando el package
connection y haciendo clic con el botón derecho y eligiendo New ­ Class. Informe el nombre de la clase,
verifique que el nombre del package es correcto y haga clic en Finish.

Añada los imports para la capa de persistencia:

package control.connection;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class Conexion {


}

Las clases del package javax.persistence permiten acceder a la base de datos y a sus datos.

El código de la clase TestJPA permite formarse una idea previa de la manera de codificar con JPA:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

crear una fábrica de administradores de entidades para la base de datos,

crear un administrador de entidades a partir de esta fábrica,

utilizar el administrador de entidades,

cerrar el administrador de entidades,

cerrar la fábrica.

Las primeras y últimas etapas que están en relación con la fábrica deberían realizarse respectivamente al inicio y al
final del ciclo de vida de la aplicación.

La clase Conexion conservará por lo tanto la referencia de una fábrica para cerrarla y liberar los recursos
asociados.

Añada la siguiente propiedad:

private final EntityManagerFactory fabrica;

Para respetar el principio de encapsulación, este atributo se declara private. Se aisla su acceso, por lo tanto no
tiene ni accesor, ni mutador.

Para proteger la modificación de esta propiedad que es vital para la aplicación, se marca como final, lo que impide
toda modificación una vez que se inicializa el atributo.

¡Es posible modificar las propiedades en el interior de un atributo final!

Existen otras maneras de escribir esta clase:

Declararla final: no se podrá heredar de ella.

Declararla abstract: no podrá instanciarse.

Declarar sus atributos static: las instancias de la clase no poseerán estos atributos. El acceso se realiza entonces
según la sintaxis clase.accesor.

La manera de escribirlo es una cuestión de elección: personal o de acuerdo con el equipo de proyecto.

Los atributos y métodos estáticos son notoriamente más difíciles de probar.

Después hay que inicializar esta propiedad fabrica. Una propiedad final solo se puede inicializar de dos maneras:
en su declaración o en la construcción del objeto. Optamos por inicializarla en el constructor.

Cree el constructor de la clase Conexion.

Conexion(){
fabrica = Persistence.createEntityManagerFactory("eni‐acces");
}

Se utiliza el método estático createEntityManagerFactory de la clase Persistence en este constructor. El


parámetro de llamada de este método es el nombre que se indicó en el tag XML persistence­unit del archivo
persistence.xml.

El interés de utilizar este método es poder proveer varias unidades de persistencia en función de las necesidades.
Entonces es posible declarar una unidad de persistencia específica al desarrollo, donde se eliminan y recrean las
tablas y donde se utiliza una base de datos específica. Se utiliza otra unidad de persistencia para la producción y
otra para los test. ¡Esto permite evitar modificar o eliminar definitivamente los registros vitales de la producción!

Si se valida esta manera de codificar, basta con modificar el constructor:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Conexion(String unidad){
fabrica = Persistence.createEntityManagerFactory(unidad);
}

private static final Conexion conexion


= new Conexion("eni‐acces");

public static Conexion getConexion() {


return conexion ;
}

Se asegura el enlace con la base de datos mediante esta propiedad de tipo EntityManagerFactory, gracias a objetos
de tipo EntityManager y a la ayuda que presta el método de instancia createEntityManager.

Se proporciona la clase EntityManager en javax.persistence, que es el punto de acceso que permite crear, leer,
modificar y eliminar los datos de la base de datos con las clases del dominio.

El método público estático getConexion() permite después recuperar el objeto que servirá para toda la aplicación.
Este método devuelve justo el contenido de la propiedad privada estática y final conexion. Esta última propiedad
es estática para permitir al método público estático acceder a ella. Es privada, así nada ni nadie puede acceder por
otros medios salvo el método público. Es, por último, final para evitar cualquier tentativa de modificación.

El objeto de tipo Conexion enmascara por lo tanto al máximo los detalles técnicos de acceso a la base de datos, lo
que permite facilitar cambios eventuales en el código.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Puesta en marcha del formulario de conexión


El formulario FConexion pasará aquí del estatus de maqueta al de formulario operacional.

Empiece por añadir un package llamado «dialogo».

Vuelva a copiar en este package todas las clases de las maquetas.

La clase FConexion es una HMI. Se contenta con transmitir la solicitud del usuario a la clase Conexion para la
conexión a la base de datos.

Abra la clase gráfica FConexion con WindowBuilder Editor.

Complete los imports.

import control.connection.Conexion;

Vaya al método validar() y reemplace el código siguiente:

boolean valido = true; // se reemplazará este código más adelante

por :

String elNombre = txtIntroUsuario.getText();


String laContrasena = String.valueOf(
pwdContrasena.getPassword());
Conexion conexion = Conexion.getConnexion();
boolean valido = conexion.control(elNombre, laContrasena);

El control de introducción se efectúa con el método control() de la clase Conexion.

La expresión pwdContrasena.getPassword() devuelve un array de caracteres.

Por razones de seguridad en el uso de la clase JPasswordField, Java hace una distinción entre las cadenas de
caracteres declaradas con la clase String y los arrays de caracteres.

// aceptado
char[] vChar = pwdContrasena.getPassword();

// rechazado
String laContrasena = pwdContrasena.getPassword();

Hay que convertir el array de caracteres en una cadena de caracteres, conversión realizada con el método estático
valueOf() de la clase String.

laContrasena = String.valueOf(pwdContrasena.getPassword());

Estos controles se efectúan de dos maneras: si la introducción del usuario/contraseña es incorrecta, el método
control() de la clase Conexion devolverá false. Se trata de un error a fin de cuenta muy corriente, que puede
por lo tanto gestionarse sencillamente con un booleano. Por el contrario, si aparecen problemas de conexión a la
base de datos, estos problemas deben ser excepcionales. El recurso a un tratamiento de estos errores por excepción
es una manera elegante de conseguirlo.

Para ello, Java permite encapsular las instrucciones críticas del programa en un bloque try/catch.

A la palabra clave try siempre le sigue un bloque de instrucciones que definen el tratamiento a ejecutar. Si este

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private void validar() {


String elNombre = txtIntroUsuario.getText();
String laContrasena =
String.valueOf(pwdContrasena.getPassword());
try {
Conexion conexion = Conexion.getConexion();
boolean valido =
conexion.control(elNombre, laContrasena);
if (valido) {
<tratamiento de la introducción de datos incorrecta>
} else {
<tratamiento del error de introducción de datos>
}
} catch (Exception e) {
JoptionPane.showMessageDialog(null,
Messages.getString("FConexion.2"),
Messages.getString("FConexion.32"),
JOptionPane.ERROR_MESSAGE); //$NON‐NLS
}
}

fracasa, se crea o «eleva» un error o excepción. En este caso, se tiene en cuenta o «se atrapa» la excepción y se
trata en el bloque catch que siempre está situado inmediatamente después del bloque try.

Existen variantes a la gestión de excepciones. Por ejemplo, se puede indicar que un tratamiento es susceptible de
lanzar una excepción usando la palabra clave throws en la declaración del método.

La gestión de errores se hace aquí en dos tiempos. Primero nos preocupamos del driver para el SGBD MySQL. Si no
encontramos ninguna excepción, una variable booleana indica que se puede efectuar el segundo tratamiento. Este
código permite proponer mensajes personalizados en función de la excepción elevada.

private void validar() {


String elNombre = txtIntroUsuario.getText();
String laContrasena =
String.valueOf(pwdContrasena.getPassword());
try {
Conexion conexion = Conexion.getConexion();
boolean valido =
conexion.control(elNombre, laContrasena);
if (valido) {
<tratamiento de la introducción de datos correcta>
} else {
< tratamiento del error de introducción de datos >
}
} catch (DatabaseException e) {
// imposible conectarse a la base de datos
JoptionPane.showMessageDialog(null,
Messages.getString("FConexion.2"),
Messages.getString("FConexion.32"),
JOptionPane.ERROR_MESSAGE); //$NON‐NLS

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

} catch (Exception e) {
// ha ocurrido una excepción totalmente desconocida
JoptionPane.showMessageDialog(null,
"Error desconocido " +e.getMessage(),
Messages.getString("FConnexion.32"),
JOptionPane.ERROR_MESSAGE); //$NON‐NLS
}
}

Queda crear el método control() de la clase Conexion.

Abra la clase Conexion en el editor de Eclipse y complétela con:

public boolean control(String nombre, String contrasena) {


boolean verificacionIntro = false;
EntityManager administrador = fabrica.createEntityManager();

Object nombreUsuarioCorrecto =
administrador.getProperties().get(
"javax.persistence.jdbc.user");
Object contrasenaCorrecta =
administrador.getProperties().get(
"javax.persistence.jdbc.password");
verificacionIntro =
nombre.equals(nombreUsuarioCorrecto)
&& contrasena.equals(contrasenaCorrecta);

administrador.close();

return verificacionIntro;
}

Puede afinar todavía más el control de la introducción de datos verificando si el usuario por lo menos ha introducido
los campos necesarios y precisando si el error proviene del nombre o de la contraseña. Este proceso de afinar debería
probablemente situarse en la clase FConexion.

Ahora queda ocuparse de la salida de la aplicación y cerrar correctamente la conexión a la base de datos.

Cree el método que permitirá cerrar la conexión en la clase Conexion:

public void cierre() {


fabrica.close();
}

Complete después el método salir() de la clase FConexion.

private void salir() {


Conexion.getConexion().cierre();
dispose();
}

La ventana de conexión está operacional. Pruebe la aplicación modificando los parámetros para provocar
voluntariamente errores. Por ejemplo, puede quitar temporalmente la librería del conector MySQL del Build Path, o
modificar la dirección del servidor o el nombre de la base de datos. ¡La profesión de desarrollador informático
consiste principalmente en gestionar todos estos errores para crear programas robustos!
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/3
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Lectura de los registros


Llegados a este punto, es hora de realizar algunos test de escritura y lectura de los datos de la base de datos, con la
ayuda de la clase TestJPA.

Primero añada un método toString() a esta clase usando la opción Source ­ Generate toString()... del
menú contextual. Permitirá tener más comodidad para visualizar los datos.

@Override
public String toString() {
return "TestJPA [clavePrimaria=" + clavePrimaria
+ ", mensaje=" + mensaje
+ "]";
}

Modifique después el método main() de la clase TestJPA como sigue:

public static void main(String[] args) {


Conexion conexion = Conexion.getConexion();

// creación de datos de test


TestJPA test1 = new TestJPA();
test1.clavePrimaria = 1;
test1.mensaje = "mensaje 1";

TestJPA test2 = new TestJPA();


test2.clavePrimaria = 2;
test2.mensaje = "mensaje 2";

TestJPA test3 = new TestJPA();


test3.clavePrimaria = 3;
test3.mensaje = "mensaje 3";

// empezar una transacción


EntityTransaction transaccion = em.getTransaction();
transaccion.begin();

// guardar los elementos en el administrador de entidades


em.persist(test1);
em.persist(test2);
em.persist(test3);

// sincronizar el administrador con la base de datos


em.flush();

em.remove(test3);

// finalizar la transacción
transaccion.commit();

// los datos están ahora en la base de datos


}

Para efectuar una operación de escritura hacia la base de datos, JPA debe obligatoriamente encontrarse en una
transacción activa. Esta transacción se recupera con el método getTransaction() de la clase EntityManager.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Una transacción representa un conjunto de operaciones enlazadas que funciona automáticamente: si no es posible
realizar alguna de las operaciones y ocurre un error, se considera que el conjunto de las operaciones ha fracasado.
Entonces es posible volver atrás y eliminar las operaciones de la transacción como si no pasara nada: se habla en
este caso de rollback.

Para efectuar operaciones de escritura, se arranca la transacción con el método begin(), y se indica al
administrador de entidades que debe guardar nuevos datos gracias al método persist(). Estos datos no se escriben
inmediatamente en la base de datos. Varias operaciones pueden así realizarse en una misma transacción: modificar
otros datos, eliminar algunos, …

Una vez que se han ejecutado todas las operaciones deseadas, el método flush() solicita al administrador de
entidades que envíe todos los datos modificados a la base de datos. Se hace después un commit de la transacción
activa y se cierra con el método commit().

Estos dos últimos métodos realizan aproximadamente la misma acción. La diferencia es que commit() envía todos
los cambios y cierra la transacción mientras que flush() envía los cambios pero mantiene la transacción activa: a
veces es ventajoso empezar a escribir datos en la base de datos a mitad de la transacción a la que se hará el
commit más tarde.

Una vez los datos guardados en la base de datos, hay que leerlos.

Complete el método main con el código siguiente:

...
// los datos están ahora en la base de datos
TestJPA encontrado = em.find(TestJPA.class, Long.valueOf(1));
System.out.println("encontrado: " + encontrado);

Query consulta = em.createQuery("SELECT test FROM TestJPA test");


List<?> resultados = consulta.getResultList();
System.out.println("encontrados: " + resultados);

La primera línea permite encontrar directamente un registro en la base de datos si se conoce su identificador (o
clave primaria). El administrador de entidades de JPA permite entonces encontrar directamente este registro
mediante un objeto de dominio con la ayuda del método find().

Para recuperar varios datos con criterios arbitrarios, es posible efectuar una consulta a la base de datos. Para ello
basta con crear un objeto Query a partir del administrador de entidades llamando al método createQuery() que
recibe como parámetro una consulta con formato de texto, y recuperar los resultados desde la consulta con la
ayuda del método getResultList().

Basándose en lo aprendido con la clase TestJPA para acceder a los datos, se modifica la clase Conexion para
introducir buenas prácticas en el código.

La clase Conexion no puede conocer todas las operaciones a efectuar. En un determinado estado de avance en la
codificación de las funcionalidades, sería cada vez más difícil mantenerlo y hacerlo evolucionar. Sin embargo, es
posible hacer que el código que llama al método se encargue de las especificidades (consultas, modificaciones,...)
permitiendo a la vez asegurar que las reglas de acceso al EntityManager se respeten (cierre, inicio y commit de las
transacciones entre otras cosas).

Para ello, se crean dos métodos. Estos dos métodos resumen las principales acciones a efectuar sobre los datos: la
aplicación los buscará y los modificará. Será también la oportunidad de poner en práctica algunas técnicas descritas
en los capítulos anteriores.

Cree el esqueleto del método público buscar() en la clase Conexion.

public void buscar() {


EntityManager administrador = fabrica.createEntityManager();

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

try {
} finally {
administrador.close();
}
}

Este método crea un administrador de entidad desde la fábrica y lo cierra al final de una operación de búsqueda.
Para ello, se añade un bloque finally en el bloque try/catch: significa que sea cual sea el desarrollo de
operaciones, el código dentro del bloque finally se ejecutará. Esto permite asegurar que los recursos del
administrador se liberan limpiamente. Si se produce alguna excepción durante la ejecución, el código que la invoca
la recuperará y será el responsable de su gestión.

¿Por qué hacerlo así? Buscando un poco en Internet, y leyendo la documentación de las clases
EntityManagerFactory y EntityManager, puede percatarse de que la clase EntityManagerFactory es thread­
safe, mientras que la clase EntityManager no lo es. Por lo tanto no es razonable tener un atributo de tipo
EntityManager en la clase Conexion: los riesgos de corrupción o incoherencia de los datos serían demasiado
grandes.

Hay que racionalizar los accesos a los EntityManager. La manera más sencilla es tener únicamente variables locales
de este tipo: una vez que se ejecuta el método, el objeto EntityManager ya no es accesible. Además, los objetos
de este tipo son ligeros: crearlos no resulta computacionalmente (en recursos y tiempo de ejecución) hablando
pesado.

Esta manera de trabajar tiene una consecuencia directa: una vez que se cierra el administrador de entidades, las
entidades leídas se consideran como liberadas; ya no están conectadas a la base de datos y no se actualizarán más en
caso de modificación. Estas entidades se convierten entonces en simples estructuras que contienen los datos leídos.

Hay que usar este EntityManager y esto solo se puede hacer dentro del bloque try.

Como recordatorio, no se trata de crear todas las maneras posibles de buscar datos en la clase Conexion. El
número de posibilidades ya es considerable en esta aplicación, y solo se puede multiplicar.

Se crea una manera genérica de trabajar: el problema es ejecutar código dentro del bloque try, con el
EntityManager como parámetro y devolver resultados al código que llama el método. La interfaz
java.util.function.Function responde exactamente a esta problemática: se trata de una interfaz funcional que
tiene un único método apply(), que recibe un objeto como parámetro y devuelve un objeto como resultado.

Modifique el método buscar() para introducir un parámetro de tipo Function y devolver un resultado.

public Object buscar(Function<EntityManager, Object> funcion) {


EntityManager administrador = fabrica.createEntityManager();
try {
Object resultado = funcion.apply(administrador);
return resultado;
} finally {
administrador.close();
}
}

La interfaz Function es genérica: es posible declarar los tipos de parámetros y del resultado.

Como el tipo de resultado no es conocido en este punto, se declara como genérico a su vez.

Modifique el método buscar() para hacer su resultado genérico:

Para usarlo, bastará por ejemplo con escribir el siguiente código utilizando expresiones lambda:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public <R> R buscar(Function<EntityManager, R> funcion) {


EntityManager administrador = fabrica.createEntityManager();
try {
R resultado = funcion.apply(administrador);
return resultado;
} finally {
administrador.close();
}
}

List<Cliente> clientes = laConexion.buscar(


(administrador) ‐> {
Query query = administrador.createQuery(
"SELECT c FROM Cliente c");
return query.getResultList();
});

o bien:

Cliente cliente = laConexion.buscar(


(administrador) ‐> {
// el objeto de consulta es la cadena de caracteres
// correspondiente
Query query = administrador.createQuery(consulta.toString());

return (Cliente) query.getSingleResult();


});

Cree el método modificar() con el siguiente código:

public <R> R modificar(Function<EntityManager, R> funcion) {


EntityManager administrador = fabrica.createEntityManager();
try {
EntityTransaction transaccion = administrador.getTransaction();
transaccion.begin();

R resultado = funcion.apply(administrador);

transaccion.commit();
return resultado;
} finally {
administrador.close();
}
}

No se gestionan los rollbacks en este ejemplo. Un rollback es una operación de la transacción que consiste en volver
atrás para obtener de nuevo un conjunto de datos coherente si la transacción ha fracasado.

¡Y es casi todo! JPA simplifica enormemente los accesos a la base de datos. Anteriormente, era necesario escribir
mucho código para crear una conexión con JDBC, cambiar las clases del driver MySQL, configurar este acceso
programáticamente,... JPA se encarga de todas estas etapas siempre y cuando se provea el archivo correcto de
persistencia.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La técnica de codificación de la clase Conexion permite enmascarar los detalles técnicos del acceso a la base de
datos: únicamente esta clase puede crear objetos de tipo EntityManager o EntityManagerFactory.

Esto evita al desarrollador estar pendiente de la transacción. Por el contrario implica que tenga cuidado en la
utilización del parámetro EntityManager. En este punto, puede ser interesante empezar con un framework
especializado como Spring, PicoContainer o Guice.

Puede ahora eliminar la clase TestJPA ya que su rol de acceso a la base de datos se ha terminado. Ya no servirá en
el resto de nuestro proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
Se trata en este capítulo de crear las clases para las entidades ModoPagos, Cliente, Articulo y Pedido que
representan el modelo de dominio principal resultante del análisis del proyecto.

Las clases de entidades tienen en su mayoría tablas correspondientes en la base de datos.

Las entidades tienen dos vertientes funcionales: una describe los datos a manipular, la otra las operaciones que
permiten su interacción con la base de datos.

Este segundo aspecto concierne principalmente a las operaciones CRUD (Create, Read, Update, Delete, es decir
Crear, Leer, Modificar, Eliminar) que permiten respectivamente realizar operaciones de creación, lectura,
actualización y eliminación de registros de la base de datos. A lo largo de este capítulo se podrá ver cómo
codificarlas poco a poco.

Es tentador mezclar las operaciones CRUD en el interior de la clase de datos. Esto plantea varios problemas: las
responsabilidades funcionales se mezclan entonces; el código resultante se encuentra muchas veces con artificios
técnicos; el aspecto pedagógico se diluye y ¡el objetivo de este libro es ante todo pedagógico!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de los errores


Una gran parte de las actividades de desarrollo de una aplicación es la gestión de los errores que acontecen a lo
largo de las actividades de esta aplicación.

En lo referente a la base de datos, estos errores tomarán la forma de excepciones lanzadas por JPA cuando se
llame a los métodos de la clase EntityManager. Estos errores heredan de la clase RuntimeException, no forman
por lo tanto parte de la firma de la clase, pero son aun así susceptibles de ocurrir. En vez de gestionarlos a través
de un bloque try/catch en esta parte de código, un capítulo posterior explicará cómo será responsabilidad de los
métodos que las llaman el gestionarlos.

Esta manera de trabajar simplifica la legibilidad del código, y contribuye a una mejor separación de las tareas: ¡no
es normal para una clase técnica mostrar cuadros de diálogo informando de estos errores!

Tenga en mente que pueden ocurrir excepciones en la ejecución de estos métodos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clase ModoPagos
Se empieza por la clase ModoPagos. Es la más sencilla de nuestras modelizaciones, y está en el corazón de la
aplicación. ¡Después de todo, se trata de una aplicación de gestión de pedidos, y sin pagos esta aplicación no serviría
de nada en una empresa privada!

Aquí tiene el código de esta clase, presente en el package entidad:

package entidad;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Transient;

@Entity
public class ModoPagos implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
private int codigo;

@Basic
private String tipo;

@Transient
private transient boolean ignorado;

/*
* Constructor 1.
* Utilizado por JPA.
*/
public ModoPagos(){
super();
}

/*
* Constructor 2
* Para la gestión de pedidos
*/
public ModoPagos(String tipo){
this();
this.tipo = tipo;
}

/*
* Accesores
*/
public int getCodigo() {
return this.codigo;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
public String getTipo() {
return this.tipo;
}

/*
* Mutadores
*/
public void setTipo(String tipo) {
this.tipo = tipo;
}

/**
* Describe el modo de pago
* de manera textual.
*/
@Override
public String toString() {
return tipo;
}
}

Existe una anotación a nivel de la clase: @Entity. Permite a JPA darse cuenta de las instancias de esta clase
corresponden a líneas en la base de datos para la tabla cuyo nombre es el de la clase por defecto.

Esta clase contiene un código, que es el identificador técnico que permite crear relaciones (este identificador se
llama también clave primaria) y un tipo que es la descripción textual del modo de pago.

JPA permite relacionar estas dos propiedades con columnas de la base de datos.

La propiedad codigo posee una anotación @Id. Permite indicar que la clave primaria de la fila se guardará en esta
propiedad. Corresponderá al valor presente en la columna CODIGO, es decir el nombre de la propiedad por defecto.

También posee una anotación @GeneratedValue, para indicar que es la base de datos la que proporciona este
valor en una inserción de un registro en la tabla de la base de datos.

La propiedad codigo no tiene un mutador, ya que la base de datos fija el valor de esta propiedad.

La propiedad tipo está también marcada por una anotación: @Basic. Esta anotación es opcional (por defecto todas
las propiedades se consideran con un equivalente en base de datos).

Estas dos propiedades son privadas en el interior de la clase y los accesores y mutadores permiten acceder y
modificar a sus valores.

Se anota la propiedad ignorado con @Transient. Este atributo no será por lo tanto mapeado por JPA y no tendrá
su equivalencia en la base de datos.

La clase está marcada con la interfaz Serializable. Esta marca permite a la aplicación transformar las instancias de
ModoPagos en formato binario y enviarlas a otras aplicaciones.

Este mecanismo no sirve en la aplicación actualmente, pero se considera como una buena práctica marcar las clases
de entidades como que implementan la interfaz Serializable.

Para conservar la misma semántica para el mecanismo de serialización, la palabra clave transient se añade también a
la propiedad ignorado.

Por último, se crea un constructor por defecto. Es obligatorio tener este constructor ya que JPA lo utiliza para crear
un objeto de dominio recuperado de las lecturas en base de datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/2
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

CRUD de la clase ModoPagos


La clase CRUD del modo de pago se llama ModoPagosCrud en este proyecto.

Cree un package crud en el package entidad.

Cree la clase ModoPagosCrud en este package.

package entidad.crud;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import control.connection.Conexion;
import entidad.ModoPagos;

public class ModoPagosCrud {

/**
*
*/
private final Conexion laConexion;

/**
* Constructor
*/
public ModoPagosCrud(Conexion unaConexion) {
laConexion = unaConexion;
}
}

Esta clase posee un constructor con un parámetro: la instancia de Conexion. Esto evita utilizar métodos estáticos
durante el ciclo de vida de la aplicación y mejora las posibilidades de test unitarios de esta clase.

El CRUD debe poder crear, leer, modificar y eliminar una línea de datos. Se añadirá una posibilidad de búsqueda a
este conjunto de funcionalidades.

1. Crear
Cree un nuevo método en la clase.

/**
* Añadir un nuevo elemento en la base de datos.
*
* @param tipo
* el nombre textual del modo de pago.
* @return el modo de pago creado
* o null si no se puede crear.
*/
public ModoPagos crear(String tipo) {
return laConexion.modificar((administrador) ‐> {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

if ("".equals(tipo)) {
throw new IllegalArgumentException(
"El nombre del modo de pago es obligatorio");
} else {
ModoPagos pagos = new ModoPagos(tipo);
administrador.persist(pagos);
return pagos;
}
});
}

Este método utiliza la clase Conexion y sus métodos.

Su rol principal es crear un objeto ModoPagos con la información indicada en los parámetros. La clase Conexion
se encarga de crear el administrador de entidades, arrancar la transacción, llamar a la función pasada como
parámetro y finalizar la transacción.

Este método verifica también el valor del parámetro con la ayuda de la siguiente instrucción:

if ("".equals(tipo)) {
...
} else {
...
}

y lanza una excepción específica para prevenir al método que hace uso de este método de un valor no conforme.

Se invierte el test respecto a lo que puede ver generalmente; type.equals(""). Esta inversión permite no preocuparse
del caso eventual de un parámetro tipo no incializado y por lo tanto evitar los NullPointerException.

2. Leer
Cree un nuevo método en la clase.

/**
* Lectura de registros.
*
* @return la lista de los modos de pago
* o una lista vacía si no existe ninguno.
*/
public List<ModoPagos> leer() {
return laConexion.buscar(
(administrador) ‐> {
Query consulta = administrador.createQuery(
"SELECT m FROM ModoPagos m");
return consulta.getResultList();
});
}

En el caos de la lectura, no es necesario utilizar una transacción, el método llama por lo tanto simplemente a
buscar() de la clase Conexion.

Después se crea una consulta a partir de este administrador, con una descripción textual de esta consulta.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Esta descripción no está escrita en SQL sino en JPQL, que es la versión orientada a objetos de SQL. En vez de
escribir "SELECT * FROM modopagos", que es la versión SQL «clásica», recuperamos los objetos en JPQL a
partir de la clase: en "SELECT m FROM ModoPagos m", ModoPagos corresponde a la clase que se quiere
obtener.

3. Modificar
Cree dos nuevos métodos en la clase.

/**
* Modificación de un regitro.
*
* @param codigo
* la clave primaria del modo de pago.
* @param tipo
* la nueva descripción del modo de pago.
* @return true si se ha modificado el modo, en caso contrario false.
*/
public boolean modificar(int codigo, String tipo) {
return laConexion.modificar((administrador) ‐> {
boolean modificado = false;
if ("".equals(tipo)) {
throw new IllegalArgumentException(
"El nombre del modo de pago es obligatorio");
} else {
ModoPagos modo = buscar(codigo);
if (modo != null) {
modo.setType(tipo);
modificado = true;
}
}
return modificado;
});
}

/**
* Busca un modo de pago específico.
*
* @param codigo
* la clave primaria del modo de pago.
* @return el modo de pago.
*/
private ModoPagos buscar(int codigo) {
return laConexion.buscar((administrador) ‐> {
ModoPagos modo = administrador.find(ModoPagos.class, codigo);
return modo;
});
}

Después de controlar los parámetros de entrada, el método modificar buscará en la base de datos el modo de
pago que corresponde al código indicado. Si el objeto devuelto no es null, esto quiere decir que el modo existe,
entonces se modifica dentro de la transacción. Cuando termina la transacción, se ha modificado el dato
correspondiente en la base de datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El método buscar utilizará el método find de la clase EntityManager para encontrar directamente el objeto
correspondiente a la clave primaria pasada como parámetro.

4. Buscar
Cree un nuevo método en la clase.

/**
* Búsqueda de un pedido.
*
* @param busqueda
* una parte del tipo de modo de pago a buscar.
* @return la lista de modos de pago
* o una lista vacía si no existe ninguno.
*/
public List<ModoPagos> buscar(String busqueda) {
return laConexion.buscar((administrador) ‐> {

String consulta = "";


consulta += "SELECT m ";
consulta += "FROM ModoPagos m ";
consulta += "WHERE m.tipo LIKE ’%" + busqueda + "%’";

Query query = administrador.createQuery(consulta);


return query.getResultList();
});
}

La búsqueda consiste en crear una consulta global con parámetros y devolver los resultados.

Si ningún registro corresponde a la consulta, JPA devuelve una lista vacía como resultado.

La consulta textual se construye simplemente por concatenación de cadenas de caracteres para obtener una
consulta parametrizada. Se verán varias técnicas más adelante en el capítulo para aligerar esta construcción.

No utilice nunca el operador += para construir una cadena de caracteres: ¡se trata de una de las técnicas de
construcción menos eficaces!

Esta consulta es una consulta de selección, como la que se codificó para la lectura, pero con una cláusula WHERE
que indica qué criterios adicionales deben aplicarse. En esta cláusula WHERE, un criterio LIKE permite buscar un
patrón determinado.

En la construcción de esta consulta, el formateo del parámetro es importante: debe estar entre dos comillas
simples o apóstrofes (’) y entre dos signos de porcentaje (%). Las comillas simples son obligatorias para que la
base de datos entienda que el parámetro es una cadena de caracteres. El porcentaje sirve aquí para reemplazar
todos los demás caracteres.

Si el parámetro del criterio LIKE es ’%a’, la consulta busca todos los tipos que acaben por la letra a.

Si el parámetro del criterio LIKE es ’a%’, la consulta busca todos los tipos que empiecen por la letra a.

Si el parámetro del criterio LIKE es ’%a%’, la consulta busca todos los tipos que contengan la letra a.

5. Eliminar

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree un nuevo método en la clase.

/**
* Supresión de un registro.
*
* @param codigo
* la clave primaria del modo de pago.
* @return true si se suprime el modo, false en caso contrario.
*/
public boolean eliminar(int code) {
return laConexion.modificar((administrador) ‐> {
boolean modificado = false;
ModoPagos modo = buscar(codigo);
if (modo != null) {
administrador.remove(modo);

modificado = true;
}
return modificado;
});
}

El método eliminar buscará en la base de datos el registro correspondiente al código con la ayuda del método
buscar visto anteriormente.

Si el modo de pago existe, se llama al método remove de la clase EntityManager. Al final de la transacción, se
elimina el registro correspondiente en la base de datos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clase Cliente
Aquí tiene el código de esta clase:

package entidad;

import java.io.Serializable;
import java.time.Instant;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Cliente implements Serializable {

private static final long serialVersionUID = 1L;

// Propriedades básicas de la clase


// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
// el identificador de la entidad
@Id
private String codigo;

@Basic
private String apellido;

private String nombre;

private boolean tarjeta_fidelidad;

@Temporal(TemporalType.DATE)
private Date fecha;

@OneToOne(cascade=CascadeType.ALL)
private Direccion direccion;

// CONSTRUCTORES
// ‐‐‐‐‐‐‐‐‐‐‐‐‐
// 1er Constructor
// para la creación completa de un cliente
// limitado aquí a 5 propiedades para aligerar el código
public Cliente(String codigo,
String apellido, String nombre,
boolean tarjetaFidelidad, Instant creacion) {
this.codigo = codigo;
this.apellido = apellido;
this.nombre = nombre;
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

this.tarjeta_fidelidad = tarjetaFidelidad;
setFechaCreacion(creacion);
}

/**
* Usado por JPA.
*/
public Cliente() {
super();
}

// Getters básicos
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
public String getCodigo() {
return this.codigo;
}

public String getApellido() {


return this.apellido;
}

public String getNombre() {


return this.nombre;
}

public boolean isTarjetaFidelidad() {


return this.tarjeta_fidelidad;
}

public Instant getFechaCreacion() {


return this.fecha.toInstant();
}

public Direccion getDireccion() {


return this.direccion;
}

// Setters
public void setCodigo(String codigo) {
this.codigo = codigo;
}

public void setApellido(String apellido) {


this.apellido = apellido;
}

public void setNombre(String nombre) {


this.nombre = nombre;
}

public void setTarjetaFidelidad(boolean tarjeta_fidelidad) {


this.tarjeta_fidelidad = tarjeta_fidelidad;
}

public void setFechaCreacion(Instant fecha_creacion) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

this.fecha = Date.from(fecha_creacion);
}

public void setDireccion(Direccion direccion) {


this.direccion = direccion;
}
}

Al contrario que para la clase ModoPagos, la propiedad código no posee la anotación @GeneratedValue. Esto
quiere decir que deberá asignar este código manualmente, ya que la base de datos no lo gestiona
automáticamente.

Se almacena la fecha de creación del cliente en el sistema. JPA utiliza la anotación @Temporal para convertir
automáticamente el objeto Date al formato SQL DATE, con la ayuda de la indicación adicional: la variable
TemporalType.DATE.

En el momento actual, la versión 2.1.0 de JPA no gestiona las propiedades de tipo time de Java 8. No es posible por
lo tanto declarar atributos de tipo java.time.Instant o java.time.LocalDate. Solo gestiona los tipos java.util.Date o
java.util.Calendar nativamente.

Por lo tanto es conveniente hacer la transformación deseada en el código con ayuda de los métodos:

public void setFechaCreacion(Instant fecha_creacion) {


this.fecha = Date.from(fecha_creacion);
}

y:

public Instant getFechaCreacion() {


return this.fecha.toInstant();
}

Una vez introducido este código, la información se convierte automáticamente y se almacena en la base de datos
gracias a JPA.

La clase Cliente establece una relación con una instancia de la clase Direccion. Se considera cada cliente en la
aplicación con una dirección como máximo, y cada dirección corresponde a un solo cliente. Esta relación se
formaliza con la anotación @OneToOne. Esta anotación se parametriza después con cascade, que indica que en
una operación sobre una entidad de la clase Cliente, la dirección enlazada se verá también afectada por esta
operación.

La clase Direccion es muy simple:

package entidad;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Direccion implements Serializable {

private static final long serialVersionUID = 1L;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

@Id
@GeneratedValue
private Long id;

private String calle;

private String codigoPostal;

private String ciudad;

public String getCalle() {


return this.calle;
}

public void setCalle(String calle) {


this.calle = calle;
}

public String getCodigoPostal() {


return this.codigoPostal;
}

public void setCodigoPostal(String codigoPostal) {


this.codigoPostal = codigoPostal;
}

public String getCiudad() {


return this.ciudad;
}

public void setCiudad(String ciudad) {


this.ciudad = ciudad;
}
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

CRUD de la clase Cliente


Ahora es momento de crear la clase de acceso a los clientes.

Cree la clase ClienteCrud en el package ”crud”.

package entidad.crud;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import control.connection.Conexion;
import entidad.Cliente;

public class ClienteCrud {

// Propriedad para establecer la conexion con la BD


// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
private final Conexion laConexion;

// CONSTRUCTOR
// ‐‐‐‐‐‐‐‐‐‐‐‐‐
public ClienteCrud(Conexion conexion) {
this.laConexion = conexion;
}
}

El constructor de esta clase sigue el principio de la clase ModoPagosCrud.

1. Crear
Cree un nuevo método en la clase.

// Añadir un nuevo cliente en la BD


// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
public void crear(Cliente cliente) {
laConexion.modificar((administrador) ‐> {
administrador.persist(cliente);
return cliente;
});
}

A diferencia de ModoPagosCrud, este método recibe un cliente completo como parámetro, y no devuelve nada. Si
termina con normalidad, se ha guardado el cliente en la base de datos. Si la operación no se ha podido realizar, se
lanzará una excepción de tipo RuntimeException por JPA, interrumpiendo el hilo normal de ejecución.

2. Leer
Cree un nuevo método en la clase.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

/*
* Lectura y recuperación de registros de la BD
*/
public List<Cliente> leer() {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createQuery("SELECT c FROM Cliente c");
return query.getResultList();
});
}

Este método enumera todos los clientes almacenados en el sistema.

Para las necesidades de la aplicación, es necesario disponer de una manera de recuperar un cliente
determinado a partir de su código (su clave primaria).

Cree un nuevo método en la clase.

public Cliente leer(String codigo) {


StringBuilder consulta = new StringBuilder("SELECT c ");
consulta.append("FROM Cliente c ");
consulta.append("WHERE c.codigo = ");
consulta.append("’");
consulta.append(codigo);
consulta.append("’");

return laConexion.buscar((administrador) ‐> {


Query query = administrador.createQuery(consulta.toString());

return (Cliente) query.getSingleResult();


});
}

En vez de concatenar directamente las cadenas de caracteres, se utiliza una instancia de la clase StringBuilder,
que permite acelerar este proceso. Una vez construida la consulta, puede utilizarse con el método toString() de
StringBuilder.

A partir de aquí, como se busca un único cliente, se llama al método getSingleResult() del objeto Query.
Siendo el valor devuelto por el método getSingleResult() un simple objeto y estando seguros que se trata de
un cliente, basta con hacer un cast de este objeto con el tipo Cliente para que el método sea más cómodo de
usar con la notación (Cliente).

¡Podría ser más sencillo utilizar el método find de la clase EntityManager pero sería menos didáctico!

3. Modificar
Cree un nuevo método en la clase.

// Modificación de un cliente en la BD
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
public Cliente modificar(Cliente elCliente) {
return laConexion.modificar((administrador) ‐> {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

String codigo = elCliente.getCodigo();


Cliente cliente = administrador.find(Cliente.class, codigo);

if (cliente == null) {
throw new IllegalArgumentException(
"Ningún cliente para el código " + codigo);
} else {
cliente.setApellido(elCliente.getApelido());
cliente.setNombre(elCliente.getNombre());
cliente.setTarjetaFidelidad(elCliente.isTarjetaFidelidad());

return cliente;
}
});
}

El método find de la clase EntityManager se utiliza aquí para encontrar el cliente con un código determinado.

Después se modifica el cliente obtenido con las propiedades del cliente pasado como parámetro, con la excepción
notable de la fecha de creación que no se puede modificar. Esta simple técnica permite limitar los parámetros del
método al mismo tiempo que se controlan las propiedades que se pueden modificar o no.

Después se devuelve el cliente modificado.

4. Eliminar
Cree un nuevo método en la clase.

// Supresión de un cliente en la BD
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
public boolean eliminar(String vCodigo) {
return laConexion.modificar((administrador) ‐> {

// Verificar antes que no existe ningún pedido


String consultaCliente = "SELECT COUNT(ped.codigo)"
+" FROM Pedido ped "
+ " WHERE ped.cliente.codigo LIKE ’"
+ vCodigo + "’";
Query consulta = administrador.createQuery(consultaCliente);

long resultado = (long) consulta.getSingleResult();


if (resultado != 0) {
throw new IllegalArgumentException(
"Existen pedidos ("
+ resultado + ") para este cliente."
+ " Supresión prohibida.");
}
Cliente cliente = administrador.find(Cliente.class, vCodigo);

if (cliente == null) {
throw new IllegalArgumentException(
"Ningún registro se corresponde con el código "
+ vCodigo + ".");
} else {
administrador.remove(cliente);
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
return true;
});
}

Este método es un poco más complejo que el método eliminar() de ModoPagosCrud, ya que hay que
asegurarse de no eliminar un cliente que tenga pedidos almacenados en el sistema.

Para ello, se realiza una primera consulta que comprueba cuántos pedidos tiene el cliente con el código indicado:

String consultaCliente = "SELECT COUNT(ped.codigo) "


+"FROM Pedido ped "
+ " WHERE ped.cliente.codigo LIKE ’"
+ vCodigo + "’";
Query consulta = administrador.createQuery(consultaCliente);
long resultado = (long) consulta.getSingleResult();

La palabra clave COUNT es estándar en SQL y devuelve el número de resultados como número de tipo long.

Al final, se presenta la propiedad cliente en el pedido. Se puede acceder a ella, así como a sus subpropiedades con
el separador punto (.). Aquí, ped.cliente.codigo corresponde al código del cliente del pedido «ped».

5. Buscar
Para efectuar búsquedas sobre los clientes, es el momento de introducir una herramienta de JPA para construir
consultas. En efecto, resulta molesto construir consultas «manualmente» con parámetros y es completamente
contrario a un buen rendimiento. Como las consultas de búsqueda son siempre las mismas excepto por los
parámetros, es mejor preparar estas consultas en JPA. Estas consultas se llaman consultas con nombre o
NamedQuery.

Inserte la anotación siguiente en la clase Cliente.

@Entity
@NamedQueries({
@NamedQuery( name="buscarCliente",
query="SELECT c FROM Cliente c "
+ "WHERE c.codigo LIKE :busqueda "
+ "OR c.apellido LIKE :busqueda "
+ "OR c.nombre LIKE :busqueda"),
@NamedQuery( name="clientePreciso",
query="SELECT c FROM Cliente c "
+ "WHERE c.codigo LIKE ?1 "
+ "AND c.apellido LIKE ?2 "
+ "AND c.nombre LIKE ?3")
})
public class Cliente {
...
}

Estas consultas serán accesibles por su nombre, especificado con el atributo name.

Identifique las consultas con nombre de manera que no resulten ambiguas: ¿una consulta llamada «buscar» tendrá
relación con los clientes, los artículos o los pedidos?

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree después un nuevo método en la clase ClienteCrud.

// Búsqueda en la BD
// ‐‐> con una cadena de caracteres cualquiera
public List<Cliente> buscar(String busqueda) {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createNamedQuery("buscarCliente");
query.setParameter("busqueda", "%" + busqueda + "%");
return query.getResultList();
});
}

Este método interroga JPA para obtener la consulta guardada con el nombre «buscarCliente» con la ayuda del
método createNamedQuery de la clase EntityManager. Una vez obtenida la consulta, el parámetro llamado
”busqueda” se fija con el valor deseado, sin las comillas simples.

Los nombres de los parámetros son arbitrarios, deben simplemente estar anotados en la consulta textual como «:
<nombre del parámetro>», aquí «busqueda». El tipo del parámetro se infiere respecto a la columna de la base de
datos.

Cree este método en la clase ClienteCrud.

// Búsqueda en la BD
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
public List<Cliente> buscar(String vCodigo, String vApellido, String
vNombre) {
String codigo, apellido, nombre;
if (vCodigo.equals("")) {
codigo = "%";
} else {
codigo = "%" +vCodigo +"%";
}
if (vApellido.equals("")) {
apellido = "%";
} else {
apellido = "%" +vApellido +"%";
}
if (vNombre.equals("")) {
nombre = "%";
} else {
nombre = "%" +vNombre +"%";
}

return laConexion.buscar((administrador) ‐> {


Query query = administrador.createNamedQuery("clienteDeterminado");
query.setParameter(1, codigo);
query.setParameter(2, apellido);
query.setParameter(3, nombre);

return query.getResultList();
});
}

Se trata de una variante de la manera anterior donde los parámetros están indicados de manera ordinal: por un
número.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Estos parámetros deben anotarse como ”?<posición del parámetro>”, aquí "?1", "?2", "?3".

Es obligatorio inicializar todos los parámetros antes de poder ejecutar estas consultas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/6
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clase Articulo
Continúe creando la clase Articulo en el package entidad.

package entidad;

import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Articulo implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private String codigo;

@ManyToOne(cascade = { CascadeType.PERSIST })
private Categoria categoria;

@ManyToMany(fetch = FetchType.EAGER)
private Set<Proveedor> proveedores = new HashSet<>();

private String designacion;

private int cantidad;

@Column(name="precio_unitario")
private double precioUnitario;

@Temporal(TemporalType.DATE)
private Date fecha;

/*
* Constructor
*/
public Articulo(String codigo, String codigoCategoria,
String designacion,
int cantidad, double precioUnitario,
Instant fecha) {
this(code,
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

new Categoria().setCodigo(codigoCategoria),
designacion,
cantidad, precioUnitario,
fecha);
}

public Articulo(String codigo, Categoria categoria,


String designacion,
int cantidad, double precioUnitario,
Instant fecha) {
this.codigo = codigo;
this.categoria = categoria;
this.designacion = designacion;
this.cantidad = cantidad;
this.precioUnitario = precioUnitario;
this.fecha = Date.from(fecha);
}

/*
* Constructor 2
*/
public Articulo() {
}

/*
* Accesores
*/
public String getCodigo() {
return codigo;
}

public Categoria getCategoria() {


return categoria;
}

public String getDesignacion() {


return designacion;
}

public int getCantidad() {


return cantidad;
}

public double getPrecioUnitario() {


return precioUnitario;
}

public Instant getFecha() {


return fecha.toInstant();
}

public Set<Proveedor> getProveedores() {


return this.proveedor;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

/*
* Mutadores
*/
public void setCodigo(String codigo) {
this.codigo = codigo;
}

public void setReferencia(Categoria categoria) {


this.categoria = categoria;
}

public void setDesignation(String designation) {


this.designation = designation;
}

public void setCantidad(int cantidad) {


this.cantidad = cantidad;
}

public void setPrecioUnitario(double precio_unitario) {


this.precioUnitario = precio_unitario;
}

public void setProveedores(Set<Proveedor> proveedores) {


this.proveedores = proveedores;
}

public void setFecha(Instant fecha) {


this.fecha = Date.from(fecha);
}
}

Esta clase introduce un nuevo tipo de anotación @ManyToOne.

Esta anotación significa que un artículo puede estar ligado a una única categoría, y a la inversa, una categoría
puede estar ligada a varios artículos, lo que corresponde al espíritu de la aplicación.

Se trata de uno de los tipos estándar de relación entre objetos definidos por JPA. Los demás son @OneToOne,
@OneToMany (la relación inversa de @ManyToOne) y @ManyToMany.

Observe que así la relación Articulo­Categoria es unidireccional. Un artículo conoce su categoría pero no podemos
buscar sencillamente los artículos a partir de una categoría. Se trata aquí de una característica de navegación para
esta relación.

Para transformar esta relación en una navegación bidireccional, se tendría que anotar la clase Categoria. La clase
Pedido implementará esta navegación bidireccional.

En la base de datos, esta relación se materializará en la tabla ARTICULO, con la introducción de una columna que
contenga el código de la categoría para cada artículo. Por defecto esta columna tendrá como nombre
CATEGORIA_CODIGO, es decir el nombre de la clase referenciada concatenada con un underscore o guion bajo (_)
seguido del nombre de la propiedad del identificador o clave primaria.

En otros términos, la tabla ARTICULO contiene la información relativa a la relación, la tabla CATEGORIA no contiene
nada relativo a esta relación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La propiedad proveedores también se encuentra anotada, esta vez con @ManyToMany. Esta anotación formaliza
que cada artículo puede comprarse a varios proveedores y que cada proveedor puede vender varios artículos.

En la base de datos, esta relación se almacena en una tabla intermedia, llamada tabla de asociación, compuesta por
dos columnas: una columna almacena la clave primaria del artículo, la otra la clave primaria del proveedor.

La anotación @ManyToMany se parametriza con fetch = FetchType.EAGER. En efecto, las relaciones de tipo
@ManyToMany son lazy (perezosas en español) por defecto: la propiedad correspondiente solo se rellena (fetch) en
el primer acceso. Para facilitar el uso y considerando que solo existen pocos proveedores por cada artículo, la
propiedad proveedores se rellenará directamente al recuperar el artículo de base.

Por último, la propiedad precioUnitario se anota con @Column, que permite indicar información específica: aquí,
los datos de precio se almacenan en la columna precio_unitario y no en la columna PRECIOUNITARIO.

Observe la parte específica del primer constructor para el parámetro categoría.

new Categoria().setCodigo(codigo_categoria)

¿Cómo puede funcionar esto?

La clase Categoria utiliza lo que se llama una interfaz fluida (en inglés fluent interface). Se trata de una
técnica de programación que permite encadenar las llamadas a los métodos, sobre todo las de tipo
setXXX, devolviendo la instancia. Así encontrará el siguiente código en la clase Categoria:

public Categoria setCodigo(String codigo) {


this.codigo = codigo;
return this;
}

Esto permite crear un objeto, asignarle una propiedad y continuar trabajando con él.

Esta manera de codificar permite aumentar la legibilidad del código y está muy en boga desde hace algunos años.
La encontramos por ejemplo en el principio del builder pattern, en las clases del package java.time y en muchas
otras librerías.

LocalDate hoy = LocalDate.now();

LocalDate penúltimoDiaDelMes =
hoy.with(TemporalAdjusters.lastDayOfMonth()).minusDays(1);

En este último ejemplo, los objetos devueltos por lo métodos with y minusDays son copias modificadas del objeto
original, siendo la mayoría de las clases del package java.time inmutables (en inglés: immutable).

No se explican las clases Categoria y Proveedor en este libro. Se deja su codificación para su libre iniciativa como
ejercicio.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

CRUD de la clase Articulo


Cree ahora la clase de acceso a los artículos.

Cree una clase ArticuloCrud en el package entidad.crud.

package entidad.crud;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import control.connection.Conexion;
import entidad.Articulo;
import entidad.Categoria;

public class ArticuloCrud {

private final Conexion laConexion;

/*
* Constructor
*/
public ArticuloCrud(Conexion conexion) {
this.laConexion = conexion;
}
}

1. Crear
Cree un nuevo método en la clase.

/**
* Añadir un nuevo artículo en la BD.
*
* @throws IllegalArgumentException
* si resulta imposible crear el artículo.
*/
public void crear(Articulo articulo) {
laConexion.modificar((administrador) ‐> {
administrador.persist(articulo);
return articulo;
});
}

La novedad de este método respecto a los métodos crear anteriores es indicar en JavaDoc que puede producirse
una excepción durante su ejecución.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se trata de un indicador adicional para el que use este método. ¡Sin embargo esta indicación supone que el
desarrollador lee esta documentación para tenerla en cuenta!

2. Leer
Cree dos nuevos métodos en la clase.

/**
* Lectura y recuperación de los registros en la BD.
* @throws IllegalArgumentException
* si no se pueden leer los artículos.
*/
public List<Articulo> leer() {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createQuery(
"SELECT a FROM Articulo a");
return query.getResultList();
});
}

/**
* leer un artículo determinado de la BD.
* @throws IllegalArgumentException
* si no se puede leer el artículo.
*/
public Articulo leer(String codigo) {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createQuery(
"SELECT a FROM Articulo a WHERE a.codigo = :codigo");
query.setParameter("codigo", codigo);
return (Articulo) query.getSingleResult();
});
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Este código muestra por ejemplo que las consultas parametrizadas pueden crearse al instante sin almacenarse en
un consulta con nombre.

Las consultas con nombre presentan un mejor rendimiento en cuanto a velocidad de ejecución ya que no necesitan
que se las instancie.

3. Modificar
Cree un nuevo método en la clase.

/**
* Modificación de un artículo en la BD.
* @throws IllegalArgumentException
* si no se puede modificar el artículo.
*/
public boolean modificar(String vCodigo, String vReferencia,
String vDesignacion, int vCantidad, double vPu) {
return laConexion.modificar((administrador) ‐> {

Articulo articulo = administrador.find(Articulo.class, vCodigo);

if (articulo == null) {
throw new IllegalArgumentException(
"Ningún artículo para el código " + vCodigo);
} else {
articulo.setReferencia(
new Categoria().setCodigo(vReferencia));
articulo.setDesignacion(vDesignacion);
articulo.setCantidad(vCantidad);
articulo.setPrecioUnitario(vPu);
}
return true;
});
}

Este método modificar() no recibe como parámetro un artículo, sino las nuevas propiedades del artículo a
modificar.

Compare la legibilidad de este método con el método modificado de la clase ClienteCrud. ¿Cuál de las dos
maneras de codificar le parece más legible?

4. Eliminar
Cree un nuevo método en la clase.

/**
* Supresión de un cliente en la BD.
* @throws IllegalArgumentException
* si no se puede suprimir el artículo.
*/
public boolean eliminar(String vCodigo) {
return laConexion.modificar((administrador) ‐> {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Articulo articulo = administrador.find(Articulo.class, vCodigo);


if (articulo == null) {
throw new IllegalArgumentException(
"Ningún artículo para el código " + vCodigo);
} else {
administrador.remove(articulo);
}
return true;
});
}

5. Buscar
Cree dos nuevos métodos en la clase.

/*
* Búsqueda en la BD
*/
@SuppressWarnings("unchecked")
public List<Articulo> buscarTodos(String busqueda) {
return laConexion.buscar((administrador) ‐> {
String consulta = "";
+ "SELECT a "
+ "FROM Articulo a "
+ "WHERE a.codigo LIKE ’%" + busqueda + "%’ "
+ "OR a.categoria.codigo LIKE ’%" + busqueda + "%’ "
+ "OR a.designacion LIKE ’%" + busqueda + "%’ ";

Query query = administrador.createQuery(consulta);


return query.getResultList();
});
}

/*
* Búsqueda rápida sobre el código
*/
@SuppressWarnings("unchecked")
public List<Articulo> buscarPorCodigo(String vCodigo) {
return laConexion.buscar((administrador) ‐> {
Query consulta = administrador.createQuery(
"SELECT a FROM Articulo a WHERE a.codigo LIKE ?1");
consulta.setParameter(1, vCodigo);

return consulta.getResultList();
});
}

Habrá observado con total seguridad las advertencias generadas por Eclipse en el editor sobre las líneas que
incluyen la llamada al método getResultList(). Este aviso se presenta mediante un subrayado amarillo sobre la
línea.

Eclipse le advierte de esta manera de que no es seguro que la lista devuelta contenga realmente objetos del tipo
esperado. Esto se debe al hecho de que el objeto Query no puede saber a ciencia cierta el tipo de los objetos
devueltos en la lista.

Para arreglar este problema, es posible utilizar los Quick­Fix de Eclipse.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Sitúe el cursor del ratón sobre la línea subrayada y utilice la combinación de teclas [Ctrl] 1 o haga clic con el
botón derecho y seleccione Quick­Fix.

Eclipse propone una lista de posibilidades para arreglar el problema.

Haga clic en la opción Add @SuppressWarnings ’unchecked’.

El método queda desde ahora anotado, y el aviso desaparece.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

CRUD de la clase Articulo


Cree ahora la clase de acceso a los artículos.

Cree una clase ArticuloCrud en el package entidad.crud.

package entidad.crud;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import control.connection.Conexion;
import entidad.Articulo;
import entidad.Categoria;

public class ArticuloCrud {

private final Conexion laConexion;

/*
* Constructor
*/
public ArticuloCrud(Conexion conexion) {
this.laConexion = conexion;
}
}

1. Crear
Cree un nuevo método en la clase.

/**
* Añadir un nuevo artículo en la BD.
*
* @throws IllegalArgumentException
* si resulta imposible crear el artículo.
*/
public void crear(Articulo articulo) {
laConexion.modificar((administrador) ‐> {
administrador.persist(articulo);
return articulo;
});
}

La novedad de este método respecto a los métodos crear anteriores es indicar en JavaDoc que puede producirse
una excepción durante su ejecución.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se trata de un indicador adicional para el que use este método. ¡Sin embargo esta indicación supone que el
desarrollador lee esta documentación para tenerla en cuenta!

2. Leer
Cree dos nuevos métodos en la clase.

/**
* Lectura y recuperación de los registros en la BD.
* @throws IllegalArgumentException
* si no se pueden leer los artículos.
*/
public List<Articulo> leer() {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createQuery(
"SELECT a FROM Articulo a");
return query.getResultList();
});
}

/**
* leer un artículo determinado de la BD.
* @throws IllegalArgumentException
* si no se puede leer el artículo.
*/
public Articulo leer(String codigo) {
return laConexion.buscar((administrador) ‐> {
Query query = administrador.createQuery(
"SELECT a FROM Articulo a WHERE a.codigo = :codigo");
query.setParameter("codigo", codigo);
return (Articulo) query.getSingleResult();
});
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Este código muestra por ejemplo que las consultas parametrizadas pueden crearse al instante sin almacenarse en
un consulta con nombre.

Las consultas con nombre presentan un mejor rendimiento en cuanto a velocidad de ejecución ya que no necesitan
que se las instancie.

3. Modificar
Cree un nuevo método en la clase.

/**
* Modificación de un artículo en la BD.
* @throws IllegalArgumentException
* si no se puede modificar el artículo.
*/
public boolean modificar(String vCodigo, String vReferencia,
String vDesignacion, int vCantidad, double vPu) {
return laConexion.modificar((administrador) ‐> {

Articulo articulo = administrador.find(Articulo.class, vCodigo);

if (articulo == null) {
throw new IllegalArgumentException(
"Ningún artículo para el código " + vCodigo);
} else {
articulo.setReferencia(
new Categoria().setCodigo(vReferencia));
articulo.setDesignacion(vDesignacion);
articulo.setCantidad(vCantidad);
articulo.setPrecioUnitario(vPu);
}
return true;
});
}

Este método modificar() no recibe como parámetro un artículo, sino las nuevas propiedades del artículo a
modificar.

Compare la legibilidad de este método con el método modificado de la clase ClienteCrud. ¿Cuál de las dos
maneras de codificar le parece más legible?

4. Eliminar
Cree un nuevo método en la clase.

/**
* Supresión de un cliente en la BD.
* @throws IllegalArgumentException
* si no se puede suprimir el artículo.
*/
public boolean eliminar(String vCodigo) {
return laConexion.modificar((administrador) ‐> {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Articulo articulo = administrador.find(Articulo.class, vCodigo);


if (articulo == null) {
throw new IllegalArgumentException(
"Ningún artículo para el código " + vCodigo);
} else {
administrador.remove(articulo);
}
return true;
});
}

5. Buscar
Cree dos nuevos métodos en la clase.

/*
* Búsqueda en la BD
*/
@SuppressWarnings("unchecked")
public List<Articulo> buscarTodos(String busqueda) {
return laConexion.buscar((administrador) ‐> {
String consulta = "";
+ "SELECT a "
+ "FROM Articulo a "
+ "WHERE a.codigo LIKE ’%" + busqueda + "%’ "
+ "OR a.categoria.codigo LIKE ’%" + busqueda + "%’ "
+ "OR a.designacion LIKE ’%" + busqueda + "%’ ";

Query query = administrador.createQuery(consulta);


return query.getResultList();
});
}

/*
* Búsqueda rápida sobre el código
*/
@SuppressWarnings("unchecked")
public List<Articulo> buscarPorCodigo(String vCodigo) {
return laConexion.buscar((administrador) ‐> {
Query consulta = administrador.createQuery(
"SELECT a FROM Articulo a WHERE a.codigo LIKE ?1");
consulta.setParameter(1, vCodigo);

return consulta.getResultList();
});
}

Habrá observado con total seguridad las advertencias generadas por Eclipse en el editor sobre las líneas que
incluyen la llamada al método getResultList(). Este aviso se presenta mediante un subrayado amarillo sobre la
línea.

Eclipse le advierte de esta manera de que no es seguro que la lista devuelta contenga realmente objetos del tipo
esperado. Esto se debe al hecho de que el objeto Query no puede saber a ciencia cierta el tipo de los objetos
devueltos en la lista.

Para arreglar este problema, es posible utilizar los Quick­Fix de Eclipse.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Sitúe el cursor del ratón sobre la línea subrayada y utilice la combinación de teclas [Ctrl] 1 o haga clic con el
botón derecho y seleccione Quick­Fix.

Eclipse propone una lista de posibilidades para arreglar el problema.

Haga clic en la opción Add @SuppressWarnings ’unchecked’.

El método queda desde ahora anotado, y el aviso desaparece.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

CRUD de la clase Pedido


Cree una clase PedidoCrud en el package entidad.crud.

package entidad.crud;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import control.connection.Conexion;
import entidad.Pedido;

public class PedidoCrud {

private Conexion laConexion;

/*
* Constructor 1
*/
public PedidoCrud(Conexion conexion) {
this.laConexion = conexion;
}
}

1. Crear
Cree un nuevo método en la clase.

/**
* Añadir un nuevo pedido en la BD.
* @throws EniException si es imposible crear el pedido.
*/
public void crear(Pedido pedido) throws EniException {
try {
laConexion.modificar((administrador) ‐> {
admnistrador.persist(pedido);
return pedido;
});
} catch(RuntimeException e) {
throw new EniException(e.getMessage(), e);
}
}

Este método tiene la particularidad de recuperar y transformar la excepción RuntimeException, que puede que
JPA lance, en una excepción específica, que no es de tipo RuntimeException. Esta última debe por lo tanto
obligatoriamente indicarse en la firma del método con la palabra clave throws. A partir de aquí, todo código que
invoque a este método debe explícitamente gestionar esta excepción, en caso contrario el código no compilará.

Se trata de una técnica eficaz para no arriesgarse a olvidar la gestión de un eventual error durante la ejecución de
la aplicación.

Sin embargo se debe crear esta excepción específica.


http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree una clase EniException en el package entidad.

package entidad.crud;

public class EniException extends Exception {

private static final long serialVersionUID = 1L;

public EniException() {
super();
}

public EniException(String mensaje, Throwable causa) {


super(mensaje, causa);
}

public EniException(String mensaje) {


super(mensaje);
}

public EniException(Throwable causa) {


super(causa);
}
}

2. Leer
Cree un nuevo método en la clase para leer todos los pedidos.

/*
* Lectura y recuperación de registros en la BD
*/
public List<Pedido> leer() {
return laConexion.buscar((administrador) ‐> {
TypedQuery<Pedido> query = administrador.createQuery(
"SELECT p FROM Pedido p",
Pedido.class);

return query.getResultList();
});
}

Este método utiliza una versión del método createQuery disponible desde JPA 2.0, que recibe un parámetro
adicional de tipo Class. Permite obtener un objeto de tipo TypedQuery que deriva de Query.

Este objeto está tipado con la clase que se pasa como parámetro, y permite evitar los @SuppressWarning vistos
anteriormente.

Si se conoce el tipo de resultado, es más ventajoso usar esta versión.

Cree un método en la clase para leer un pedido determinado.

/*
* Consulta equivalente a
* "SELECT p FROM Pedido p WHERE p.codigo = :codigo"

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

*/
public Pedido leer(String codigo) {
return laConexion.buscar((administrador) ‐> {
CriteriaBuilder cb = administrador.getCriteriaBuilder();
CriteriaQuery<Pedido> criterio =
cb.createQuery(Pedido.class);
ParameterExpression<String> parametro =
cb.parameter(String.class);
Root<Pedido> objeto = criterio.from(Pedido.class);
criterio.select(objeto)
.where(cb.equal(objeto.get("codigo"), parametro));

TypedQuery<Pedido> query = administrador.createQuery(criterio);

query.setParameter(parametro, codigo);
return query.getSingleResult();
});
}

Este método utiliza la API Criteria de JPA. Permite crear consultas programáticamente en vez de pasar consultas
en forma de cadena de caracteres.

Su principal ventaja es que los eventuales errores en la sintaxis de la consulta se detectan en tiempo de
compilación y no en tiempo de ejecución, como en los ejemplos anteriores.

Su principal inconveniente es que es menos legible para aquellos desarrolladores habituados a la sintaxis SQL,
mientras que el lenguaje JPQL es al final muy cercano a SQL.

3. Modificar
Cree un nuevo método en la clase.

/*
* Modificación de un pedido en la BD
*/
public Pedido modificar(Pedido elPedido) throws EniException
{
Object devuelto = laConexion.modificar((administrador) ‐> {
String codigo = elPedido.getCodigo();

Pedido pedido = administrador.find(Pedido.class, codigo);

if (pedido == null) {
return new EniException(
"Ningún pedido para el código " + codigo);
} else {
try {
pedido.setCliente(elPedido.getCliente());

for (Linea unaLinea : elPedido.getLineas()) {


pedido.añadir(unaLinea);
}

pedido.setPago(elPedido.getPago());
if (elPedido.getLineas().isEmpty()) {
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

pedido.getLineas().clear();
}
} catch(RuntimeException e) {
return new EniException(e.getMessage(), e);
}

return pedido;
}
});
Pedido pedido = null;
if (devuelto instanceof Pedido) {
pedido = (Pedido) devuelto;
}
if (devuelto instanceof EniException) {
throw (EniException) devuelto;
}
return pedido;
}

La interfaz Function no tiene en cuenta las excepciones verificadas. Si quiere continuar advirtiendo al
desarrollador de que el método es susceptible de lanzar una excepción, no debe lanzar la excepción desde el
bloque del método apply() de la interfaz Function sino fuera de este.

El resultado se verifica después en relación a un tipo de clase gracias a la instrucción instanceof.

4. Eliminar
Cree un nuevo método de la clase.

/*
* Supresión de un pedido de la BD
*/
public void eliminar(String vCodigo) throws EniException {
EniException problema =
laConexion.modificar((administrador) ‐> {
EniException excepcion = null;
Pedido pedido = administrador.find(Pedido.class, vCodigo);

if (pedido == null) {
exception = new EniException(
"Ningún pedido para el código " + vCodigo);
} else {
try {
administrador.remove(pedido);
} catch(RuntimeException e) {
excepcion = new EniException(e.getMessage(), e);
}
}
return excepcion;
});
if (problema != null) {
throw problema;
}
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La verificación se hace aquí simplemente en función a la no inicialización de la variable devuelta, es decir si el


valor devuelto por el método modificar() es igual a null.

5. Buscar
Cree un nuevo método en la clase.

/*
* Búsqueda en la BD
*/
public List<Pedido> buscar(String busqueda) {
return laConexion.buscar((administrador) ‐> {
TypedQuery<Pedido> query =
administrador.createNamedQuery(
"buscarPedido", Pedido.class);
query.setParameter("busqueda", "%" + busqueda +"%");
return query.getResultList();
});
}

Este método muestra por ejemplo que también es posible crear consultas con nombre Y tipadas.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/5
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de las entidades por JPA


La codificación de entidades y de sus clases CRUD está casi terminada.

Las clases Categoria y Proveedor y sus CRUD asociados todavía no se han abordado. Le dejamos este trabajo como
ejercicio.

Una vez codificadas estas clases, queda indicar a JPA que estas clases deben tenerse en cuenta en las consultas
hacia la base de datos.

Abra el archivo persistence.xml. Puede utilizar el atajo de teclado [Crtl][Shift] R para abrir un cuadro de
diálogo de búsqueda rápida. Empiece a teclear el nombre del archivo en la zona de búsqueda. Se escaneará el
espacio de trabajo con el fin de mostrar los archivos correspondientes. Haga doble clic en el archivo deseado.

Modifique el archivo insertando elementos XML class:

Guarde el archivo pulsando [Ctrl] S.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Presentación del concepto


El proyecto Luna se ha construido teniendo en cuenta la separación de los objetos de dominio (los datos) de su
representación gráfica, y esto desde la fase de análisis.

El modelo MVC (Modelo­Vista­Controlador) es un patrón de arquitectura que formaliza esta separación


introduciendo una capa de software intermedia, el controlador.

En un contexto MVC, los datos constituyen los modelos, las clases gráficas las vistas. Modelo y Vista son
totalmente independientes el uno del otro: no se conocen. ¿Cómo entonces las acciones que los usuarios realizan
sobre las vistas pueden modificar los modelos y a la inversa, cómo pueden mostrarse los datos esperados? Son
precisamente los controladores los que se encargaran de este enlace.

Otra particularidad importante: el modelo, si no conoce directamente las vistas que muestran sus datos, tiene al
menos la posibilidad de notificar a estas cualquier cambio que les afecte.

El esquema muestra que el usuario no tiene ningún conocimiento del controlador. Sin embargo es el encargado de
transmitir de manera transparente para el usuario las peticiones al modelo para su tratamiento. Las vistas se
actualizan mediante el modelo a través de un sistema de notificación basado en la noción de eventos y
escuchadores de eventos.

Las ventajas de esta arquitectura, que se basa en una separación de responsabilidades, son múltiples:

Si se deben comunicar a la aplicación los cambios de los datos, el modelo se verá impactado así como
probablemente el controlador, mientras que la vista sigue siendo la misma. Si deben realizarse cambios gráficos, se
modificará la vista, dejando el modelo y el controlador sin alterar. Esto facilita el mantenimiento de la aplicación.

La distribución del modelo, de la vista y del controlador permite escribir más facilmente test unitarios para la lógica
del dominio.

Esta modularidad permite a diferentes desarrolladores trabajar simultáneamente sobre los aspectos lógicos del
dominio y la interfaz de usuario.

Para la gestión de los clientes, artículos y pedidos del proyecto Luna, se utiliza este método de diseño para mostrar
los datos en tablas gráficas que tendrán la capacidad de actualizarse una vez modificados los datos.

Se utiliza un componente gráfico de Swing, llamado JTable, construido sobre este patrón de diseño MVC para este
fin, y esta separación de responsabilidades se retoma a un nivel superior.

Se aborda el aspecto gráfico en primer lugar.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

MVC y JTable
Las clases ya existentes permiten establecer la conexión con la base de datos y la importación de estos datos en el
modelo del dominio de la aplicación.

Se usa un componente Swing de tipo JTable para permitir la representación gráfica de los datos con la forma de
una tabla de doble entrada, en filas y columnas.

JTable está construido sobre los principios de la arquitectura MVC, como la mayoría de los componentes Swing que
representan datos complejos como las JList y los JTree. Entender los mecanismos utilizados en una JTable facilita el
uso de los otros tipos de componentes.

Instanciar un JTable se hace como con cualquier otra clase: basta con llamar a su constructor.

JTable tabla = new JTable();

De momento, la tabla está vacía. Es necesario importar datos en ella.

Para ello, existen varias maneras: proporcionar directamente todos los datos de las filas y columnas en la
construcción, proporcionar una lista de objetos que representan cada una una línea de datos, o usar un modelo de
tabla. Es esta última opción la elegida para la aplicación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de clientes
Se utiliza el paradigma MVC empezando por la gestión de clientes. Los pasos a seguir para los artículos y los pedidos
son similares.

1. Modelo del dominio


La clase Cliente es una entidad que pertenece a la M de MVC, el modelo. Se explicó en el capítulo Entidades.

Su clase asociada ClienteCrud permite realizar operaciones desde la base de datos, entre ellas la búsqueda de
clientes.

Aquí tiene la lista de métodos disponibles en la clase ClienteCrud:

La tabla podrá mostrar los clientes que provienen de la base de datos. Para ello, el método leer() de la clase
ClienteCrud intervendrá en algún momento.

2. Modelo gráfico
La clase JTable necesita datos para mostrar. La plantilla de la tabla proporciona estos datos, es decir un objeto
que implementa la interfaz TableModel. Este modelo de tabla es diferente del modelo de dominio ya que solo
concierne el aspecto gráfico.

Este modelo de tabla proporciona al componente gráfico (por lo tanto a la vista):

El número de columnas gracias al método getColumnCount().

El nombre de las columnas a mostrar con el método getColumnName(int columna), siendo el parámetro el
índice de la columna (empezando por 0).

El número de filas gracias al método getRowCount().

El valor de la celda con el método getValueAt(int fila, int columna), siendo el primer parámetro el índice de la
fila, el segundo el de la columna.

una manera de añadir escuchadores a este modelo gracias a dos métodos: addTableModelListener y
removeTableModelListener.

Proporciona también un medio para editar directamente cualquier celda de la tabla desde la interfaz gráfica gracias
a los métodos isCellEditable y setValueAt. No se utilizarán estos métodos en la aplicación.

Se crea un modelo de tabla para los clientes en el package control.modelo.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree una nueva clase llamada ModeloClientes en el package control.modelo. Esta clase hereda de la
clase AbstractTableModel que pertenece al package javax.swing.table.

Para acelerar el descubrimiento de la clase AbstractTableModel en el cuadro de diálogo de creación de la clase,


puede utilizar el siguiente truco: en el campo Superclass, teclee las mayúsculas de la clase a encontrar, o sea ATM y
utilice la combinación de teclas [Ctrl][Espacio]. Eclipse propondrá entonces la lista de clases que conoce relativas a lo
que acaba de introducir.

La clase AbstractTableModel proporciona un inicio de implementación de la interfaz TableModel, con un código


que permite gestionar el guardado de las vistas para el modelo (se crean por lo tanto los métodos
addTableModelListener y removeTableModelListener) y métodos para notificar a la vista de que el modelo
ha cambiado.

Eclipse ha incluido la importación siguiente en la clase ModeloClientes.

import javax.swing.table.AbstractTableModel;

La clase ModeloClientes debe sin embargo obligatoriamente redefinir varios métodos de la interfaz TableModel.
Por este motivo, Eclipse ha creado estos métodos insertándoles valores por defecto (0 y null).

package control.modelo;

import javax.swing.table.AbstractTableModel;

public class ModeloClientes extends AbstractTableModel {

@Override
public int getRowCount() {
// TODO Auto‐generated method stub
return 0;
}

@Override
public int getColumnCount() {
// TODO Auto‐generated method stub
return 0;
}

@Override
public Object getValueAt(int rowIndex, int columnIndex) {
// TODO Auto‐generated method stub
return null;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Debe completar esta clase empezando por las columnas.

Las columnas son fijas: no cambiarán en la aplicación, y corresponden al código, apellido, nombre, posesión de la
tarjeta de fidelidad y la fecha de creación.

Añada un atributo TITULOS en la clase ModeloClientes.

private static final String[] TITULOS =


{"Código", "Apellido", "Nombre", "Tarjeta fidelidad",
"Fecha creación"};

Resulta trivial escribir los métodos getColumnCount y getColumnName:

public int getColumnCount() {


return TITULOS.length;
}

public String getColumnName(int columnIndex) {


return TITULOS[columnIndex];
}

La siguiente etapa consiste en recuperar el número de clientes. Lo más sencillo es construir el modelo de tabla
con una lista de clientes que se pasa como parámetro al constructor.

Sería posible recuperar las columnas desde la base de datos, pero esto hace el modelo más complejo.

Modifique el constructor con el siguiente código y agregue el atributo losDatos.

private final List<Cliente> losDatos;

public ModeloClientes(List<Cliente> losClientes) {


losDatos = new ArrayList<>(losClientes);
}

El hecho de crear una nueva lista a partir de una lista existente permite guardar un control absoluto sobre esta
nueva lista.

Se pueden entonces escribir los métodos getRowCount() y getValueAt() de la siguiente manera:

public int getRowCount() {


return losDatos.size();
}

public Object getValueAt(int rowIndex, int columnIndex) {


Cliente cliente = getCliente(rowIndex);
switch(columnIndex){
case 0:
return cliente.getCodigo();
case 1:
return cliente.getApellido();
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

case 2:
return cliente.getNombre();
case 3:
return cliente.isTarjetaFidelidad();
case 4:
return cliente.getFechaCreacion();
default:
return null;
}
}

En este punto, se dispone de un modelo de tabla que permite mostrar los clientes.

Añada un pequeño método para encontrar un cliente a partir de su número de fila. Será útil más tarde.

public Cliente getCliente(int numeroFila) {


return losDatos.get(numeroFila);
}

Después debe ocuparse del enlace con la clase ClienteCrud. Este enlace se hará en una clase «Controlador» que
no controlará la tabla gráfica, sino que enlazará la recuperación de datos de la base de datos y la importación de
los mismos en el modelo gráfico.

Cree una clase ControlCliente en el package control.

public class ControlCliente {

private final ClienteCrud crud;


private final ModeloClientes elModeloClientes;

public ControlCliente(Conexion conexion) {


crud = new ClienteCrud(conexion);
List<Cliente> clientes = crud.leer();
elModeloClientes = new ModeloClientes(clientes);
}

public ModeloClientes getModelo() {


return elModeloClientes;
}
}

Esta clase instancia como atributos finales un nuevo objeto de tipo ClienteCrud para acceder a los datos de la base
de datos, lee los clientes y los inserta en el modelo gráfico para la tabla a mostrar. Este modelo gráfico se puede
recuperar después gracias al método getModelo().

Cree una clase de test unitario y añada el siguiente método:

@Test
public void test() {
Conexion conexion = Conexion.getConexion();

ClienteCrud crud = new ClienteCrud(conexion);


Cliente unCliente = new Cliente("codigo",
"Apellido", "Nombre",
true, Instant.now());

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

crud.crear(unCliente);

ControlCliente control = new ControlCliente(conexion);


ModeloClientes modelo = control.getModelo();

Cliente cliente = modelo.getCliente(0);

System.out.println("Apellido del primer cliente " + client.getApellido());


conexion.cierre();
}

Como puede constatar, la clase ControlCliente recibe un objeto Conexion como parámetro en su constructor.

¿De dónde procede este objeto? Sería tentador utilizar el método estático getConexion() de la clase Conexion
para recuperarlo, pero esta manera no se puede testear.

Este objeto se crea en realidad una única vez en la aplicación al validar el usuario. Después se pasa a la instancia
de la clase FBienvenida que lo almacena en un atributo privado. Cuando el usuario hace clic en el botón para
mostrar los clientes, se pasa este atributo de conexión al panel correspondiente.

Se trata por lo tanto de crear una cadena de paso del objeto Conexion de la ventana de conexión a la ventana
de clientes.

Vaya al método validar() de la clase FConexion y añada el objeto conexion como parámetro del
constructor de la clase FBienvenida.

if (valido) {
Conexion conexion = Conexion.getConexion();
FConexion.this.dispose();
FBienvenida laVentanaMenu = new FBienvenida(conexion);

laVentanaMenu.setVisible(true);
}

Eclipse advierte de un error. Propone soluciones pasando el cursor del ratón por encima de la línea con el error.

Elija la opción Change constructor ’FBienvenida()’: Add parameter ’Conexion’.

Después guarde el parámetro de conexión en la clase FBienvenida como atributo privado y final.

private final Conexion conexion;

...

public FBienvenida(Conexion conexion) {


this.conexion = conexion;
...
}
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Acuérdese de que la palabra clave this corresponde al objeto actual. Prefijar un atributo o método con ”this”
permite evitar ambiguedades cuando el nombre de alguna variable y de ciertos atributos son idénticos.

Pase después este atributo como parámetro al panel de tipo PClientes, en el método mostrarClientes().

protected void mostrarClientes() {


JDialog dialog = new JDialog(this);
PClientes clientes = new PClientes();
clientes.setConexion(conexion);
clientes.setVentana(dialog);
dialog.setContentPane(clientes);
...
}

Esto implica crear un método setConexion() en la clase PClientes.

Cree el método setConexion() en la clase PClientes.

public void setConexion(Conexion conexion) {


this.conexion = conexion;
this.controlCliente = new ControlCliente(conexion);

this.tabla.setModel(controlCliente.getModelo());

La conexión se almacena como atributo de la clase, así como el control de cliente.

El modelo de la tabla se inicializa después gracias a la llamada al método getModelo() de la clase


ControlCliente.

A partir de ahora, los datos de clientes de la base de datos se muestran en la tabla del módulo de clientes.

Ahora se debe mejorar la presentación de estos datos.

3. Personalización de la visualización de la tabla


Cada fila de la tabla corresponde a un cliente.

En esta fila, cada celda corresponde a una propiedad de este cliente, siendo el valor de esta propiedad un objeto.

Por defecto, cada celda de una fila se formatea con el método toString() del objeto correspondiente.

Por este motivo la columna «Tarjeta fidelidad» muestra true o false, y la fecha de creación tiene este formato tan
particular.

La clase JTable propone dos maneras para personalizar la apariencia, que se expondrán una tras otra.

Abra la clase ModeloClientes y añada el siguiente método:

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

@Override
public Class<?> getColumnClass(int columnIndex){
Class<?> clase = null;
switch(columnIndex){
case 3:
clase = Boolean.class;
break;
case 4:
clase = Instant.class;
break;
default:
clase = super.getColumnClass(columnIndex);
break;
}
return clase;
}

En Java, los índices empiezan en 0.

Este método permite asignar índices a la JTable en función del tipo de datos a mostrar, devolviendo la clase del
objeto cuyo aspecto se quiere modificar.

Este método se redefine, el método ya existe en la clase madre AbstractTableModel. Para invocar a la
implementación de esta clase madre, se utiliza la palabra clave super.

Si vuelve a ejecutar la aplicación, obtendrá el siguiente aspecto.

Se mejora la visualización de valores booleanos: casillas de selección reemplazan los textos true o false.

Es posible mejorar un poco más este aspecto:

Los apellidos estarán en negrita.

Una tarjeta centrada aparecerá para los que tengan una tarjeta de fidelidad en vez de la casilla de selección.

Las fechas contendrán el día, el mes y el año en formato español (dd/MM/aaaa)

Para conseguir este resultado, se modificarán las clases llamando a una clase especializada en la definición del
aspecto de las celdas de tablas: DefaultTableCellRenderer.

Esta clase hereda de JLabel y puede por lo tanto mostrar un texto y/o una imagen.

Implementa también la interfaz TableCellRenderer y por lo tanto permite especificar lo que se tiene que
mostrar dentro de una celda; para ello debe redefinir el método getTableCellRendererComponent.

Este método recibe seis parámetros:

public Component getTableCellRendererComponent(


JTable table,
Object value,
boolean isSelected,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

boolean hasFocus,
int row,
int column)

table: es el objeto del componente gráfico.

value: es el objeto que representa el valor a mostrar en la celda.

isSelected: true si la celda está seleccionada, false en caso contrario.

hasFocus: true si la celda tiene el foco gráfico, false en caso contrario.

row: el número (el índice) de la fila a la que pertenece la celda.

column: el número (el índice) de la columna a la que pertenece la celda.

Este método contiene también un resultado a devolver de tipo JComponent. Se trata del componente gráfico
utilizado para la visualización de la celda.

Para la clase DefaultTableCellRenderer, basta en general con devolver el renderer, es decir finalizar el método
con la siguiente línea:

return this;

Cree en el package dialogo.aspecto la clase NegritaRenderer.

package dialogo.aspecto;

import java.awt.Component;
import java.awt.Font;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class NegritaRenderer extends DefaultTableCellRenderer {


private static final long serialVersionUID = 1L;

public Component getTableCellRendererComponent(


JTable table, Object value,
boolean isSelected,
boolean hasFocus,
int row, int column) {
super.getTableCellRendererComponent(table, value,
isSelected, hasFocus,
row, column);

setFont(getFont().deriveFont(Font.BOLD));

return this;
}
}

Esta clase es muy sencilla y se encarga simplemente de poner en negrita lo que se tiene que mostrar.

No olvide llamar al método getTableCellRendererComponent de la clase madre con la instrucción super. Esto
permite inicializar muchos pequeños detalles gráficos (el color de fondo si la fila está seleccionada por ejemplo) que
son bastante molestos de implementar por uno mismo.

Esta clase es tan sencilla que ni siquiera se preocupa del valor a mostrar.
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Aqui tiene la manera de cambiar el aspecto de un valor booleano para que aparezca una imagen.

Cree la clase BooleanoRenderer siempre en el mismo package dialogo.aspecto.

package dialogo.aspecto;

import java.awt.Component;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class BooleanoRenderer extends DefaultTableCellRenderer {


private static final long serialVersionUID = 1L;

private final Icon tarjeta;

public BooleanoRenderer() {
super();
tarjeta = new ImageIcon(
getClass().getResource(
"/imagenes/gestion/Pending‐Invoice‐32.png"));
}

public Component getTableCellRendererComponent(JTable table,


Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {

super.getTableCellRendererComponent(table,
value,
isSelected, hasFocus, row, column);

Boolean tarjetaFidelidad = (Boolean)value;


setText("");
if(tarjetaFidelidad){
setIcon(tarjeta);
} else {
setIcon(null);
}
setHorizontalAlignment(CENTER);
return this;
}
}

Esta clase crea un objeto de tipo Icon que mostrará si el cliente dispone de una tarjeta de fidelidad.

Se recupera después el valor a mostrar. Como se trata con seguridad de un booleano, se hace directamente un
cast sobre este valor y se asigna el icono si el booleano vale true. Si vale false, se asigna un icono nulo, lo que
equivale a ”no mostrar ninguna imagen”.

Se crea un renderer para optimizar el aspecto gráfico. En particular, se crea un único componente gráfico con el
operador new para todas las celdas de la columna. Si olvida poner el icono a null, las visualizaciones serán
incoherentes al cabo de un tiempo. ¡Y no existe nada peor que un bug aleatorio!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree la clase InstantRenderer siempre en el mismo package dialogo.aspecto.

package dialogo.aspecto;

import java.awt.Component;
import java.time.Instant;

import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

import control.utilidades.GestionFechas;

public class InstantRenderer extends DefaultTableCellRenderer {


private static final long serialVersionUID = 1L;

public Component getTableCellRendererComponent(JTable table,


Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
Instant instant = (Instant) value;

String texto = GestionFechas.fechaEnCadenaES(instant);

this.setText(texto);
this.setHorizontalAlignment(CENTER);

return this;
}
}

Después cree la clase GestionFechas en el package control.utilidades.

package control.utilidades;

import java.text.ParseException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class GestionFechas {

private static final String MATRIZ_ES = "dd/MM/yyyy";

public static String fechaEncadenaES(Instant fecha) {


ZoneId huso = ZoneId.systemDefault();
Locale locale = Locale.ES;
DateTimeFormatter formato =
DateTimeFormatter.ofPattern(MATRIZ_ES, locale);
ZonedDateTime fechaEnHuso = fecha.atZone(huso);
return formato.format(fechaEnHuso);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

Esta clase convierte un objeto de tipo Instant en una cadena de caracteres con formato español.

El componente JTable va desde este momento a poder aprovechar todas estas presentaciones personalizadas.

Abra la clase PClientes y vaya al método setConexion().

Añada el siguiente código al final del método.

TableColumnModel modeloColumna = tabla.getColumnModel();

TableColumn nombres = modeloColumna.getColumn(1);


nombres.setCellRenderer(new NegritaRenderer());

TableColumn tarjetas = modeloColumna.getColumn(3);


tarjetas.setCellRenderer(new BooleanoRenderer());

TableColumn fechas = modeloColumna.getColumn(4);


fechas.setCellRenderer(new InstantRenderer());

La aplicación muestra a partir de ahora la tabla con el formato deseado.

La visualización de la fecha es todavía un poco pequeña con respecto al resto de la tabla, y cuando se selecciona
una fila, no cambia el color de fondo. Esto es debido al hecho de que el método
super.getTableCellRendererComponent no se ha llamado en la clase InstantRenderer.

Añada la llamada siguiente en el método getTableCellRendererComponent de la clase


InstantRenderer.

super.getTableCellRendererComponent(table,
value,
isSelected, hasFocus,
row, column);

Sitúe esta llamada al final del método. Compruebe el aspecto.

Desplace después esta llamada al inicio del método. ¿Qué diferencia observa?

4. Gestión de un simple clic


Aquí se trata de trasladar los valores de la fila seleccionada a los campos situados debajo de la tabla. El objetivo es
ofrecer una mejor lectura permitiendo de un golpe de vista leer el conjunto de datos del cliente.

Añada el siguiente código al final del método setConexion().

table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
// gestión de un simple clic en la fila de la tabla
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

// para trasladar los datos


// a los campos correspondientes
int numFila = tabla.getSelectedRow();
if (numFila >= 0) {
ModeloClientes modelo = controlCliente.getModelo();
Cliente cliente = modelo.getCliente(numFila);
txtCodigo.setText(cliente.getCodigo());
txtApellido.setText(cliente.getApellido());
txtNombre.setText(cliente.getNombre());
tarjetaFidelidad.setSelected(cliente.isTarjetaFidelidad());
// ponemos la fecha de creación con el formato español
String fecha = GestionFechas.fechaEnCadenaES(
cliente.getFechaCreacion());
txtFechaCreacion.setText(fecha);
}
}
});

5. Operaciones sobre el modelo


Ya se han creado las siguientes clases:

La clase entidad Cliente representa los datos de un cliente en la base de datos.

La clase ClienteCrud realiza operaciones básicas sobre estos datos: creación de un cliente, lectura de un cliente o
de todos los clientes, modificación y supresión de un cliente y búsqueda entre el conjunto de clientes.

La clase del modelo gráfico para la JTable ModeloClientes se encarga de gestionar la lista de clientes mostrada en
la tabla.

La clase ControlCliente se encarga de llamar al CRUD del cliente y tiene conocimiento del modelo de la tabla.

La clase gráfica de tipo panel PClientes que contiene la JTable se encarga de mostrar el modelo de la tabla.

La clase gráfica de tipo panel PCliente se encarga de mostrar un cliente concreto. Su misión es también apoyar
en la creación de un nuevo cliente y modificar un cliente existente.

Se trata, a continuación, de realizar las operaciones siguientes: añadir, modificar, buscar y eliminar, debiendo
guardar los datos en la base de datos.

a. Añadir un cliente

La sucesión general de operaciones para añadir es:

Apertura del panel PClientes para obtener la lista de clientes.

Hacer clic en el botón Añadir para mostrar el panel PCliente de creación de un cliente.

Edición de los datos del cliente.

Haga clic en el botón de guardar.

Se llama al ControlCliente para guardar el cliente con la ayuda de la clase ClienteCrud, y se actualiza el modelo
de la tabla.

Se refresca entonces la JTable enlazada al modelo para mostrar el nuevo cliente.

En esta cadena de operaciones, cabe destacar un punto importante: para que todo funcione como se desea, la
instancia PCliente debe tener el mismo ControlCliente que la instancia PClientes. Lo más sencillo es pasarlo
con un método.

Añada la siguiente línea en el método actionPerformed del escuchador de acciones del botón Añadir de
la clase PClientes.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private class ActionAnadir extends AbstractAction {


private static final long serialVersionUID = 1L;

public ActionAnadir() {
putValue(NAME, "Añadir");
putValue(SHORT_DESCRIPTION,"Añadir un nuevo cliente");
}

public void actionPerformed(ActionEvent e) {

PCliente anadir = new PCliente();


anadir.setActionCancelar(accionCancelar);
anadir.setControlCliente(controlCliente);
...

cambiarPanel(anadir ,"Añadir un nuevo cliente");


}
}

Cree un método setControlCliente() en la clase PCliente. Implica crear en la clase un atributo privado
no final de tipo ControlCliente.

void setControlCliente(ControlCliente controlCliente) {


this.controlCliente = controlCliente;
}

Cree el mismo método en la clase PClienteBuscar.

Añada una acción al botón Añadir, cuya clase hereda de la clase AbstractAction y se llamará
AccionPrincipal.

private class AccionPrincipal extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionPrincipal() {
configurarAccion(this);
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
String apellido = txtApellido.getText();
String nombre = txtNombre.getText();
boolean tarjeta = tarjetaFidelidad.isSelected();
Instant creadoEl = (Instant) fechaCreacion.getValue();

accionPrincipal(codigo,
apellido, nombre,
tarjetaFidelidad, creadoEl);
}
}

protected void configurarAccion(AbstractAction accion) {


accion.putValue(AbstractAction.NAME, "Guardar");
accion.putValue(AbstractAction.SHORT_DESCRIPTION,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

"Guardar el nuevo cliente");


}

protected void accionPrincipal(String codigo,


String apellido, String nombre,
boolean fidelidad, Instant creadoEl){
}

La clase PCliente proporcionará un código en el método accionPrincipal() que permita la creación de un


nuevo cliente. Este código se redefinirá en una subclase, por ejemplo para la modificación. El método
configurarAccion() tiene el mismo objetivo.

La principal ventaja del método accionPrincipal es que los parámetros ya están informados con los valores
introducidos por el usuario, se solicitaron ya los componentes gráficos para recuperar su valor: el método es más
senciilo de probar.

Queda un punto a resolver antes de codificar la lógica de creación: se trata de la recuperación de la fecha de
creación.

En el capítulo de Maquetas, se propuso un componente JFormattedTextField para mostrar la fecha de


creación de un cliente. La clase JFormattedTextField necesita de una instancia de una clase específica para
formatear la fecha del cliente como cadena de caracteres y convertir el valor introducido por el usuario en un
objeto de tipo Instant.

Añada el siguiente código en el constructor de la clase PCliente, un poco antes de la creación del atributo
fechaCreacion.

JFormattedTextField.AbstractFormatter formatter =
new JFormattedTextField.AbstractFormatter() {

private static final long serialVersionUID = 1L;

@Override
public String valueToString(Object value)
throws ParseException {
String text = "";
if (value instanceof Instant) {
Instant instant = (Instant) value;

text = GestionFechas.fechaEnCadenaES(instant);
}
return text;
}

@Override
public Object stringToValue(String text)
throws ParseException {
try {
return GestionFechas.cadenaESEnFecha(text);
} catch (ParseException e) {
return null;
}
}
};
fechaCreacion = new JFormattedTextField(formatter);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

AbstractFormatter permite transformar un objeto cualquiera en una cadena de caracteres para poderlo
mostrar en el JFormattedTextField. De manera inversa, permite convertir una cadena de caracteres en un
objeto.

Para utilizarlo en este caso preciso, basta con usar el siguiente código:

fechaCreacion.setValue(cliente.getFechaCreacion());
Instant creadoEl = (Instant) fechaCreacion.getValue();

Cree el método cadenaESEnFecha() en la clase GestionFechas.

public static Instant cadenaESEnFecha(String laFechaCadena)


throws ParseException {
DateTimeFormatter formato =
DateTimeFormatter.ofPattern(MATRIZ_ES);
LocalDate fecha = LocalDate.parse(laFechaCadena, formato);
LocalDateTime medianoche = fecha.atStartOfDay();
ZoneId huso = ZoneId.systemDefault();
ZonedDateTime aqui = medianoche.atZone(huso);
return aqui.toInstant();
}

Complete por fin el método accionPrincipal() de la clase PCliente.

protected void accionPrincipal(String codigo,


String apellido, String nombre,
boolean fidelidad, Instant creadoEl){
if (!codigo.equals("")) {
controlCliente.crear(codigo, apellido, nombre,
fidelidad, creadoEl);

// vacíar los campos para un nuevo cliente


txtCodigo.setText("");
txtApellido.setText("");
txtNombre.setText("");
// volvemos a poner la fecha del día por defecto
fechaCreacion.setValue(Instant.now());
tarjetaFidelidad.setSelected(false);

txtCodigo.requestFocus();
} else {
JOptionPane.showMessageDialog(null,
"La introducción del código de cliente"
+ " es obligatoria",
"Verifique lo introducido",
JOptionPane.ERROR_MESSAGE);
}
}

Este método delega la operación de creación al control cliente. El ControlCliente usa el ClienteCrud para
guardar en base de datos, y el modelo de tabla se actualiza mediante el ControlCliente después. Todas estas
operaciones se enmascaran por el hecho de que la acción gráfica para añadir solo está destinada a un objeto
«sencillo».

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para facilitar la adición de nuevos clientes, se reinicializan después los campos de introducción de datos, con el
campo de fecha de creación indicando el instante presente.

También debe codificar el método crear() de la clase ControlCliente.

Abra la clase ControlCliente y añada el método crear().

public Client crear(String codigo,


String apellido, String nombre,
boolean tarjeta, Instant creadoEl) {
if (creadoEl == null ) {
creadoEl = Instant.now();
}
// creación de una instancia cliente para obtener el CRUD
Cliente elCliente = new Cliente(codigo, apellido, nombre,
tarjeta, creadoEl);
try {
// 1. Guardar primero en la BD
crud.crear(elCliente);

return elCliente;
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna creación realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
return null;
}

La próxima etapa consiste en ocuparse de la modificación de un cliente.

b. Modificación de un cliente

La modificación se realiza desde la clase PClientes. Después de seleccionar una fila de la tabla de clientes, el
acceso a su modificación se realiza haciendo clic en el botón Modificar, o haciendo doble clic en esta fila.

Sea cual sea el modo de acceso elegido, el tratamiento es el mismo. Por esta razón se crea un método
modificacion(), que tiene como objetivo verificar que se ha seleccionado realmente una fila, y en caso
afirmativo extraer las datos a pasar al panel PCliente.

Modifique la acción del botón Modificar como sigue:

private class ActionModificar extends AbstractAction {


private static final long serialVersionUID = 1L;
public ActionModificar() {
putValue(NAME, "Modificar");
putValue(SHORT_DESCRIPTION,
"Modificar el cliente seleccionnado");
}

public void actionPerformed(ActionEvent e) {


modificacion();
}
}
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 16/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree el método modificacion() en la clase PClientes.

Aquí tiene lo que se espera de este método con más detalle:

Obligación del usuario de seleccionar antes una fila de la tabla.

Recuperación de los datos de la fila.

Paso de estos datos a una nueva instancia de la clase PCliente.

Cambio de la información del panel PCliente para reflejar la operación de modificación: su texto y su icono.

Modificación de la acción del botón principal.

Cambio de la información de la ventana: principalmente su título.

Visualización del panel de modificación.

Añada en este método el código correspondiente a la comprobación de la selección de una fila:

int numeroFila = tabla.getSelectedRow();


if (numeroFila < 0) {
// si ninguna fila seleccionada
JOptionPane.showMessageDialog(null, "Seleccione antes"
+ " la fila que desea modificar" + ’\n’
+ "o efectúe un doble clic en dicha fila",
"MODIFICACION", JOptionPane.INFORMATION_MESSAGE);
} else {
// se ha seleccionado una fila
}

Los números de fila empiezan por cero. Si no se ha seleccionado ninguna fila, el método getSelectedRow()
devuelve ­1.

Desde el momento en que se obtiene una fila, se puede recuperar el cliente desde el modelo:

Cliente cliente = controlCliente.getModelo().getCliente(numeroFila);

Ahora se trata de hacer que el panel PCliente conozca al cliente seleccionado. La opción elegida es pasarlo como
parámetro del constructor.

Cree un nuevo constructor de la clase PCliente que reciba como parámetro un objeto Cliente. El
constructor existente llamará al nuevo constructor creado con un parámetro null.

PCliente() {
this(null);
}

PCliente(Cliente cliente) {
createContents();
if (cliente != null) {
txtCodigo.setText(cliente.getCodigo());

fechaCreacion.setValue(cliente.getFechaCreacion());

txtApellido.setText(cliente.getApellido());
txtNombre.setText(cliente.getNombre());
tarjetaFidelidad.setSelected(cliente.isTarjetaFidelidad());

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 17/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

Este constructor actualiza los widgets gráficos del panel con la información después de crear dichos widgets.

En caso de modificación de un cliente, está terminantemente prohibido cambiar el código identificador. ¡Este
sirve para establecer las relaciones con los pedidos del cliente en base de datos! De la misma forma está
prohibida toda modificación de la fecha de creación, gracias el método setEditable() del componente gráfico.

// se impide la modificación
// de los campos código y fecha de creación
txtCodigo.setEditable(false);
fechaCreacion.setEditable(false);

El mismo panel usado para añadir utilizará el constructor por defecto, sin cliente pasado como parámetro, y no
tendrá por lo tanto ninguna inicialización de widgets.

Para modificar los labels del panel y la acción del botón principal, añada los accesores de estos widgets gráficos
en la clase PCliente.

Convierta la variable del label del título en atributo, si no lo ha hecho ya. Para ello, seleccione la línea de la
declaración y utilice la combinación de teclas [Ctrl] 1, y elija la opción Convert local variable to field.

Después vaya a la declaración de la propiedad del label del título, utilice [Ctrl] 1 y elija la opción Create
getter and setter for ’lblTitulo’. Elimine después el setter (es decir el mutador): no servirá de nada.

Vuelva al método modificacion() de la clase PClientes y añada el código siguiente después de la


creación del panel.

PCliente edicion = new PCliente(cliente);


edicion.getLblTitulo().setText("Edición");
edicion.getLblTitulo().setIcon(new ImageIcon(
getClass().getResource(
"/imagenes/gestion/cliente/User‐Modify‐64.png")));

El título del panel es ahora correcto cuando realizamos una modificación.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 18/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Queda ocuparse del botón principal. Este debe mostrar un ícono y un texto correcto, y claro ¡modificar
el cliente en vez de crear uno nuevo!

Modifique el código del método modificación() de la siguiente manera:

PCliente edicion = new PCliente(cliente) {

private static final long serialVersionUID = 1L;

@Override
protected void configurarAccion(AbstractAction accion) {
accion.putValue(Action.NAME, "Modificar");
accion.putValue(Action.LARGE_ICON_KEY,
new ImageIcon(
getClass().getResource(
"/imagenes/gestion/Save‐48.png")));
}

@Override
protected void accionPrincipal(String codigo,
String apellido, String nombre,
boolean fidelidad, Instant creadoEl) {
try {
boolean modificado = controlCliente.modificar(codigo,
apellido, nombre,
fidelidad, creadoEl);
if (modificado) {
accionCancelar.actionPerformed(null);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
};

Este código crea una clase interna anónima que hereda de PCliente, en la cual las operaciones críticas están
redefinidas. Aquí, las operaciones críticas son la configuración de la acción del botón y el código de la acción
principal.

Existen varias maneras de hacerlo, de las cuales algunas evitan la herencia. ¡Esta tiene sobre todo un objetivo
didáctico!

Codifique el método modificar() de la clase ControlCliente.

public boolean modificar(String codigo, String apellido, String nombre,


boolean tarjetaFidelidad, Instant creacion)
throws ParseException {

// creación de una instancia cliente para


// albergar los datos a modificar por el CRUD
Cliente elCliente = new Cliente(codigo, apellido, nombre,
tarjetaFidelidad, creacion);
// 1. Guardar primero en la BD

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 19/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

try {
elCliente = crud.modificar(elCliente);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna modificación efectuada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
return false;
}

El botón Modificar realiza ahora su rol, con las restricciones que se le han dado.

La próxima etapa consiste en mostrar el panel de modificación de cliente al hacer doble clic sobre una de las filas
de la tabla.

Añada las siguientes líneas en el escuchador de la tabla que se codificó cuando se gestionó el clic sencillo.

// gestión del doble clic en una fila de la tabla


if (evt.getClickCount() == 2) {
modificacion();
}

El evento de clic en la tabla proporciona información adicional, como el número de clics consecutivos. Es
accesible desde el método getClickCount() de la clase MouseEvent.

En lo sucesivo es posible añadir y modificar un cliente desde la interfaz gráfica. Tiene ahora que ocuparse de la
eventual supresión.

c. Supresión de un cliente

Esta supresión se lleva a cabo gracias al botón presente en la clase PClientes.

Abra la clase PClientes y navegue en el código hasta la acción de supresión en el método


actionPerformed().

private class ActionEliminar extends AbstractAction {


private static final long serialVersionUID = 1L;
public ActionEliminar() {
putValue(NAME, "Eliminar");
putValue(SHORT_DESCRIPTION,
"Eliminar el cliente seleccionado");
}

public void actionPerformed(ActionEvent e) {


// código ejecutado al hacer clic en el botón Eliminar
}
}

Modifique el método actionPerformed de la siguiente manera:

public void actionPerformed(ActionEvent e) {


int numeroFila = tabla.getSelectedRow();
if (numeroFila < 0) {
// si no se ha seleccionado ninguna fila
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 20/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.showMessageDialog(null,
"Seleccione una fila antes.",
"Eliminar",
JOptionPane.INFORMATION_MESSAGE);
} else {
// pedir confirmación al usuario
}
}

De la misma manera que para la modificación, este código comprueba que se ha seleccionado
realmente alguna fila. En caso contrario, muestra un cuadro de diálogo con información para el usuario
indicándole qué hacer.

Complete el método con el siguiente código:

if (numeroFila < 0) {
// el usuario no ha seleccionado ninguna fila
} else {
int eleccion = JOptionPane.showConfirmDialog(
null,
String.format("¿Desea eliminar la ficha del cliente?"
+ "\ncódigo : %s"
+ "\napellido : %s",
table.getValueAt(numeroFila, 0),
table.getValueAt(numeroFila, 1)),
"ELIMINAR", JOptionPane.YES_NO_OPTION);

// 0: sí 1: no
if (eleccion == JOptionPane.YES_OPTION) {
controlCliente.eliminar(numeroFila);
}
}

Este código abre un cuadro de diálogo de confirmación para el usuario, dejándole la elección entre responder sí o
no a la pregunta formulada, gracias al método estático showConfirmDialog de la clase JOptionPane.

Este método devuelve un código específico de la respuesta del usuario. Si responde SI, el control cliente elimina
realmente la fila.

En el procesamiento de los códigos devueltos, evite usar los valores literales (0 o 1). Utilice siempre las
constantes definidas en las clases.

Codifique el método eliminar() de la clase ControlCliente.

public void eliminar(int numFila) {


Cliente cliente = elModeloClientes.getCliente(numFila);
try {
crud.eliminar(cliente.getCodigo());

} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"No se ha eliminado nada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 21/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.ERROR_MESSAGE);
}
}

d. Búsqueda de un cliente

Se acaba esta primera parte del procesamiento por las búsquedas. Las impresiones y exportaciones se detallarán
en el capítulo Aplicación final.

Complete el método del escuchador correspondiente a la acción del botón Buscar.

private class AccionBuscar extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionBuscar() {
putValue(NAME, "Buscar");
putValue(SHORT_DESCRIPTION,
"Buscar entre los clientes");
}

public void actionPerformed(ActionEvent e) {

// créación de la ventana
PClienteBusqueda busqueda = new PClienteBusqueda();
busqueda.setAccionCancelar(accionCancelar);
busqueda.setControlCliente(controlCliente);
...
cambiarPanel(busqueda, "Búsqueda de cliente(s)");
}
}

A diferencia de las demás acciones, no hay que compartir el control cliente con la busqueda. La busqueda filtrará
en la tabla los clientes encontrados y al final de la búsqueda el objetivo es encontrar todos los clientes, no
filtrados.

Añada después una acción en el botón Buscar de la clase PClienteBusqueda.

private class AccionPrincipal extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionPrincipal() {
putValue(NAME, "Buscar");
putValue(SHORT_DESCRIPTION,
"Buscar entre los clientes existentes");
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
String apellido = txtApellido.getText();
String nombre = txtNombre.getText();

controlCliente.buscar(codigo, apellido, nombre);


}
}

Codifique el método buscar() de la clase ControlCliente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 22/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void buscar(String codigo, String apellido, String nombre) {


try {
List<Cliente> leidos = crud.buscar(codigo, apellido, nombre);
// los clientes leídos se utilizarán más tarde
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna búsqueda efectuada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

6. Actualización de la tabla

a. Notificación

Recuerde que en el modelo MVC existe una estricta separación de las vistas y los datos. Por lo tanto para que la
vista, en este caso el componente JTable, actualice la visualización gráfica de los datos, hay que informarle del
cambio de estado del modelo de tabla. Se trata ni más ni menos que de la aplicación del concepto de
notificación.

Para que el modelo pueda notificar a las vistas sus cambios de estado, debe disponer de métodos particulares,
siendo lo ideal tener un método para añadir, otro para modificar, otro para eliminar, etc. La clase
AbstractTableModel ya dispone de estos métodos y, como guinda del pastel, no necesita ni siquiera
redefinirlos. Pueden utilizarse como están.

Abra la clase ModeloClientes, y navegue en el método, por ejemplo el constructor.

Introduzca las letras «fire» y presione [Ctrl][Espacio].

Este atajo le muestra todos los métodos que puede utilizar de la clase que empiezan por las letras
«fire».

Queda simplemente elegir el método correspondiente.

Cree los métodos que el resto del código podrá invocar para notificar al modelo de los cambios.

public void creado(Cliente elCliente) {


losDatos.add(elCliente);
int index = losDatos.size() ‐1;
fireTableRowsInserted(index, index);
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 23/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void eliminado(int rowIndex) {


losDatos.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}

// ===========================================================
// permite actualizar el modelo
// después de nuevas búsquedas
// e informar a las vistas que muestran este modelo
public void leido(List<Cliente> nuevosDatos){
losDatos.clear();
losDatos.addAll(nuevosDatos);
fireTableDataChanged();
}

El método creado() incluye el cliente pasado como parámetro en la lista de clientes. Recupera después el
último índice de esta lista, y usa el método fireTableRowsInserted con este índice. El método
fireTableRowsInserted notificará al componente gráfico de que se ha insertado una fila, y por lo tanto
actualizará el aspecto gráfico para tener en cuenta este nuevo cliente.

El método eliminado() suprime un cliente respecto a su índice en la lista. Se llama después al método
fireTableRowsDeleted() para solicitar al componente gráfico que se refresque teniendo en cuenta que se ha
suprimido una fila concreta.

El método leido() considera que se han modificado todas las filas, y borra por lo tanto todas las existentes con
el método clear() de la lista y añade en dicha lista todos los clientes recuperados. Como se trata de una
modificación global de los datos, se notifica al componente gráfico con fireTableDataChanged() que hay que
refescar globalmente la vista.

En el caso en el que se modifica un cliente, resulta práctico crear un pequeño método que devuelva el número
de fila del cliente modificado.

private int getNumeroFila(String unCodigo) {


int numFila = ‐1;

for (int idx = 0; idx < losDatos.size(); ++idx) {


String codigo = losDatos.get(idx).getCodigo();
if(codigo.equals(unCodigo)) {
numFila = idx;
break;
}
}
return numFila;
}

public void modificado(Cliente elCliente) {


int numeroFila = getNumeroFila(elCliente.getCodigo());
if (numeroFila >= 0) {
losDatos.set(numeroFila, elCliente);
fireTableRowsUpdated(numeroFila, numeroFila);
}
}

El método modificado() recupera el número de fila correspondiente al cliente, y si se encuentra en la tabla,


actualiza este cliente en la lista. Se notifica a la tabla de este cambio llamando al método
fireTableRowsUpdated().

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 24/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

La clase ModeloClientes es cada vez más habilidosa. Ahora es capaz de:

Transformar un conjunto de datos de clientes en una tabla de doble entrada (filas y columnas).

Gestionar ella misma todo lo concerniente al modelo.

Notificar, por lo tanto informar, a las vistas afectadas con cualquier cambio del modelo.

Llame a los métodos creado(), modificado(), eliminado() y leido() de este modelo de tabla desde el
ControlCliente.

Añada la llamada a los métodos en la clase ControlCliente.

public Cliente crear(String codigo,


String apellido, String nombre,
boolean tarjeta, Instant creadoEl) {
...
try {
// 1. Guardar primero en la BD
crud.crear(elCliente);

// 2. Añadir en el modelo
elModeloClientes.creado(elCliente);

return elCliente;
} catch (Exception e) {
...
}
return null;
}

public boolean modificar(String codigo, String apellido, String nombre,


boolean tarjetaFidelidad, Instant creacion)
throws ParseException {

...
// 1. Guardar primero en la BD
try {
elCliente = crud.modificar(elCliente);

if (elCliente != null) {
elModeloClientes.modificado(elCliente);
return true;
}

} catch (Exception e) {
...
}
return false;
}

public void eliminar(int numFila) {


Cliente cliente = elModeloClientes.getCliente(numFila);
try {
crud.eliminar(cliente.getCodigo());

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 25/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

// eliminar la fila en el modelo


elModeloClientes.eliminado(numFila);
} catch (Exception e) {
...
}
}

public void buscar(String codigo, String apellido, String nombre) {


try {
List<Cliente> leidos = crud.buscar(codigo, apellido, nombre);
elModeloClientes.leido(leidos);

} catch (Exception e) {
...
}
}

b. Eventos

No se trata de interesarse en los eventos clásicos como el clic del ratón o una tecla pulsada, sino en los eventos
relativos a la modificación del estado del modelo de datos: añadir, eliminar, etc. Esto concierne a la vez a los
datos y a la estructura necesaria.

c. Escuchador de eventos

En este momento, es posible abordar otro concepto utilizado en el patrón de diseño MVC, el de escuchador de
eventos.

Efectivamente, si bien la clase ModeloClientes es capaz de informar a sus vistas, ¡sería interesante que estas
la escuchen! A esto se añade otra restricción: ¡las vistas no tienen el permiso de escuchar a cualquiera!
Únicamente están en relación con algunos objetos privilegiados, situados en un conjunto de datos especiales,
previsto a este respeto.

Vea el primer punto, el de la escucha.

Se añaden métodos del tipo «fireXXX» a la clase ModeloClientes para que el modelo se pueda expresar. ¿Qué
se deberá añadir como código para que el componente JTable entienda las notificaciones del modelo? En
realidad nada. La mayor parte de los componentes sofisticados de Java como JTable, JList o JTree están dotados
de un «buen oído» por la sencilla razón de que se construyen según la arquitectura MVC.

Con todo rigor, JTable, JList y JTree no se construyen según MVC sino según MV*. Los desarrolladores Java
decidieron históricamente fusionar los controladores y las vistas en estos componentes gráficos por razones de
simplicidad.

Pertenecen a estos componentes que disponen de modelos para acoger sus datos (TableModel para JTable,
ListModel para JList, TreeModel para JTree,...). El componente JTable se actualizará por lo tanto
automáticamente cuando el modelo de datos llame a los métodos ”fireXXX”.

Compruebe la aplicación para las operaciones de modificación y supresión. Debería confirmar que el JTable
modifica sus datos.

Veremos ahora el segundo aspecto importante de la restricción.

Dejando aparte a los privilegiados, como el componente JTable, ¿cómo podría informar el modelo a otro
interlocutor si no le conoce? Simplemente añadiendo un escuchador en la lista de escuchadores a notificar, esto
gracias al método addTableModelListener.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 26/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En el método setConexion() de la clase PClientes, añada la siguiente línea:

controlCliente.getModelo().addTableModelListener(this);

Eclipse le informa de un error en esta línea. Es normal, este método debe pasar como parámetro un
objeto que implemente la interfaz TableModelListener.

Haga clic sobre la línea con error y utilice la combinación de teclas [Ctrl] 1 para que aparezcan los Quick Fix
de Eclipse.

Elija la opción Let ’PClientes’ implement ’TableModelListener’.

El tooltip sobre fondo amarrillo (en Windows) da una idea de las modificaciones que se efectuarán.

La clase PClientes muestra un error ya que implementa la interfaz TableModelListener cuando aún no existen
los métodos de esta interfaz.

Esta interfaz implementa un escuchador de eventos, o siendo más precisos de objetos eventos relativos a los
datos o metadatos del modelo de tabla. Posee un único método tableChanged(TableModelEvent e), que se
redefinirá (es evidentemente una obligación) y completará en función de las necesidades de la aplicación.

Pase por encima del nombre de la clase con el ratón. Aparece un tooltip que describe el error y que sugiere
soluciones. Elija la primera, Add unimplemented methods.

Eclipse crea para usted en la clase PClientes el método importante de la interfaz


TableModelListener.

@Override
public void tableChanged(TableModelEvent e) {
// TODO Auto‐generated method stub

Renombre el parámetro «e» por «unEvento».

Una buena práctica de codificación es definir por lo menos tres caracteres para cualquier variable, atributo o
método, aunque sea local. ¡Nombrar una variable con un único caracter como e no hará su código más rápido
sino que lo hará seguramente menos legible!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 27/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Los eventos de la clase TableModelEvent tienen todos un tipo, accesible mediante el método getType().
Estos tipos de eventos se definen en la clase TableModelEvent como atributos estáticos.

Añada simples trazas con System.out.println para comprobar el comportamiento de este método.

public void tableChanged(TableModelEvent unEvento) {


switch (unEvento.getType()) {
case TableModelEvent.INSERT:
System.out.println("Se ha actualizado la tabla, "
+ "¡¡¡ha habido una inserción!!!");
break;
case TableModelEvent.DELETE:
System.out.println("Se ha actualizado la tabla, "
+ "¡¡¡ha habido una supresión!!!");
break;
case TableModelEvent.UPDATE:
System.out.println("Se ha actualizado la tabla,"
+ " ¡¡¡ha habido una modificación!!!");
break;
default:
break;
}
}

Una aplicación profesional no debe nunca usar los métodos print de System.out sino más bien utilizar los
mecanismos de traza de Java del package java.util.logging o de librerías de logging como SLF4J
(http://www.slf4j.org/), Log4j 2 (http://logging.apache.org/log4j/2.x/), o Logback (http://logback.qos.ch/).

Gracias a este sencillo mecanismo, se puede disponer de notificaciones sobre cualquier modificación del modelo
de datos.

La clave a recordar es grabar estos escuchadores en el modelo con el método addTableModelListener.

Queda un punto por ver: el caso específico de la búsqueda.

7. Nuevo modelo de tabla


En efecto, si se busca un cliente, los datos de la tabla se filtran para mostrar únicamente los resultados de la
búsqueda. En el estado actual del código, una vez finalizada la búsqueda, se vuelve a mostrar todo el panel
general de clientes... ¡con los datos encontrados! Por lo tanto faltarían clientes.

Para resolver este pequeño problema, el panel de búsqueda debería disponer de su propio modelo de tabla. Basta
para ello con asignarle un nuevo objeto ControlCliente. Así, las dos partes del módulo, la búsqueda y la
visualización, serán independientes.

Modifique el método actionPerformed de la acción del botón Buscar de la clase PClientes.

public void actionPerformed(ActionEvent e) {

// ‐‐‐ BÚSQUEDA EN MODO FICHA ‐‐‐


// creación de la ventana
PClienteBusqueda busqueda = new PClienteBusqueda();
busqueda.setAccionCancelar(accionCancelar);
ControlCliente control = new ControlCliente(conexion);
busqueda.setControlCliente(control);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 28/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

cambiarPanel(busqueda, "Búsqueda de cliente(s)");


}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 29/29
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Notificaciones no gráficas
Los conceptos de notificación, eventos y escuchadores no están reservados a las clases gráficas. Es posible utilizar
este mecanismo en cualquier parte de una aplicación con unas pocas líneas.

Ponga en marcha esta técnica para la clase ControlCliente, para notificar a cualquier objeto de que se ha
modificado un cliente.

Abra la clase ControlCliente y añada un atributo llamado notificaciones, de tipo


java.beans.PropertyChangeSupport:

...
import java.beans.PropertyChangeSupport;
...
public class ControlCliente {

private final ClienteCrud crud;


private final ModeloClientes elModeloClientes;

private final PropertyChangeSupport notificaciones =


new PropertyChangeSupport(this);
...

PropertyChangeSupport es una clase de utilidad de Java que ya posee el código necesario para añadir y quitar
escuchadores y generar eventos destinados a estos escuchadores.

Utilice la opción Source ­ Generate Delegate Methods... del menú contextual para añadir los métodos
addPropertyChangeListener() y removePropertyChangeListener() en la clase ControlCliente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se obtiene entonces el siguiente código:

private final PropertyChangeSupport notificaciones


= new PropertyChangeSupport(this);

public void addPropertyChangeListener(


PropertyChangeListener listener) {
this.notificaciones.addPropertyChangeListener(listener);
}

public void removePropertyChangeListener(


PropertyChangeListener listener) {
this.notificatcones.removePropertyChangeListener(listener);
}

Estos métodos permitirán grabar escuchadores sobre los eventos que genere la clase ControlCliente. Estos
escuchadores tienen únicamente la restricción de implementar la interfaz PropertyChangeListener.

La siguiente etapa consiste en generar un evento cuando la clase ControlCliente modifica un cliente. Esto se hace
llamando al método firePropertyChange de la instancia notificaciones de la clase PropertyChangeSupport.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Este método recibe como parámetro una cadena de caracteres arbitraria, el antiguo valor y el nuevo. Salvo el
primer parámetro, los valores restantes pueden ser nulos.

Cambie el método modificar() de la clase ControlCliente.

public boolean modificar(String codigo, String apellido, String nombre,


boolean tarjetaFidelidad, Instant creacion) throws
ParseException {

...
Cliente antiguoCliente = crud.leer(codigo);
...
// 1. Guardar primero en la BD
elClient = crud.modificar(elCliente);
notificaciones.firePropertyChange("cliente",
antiguoCliente,
elCliente);
// 2. Después añadir en el modelo ‐‐> ACTUALIZACIÓN del JTable auto
...
} catch
...
}
}

En la clase Cliente, utilice la opción Source ­ Generate toString() del menú contextual. Esto generará
automáticamente el método toString(), que permite visualizar explícitamente objetos de tipo Cliente en la
consola.

@Override
public String toString() {
return "Cliente [codigo=" + this.codigo
+ ", apellido=" + this.apellido
+ ", nombre=" + this.nombre
+ ", tarjeta_fidelidad=" + this.tarjeta_fidelidad
+ ", fecha=" + this.fecha + "]";
}

Para acabar, añada un escuchador, por ejemplo en el método setConexion() de la clase PClientes.

controlCliente.addPropertyChangeListener(evt ‐> {
System.out.println("Cambio " +evt.getPropertyName());
System.out.println("Antiguo valor " +evt.getOldValue());
System.out.println("Nuevo valor " +evt.getNewValue());
});

¡En toda aplicación digna de este nombre, nunca debería olvidar quitar los escuchadores (darse de baja)!

Pruebe este código ejecutando la aplicación y modificando un cliente. Debe ver en la consola las trazas de esta
modificación.

Para darse el gusto e ilustrar con un pequeño ejemplo de otros usos de Java, se modifica este código para
abrir además una página web.

Añada un método abrirPaginaWeb() en la clase PClientes.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private void abrirPaginaWeb(String direccion) {


if(Desktop.isDesktopSupported()) {
Desktop lanzador = Desktop.getDesktop();
if(lanzador.isSupported(Desktop.Action.BROWSE)) {
try {
lanzador.browse(new URI(direccion));
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
} else {
System.out.println("Ningún navegador web");
}
} else {
System.out.println("Ningún lanzador");
}
}

El anterior es un código muy sencillo: no se realiza ninguna gestión de errores.

Modifique el escuchador con el código:

controlCliente.addPropertyChangeListener(evt ‐> {
System.out.println("Cambio " +evt.getPropertyName());
System.out.println("Antiguo valor " +evt.getOldValue());
System.out.println("Nuevo valor " +evt.getNewValue());
abrirPaginaWeb("www.google.es");
});

¡Recuerde suprimir estos métodos después de divertirse! O mejor, desplácelos a una clase de test.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Estructura y fuentes
La estructura general, la clase ModeloCliente, la clase ControlCliente y las acciones de los botones de los paneles
PClientes y la clase PCliente se pueden ver a continuación.

Aquí tiene la estructura general de packages:

1. ModeloClientes
En esta sección encontrará el código fuente de la clase ModeloClientes.

package control.modelo;

/*
* Clase que contiene el modelo de datos de Clientes.
* Debe extender la clase abstracta AbstractTableModel
*/

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import javax.swing.table.AbstractTableModel;

import entidad.Cliente;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public class ModeloClientes extends AbstractTableModel {


private static final long serialVersionUID = 1L;

// representa las filas del modelo


private final List<Cliente> losDatos;

// los encabezados de las columnas


private static final String[] TITULOS = { "Código", "Apellido",
"Nombre", "Tarjeta Fidelidad", "Fecha Creación" };

public ModeloClientes(List<Cliente> losClientes) {


losDatos = new ArrayList<>(losClientes);
}

public int getRowCount() {


return losDatos.size();
}

public int getColumnCount() {


return TITULOS.length;
}

public String getColumnName(int columnIndex) {


return TITULOS[columnIndex];
}

// para acceder al valor de una celda


public Object getValueAt(int rowIndex, int columnIndex) {
Cliente cliente = getCliente(rowIndex);
switch (columnIndex) {
case 0:
return cliente.getCodigo();
case 1:
return cliente.getApellido();
case 2:
return cliente.getNombre();
case 3:
return cliente.isTarjetaFidelidad();
case 4:
return cliente.getFechaCreacion();
default:
return null;
}
}

public Cliente getCliente(int numeroFila) {


return losDatos.get(numeroFila);
}

/*
* útiles para los renderers por defecto que aplicarán un estilo
* de presentación de datos en función de la clase
*/
@Override
public Class<?> getColumnClass(int columnIndex) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Class<?> clase = null;


switch (columnIndex) {
case 3:
clase = Boolean.class;
break;
case 4:
clase = Instant.class;
break;
default:
clase = super.getColumnClass(columnIndex);
break;
}
return clase;
}

// para obtener el número de fila a partir del código


// al buscar en la lista
private int getNumFila(String unCodigo) {
int numFila = ‐1;

for (int idx = 0; idx < losDatos.size(); ++idx) {


String codigo = losDatos.get(idx).getCodigo();
if (codigo.equals(unCodigo)) {
numFila = idx;
break;
}
}
return numFila;
}

public void creado(Cliente elCliente) {


losDatos.add(elCliente);
int index = losDatos.size() ‐ 1;
fireTableRowsInserted(index, index);
}

public void eliminado(int indexFila) {


losDatos.remove(indexFila);
// notificación de la eliminación
// desde la fila indexFila hasta la fila indexLinea
fireTableRowsDeleted(indexFila, indexFila);
}

public void modificado(Cliente elCliente) {


int numeroFila = getNumFila(elCliente.getCodigo());
if (numeroFila >= 0) {
losDatos.set(numeroFila, elCliente);
fireTableRowsUpdated(numeroFila, numeroFila);
}
}

/*
* permite actualizar el modelo después de nuevas búsquedas e
* informar a las vistas que muestran este modelo
*/

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void leido(List<Cliente> nuevosDatos) {


losDatos.clear();
losDatos.addAll(nuevosDatos);
fireTableDataChanged();
}
}

2. ControlCliente
En esta sección encontrará el código fuente de la clase ControlCliente.

package control;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.ParseException;
import java.time.Instant;
import java.util.Collections;
import java.util.List;

import javax.swing.JOptionPane;

import control.connection.Conexion;
import control.modelo.ModeloClientes;
import entidad.Cliente;
import entidad.crud.ClienteCrud;

public class ControlCliente {

private final ClienteCrud crud;


private final ModeloClientes elModeloClientes;

private final PropertyChangeSupport notificaciones


= new PropertyChangeSupport(this);

public void addPropertyChangeListener(


PropertyChangeListener listener) {
this.notificaciones.addPropertyChangeListener(listener);
}

public void removePropertyChangeListener(


PropertyChangeListener listener) {
notificaciones.removePropertyChangeListener(listener);
}

public ControlCliente(Conexion conexion) {


crud = new ClienteCrud(conexion);
List<Cliente> clientes = null;
try {
clientes = crud.leer();
} catch (Exception e){
JOptionPane.showMessageDialog(null,
"Ninguna lectura realizada en la BD.\n\n"
+e.getMessage(),
"Problema encontrado",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.ERROR_MESSAGE);
clientes = Collections.emptyList();
}

elModeloClientes = new ModeloClientes(clientes);


}

public ModeloClientes getModelo() {


return elModeloClientes;
}

public Cliente crear(String codigo, String apellido, String nombre,


boolean tarjeta, Instant creadoEl) {
if (creadoEl == null ) {
creadoEl = Instant.now();
}
// creación de una instancia cliente para obtener el CRUD
Cliente elCliente = new Cliente(codigo, apellido, nombre,
tarjeta, creadoEl);
try {
// 1. guardar primero en la BD
crud.crear(elCliente);

// 2. depués añadir en el modelo


elModeloClientes.creado(elCliente);
return elCliente;
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna creación realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}

return null;
}

public boolean modificar(String codigo, String apellido,


String nombre,
boolean tarjetaFidelidad, Instant creacion)
throws ParseException {

// creación de una instancia cliente para albergar


// los datos a modificar por el CRUD
Cliente elCliente = new Cliente(codigo, apellido, nombre,
tarjetaFidelidad, creacion);
try {
Cliente antiguoCliente = crud.leer(codigo);
// 1. guardar primero en la BD
elCliente = crud.modificar(elCliente);
notificaciones.firePropertyChange("cliente",
antiguoCliente,
elCliente);
// 2. después añadir en el modelo
// ‐‐> ACTUALIZACIÓN del JTable auto

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

if (elCliente != null) {
elModeloClientes.modificado(elCliente);
return true;
}
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna modificación realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
return false;
}

public void eliminar(int numeroFila) {


Cliente cliente = elModeloClientes.getCliente(numeroFila);
try {
crud.eliminar(cliente.getCodigo());

// supresión de la fila en el modelo


elModeloClientes.eliminado(numeroFila);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna supresión realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public void buscar(String codigo, String apellido, String nombre) {


try {
List<Cliente> leidos = crud.buscar(codigo, apellido,
nombre);
elModeloClientes.leido(leidos);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna búsqueda realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public void buscar(String texto) {


try {
List<Cliente> leidos = crud.buscar(texto);
elModeloClientes.leido(leidos);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna búsqueda realizada en la BD.\n\n"
+ e.getMessage(), "Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

3. PClientes
En esta sección encontrará el código fuente de la clase PClientes, que define la interfaz gráfica que enumera los
clientes.

public class PClientes extends JPanel


implements TableModelListener {

private static final long serialVersionUID = 1L;

// propiedades no gráficas
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
private ControlCliente controlCliente;
private Conexion conexion;

// propiedades gráficas
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
private JTable tabla;
private JTextField txtCodigo;
private JTextField txtFechaCreacion;
private JTextField txtNombre;
private JTextField txtApellido;
private JTextField txtDireccion;
private JTextField txtTelfijo;
private JTextField txtMovil;
private JTextField txtEmail;

private JTextArea textArea;

private JCheckBox tarjetafidelidad;

private final Action cancelar = new AccionCancelar();


private final Action accionAnadir = new AccionAnadir();
private final Action accionBuscar = new AccionBuscar();
private final Action accionModificar = new AccionModificar();
private final Action accionEliminar = new Accioneliminar();

private final Action accionBienvenida = new AccionBienvenida();

private JDialog ventana;

private JButton btnModificar;

private JButton btnBuscar;

private JButton btnAnadir;

public PClientes setConexion(Conexion conexion) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

this.conexion = conexion;
controlCliente = new ControlCliente(conexion);

tabla.setModel(controlCliente.getModelo());
controlCliente.getModelo().addTableModelListener(this);
controlCliente.addPropertyChangeListener(evt ‐> {
System.out.println("Cambio " +evt.getPropertyName());
System.out.println("Antiguo valor " +evt.getOldValue());
System.out.println("Nuevo valor " +evt.getNewValue());
});
// gestión del aspecto de las columnas del JTable
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
TableColumnModel modeloColumna = tabla.getColumnModel();
TableColumn nombres = modeloColumna.getColumn(1);
nombres.setCellRenderer(new NegritaRenderer());
TableColumn tarjetas = modeloColumna.getColumn(3);
tarjetas.setCellRenderer(new BooleanoRenderer());
TableColumn fechas = modeloColumna.getColumn(4);
fechas.setCellRenderer(new InstantRenderer());

tabla.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// gestión del clic sencillo en alguna fila de la tabla
// para poner los datos
// en los campos correspondientes
int numFila = tabla.getSelectedRow();
if (numFila >= 0) {
ModeloClientes modelo = controlCliente.getModelo();
Cliente cliente = modelo.getCliente(numFila);
txtCodigo.setText(cliente.getCodigo());
txtApellido.setText(cliente.getApellido());
txtNombre.setText(cliente.getNombre());
tarjetaFidelidad.setSelected(client.isTarjetafidelidad());
// se pone la fecha de creación con el formato dd‐MM‐yyyy
String strFecha =
GestionFechas.fechaEnCadenaES(
cliente.getFechaCreacion());
txtFechaCreacion.setText(strFecha);
}

// gestión del doble clic en una fila de la tabla


// para modificar en modo ficha
if (e.getClickCount() == 2) {
modificacion();
}
}
});

return this;
}

public void setVentana(JDialog ventana) {


this.ventana = ventana;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private JDialog getVentana() {


return ventana;
}

private void cambiarPanel(JPanel panel, String titulo) {


Window ventana = getVentana();
if (ventana instanceof JDialog) {
JDialog dialogo = (JDialog) ventana;
dialogo.setContentPane(panel);
dialogo.setTitle(titulo);
}
ventana.revalidate();
}

private ImageIcon getImagen(String imagen) {


return new ImageIcon(getClass().getResource(imagen));
}
...
}

// método de modificación invocado con un clic en Modificación


// o con un doble clic en alguna fila de la tabla
private void modificacion() {
int numeroFila = tabla.getSelectedRow();
if (numeroFila < 0) {
// si ninguna fila seleccionada
JOptionPane.showMessageDialog(null,
"Seleccione antes"
+ " la fila a modificar" + ’\n’
+ "o efectúe un doble clic en la fila",
"MODIFICACIÓN", JOptionPane.INFORMATION_MESSAGE);
} else {
// se recuperan antes los datos
// a partir de la fila seleccionada
Cliente cliente =
controlCliente.getModelo().getCliente(numeroFila);

// se crea la ventana Ficha Cliente


PCliente edicion = new PCliente(cliente) {

private static final long serialVersionUID = 1L;

@Override
protected void configurarAccion(
AbstractAction accion) {
action.putValue(Action.NAME, "Modificar");
action.putValue(Action.LARGE_ICON_KEY,
getImage(
"/imagenes/gestion/Save‐48.png"));
}

@Override
protected void accionPrincipal(String codigo,
String apellido,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

String nombre,
boolean fidelidad,
Instant creadoEl) {
try {
boolean modificado =
controlCliente.modificar(
codigo, apellido, nombre,
fidelidad, creadoEl);
if (modificado) {
cancelar.actionPerformed(null);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
};
edicion.getLblTitulo().setText("Edición");
edicion.getLblTitulo().setIcon(getImagen(
"/imagenes/gestion/cliente/User‐Modify‐64.png"));
edicion.setAccionCancelar(accionCancelar);
edicion.setAccionExport(accionExport);

btnModificar.setIcon(getImagen(
"/imagenes/gestion/Data‐Edit‐48.png"));
cambiarPanel(edicion, "Modificación del cliente "
+cliente.getNombre() +" "
+cliente.getApellido()
+"["+cliente.getCodigo() +"]");
}
}

private class AccionBienvenida extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionBienvenida() {
putValue(NAME, "Bienvenida");
putValue(SHORT_DESCRIPTION,
"Volver a la pantalla de bienvenida");
}

public void actionPerformed(ActionEvent e) {


getVentana().dispose();
}
}

private class AccionCancelar extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionCancelar() {
putValue(NAME, "Cancelar");
putValue(SHORT_DESCRIPTION,
"Cancelar la acción en curso");
}

public void actionPerformed(ActionEvent e) {


cambiarPanel(PClientes.this, "Gestión de Clientes");
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

private class AccionEliminar extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionEliminar() {
putValue(NAME, "Eliminar");
putValue(SHORT_DESCRIPTION,
"Eliminar el cliente seleccionado");
}

public void actionPerformed(ActionEvent e) {


int numeroFila = tabla.getSelectedRow();
if (numeroFila < 0) {
// si no se ha seleccionado ninguna fila
JOptionPane.showMessageDialog(null,
"Seleccione antes una fila.",
"Eliminar",
JOptionPane.INFORMATION_MESSAGE);
} else {
int eleccion = JOptionPane.showConfirmDialog(
null,
String.format(
"¿Desea eliminar "
+"la ficha del cliente?"
+ "\ncódigo : %s"
+ "\napellido : %s",
tabla.getValueAt(numeroFila, 0),
tabla.getValueAt(numeroFila, 1)),
"ELIMINAR",
JOptionPane.YES_NO_OPTION);
// 0 : si 1 : no
if (eleccion == JOptionPane.YES_OPTION) {
controlCliente.eliminar(numeroFila);
}
}
}
}

private class AccionModificar extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionModificar() {
putValue(NAME, "Modificar");
putValue(SHORT_DESCRIPTION,
"Modificar el cliente seleccionado");
}

public void actionPerformed(ActionEvent e) {


modificacion();
}
}

4. PCliente

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private class AccionBuscar extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionBuscar() {
putValue(NAME, "Buscar");
putValue(SHORT_DESCRIPTION,
"Buscar entre los clientes");
}

public void actionPerformed(ActionEvent e) {

// ‐‐‐ BÚSQUEDA EN MODO FICHA ‐‐‐


// creación de la ventana
PClienteBusqueda busqueda = new PClienteBusqueda();
busqueda.setAccionCancelar(accionCancelar);
ControlCliente control = new ControlCliente(conexion);
busqueda.setControlCliente(control);

btnBuscar.setIcon(getImage(
"/imagenes/gestion/Search‐48.png"));
cambiarPanel(busqueda, "Búsqueda de cliente(s)");
}
}

private class AccionAñadir extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionAñadir() {
putValue(NAME, "Añadir");
putValue(SHORT_DESCRIPTION,
"Añadir un nuevo cliente");
}

public void actionPerformed(ActionEvent e) {


// ‐‐‐ AÑADIR EN MODO FICHA ‐‐‐
PCliente anadir = new PCliente();
anadir.setAccionCancelar(accionCancelar);
anadir.setControlCliente(controlCliente);

btnAnadir.setIcon(getImage(
"/imagenes/gestion/Add‐New‐48.png")));
cambiarPanel(anadir, "Añadir un nuevo cliente");
}
}

En esta sección encontrará el código fuente de la clase PCliente, que permite visualizar un cliente determinado.

public class PCliente extends JPanel {

private static final long serialVersionUID = 1L;

// propriedades no gráficas
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
private ControlCliente controlCliente;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

// propriedades gráficas
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
private JTextField txtCodigo;
private JFormattedTextField fechaCreacion;
private JTextField txtNombre;
private JTextField txtApellido;
private JTextField txtDireccion;
private JTextField txtTelfijo;
private JTextField txtMovil;
private JTextField txtEmail;

private JTextArea textArea;

private JCheckBox tarjetafidelidad;

private final Action accionPrincipal = new AccionPrincipal();

private JLabel lblTitulo;


private JTextField txtCodigopostal;
private JTextField txtCiudad;

private JButton btnBienvenida;

private final class Formateador extends


JFormattedTextField.AbstractFormatter {
private static final long serialVersionUID = 1L;

@Override
public String valueToString(Object value)
throws ParseException {
String text = "";
if (value instanceof Instant) {
Instant instant = (Instant) value;

text =
GestionFechas.fechaEnCadenaES(instant);
}
return text;
}

@Override
public Object stringToValue(String text)
throws ParseException {
try {
return
GestionFechas.cadenaESenFecha(text);
} catch (ParseException e) {
return null;
}
}
}

PCliente setControlCliente(ControlCliente controlCliente) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

this.controlCliente = controlCliente;
return this;
}

PCliente(Cliente cliente) {

JFormattedTextField.AbstractFormatter formatter =
new Formateador();
fechaCreacion = new JFormattedTextField(formatter);

createContents();
if (cliente != null) {
txtCodigo.setText(cliente.getCodigo());
// se impide la modificación
// de los campos código y fecha de creación
txtCodigo.setEditable(false);
fechaCreacion.setValue(cliente.getFechaCreacion());
fechaCreacion.setEditable(false);

txtApellido.setText(cliente.getApellido());
txtNombre.setText(cliente.getNombre());
tarjetafidelidad.setSelected(cliente.isTarjetaFidelidad());
}
}
...
}

private class AccionPrincipal extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionPrincipal() {
configurarAccion(this);
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
String apellido = txtApellido.getText();
String nombre = txtNombre.getText();
boolean tarjetaFidelidad = tarjetafidelidad.isSelected();
Instant creadoEl = (Instant) fechaCreacion.getValue();

accionPrincipal(codigo, apellido, nombre,


tarjetaFidelidad, creadoEl);
}
}

protected void configurarAccion(AbstractAction accion) {


accion.putValue(AbstractAction.NAME, "Guardar");
accion.putValue(AbstractAction.SHORT_DESCRIPTION,
"Guardar el cliente nuevo");
}

protected void accionPrincipal(String codigo,


String apellido, String nombre,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

boolean fidelidad, Instant creadoEl) {


if (!codigo.equals("")) {
controlCliente.crear(codigo, apellido, nombre,
fidelidad, creadoEl);

// se vacían los campos para añadir un nuevo cliente


txtCodigo.setText("");
txtApellido.setText("");
txtNombre.setText("");
// se vuelve a indicar la fecha por defecto
fechaCreacion.setValue(Instant.now());
tarjetafidelidad.setSelected(false);

txtCodigo.requestFocus();
} else {
JOptionPane.showMessageDialog(null,
"La introducción del código de cliente"
+ " es obligatoria",
"Verifique lo introducido",
JOptionPane.ERROR_MESSAGE);
}
}

JLabel getLblTitulo() {
return lblTitulo;
}

void setAccionCancelar(Action accion) {


btnBienvenida.setAction(accion);
UI.quitarAspectoBoton(btnBienvenida, "Cancelar");
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Introducción
Este capítulo está dedicado a los otros dos módulos importantes del proyecto, los módulos artículo y pedido.

En la gestión de clientes, se ha manipulado una sola tabla de la base de datos. Esta vez, el trabajo abarca un
entorno multitabla.

Se usa también el design pattern MVC. Se expusieron y explicaron los conceptos subyacentes en el capítulo Modelo
MVC.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Clases asociadas
A diferencia del módulo cliente, los módulos artículo y pedido hacen intervenir las relaciones entre diferentes
objetos.

Por ejemplo, un pedido hace referencia a líneas de pedido, que se almacenan físicamente en una tabla separada.
Esta referencia se establece gracias al sistema de clave primaria ­ clave foránea: para cada línea de pedido de la
tabla correspondiente, existe una columna que indica la clave primaria de la tabla asociada. Esta columna
almacena la clave foránea del pedido para cada línea.

JPA permite gestionar directamente este principio de clave, y por lo tanto preservar la integridad referencial de la
base de datos: la base de datos queda en un estado coherente.

Sin embargo es importante comprender este principio ya que las consultas utilizadas para interrogar a la base de
datos deben tener en cuenta este mecanismo.

Las clases asociadas son de dos tipos: entidad y control.

El tipo Entidad hace referencia a una base de datos que modeliza el dominio, en el package entidad, y a una clase
CRUD, que describe las operaciones posibles, situada en el package entidad.crud.

El tipo control hace referencia a una clase que modeliza el modelo del componente gráfico asociado, situado en el
package control.modelo y a una clase que enlaza el CRUD con los modelos en el package control.

Para no hacer este capítulo innecesariamente pesado, puede descargar las clases asociadas desde la página
Información.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/1
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de los artículos


Se hace un resumen del punto en el que se encuentran los artículos. La aplicación ya dispone de:

la clase Articulo,

la clase ArticuloCrud,

la clase FArticulos actualmente como maqueta.

1. Visualización
Para mostrar los datos de los artículos en el JTable de la clase FArticulos, los pasos son idénticos a los vistos para
la clase PClientes del capítulo Modelo MVC:

Creación de la clase que representa el modelo de datos gráfico.

Añadir el modelo al JTable para su visualización.

a. Creación del modelo gráfico

Cree una nueva clase ModeloArticulos y guárdela en el package control.modelo.

El código es muy similar al de la clase ModeloClientes.

package control.modelo;

import java.util.ArrayList;
import java.util.List;

import javax.swing.table.AbstractTableModel;

import entidad.Articulo;

public class ModeloArticulos extends AbstractTableModel {


private static final long serialVersionUID = 1L;
private final List<Articulo> losDatos;
private final String[] losTitulos =
{"Código", "Código Categoría", "Designación",
"Cantidad", "Precio unitario"};

public ModeloArticulos(List<Articulo> losArticulos) {


losDatos = new ArrayList<>(losArticulos);
}

public int getRowCount() {


return losDatos.size();
}
public int getColumnCount() {
return losTitulos.length;
}
public String getColumnName(int columnIndex) {
return losTitulos[columnIndex];
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public Object getValueAt(int rowIndex, int columnIndex) {


Articulo unArticulo = getArticulo(rowIndex);
switch(columnIndex){
case 0:
return unArticulo.getCodigo();
case 1:
return unArticulo.getCategoria().getCodigo();
case 2:
return unArticulo.getDesignacion();
case 3:
return unArticulo.getCantidad();
case 4:
return unArticulo.getPrecioUnitario();
default:
return null;
}
}

public void creado(Articulo unArticulo) {


losDatos.add(unArticulo);
int linea = losDatos.size() ‐1;
fireTableRowsInserted(linea, linea);
}

public void eliminado(int rowIndex) {


losDatos.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}

public void modificado(int numeroLinea, Articulo unArticulo) {


losDatos.set(numeroLinea, unArticulo);
fireTableRowsUpdated(numeroLinea, numeroLinea);
}

public void leido(List<Articulo> nuevosDatos){


losDatos.clear();
losDatos.addAll(nuevosDatos);
fireTableDataChanged();
}

public Articulo getArticulo(int linea) {


return losDatos.get(linea);
}
}

b. Creación del control

El control de artículos se hace gracias a una clase específica. La clase gráfica no conocerá este tipo, y por lo
tanto no tendrá que gestionar directamente los accesos a la base de datos.

Esta manera de trabajar permite obtener un código modular y asignar funcionalidades bien identificadas a las
diferentes clases de la aplicación.

Cree una clase ControlArticulo en el package control.

Contrariamente a la clase ControlCliente, no existe la gestión de las excepciones a este nivel. En efecto, no
debería existir aquí la visualización de un cuadro de diálogo en caso de error: la visualización gráfica no es
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

package control;

import java.awt.Window;
import java.time.Instant;
import java.util.Collections;
import java.util.List;

import control.connection.Conexion;
import control.estado.JasperFacade;
import control.modelo.ModeloArticulos;
import dialogo.FExport;
import entidad.Articulo;
import entidad.crud.ArticuloCrud;

public class ControlArticulo {

private final ArticuloCrud crud;


private final ModeloArticulos elModeloArticulo;

public ControlArticulo(Conexion laConexion) {


crud = new ArticuloCrud(laConexion);
List<Articulo> vacio = Collections.emptyList();
elModeloArticulo = new ModeloArticulos(vacio);
}

public void inicializar() {


List<Articulo> articulos = crud.leer();
elModeloArticulo.leido(articulos);
}

public boolean crear(String codigo, String referencia,


String designacion,
int cantidad, double precioUnitario) {
Instant ahora = Instant.now();

Articulo unArticulo = new Articulo(codigo, referencia,


designacion,
cantidad, precioUnitario,
ahora);

crud.crear(unArticulo);
elModeloArticulo.creado(unArticulo);
return true;
}

public boolean modificar(int numeroLinea,


String codigo, String referencia,
String designacion,
int cantidad, double precioUnitario) {
Instant ahora = Instant.now();

Articulo unArticulo = new Articulo(codigo, referencia,


designacion,
cantidad, precioUnitario,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

ahora);
boolean modif = crud.modificar(codigo, referencia,
designacion,
cantidad, precioUnitario);
if (modif) {
elModeloArticulo.modificado(numeroLinea, unArticulo);
}
return modif;
}

public void eliminar(int numeroLinea, String codigo) {


crud.eliminar(codigo);
elModeloArticulo.eliminado(numeroLinea);
}

public void buscar(String busqueda) {


List<Articulo> nuevaLista = crud.buscarTodos(busqueda);
elModeloArticulo.leido(nuevaLista);
}

public void refrescar() {


elModeloArticulo.leido(crud.leer());
}

public ModeloArticulos getModelo() {


return elModeloArticulo;
}
}

habitualmente responsabilidad del control.

Para simplificar el desarrollo, se crea una clase que utiliza un objeto ControlArticulo, que efectuará estas
operaciones de visualización en caso de error.

Para ello, el patrón de diseño Decorator es una fuente de inspiración: la nueva clase recibe como parámetro un
objeto de tipo ControlArticulos y tiene exactamente los mismos métodos que esta.

Para respetar el espíritu del patrón de diseño Decorator, se debería crear una interfaz que formaliza la
funcionalidad de un control de artículo implementado por las clases ControlArticulo y ControlAticuloDecorado.

Cree una clase ControlArticuloDecorado.

package control;

import java.awt.Window;
import java.util.function.Function;

import javax.swing.JOptionPane;

import control.connection.Conexion;
import control.modelo.ModeloArticulos;

public class ControlArticuloDecorado {

private final ControlArticulo control;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public ControlArticuloDecorado(Conexion laConexion) {


this(new ControlArticulo(laConexion));
}

public ControlArticuloDecorado(ControlArticulo control) {


this.control = control;
}

public void inicializar() {


try {
control.inicializar();
} catch (Exception e){
JOptionPane.showMessageDialog(null,
"Ninguna lectura realizar en la BD.\n\n"
+e.getMessage(),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public ModeloArticulos getModelo() {


return this.control.getModelo();
}

public boolean crear(String codigo, String referencia,


String designacion,
int cantidad, double precioUnitario) {
try {
return control.crear(codigo, referencia,
designacion,
cantidad, precioUnitario);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Ninguna inserción realizada en la BD.\n\n"
+ e.getMessage(),
"Problema encontrado", JOptionPane.ERROR_MESSAGE);
return false;
}
}

public boolean modificar(int numeroLinea, String codigo,


String referencia,
String designacion,
int cantidad, double precioUnitario) {
try {
return control.modificar(numeroLinea, codigo,
referencia, designacion,
cantidad, precioUnitario);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna modificación realizada en la BD.\n\n"
+ e.getMessage(),
"Problema encontrado",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.ERROR_MESSAGE);
return false;
}
}

private void operacion(Runnable operacion,


Function<Exception, String> mensaje) {
try {
operacion.run();
} catch (Exception exception) {
JOptionPane.showMessageDialog(null,
mensaje.apply(exception),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public void eliminar(int numLinea, String codigo) {


operacion(() ‐> control.eliminar(numLinea, codigo),
e ‐> "Ninguna supresión realizada en la BD.\n\n"
+ e.getMessage());
}

public void buscar(String busqueda) {


operacion(() ‐> control.buscar(busqueda),
e ‐> "Ninguna búsqueda realizada en la BD.\n\n"
+ e.getMessage());
}

public void refrescar() {


operacion(() ‐> control.refrescar(),
e ‐> "Ninguna lectura realizada en la BD.\n\n"
+ e.getMessage());
}
}

La única responsabilidad de esta clase es mostrar un cuadro de diálogo en caso de error.

Para obtener una mejor legibilidad, se crea un método privado operacion().

private void operacion(Runnable operacion,


Function<Exception, String> mensaje) {
try {
operacion.run();
} catch (Exception exception) {
JOptionPane.showMessageDialog(null,
mensaje.apply(exception),
"Problema encontrado", JOptionPane.ERROR_MESSAGE);
}
}

Este método recibe dos parámetros: un objeto que implementa la interfaz Runnable que posee un único
método llamado run(), y un objeto que implementa la interfaz Function que posee un único método apply
que recibe como parámetro la eventual excepción y devuelve una cadena de caracteres que describe el error.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Estas dos interfaces tienen un único método y por lo tanto son interfaces funcionales. Es posible crear
expresiones lambdas para simplificar la legibilidad del código.

public void eliminar(int numLinea, String codigo) {


operacion(() ‐> control.eliminar(numLinea, codigo),
e ‐> "Ninguna supresión realizada en la BD.\n\n"
+ e.getMessage());
}

Este código es equivalente a:

public void eliminar(int numLinea, String codigo) {


operacion(new Runnable() {
public void run() {
control.eliminar(numLinea, codigo);
}
}, new Function<Exception, String>() {

@Override
public String apply(Exception e) {
return "Ninguna supresión realizada "
+ "en la BD.\n\n"
+ e.getMessage();
}
});
}

Ahora queda hacer que la ventana gráfica utilice un objeto de tipo ControlArticuloDecorado.

Complete el código del método setConexion() de la clase FArticulos.

// propiedad de control de la clase gráfica


private ControlArticuloDecorado controlArticulo;

...

public FArticulos setConexion(Conexion conexion) {


controlArticulo = new ControlArticuloDecorado(conexion);
controlArticulo.inicializar();

tabla.setModel(controlArticulo.getModelo());

// gestión del aspecto de las columnas del JTable


// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
TableColumnModel columnas = tabla.getColumnModel();
TableColumn columna = columnas.getColumn(1);
columna.setCellRenderer(new NegritaRenderer());
}

2. Añadir
Se introduce una variación con respecto al código del módulo cliente: añadir (y todas las demás operaciones sobre
los artículos) se hace directamente desde la ventana FArticulos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Abra la clase FArticulos.

Seleccione el botón Añadir y agregue una acción que se ejecutará al hacer clic encima.

private class AccionAnadir extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionAnadir() {
putValue(NAME, "Añadir");
putValue(SHORT_DESCRIPTION,
"Añadir un nuevo artículo");
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
if (!codigo.equals("")) {
String referencia = txtCategoria.getText();
String designacion = txtDesignacion.getText();
int cantidad = (Integer)
sliderCantidad.getValue();
double precio = ((Number)
txtPrecioUnitario.getValue())
.doubleValue();
boolean creacion =
controlArticulo.crear(codigo, referencia,
designacion,
cantidad, precio);
if (creacion) {
borrarIntroduccion();
}
} else {
JOptionPane.showMessageDialog(null,
"El código de artículo es obligatorio",
"Alerta",
JOptionPane.WARNING_MESSAGE);
}
}
}

El código del método actionPerformed se descompone de la siguiente manera:

Primero se recupera el texto correspondiente al código del artículo, introducido por el usuario.

Se realiza después una comprobación: si el código está vacío, es imposible crear el artículo y por lo tanto aparece
un mensaje de error.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Si se supera la comprobación, se recuperan los otros datos introducidos por el usuario, y el


ControlArticuloDecorado crea un nuevo artículo a partir de esta información. Si no se puede crear un nuevo
artículo, el código de la clase ControlArticuloDecorado se ejecuta y aparece un cuadro de diálogo de error.

Si se ha creado el artículo correctamente, se borran los datos introducidos por el usuario gracias al método
borrarIntroduccion().

Cree el método borrarIntroduccion().

private void borrarIntroduccion() {


txtCodigo.setText("");
txtCategoria.setText("");
txtDesignacion.setText("");
sliderCantidad.setValue(Integer.valueOf(1));
txtPrecioUnitario.setValue(Double.valueOf(0));
txtCodigo.requestFocus();
}

El botón Borrar no suprime el artículo. Su cometido es borrar todos los datos introducidos por el
usuario.

Seleccione el botón Borrar en modo Design y añádale una nueva acción.

private class AccionBorrar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionBorrar() {
putValue(NAME, "Borrar");
putValue(SHORT_DESCRIPTION,
" Borrar los datos introducidos");
}

public void actionPerformed(ActionEvent e) {


borrarIntroduccion();
}
}

3. Modificación
Para activar el botón Modificar, antes es preciso hacer doble clic en alguna de las filas del JTable para
seleccionarla. Los valores aparecen entonces en los campos de la zona de introducción de datos.

a. Selección de un artículo

Seleccione el componente gráfico de tipo JTable en modo Design. Añada un escuchador de eventos para
el clic del ratón gracias a la operación Add Event Handler ­ mouse ­ mousePressed del menú
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

contextual.

Añada el código siguiente en el método mousePressed que acaba de crear con WindowBuilder.

tabla.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 2) {
int linea = tabla.getSelectedRow();
txtCodigo.setText(String.valueOf(
table.getValueAt(linea, 0)));
txtCategoria.setText(String.valueOf(
table.getValueAt(linea, 1)));
txtDesignacion.setText(String.valueOf(
table.getValueAt(linea, 2)));

sliderCantidad.setValue(
table.getValueAt(linea,3));
txtPrecioUnitario.setValue(
table.getValueAt(linea,4));
boton_modo_anadir_o_edicion(false);
}
}
});

Este método recupera los valores del artículo a partir de las celdas del JTable.

La versión alternativa siguiente utiliza directamente el artículo seleccionado:

tabla.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 2) {
int numeroLinea = tabla.getSelectedRow();
Articulo articulo =
controlArticulo.getModelo()
.getArticulo(numeroLinea);
txtCodigo.setText(articulo.getCodigo());
txtCategoria.setText(
articulo.getCategoria().getCodigo());
txtDesignacion.setText(articulo.getDesignacion());

sliderCantidad.setValue(articulo.getCantidad());
txtPrecioUnitario.setValue(
articulo.getPrecioUnitario());
boton_modo_anadir_o_edicion(false);
}
}
});

¿Cuál de las dos maneras prefiere?

Los botones Añadir y Borrar se deshabilitan gracias al método boton_modo_anadir_o_edicion() para


ganar en claridad.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private void boton_modo_anadir_o_edicion(boolean anadir) {


if (anadir) {
accionAnadir.setEnabled(true);
accionEliminar.setEnabled(false);
accionModificar.setEnabled(false);
accionBorrar.setEnabled(true);
} else {
accionAnadir.setEnabled(true);
accionEliminar.setEnabled(true);
accionModificar.setEnabled(true);
accionBorrar.setEnabled(true);
}
}

b. Guardar la modificación

Seleccione el botón Modificar y añádale una acción.

private class AccionModificar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionModificar() {
putValue(NAME, "Modificar");
putValue(SHORT_DESCRIPTION,
"Modificar el artículo seleccionado");
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
if ("".equals(codigo)) {
JOptionPane.showMessageDialog(null,
"El código del artículo es obligatorio",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
String categoria = txtCategoria.getText();
String designacion = txtDesignacion.getText();
int cantidad = (Integer)
sliderCantidad.getValue();
double precio = ((Number)
txtPrecioUnitario.getValue())
.doubleValue();
int numeroLinea = tabla.getSelectedRow();
if (numeroLinea < 0) {
JOptionPane.showMessageDialog(null,
"Seleccione un artículo",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
boolean modif =
controlArticulo.modificar(
numeroLinea,
codigo, categoria,
designacion,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

cantidad, precio);
if (modif) {
borrarIntroduccion();

boton_modo_anadir_o_edicion(true);
}
}
}
}
}

Como antes, se realizan dos comprobaciones para validar la introducción de datos por parte del usuario. Si todo
es correcto, los botones Añadir y Borrar se vuelven a habilitar para añadir un nuevo artículo.

4. Eliminar
Al igual que para el módulo de clientes, se utiliza el botón Eliminar que se encuentra en la parte izquierda para
eliminar un artículo.

Seleccione el botón Eliminar en modo Design y agregue una acción.

private class AccionEliminar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionEliminar() {
putValue(NAME, "Eliminar");
putValue(SHORT_DESCRIPTION,
"Eliminar el artículo seleccionado");
}

public void actionPerformed(ActionEvent e) {


int linea = tabla.getSelectedRow();
if (linea < 0) {
// si no se ha seleccionado ninguna línea
JOptionPane.showMessageDialog(null,
"Debe seleccionar "
+ "una línea para eliminarla",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
int eleccion = JOptionPane.showConfirmDialog(
null,
"¿Desea realmente eliminar"
+ " el artículo seleccionado?",
"Confirmación",
JOptionPane.YES_NO_OPTION);
if (eleccion == JOptionPane.YES_OPTION) {
Articulo articulo =
controlArticulo.getModelo()
.getArticulo(linea);

String codigo = articulo.getCodigo();


controlArticulo.eliminar(linea,
codigo);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}
}
}

5. Búsqueda
A diferencia del módulo de cliente, se pone en marcha una búsqueda asistida para los artículos directamente en la
ventana.

Cada vez que se pulsa alguna tecla se realiza una actualización de la lista de artículos visibles en la tabla.

La búsqueda propuesta se realiza sobre los campos Código, Código Categoría y Designación de los artículos.

Debajo tiene un ejemplo tras introducir la letra «b»:

Tecleando una segunda letra, como una «u», la lista se reduce y solo se muestra la línea correspondiente:

Seleccione el JTextField de búsqueda en modo Design y añádale un administrador de evento de tipo


keyReleased con la opción Add event handler ­ key ­ keyReleased.

txtBusqueda.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent event) {
String busqueda = txtBusqueda.getText();
controlArticulo.buscar(busqueda);
}
});

Este código llega a su fin pero no está exento de problemas:

Si el usuario presiona varias teclas, esto desencadenará consultas a la base de datos evitables.

¡Y sobre todo, SOBRE TODO, la búsqueda se realiza en el thread gráfico!

Una versión un poco más evolucionada hace uso de clases especializadas en la gestión de la ejecución del código
en paralelo: las clases del package java.util.concurrent.

Añada este código en la clase FArticulos y organice los imports con [Ctrl][Shift] O para que la clase compile.

private final ScheduledExecutorService executor =


Executors.newSingleThreadScheduledExecutor();

private final Buscador buscador = new Buscador();

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private final class Buscador implements Runnable {

private Future<?> futuro;


private String busqueda;

@Override
public void run() {
System.out.println("Búsqueda de " +busqueda);
controlArticulo.buscar(busqueda);
this.futuro = null;
}

public void planificar(String busqueda) {


if (this.futuro != null) {
System.out.println(
"Anula la búsqueda planificada de "
+this.busqueda
+" para " +busqueda);
this.futuro.cancel(false);
} else {
System.out.println("Búsqueda planificada para "
+busqueda);
}
this.busqueda = busqueda;
Future<?> futuro = executor.schedule(this,
300,
TimeUnit.MILLISECONDS);
this.futuro = futuro;
}
}

Modifique después el escuchador de teclas como sigue:

txtBusqueda.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent event) {
String busqueda = txtBusqueda.getText();
buscador.planificar(busqueda);
}
});

Este código planifica una búsqueda pasados 300 milisegundos en un hilo de ejecución dedicado, que se obtiene
gracias al objeto de tipo ScheduledExecutorService.

Si ya existe una búsqueda planificada, se anula. Esto evita realizar búsquedas innecesarias.

Queda parar el ejecutor cuando se cierra la ventana.

@Override
public void dispose() {
executor.shutdown();
super.dispose();
}

¡Seguro que no es la única manera de conseguir este comportamiento!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se explicará la gestión de las acciones de vista previa, impresión y exportación en el último capítulo Aplicación
final.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gestión de los artículos


Se hace un resumen del punto en el que se encuentran los artículos. La aplicación ya dispone de:

la clase Articulo,

la clase ArticuloCrud,

la clase FArticulos actualmente como maqueta.

1. Visualización
Para mostrar los datos de los artículos en el JTable de la clase FArticulos, los pasos son idénticos a los vistos para
la clase PClientes del capítulo Modelo MVC:

Creación de la clase que representa el modelo de datos gráfico.

Añadir el modelo al JTable para su visualización.

a. Creación del modelo gráfico

Cree una nueva clase ModeloArticulos y guárdela en el package control.modelo.

El código es muy similar al de la clase ModeloClientes.

package control.modelo;

import java.util.ArrayList;
import java.util.List;

import javax.swing.table.AbstractTableModel;

import entidad.Articulo;

public class ModeloArticulos extends AbstractTableModel {


private static final long serialVersionUID = 1L;
private final List<Articulo> losDatos;
private final String[] losTitulos =
{"Código", "Código Categoría", "Designación",
"Cantidad", "Precio unitario"};

public ModeloArticulos(List<Articulo> losArticulos) {


losDatos = new ArrayList<>(losArticulos);
}

public int getRowCount() {


return losDatos.size();
}
public int getColumnCount() {
return losTitulos.length;
}
public String getColumnName(int columnIndex) {
return losTitulos[columnIndex];
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public Object getValueAt(int rowIndex, int columnIndex) {


Articulo unArticulo = getArticulo(rowIndex);
switch(columnIndex){
case 0:
return unArticulo.getCodigo();
case 1:
return unArticulo.getCategoria().getCodigo();
case 2:
return unArticulo.getDesignacion();
case 3:
return unArticulo.getCantidad();
case 4:
return unArticulo.getPrecioUnitario();
default:
return null;
}
}

public void creado(Articulo unArticulo) {


losDatos.add(unArticulo);
int linea = losDatos.size() ‐1;
fireTableRowsInserted(linea, linea);
}

public void eliminado(int rowIndex) {


losDatos.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}

public void modificado(int numeroLinea, Articulo unArticulo) {


losDatos.set(numeroLinea, unArticulo);
fireTableRowsUpdated(numeroLinea, numeroLinea);
}

public void leido(List<Articulo> nuevosDatos){


losDatos.clear();
losDatos.addAll(nuevosDatos);
fireTableDataChanged();
}

public Articulo getArticulo(int linea) {


return losDatos.get(linea);
}
}

b. Creación del control

El control de artículos se hace gracias a una clase específica. La clase gráfica no conocerá este tipo, y por lo
tanto no tendrá que gestionar directamente los accesos a la base de datos.

Esta manera de trabajar permite obtener un código modular y asignar funcionalidades bien identificadas a las
diferentes clases de la aplicación.

Cree una clase ControlArticulo en el package control.

Contrariamente a la clase ControlCliente, no existe la gestión de las excepciones a este nivel. En efecto, no
debería existir aquí la visualización de un cuadro de diálogo en caso de error: la visualización gráfica no es
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

package control;

import java.awt.Window;
import java.time.Instant;
import java.util.Collections;
import java.util.List;

import control.connection.Conexion;
import control.estado.JasperFacade;
import control.modelo.ModeloArticulos;
import dialogo.FExport;
import entidad.Articulo;
import entidad.crud.ArticuloCrud;

public class ControlArticulo {

private final ArticuloCrud crud;


private final ModeloArticulos elModeloArticulo;

public ControlArticulo(Conexion laConexion) {


crud = new ArticuloCrud(laConexion);
List<Articulo> vacio = Collections.emptyList();
elModeloArticulo = new ModeloArticulos(vacio);
}

public void inicializar() {


List<Articulo> articulos = crud.leer();
elModeloArticulo.leido(articulos);
}

public boolean crear(String codigo, String referencia,


String designacion,
int cantidad, double precioUnitario) {
Instant ahora = Instant.now();

Articulo unArticulo = new Articulo(codigo, referencia,


designacion,
cantidad, precioUnitario,
ahora);

crud.crear(unArticulo);
elModeloArticulo.creado(unArticulo);
return true;
}

public boolean modificar(int numeroLinea,


String codigo, String referencia,
String designacion,
int cantidad, double precioUnitario) {
Instant ahora = Instant.now();

Articulo unArticulo = new Articulo(codigo, referencia,


designacion,
cantidad, precioUnitario,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

ahora);
boolean modif = crud.modificar(codigo, referencia,
designacion,
cantidad, precioUnitario);
if (modif) {
elModeloArticulo.modificado(numeroLinea, unArticulo);
}
return modif;
}

public void eliminar(int numeroLinea, String codigo) {


crud.eliminar(codigo);
elModeloArticulo.eliminado(numeroLinea);
}

public void buscar(String busqueda) {


List<Articulo> nuevaLista = crud.buscarTodos(busqueda);
elModeloArticulo.leido(nuevaLista);
}

public void refrescar() {


elModeloArticulo.leido(crud.leer());
}

public ModeloArticulos getModelo() {


return elModeloArticulo;
}
}

habitualmente responsabilidad del control.

Para simplificar el desarrollo, se crea una clase que utiliza un objeto ControlArticulo, que efectuará estas
operaciones de visualización en caso de error.

Para ello, el patrón de diseño Decorator es una fuente de inspiración: la nueva clase recibe como parámetro un
objeto de tipo ControlArticulos y tiene exactamente los mismos métodos que esta.

Para respetar el espíritu del patrón de diseño Decorator, se debería crear una interfaz que formaliza la
funcionalidad de un control de artículo implementado por las clases ControlArticulo y ControlAticuloDecorado.

Cree una clase ControlArticuloDecorado.

package control;

import java.awt.Window;
import java.util.function.Function;

import javax.swing.JOptionPane;

import control.connection.Conexion;
import control.modelo.ModeloArticulos;

public class ControlArticuloDecorado {

private final ControlArticulo control;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public ControlArticuloDecorado(Conexion laConexion) {


this(new ControlArticulo(laConexion));
}

public ControlArticuloDecorado(ControlArticulo control) {


this.control = control;
}

public void inicializar() {


try {
control.inicializar();
} catch (Exception e){
JOptionPane.showMessageDialog(null,
"Ninguna lectura realizar en la BD.\n\n"
+e.getMessage(),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public ModeloArticulos getModelo() {


return this.control.getModelo();
}

public boolean crear(String codigo, String referencia,


String designacion,
int cantidad, double precioUnitario) {
try {
return control.crear(codigo, referencia,
designacion,
cantidad, precioUnitario);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Ninguna inserción realizada en la BD.\n\n"
+ e.getMessage(),
"Problema encontrado", JOptionPane.ERROR_MESSAGE);
return false;
}
}

public boolean modificar(int numeroLinea, String codigo,


String referencia,
String designacion,
int cantidad, double precioUnitario) {
try {
return control.modificar(numeroLinea, codigo,
referencia, designacion,
cantidad, precioUnitario);
} catch (Exception e) {
JOptionPane.showMessageDialog(
null,
"Ninguna modificación realizada en la BD.\n\n"
+ e.getMessage(),
"Problema encontrado",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.ERROR_MESSAGE);
return false;
}
}

private void operacion(Runnable operacion,


Function<Exception, String> mensaje) {
try {
operacion.run();
} catch (Exception exception) {
JOptionPane.showMessageDialog(null,
mensaje.apply(exception),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}

public void eliminar(int numLinea, String codigo) {


operacion(() ‐> control.eliminar(numLinea, codigo),
e ‐> "Ninguna supresión realizada en la BD.\n\n"
+ e.getMessage());
}

public void buscar(String busqueda) {


operacion(() ‐> control.buscar(busqueda),
e ‐> "Ninguna búsqueda realizada en la BD.\n\n"
+ e.getMessage());
}

public void refrescar() {


operacion(() ‐> control.refrescar(),
e ‐> "Ninguna lectura realizada en la BD.\n\n"
+ e.getMessage());
}
}

La única responsabilidad de esta clase es mostrar un cuadro de diálogo en caso de error.

Para obtener una mejor legibilidad, se crea un método privado operacion().

private void operacion(Runnable operacion,


Function<Exception, String> mensaje) {
try {
operacion.run();
} catch (Exception exception) {
JOptionPane.showMessageDialog(null,
mensaje.apply(exception),
"Problema encontrado", JOptionPane.ERROR_MESSAGE);
}
}

Este método recibe dos parámetros: un objeto que implementa la interfaz Runnable que posee un único
método llamado run(), y un objeto que implementa la interfaz Function que posee un único método apply
que recibe como parámetro la eventual excepción y devuelve una cadena de caracteres que describe el error.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Estas dos interfaces tienen un único método y por lo tanto son interfaces funcionales. Es posible crear
expresiones lambdas para simplificar la legibilidad del código.

public void eliminar(int numLinea, String codigo) {


operacion(() ‐> control.eliminar(numLinea, codigo),
e ‐> "Ninguna supresión realizada en la BD.\n\n"
+ e.getMessage());
}

Este código es equivalente a:

public void eliminar(int numLinea, String codigo) {


operacion(new Runnable() {
public void run() {
control.eliminar(numLinea, codigo);
}
}, new Function<Exception, String>() {

@Override
public String apply(Exception e) {
return "Ninguna supresión realizada "
+ "en la BD.\n\n"
+ e.getMessage();
}
});
}

Ahora queda hacer que la ventana gráfica utilice un objeto de tipo ControlArticuloDecorado.

Complete el código del método setConexion() de la clase FArticulos.

// propiedad de control de la clase gráfica


private ControlArticuloDecorado controlArticulo;

...

public FArticulos setConexion(Conexion conexion) {


controlArticulo = new ControlArticuloDecorado(conexion);
controlArticulo.inicializar();

tabla.setModel(controlArticulo.getModelo());

// gestión del aspecto de las columnas del JTable


// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
TableColumnModel columnas = tabla.getColumnModel();
TableColumn columna = columnas.getColumn(1);
columna.setCellRenderer(new NegritaRenderer());
}

2. Añadir
Se introduce una variación con respecto al código del módulo cliente: añadir (y todas las demás operaciones sobre
los artículos) se hace directamente desde la ventana FArticulos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Abra la clase FArticulos.

Seleccione el botón Añadir y agregue una acción que se ejecutará al hacer clic encima.

private class AccionAnadir extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionAnadir() {
putValue(NAME, "Añadir");
putValue(SHORT_DESCRIPTION,
"Añadir un nuevo artículo");
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
if (!codigo.equals("")) {
String referencia = txtCategoria.getText();
String designacion = txtDesignacion.getText();
int cantidad = (Integer)
sliderCantidad.getValue();
double precio = ((Number)
txtPrecioUnitario.getValue())
.doubleValue();
boolean creacion =
controlArticulo.crear(codigo, referencia,
designacion,
cantidad, precio);
if (creacion) {
borrarIntroduccion();
}
} else {
JOptionPane.showMessageDialog(null,
"El código de artículo es obligatorio",
"Alerta",
JOptionPane.WARNING_MESSAGE);
}
}
}

El código del método actionPerformed se descompone de la siguiente manera:

Primero se recupera el texto correspondiente al código del artículo, introducido por el usuario.

Se realiza después una comprobación: si el código está vacío, es imposible crear el artículo y por lo tanto aparece
un mensaje de error.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Si se supera la comprobación, se recuperan los otros datos introducidos por el usuario, y el


ControlArticuloDecorado crea un nuevo artículo a partir de esta información. Si no se puede crear un nuevo
artículo, el código de la clase ControlArticuloDecorado se ejecuta y aparece un cuadro de diálogo de error.

Si se ha creado el artículo correctamente, se borran los datos introducidos por el usuario gracias al método
borrarIntroduccion().

Cree el método borrarIntroduccion().

private void borrarIntroduccion() {


txtCodigo.setText("");
txtCategoria.setText("");
txtDesignacion.setText("");
sliderCantidad.setValue(Integer.valueOf(1));
txtPrecioUnitario.setValue(Double.valueOf(0));
txtCodigo.requestFocus();
}

El botón Borrar no suprime el artículo. Su cometido es borrar todos los datos introducidos por el
usuario.

Seleccione el botón Borrar en modo Design y añádale una nueva acción.

private class AccionBorrar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionBorrar() {
putValue(NAME, "Borrar");
putValue(SHORT_DESCRIPTION,
" Borrar los datos introducidos");
}

public void actionPerformed(ActionEvent e) {


borrarIntroduccion();
}
}

3. Modificación
Para activar el botón Modificar, antes es preciso hacer doble clic en alguna de las filas del JTable para
seleccionarla. Los valores aparecen entonces en los campos de la zona de introducción de datos.

a. Selección de un artículo

Seleccione el componente gráfico de tipo JTable en modo Design. Añada un escuchador de eventos para
el clic del ratón gracias a la operación Add Event Handler ­ mouse ­ mousePressed del menú
http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

contextual.

Añada el código siguiente en el método mousePressed que acaba de crear con WindowBuilder.

tabla.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 2) {
int linea = tabla.getSelectedRow();
txtCodigo.setText(String.valueOf(
table.getValueAt(linea, 0)));
txtCategoria.setText(String.valueOf(
table.getValueAt(linea, 1)));
txtDesignacion.setText(String.valueOf(
table.getValueAt(linea, 2)));

sliderCantidad.setValue(
table.getValueAt(linea,3));
txtPrecioUnitario.setValue(
table.getValueAt(linea,4));
boton_modo_anadir_o_edicion(false);
}
}
});

Este método recupera los valores del artículo a partir de las celdas del JTable.

La versión alternativa siguiente utiliza directamente el artículo seleccionado:

tabla.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.getClickCount() == 2) {
int numeroLinea = tabla.getSelectedRow();
Articulo articulo =
controlArticulo.getModelo()
.getArticulo(numeroLinea);
txtCodigo.setText(articulo.getCodigo());
txtCategoria.setText(
articulo.getCategoria().getCodigo());
txtDesignacion.setText(articulo.getDesignacion());

sliderCantidad.setValue(articulo.getCantidad());
txtPrecioUnitario.setValue(
articulo.getPrecioUnitario());
boton_modo_anadir_o_edicion(false);
}
}
});

¿Cuál de las dos maneras prefiere?

Los botones Añadir y Borrar se deshabilitan gracias al método boton_modo_anadir_o_edicion() para


ganar en claridad.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private void boton_modo_anadir_o_edicion(boolean anadir) {


if (anadir) {
accionAnadir.setEnabled(true);
accionEliminar.setEnabled(false);
accionModificar.setEnabled(false);
accionBorrar.setEnabled(true);
} else {
accionAnadir.setEnabled(true);
accionEliminar.setEnabled(true);
accionModificar.setEnabled(true);
accionBorrar.setEnabled(true);
}
}

b. Guardar la modificación

Seleccione el botón Modificar y añádale una acción.

private class AccionModificar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionModificar() {
putValue(NAME, "Modificar");
putValue(SHORT_DESCRIPTION,
"Modificar el artículo seleccionado");
}

public void actionPerformed(ActionEvent e) {


String codigo = txtCodigo.getText();
if ("".equals(codigo)) {
JOptionPane.showMessageDialog(null,
"El código del artículo es obligatorio",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
String categoria = txtCategoria.getText();
String designacion = txtDesignacion.getText();
int cantidad = (Integer)
sliderCantidad.getValue();
double precio = ((Number)
txtPrecioUnitario.getValue())
.doubleValue();
int numeroLinea = tabla.getSelectedRow();
if (numeroLinea < 0) {
JOptionPane.showMessageDialog(null,
"Seleccione un artículo",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
boolean modif =
controlArticulo.modificar(
numeroLinea,
codigo, categoria,
designacion,

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

cantidad, precio);
if (modif) {
borrarIntroduccion();

boton_modo_anadir_o_edicion(true);
}
}
}
}
}

Como antes, se realizan dos comprobaciones para validar la introducción de datos por parte del usuario. Si todo
es correcto, los botones Añadir y Borrar se vuelven a habilitar para añadir un nuevo artículo.

4. Eliminar
Al igual que para el módulo de clientes, se utiliza el botón Eliminar que se encuentra en la parte izquierda para
eliminar un artículo.

Seleccione el botón Eliminar en modo Design y agregue una acción.

private class AccionEliminar extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionEliminar() {
putValue(NAME, "Eliminar");
putValue(SHORT_DESCRIPTION,
"Eliminar el artículo seleccionado");
}

public void actionPerformed(ActionEvent e) {


int linea = tabla.getSelectedRow();
if (linea < 0) {
// si no se ha seleccionado ninguna línea
JOptionPane.showMessageDialog(null,
"Debe seleccionar "
+ "una línea para eliminarla",
"Alerta",
JOptionPane.WARNING_MESSAGE);
} else {
int eleccion = JOptionPane.showConfirmDialog(
null,
"¿Desea realmente eliminar"
+ " el artículo seleccionado?",
"Confirmación",
JOptionPane.YES_NO_OPTION);
if (eleccion == JOptionPane.YES_OPTION) {
Articulo articulo =
controlArticulo.getModelo()
.getArticulo(linea);

String codigo = articulo.getCodigo();


controlArticulo.eliminar(linea,
codigo);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}
}
}

5. Búsqueda
A diferencia del módulo de cliente, se pone en marcha una búsqueda asistida para los artículos directamente en la
ventana.

Cada vez que se pulsa alguna tecla se realiza una actualización de la lista de artículos visibles en la tabla.

La búsqueda propuesta se realiza sobre los campos Código, Código Categoría y Designación de los artículos.

Debajo tiene un ejemplo tras introducir la letra «b»:

Tecleando una segunda letra, como una «u», la lista se reduce y solo se muestra la línea correspondiente:

Seleccione el JTextField de búsqueda en modo Design y añádale un administrador de evento de tipo


keyReleased con la opción Add event handler ­ key ­ keyReleased.

txtBusqueda.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent event) {
String busqueda = txtBusqueda.getText();
controlArticulo.buscar(busqueda);
}
});

Este código llega a su fin pero no está exento de problemas:

Si el usuario presiona varias teclas, esto desencadenará consultas a la base de datos evitables.

¡Y sobre todo, SOBRE TODO, la búsqueda se realiza en el thread gráfico!

Una versión un poco más evolucionada hace uso de clases especializadas en la gestión de la ejecución del código
en paralelo: las clases del package java.util.concurrent.

Añada este código en la clase FArticulos y organice los imports con [Ctrl][Shift] O para que la clase compile.

private final ScheduledExecutorService executor =


Executors.newSingleThreadScheduledExecutor();

private final Buscador buscador = new Buscador();

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

private final class Buscador implements Runnable {

private Future<?> futuro;


private String busqueda;

@Override
public void run() {
System.out.println("Búsqueda de " +busqueda);
controlArticulo.buscar(busqueda);
this.futuro = null;
}

public void planificar(String busqueda) {


if (this.futuro != null) {
System.out.println(
"Anula la búsqueda planificada de "
+this.busqueda
+" para " +busqueda);
this.futuro.cancel(false);
} else {
System.out.println("Búsqueda planificada para "
+busqueda);
}
this.busqueda = busqueda;
Future<?> futuro = executor.schedule(this,
300,
TimeUnit.MILLISECONDS);
this.futuro = futuro;
}
}

Modifique después el escuchador de teclas como sigue:

txtBusqueda.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent event) {
String busqueda = txtBusqueda.getText();
buscador.planificar(busqueda);
}
});

Este código planifica una búsqueda pasados 300 milisegundos en un hilo de ejecución dedicado, que se obtiene
gracias al objeto de tipo ScheduledExecutorService.

Si ya existe una búsqueda planificada, se anula. Esto evita realizar búsquedas innecesarias.

Queda parar el ejecutor cuando se cierra la ventana.

@Override
public void dispose() {
executor.shutdown();
super.dispose();
}

¡Seguro que no es la única manera de conseguir este comportamiento!

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se explicará la gestión de las acciones de vista previa, impresión y exportación en el último capítulo Aplicación
final.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/15
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Imprimir y exportar
Crear informes sofisticados, producir archivos con distintos formatos o elaborar gráficos con Java requiere tiempo y
el dominio de numerosas clases y métodos adicionales. Conscientes de este hecho, varias empresas proponen
herramientas de reporting que permiten realizar de manera más rápida y segura este tipo de documentos.

Se ha elegido la librería JasperReports de la empresa JasperSoft ya que es un muy buen sistema de reporting
open source dedicado a aplicaciones Java. Esta herramienta puede funcionar de manera independiente a Eclipse o
en colaboración con este. Jaspersoft Studio es el editor gráfico WYSIWYG (What You See Is What You Get)
integrado en Eclipse que se encarga de la creación de plantillas de informes.

JasperReports es una librería escrita en Java.

1. Funcionamiento de JasperReports
Para generar informes, hay necesariamente que pasar por tres etapas: la creación del archivo Jasper describiendo
el contenido del informe, la compilación de este archivo en un formato explotable por Jasper en producción y
alimentarlo con datos para rellenar el informe.

Etapa 1: creación del archivo jrxml

JasperReports se basa en principio sobre archivos XML (eXtensible Markup Language). No es necesario dominar
XML para esta etapa ya que la creación se realiza mediante Jaspersoft Studio, que genera el archivo
correspondiente, pero es recomendable ya que llegados a cierto punto resulta más productivo intervenir
directamente en el archivo XML.

Al final de esta etapa, se obtiene un archivo XML con extensión jrxml que representa la plantilla del informe
desde el cual se pueden producir varios documentos con distintos formatos.

Etapa 2: compilación del archivo jrxml y generación del archivo Jasper

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

A partir del archivo jrxml, llamado también plantilla jrxml, Jasper procede a su compilación que permite verificar si
toda la plantilla está bien construida, sobre todo si es coherente con la norma XML y si los parámetros son
correctos.

Si la compilación finaliza correctamente, se genera un archivo binario con la extensión Jasper. Se puede entonces
utilizar este archivo para representar los datos dependiendo de las elecciones tomadas en el momento de la
creación del archivo jrxml con Jaspersoft Studio.

También es posible compilar en tiempo de ejecución el archivo binario Jasper. Se elige esta última opcíon para la
aplicación Luna.

Etapa 3: introducción de los datos en el informe

Se pueden utilizar varias fuentes de datos en el archivo Jasper: XML, CSV, un conjunto de registros que
provengan de una base de datos SQL, objetos Java con formato JavaBeans, etc.

Estas fuentes de datos servirán para generar el informe con un formato apropiado.

El documento final está entonces listo para su uso. Solo queda elegir entre todos los tipos de archivo de salida
propuestos por Jasper: PDF, DOCX, HTML, ODT, ...

2. Instalación de Jaspersoft Studio


Se puede utilizar Jaspersoft Studio directamente desde Eclipse, con la ayuda del sistema de plug­ins y features.
También se puede utilizar como una aplicación independiente.

A continuación se procede con la instalación de Jaspersoft Studio, el editor WYSIWYG de JasperSoft, dentro de
Eclipse.

Abra el Eclipse Marketplace desde el menú eligiendo la opción Help ­ Eclipse Marketplace.

En el campo de texto Find, introduzca «jaspersoft» y después haga clic en el botón Go presente en la
misma línea.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El marketplace de Eclipse propone entonces los programas correspondientes a la búsqueda: Jaspersoft Studio
debe lógicamente ser el primero en aparecer.

Haga clic en el botón Install de Jaspersoft Studio.

Eclipse propone entonces un asistente de instalación. Después de resolver los features a instalar, haga
clic en Confirm para continuar con la instalación, deje a Eclipse trabajar sobre todo lo que tiene que
instalar (esto puede tomar su tiempo), acepte los términos de licencia y haga clic en Finish.

Eclipse descarga entonces el programa (esto también puede tomar algo de tiempo), la instalación finaliza
y Eclipse le pide reiniciar el programa.

Acepte la solicitud de reinicio de Eclipse.

Después de que Eclipse se haya reiniciado, Jaspersoft Studio está disponible en Eclipse.

3. Crear un informe sencillo


Se crea un primer informe que muestra la lista de todos los clientes.

a. Preparación

Para facilitar la creación de este informe, se crea una pequeña clase que importa directamente los datos de
clientes de la base de datos.

Esta clase no se utilizará en producción, pero permite visualizar el informe durante la fase de diseño.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Cree una clase llamada FabricaClientes en el package control.informe.

package control.informe;

import control.connection.Conexion;
import entidad.crud.ClienteCrud;

public class FabricaClientes {

public static List<Cliente> createBeanCollection() {


Conexion conexion = Conexion.getConexion();
ClienteCrud crud = new ClienteCrud(conexion);
List<Cliente> clientes = crud.leer();
return clientes;
}
}

Esta clase contiene un único método estático llamado createBeanCollection que devuelve una colección de
objetos JavaBeans que servirán para alimentar el informe.

Un JavaBean es un objeto Java clásico que sigue convenciones de escritura de los métodos que acceden a sus
propiedades: los accesores empiezan todos por getXXX() y los mutadores por setXXX(). En el caso de la clase
Cliente, se accede y modifica la propiedad «apellido» con los métodos getApellido() y setApellido().

Existen varias técnicas para rellenar los datos del informe: conectarse directamente a la base de datos,
proporcionar objetos beans ,... Se utilizará esta última técnica.

Cree un adaptador de datos para los clientes seleccionando la opción File ­ New ­ Data Adapter del
menú.

La configuración del adaptador se almacena en un archivo XML.

Llame a este archivo datosClientes.xml y guárdelo en la carpeta jasper del proyecto.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Elija Collection of JavaBeans como tipo de adaptador.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Asigne un nombre a este adaptador. Para ello introduzca el nombre de la clase FabricaClientes con el
método createBeanCollection y haga clic en Finish.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se abre el archivo en el editor.

Controle todo lo que ocurre haciendo clic en el botón Test Connection.

JasperReports todavía no tiene en cuenta los objetos del package java.time, como el tipo Instant. Hay que
proporcionarle objetos de tipo java.util.Date para que pueda tratar correctamente la información temporal.

Añada un método getCreacion() que devuelva un objeto de tipo java.util.Date en la clase Cliente.

Esto tendrá como efecto definir un campo creacion en JasperReport para la clase Cliente, que se utilizará en el

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
// Método utilizado por Jasper
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
@Deprecated
public Date getCreacion() {
return fecha;
}
// ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

informe.

La anotación @Deprecated tiene por objetivo prevenir a los desarrolladores de que este método no debería
utilizarse (aparece tachado en Eclipse).

b. Creación del informe

Después de todas estas fases de preparación, es momento de empezar el primer informe.

En el menú, elija la opción File ­ New ­ Jasper Report.

Elija un modelo (un template): por ejemplo el modelo vacío (existen otros modelos disponibles si lo desea).
Haga clic en Next.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Elija el nombre del informe (Clientes.jrxml) y su ubicación (la carpeta jasper) y haga clic en Next.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 9/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Elija la fuente de datos del informe. Será el adaptador que acaba de crear.

Al contrario de lo indicado, es posible especificar más adelante los campos a mostrar en este informe.

Haga clic en Next y después en Finish.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 10/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se abre el nuevo informe en el editor de Eclipse.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 11/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Jaspersoft Studio propone una perspectiva dedicada a la creación de informes, pero no resulta muy funcional en la
versión utilizada para este libro. Puede sin embargo crear un informe, simplemente asegurándose de que la vista
Outline esté abierta. Si no fuera el caso, ábrala con la opción del menú Window ­ Show view ­ Outline.

Haga clic en el icono del botón DataSet and Query editor dialog para indicar los campos que se
utilizarán en el informe.

Se abre un cuadro de diálogo. Los datos son de tipo JavaBean.

Haga clic en la pestaña Java Bean, introduzca la clase entidad.Cliente en el campo Class Name y elija
los campos que se incluirán en el informe (codigo, tarjetaFidelidad, apellido, nombre, creacion). Haga clic
después en Add selected field(s) para que el informe los tome en cuenta.

Haga clic en OK para terminar la configuración de los campos del informe.

Modifique el ancho de las secciones (como Title y Detail 1), y deslice los campos desde la vista Outline
hasta la sección Detail 1.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 12/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

El editor añade automáticamente las etiquetas en la sección Page header.

Haga clic en la pestaña Preview debajo del editor para ver el informe.

Para modificar el formato de un campo, selecciónelo, haga un clic derecho y elija Show Properties.

Aparece la vista Properties, si no está ya abierta.

Seleccione la sección Text Field y haga clic en el botón «...» situado junto a Pattern. Esto abre una
ventana que permite elegir el formato de datos a aplicar, por ejemplo una fecha.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 13/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

También es posible elegir en esta vista de propiedades el tipo de fuente, el tamaño, la alineación del texto
(izquierda, centrada,...), el color de fondo y del texto, los bordes.

Modifique los campos hasta que esté satisfecho y guarde regularmente el informe.

4. Crear un informe parametrizado


Al no ser un libro dedicado al aprendizaje avanzado de esta excelente herramienta de reporting, solamente se
aborda la información esencial que debe conocer para crear un informe parametrizado. Dispone también de la
posibilidad de descargar los informes desde la página Información.

Guarde imágenes en la carpeta jasper/imagenes del proyecto.

Añada una imagen en el informe deslizando un elemento Image desde la paleta hacia el informe, por
ejemplo en la sección Title.

Se abre un cuadro de diálogo que permite elegir la imagen.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 14/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione la opción Workspace resource, elija la imagen a mostrar y haga clic en OK.

Aparece la imagen en el informe.

El problema con las imágenes es que se parametrizan los archivos «a fuego». Si los usuarios no tienen
exactamente el mismo árbol de carpetas en su disco duro, los informes no se podrán compilar.

Por lo tanto se añade un parámetro que especifica el nombre de la carpeta de imágenes del informe para permitir
su uso en producción.

En la vista Outline, navegue hacia la sección Parameters, efectúe un clic con el botón derecho y elija la
opción Create Parameter.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 15/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Se abre entonces la vista Properties y se dispone de un nuevo parámetro en la vista Outline.

Llame al parámetro imagenesDir y asegúrese de que está marcada la opción Is For Prompting (la
descripción es incompleta).

Seleccione después la imagen. La vista Properties se actualiza con las propiedades de la imagen.

Haga clic en el botón situado junto a la propiedad Expression. Se muestra un cuadro de diálogo que
permite modificar la ruta de la imagen.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 16/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione Parameters en el árbol de la izquierda. Haga doble clic después en el parámetro imagenesDir
del árbol central (deslice el árbol si el parámetro no aparece).

Se modifica la expresión: JasperReports añade los caracteres $P y pone el nombre del parámetro entre
llaves, pero un error «The current expression is not valid. Please verify it!» aparece.

Modifique la expresión para obtener este resultado (admitiendo que el nombre de la imagen es Moon­
256.png) y haga clic en Finish.

$P{imagenesDir}+"/Moon‐256.png"

El contenido de la imagen cambia ya que ahora está parametrizado.

Haga clic en la pestaña Preview. JasperReports le solicita ahora que defina el parámetro imagenesDir para
poder generar la visualización.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 17/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para finalizar haga clic en el botón verde debajo del parámetro. Se genera el informe con la imagen.

Una vez creado el informe tal y como usted desee, acuérdese de desmarcar la opción Is For Prompting del
parámetro imagenesDir.

5. Integración de JasperReports en Eclipse


Descargue las librerías de JasperReports, por ejemplo dirigiéndose al sitio web de SourceForge
(https://sourceforge.net/projects/jasperreports/files/jasperreports/JasperReports%206.1.0/) y descargando el
archivo jasperreports­6.1.0.jar.

Añada el jar a las librerías del proyecto.

Para añadir librerías externas, revise si es necesario el capítulo La caja de herramientas de Eclipse.

6. Puesta en marcha en el proyecto


Para ilustrar el empleo de informes Jasper en Java, se generará la lista de clientes a partir de la aplicación.

Abra la clase FClientes.

El objetivo es poder mostrar la lista de todos los clientes.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 18/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Para conseguir este resultado, la aplicación necesita:

De una clase que cubra el conjunto de procesos de creación de un informe Jasper enmascarando toda la
complejidad de este proceso.

De un informe parametrizado para mostrar correctamente las imágenes.

Para ahorrar tiempo, descárguese el informe parametrizado de clientes Clientes.jrxml.

Copie este archivo en la carpeta jasper del proyecto. Si Eclipse está abierto, seleccione esta carpeta o el
proyecto y presione la tecla [F5] para actualizar los recursos y hacer que Eclipse tome conciencia del nuevo
archivo.

Olvidar la actualización es en muchas ocasiones una fuente de preocupación...

«Solo queda» codificar.

En referencia a los principios de funcionamiento de Jasper, las etapas a realizar esta vez con el código Java se
determinan según la siguiente lista:

Localizar y cargar el archivo jrxml.

Compilar el documento jrxml para producir el documento Jasper.

Alimentar el archivo Jasper con los datos de los clientes.

Mostrar el documento final con los datos, en función del tipo de salida elegido.

Cree primero la clase Sistema en el package control.utilidades. Servirá para encontrar el archivo jrxml.

package control.utilidades;

import static java.lang.System.getProperty;

public class Sistema {

private static final String OS = "os.name";


private static final String DIR = "user.dir";
private static final String USER = "user.name";
private static final String VERSION = "os.version";
private static final String SEPARADOR = "file.separator";

public static String getSeparador() {


http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 19/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

return getProperty(SEPARADOR);
}

public static String getCarpetaActual() {


return getProperty(DIR);
}

public static String getNombreOS() {


return getProperty(OS);
}

public static String getVersionOS() {


return getProperty(VERSION);
}

public static String getNombreUsuario() {


return getProperty(USER);
}
}

La línea import static permite importar los métodos estáticos de una clase. Es posible invocarlos, entonces, como
si fuesen parte de la propia clase.

Cree un package llamado control.informe.

Cree una nueva clase llamada JasperFacade en el package control.informe.

public static JasperPrint cargarYCompilar(String informe,


Collection<?> elementos,
Object ... params)
throws JRException {
try {
// Etapa 1
String carpetaJasper = Systeme.getCarpetaActual()
+ Systeme.getSeparador() + "jasper"
+ Systeme.getSeparador();
JasperDesign design =
JRXmlLoader.load(carpetaJasper + informe);
// Etapa 2
JasperReport report =
JasperCompileManager.compileReport(design);
// Etapa 3

// se pasan los parámetros alternativamente:


// primero la clave y después el valor,
// esto repetido para cada elemento
Map<String, Object> misParametros =
new HashMap<String, Object>();
if (params != null) {
for (int i = 0; i < params.length; i += 2) {
misParametros
.put((String) params[i],
params[i + 1]);
}
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 20/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

misParametros.put("imagenesDir",
carpetaJasper + "imagenes");

JRDataSource source =
new JRBeanCollectionDataSource(elementos);
return JasperFillManager.fillReport(report,
misParametros,
source);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Error en la compilación del informe: \n"
+ e.getMessage()
+ "\nContacte con su administrador",
"Error", JOptionPane.ERROR_MESSAGE);
return null;
}
}

Proceda con los numerosos imports en una sola vez con el atajo de teclado [Ctrl][Shift] O (la letra O).

El método cargarYCompilar() realiza las tres primeras etapas mencionadas anteriormente. Recibe como
parámetro el nombre del informe a compilar, los datos para alimentar el informe en forma de colección y
parámetros opcionales.

En este método, se utiliza una colección particular: HashMap que asocia cada elemento de la colección con una
clave única de tipo String (y no con un índice numérico como con los ArrayList).

El método put(clave, valor) asocia un valor con una clave en la colección.

El método get(clave) permite encontrar el valor asociado a la clave.

La anotación del último parámetro permite añadir un número cualquiera de parámetros a un método. Se trata de
lo que se llama los varargs. Es posible, por tanto, invocar a este método de las siguientes maneras:

JasperFacade.cargarYCompilar("nombreDelInforme.jrxml",
Collections.emptyList());

JasperFacade.cargarYCompilar("nombreDelInforme.jrxml",
Collections.emptyList(),
"param1", "valor1");

JasperFacade.cargarYCompilar("nombreDelInforme.jrxml",
Collections.emptyList(),
"param1", "valor1",
"param2", "valor2");

// también es posible cometer errorres


// con esta anotación
JasperFacade.cargarYCompilar("nombredelInforme.jrxml",
Collections.emptyList(),
"param1"); // falta el valor

Una vez el objeto JasperPrint creado, es fácil realizar las operaciones de visualización previa, de impresión y de
exportación gracias a las librerías Jasper.

a. Vista previa

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 21/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Añada el método vistaPrevia() en la clase JasperFacade para obtener la previsualización:

public static void vistaPrevia(JasperPrint print) {


JasperViewer.viewReport(print, false);
}

y un método que hace todas las operaciones de golpe:

public static void vistaPrevia(String informe,


Collection<?> elementos,
Object... params) {
try {
JasperPrint print = cargarYCompilar(informe,
elementos,
params);
vistaPrevia(print);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Error al generar la vista previa: \n"
+ e.getMessage()
+ "\nContacte con el administrador",
"Error", JOptionPane.ERROR_MESSAGE);
}
}

El primer método permite, con la ayuda del método estático viewReport() de la clase JasperViewer,
visualizar directamente el informe en una nueva ventana.

Los demás métodos no presentan mayor dificultad, se añaden para cubrir todo el espectro de funcionalidades
esperado.

b. Impresión del informe

En esta sección encontrará el código fuente de los métodos de impresión de un informe.

// Impresión del informe


public static void imprimir(String informe,
Collection<?> elementos,
Object... params) {
try {
JasperPrint print = cargarYCompilar(informe,
elements,
params);
imprimir(print);
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Error al imprimir: \n"
+ e.getMessage()
+ "\nContacte con su administrador",
"Error", JOptionPane.ERROR_MESSAGE);
}
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 22/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public static void imprimir(JasperPrint print)


throws JRException {
JasperPrintManager.printReport(print, true);
}

c. Exportar a PDF

Para poder exportar los informes en los diferentes formatos fácilmente, se utilizan los enums de Java.

Cree un nuevo enum FormatoExport en el package control.informe, con ayuda de la opción File ­ New
­ Enum del menú.

package control.informe;

import java.io.File;

import javax.swing.ImageIcon;

import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;

public enum FormatoExport {


PDF,
HTML,
DOCX,
ODT,
ODS,
;

@SuppressWarnings("rawtypes")
JRAbstractExporter crearExporter() {
return null;
}

@SuppressWarnings({ "rawtypes", "unchecked" })


void exportar(JasperPrint print, File destino)
throws JRException {
JRAbstractExporter exporter = crearExporter();
exporter.setExporterInput(new SimpleExporterInput(print));
exporter.setExporterOutput(
new SimpleOutputStreamExporterOutput(destino));
exporter.exportReport();
}

FormatoExport() {
}
}

Este enum describe los diferentes formatos disponibles para exportar: se dispone de cinco formatos. Ni más, ni
menos.

Para cada uno de estos formatos, existe un método simple export(JasperPrint, File) para exportar un
informe de tipo JasperPrint a un archivo del disco duro de tipo java.io.File.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 23/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Ahora queda realizar la operación de exportación para cada formato.

Para el formato PDF, se redefine el método export:

PDF {
@Override
void export(JasperPrint print, File fichero)
throws JRException {
JasperExportManager.exportReportToPdfFile(
print,
fichero.getAbsolutePath());
}
},

Complete el enum con la exportación en formato HTML. Se usa el método estático


exportReportToHtmlFile de la clase JasperExportManager.

Para el formato DOCX, se redefine el método crearExporter() por:

DOCX{
@Override
JRDocxExporter crearExporter() {
return new JRDocxExporter();
}
},

Complete el enum con los formatos ODT y ODS. Las clases de exportación son respectivamente
JROdtExporter y JROdsExporter.

Queda llamar al método export() del enum FormatExport desde la clase JasperFacade.

Cree el método estático export() en la clase JasperFacade.

public static void export(FormatoExport formato,


String prefijo,
String informe,
Collection<?> elementos,
Object... params) {
JFileChooser save = new JFileChooser();
save.setSelectedFile(new File(prefijo + "."
+ formato.name().toLowerCase()));
int devuelto = save.showSaveDialog(save);
if (devuelto == JFileChooser.APPROVE_OPTION) {
try {
JasperPrint print = cargarYCompilar(informe,
elementos,
params);
formato.export(print, save.getSelectedFile());
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
String.format(
"Error al exportar %s: "
+ "\n%s\n"
+ "Contacte con su administrador",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 24/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

formato.name(), e.getMessage()),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}

Este método abre un cuadro de diálogo que permite elegir el nombre y la ubicación del archivo a guardar gracias
a las líneas:

JFileChooser save = new JFileChooser();


save.setSelectedFile(new File( ... );
int devuelto = save.showSaveDialog(save);
if (devuelto == JFileChooser.APPROVE_OPTION) {
...
}

El código formato.name() permite obtener el nombre del enum (PDF, ODT,...) y la instrucción
toLowerCase() permite transformar este nombre en minúsculas.

Ahora se trata de probar estos métodos desde los módulos de clientes.

Abra la clase ControlCliente.

Añada el método imprimir() que permite lanzar la impresión:

public void imprimir() {


try {
List<Cliente> datos = crud.leer();
JasperPrint print =
JasperFacade.cargarYCompilar("Clientes.jrxml", datos);
JasperFacade.imprimir(print);
} catch (JRException e) {
JOptionPane.showMessageDialog(null, "Ninguna vista previa.\n\n"
+ e.getMessage(), "Problema encontrado",

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 25/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}

El método es muy sencillo y permite efectuar todas las operaciones, y el código de la vista previa es casi el
mismo.

Este método plantea sin embargo un problema: la lectura de los clientes así como la carga y compilación de los
informes pueden demorar cierto tiempo, incluso mucho tiempo.

Esto quiere decir que la visualización gráfica de la aplicación se quedará bloqueada si se llama directamente a
este método desde la acción del botón de imprimir.

Para corregir este comportamiento, se utiliza la clase SwingWorker pero esta vez para la vista previa.

Añada el método vistaPrevia() en la clase ControlCliente.

public void vistaPrevia() {


new SwingWorker<JasperPrint, Void>() {

@Override
protected JasperPrint doInBackground()
throws Exception {
List<Cliente> clientes = crud.leer();
return
JasperFacade.cargarYCompilar("Clientes.jrxml",
clientes);
}

@Override
protected void done() {
try {
JasperPrint print = get();
JasperFacade.vistaPrevia(print);

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
} catch (ExecutionException e) {
JOptionPane.showMessageDialog(null,
"Ninguna vista previa.\n\n"
+ e.getCause().getMessage(),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}
}.execute();
}

Void (con una mayúscula) es una clase Java correspondiente al seudotipo void (con minúsculas).

Queda usar este método vistaPrevia().

Abra la clase PClientes.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 26/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

En modo Design, añada una acción para el botón de vista previa y en el método actionPerformed() de
esta acción la llamada al método vistaPrevia() de la clase ControlCliente.

private class AccionVistaPrevia extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionVistaPrevia() {
putValue(NAME, "Vista Previa");
putValue(SHORT_DESCRIPTION,
"Vista previa antes de la impresión");
}

public void actionPerformed(ActionEvent e) {


controlCliente.vistaPrevia();
}
}

Realice la misma operación para la acción de imprimir:

private class AccionImprimir extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionImprimir() {
putValue(NAME, "Imprimir");
putValue(SHORT_DESCRIPTION,
"Imprimir los datos de clientes");
}

public void actionPerformed(ActionEvent e) {


controlCliente.imprimir();
}
}

Añada la clase acción para realizar la exportación como sigue:

private class AccionExport extends AbstractAction {


private static final long serialVersionUID = 1L;
public AccionExport() {
putValue(NAME, "Exportar");
putValue(SHORT_DESCRIPTION,
"Exportar los clientes en un archivo");
}

public void actionPerformed(ActionEvent e) {


controlCliente.export(getVentana());
}
}

El compilador informa de un error ya que el método export() todavía no existe en la clase ControlCliente.

Cree el método export() con la ayuda del Quick Fix de Eclipse. Presione [Ctrl] 1 y elija la opción de
creación de nuevos métodos.

Complete el método de exportar con el siguiente código:

El cuadro de diálogo FExport permite mostrar una lista con los diferentes formatos permitidos.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 27/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

public void export(Window parent) {


new SwingWorker<List<Cliente>, Void>() {

@Override
protected List<Cliente> doInBackground()
throws Exception {
return crud.leer();
}

@Override
protected void done() {
try {
List<Cliente> elementos = get();
FExport laVentana = new FExport(parent,
"clientes",
"Clientes.jrxml",
elementos);
laVentana.setModal(true);
laVentana.setLocationRelativeTo(null);
laVentana.setVisible(true);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
} catch (ExecutionException e) {
JOptionPane.showMessageDialog(null,
"Imposible exportar.\n\n"
+ e.getCause().getMessage(),
"Problema encontrado",
JOptionPane.ERROR_MESSAGE);
}
}
}.execute();
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 28/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Dispone del código de la clase FExport como descarga.

Uno de los puntos importantes de la clase FExport es la construcción de la lista.

ExportRenderer renderer = new ExportRenderer();


DefaultListModel<FormatoExport> modelo = new DefaultListModel<>();
for( FormatoExport type : FormatoExport.values()) {
modelo.addElement(tipo);
}
tipo_export = new JList<FormatoExport>();
tipo_export.setModel(modelo);
tipo_export.setCellRenderer(renderer);
tipo_export.setSelectionMode(
ListSelectionModel.SINGLE_INTERVAL_SELECTION);
tipo_export.setLayoutOrientation(JList.HORIZONTAL_WRAP);

Utiliza un modelo por defecto de tipo DefaultListModel, que se rellena con los valores del enum
FormatoExport en el bucle for. Para ello, todos los valores del enum se obtienen mediante el método
values().

El renderer es, en sí, también muy sencillo:

package dialogo.aspecto;

import java.awt.Component;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 29/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JList;

import control.informe.FormatoExport;

public class ExportRenderer extends DefaultListCellRenderer {


private static final long serialVersionUID = 1L;

public ExportRenderer() {
super();
}

public Component getListCellRendererComponent(JList<?> list,


Object value, int index,
boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
FormatoExport tipo = (FormatoExport)value;
String txt = tipo.getDescripcion();
ImageIcon icono = tipo.getIcono();

setIcon(icono);
setText(txt);

return this;
}
}

El texto y la imagen de cada formato de exportación se obtienen desde el enum gracias a los métodos
getDescripcion() y getIcono().

Cree estos métodos en el enum:

FormatoExport(String icono, String archivo) {


this.icono =
new ImageIcon(FormatoExport.class.getResource(icono));
this.archivo = archivo;
}

private final ImageIcon icono;


private final String archivo;

public ImageIcon getIcono() {


return icono;
}

public String getDescripcion() {


return archivo;
}

Los valores se indican en la construcción de las constantes del enum.

A continuación, quedan por construir las contantes del enum FormatoExport con los parámetros adecuados.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 30/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

PDF("/imagenes/export/Adobe‐Acrobat‐48.png", "PDF (*.pdf)") {


...
},
DOCX("/imagenes/export/Firefox‐48.png", "Página web (*.html)") {
...
},
HTML("/imagenes/export/Microsoft‐Office‐2013‐02‐48.png",
...
},
ODT("/imagenes/export/odt.png",
"Documento Open/Libre Office (*.odt)") {
...
},
ODS("/imagenes/export/ods.png",
"Hoja de cálculo Open/Libre Office (*.ods)") {
...
},
;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 31/31
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Gráficos
Se ha elegido JFreeChart para crear gráficos que, como JasperReports, es open source y está enteramente escrito
en Java. JFreeChart es una librería que proporciona una API que permite a Java crear gráficos desde fuentes de
datos y cuya salida es una imagen JPEG o PNG.

1. Creación con Jaspersoft Studio y JFreeChart


Para hacer la creación de gráficos aún más sencilla, se puede trabajar de nuevo con Jaspersoft Studio en tándem
con JFreeChart.

Así se dispone de un editor gráfico para dibujar los gráficos, insertar eventuales parámetros y probar el resultado
final como se acaba de hacer con la vista previa, la impresión y la exportación de clientes.

Se crea un gráfico sencillo, que mostrará los porcentajes de número de pedidos por mes para un año dado.

Descargue las librerías de JFreeChart del sitio web de SourceForge en la dirección siguiente:
https://sourceforge.net/projects/jfreechart/files/

Descomprima el archivo y añada las siguientes librerías a las ya presentes en el proyecto luna.

Antes de empezar con la creación del informe, hay que interesarse en el modelo de datos.

Cada estadística posee dos propiedades: el nombre del mes y el número de pedidos de este mes.

Cree una clase Estadistica en el package dialogo.estadisticas.

Esta clase utiliza el enum Month del package java.time para conseguir el nombre del mes en cuestión.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

package dialogo.estadisticas;

import java.time.Month;
import java.time.format.TextStyle;
import java.util.Locale;

public class Estadistica {


private final Long contador;

private final String mes;

public Estadistica(Object obj) {


this( (Integer) ((Object[]) obj)[0],
(Long) ((Object[]) obj)[1]);
}

private Estadistica(Integer mes, Long numero) {


this(Month.of(mes), numero);
}

public Estadistica(Month mes, Number numero) {


this.mes = mes.getDisplayName(TextStyle.FULL,
Locale.getDefault());
contador = numero.longValue();
}

public Long getContador() {


return contador;
}

public String getMes() {


return mes;
}
}

Se inyectará una colección de este tipo de datos en el informe. Las propiedades que interesan en el informe son
por lo tanto contador y mes.

Cree un nuevo adaptador de datos para probar el gráfico sin inyectar datos reales.

package dialogo.estadisticas;

import java.time.Month;
import java.util.ArrayList;
import java.util.List;

public class FabricaEstaditicas {

public static List<Estadistica> createBeanCollection() {


List<Estadistica> estadisticas = new ArrayList<>();
estadisticas.add( new Stat(Month.JANUARY, 3));
estadisticas.add( new Stat(Month.FEBRUARY, 21));
estadisticas.add( new Stat(Month.MARCH, 142));
return estadisticas;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

}
}

Cree un nuevo informe en blanco y guárdelo con el nombre estadisticas.jrxml.

Abra el editor de datos, seleccione el adaptador que acaba de crear y elija la clase Estadistica en la pestaña
Java Bean. Seleccione después las propiedades contador y mes.

Añada los campos seleccionados con el botón Add selected field(s) y haga clic en OK.

Reduzca a cero el tamaño de todas las secciones excepto la llamada Título.

En la paleta, seleccione el elemento gráfico (con un icono de gráfico circular) y deslícelo hasta la sección
Título.

Seleccione después el tipo de gráfico Pie Chart. Haga clic en Next.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Seleccione después la serie de datos a mostrar:

Key debe indicar $F{mes}.

Value debe indicar $F{contador}.

Haga clic en Finish.

Seleccione después el gráfico y muestre sus propiedades. En la sección Chart Plot, introduzca el formato de
la etiqueta como: {0}: {1} pedido(s) ({2}).

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Compruebe el aspecto haciendo clic en la pestaña Preview.

Dispone del archivo estadisticas.jrxml como descarga desde la página Información.

2. Explotación de gráficos desde la aplicación


Queda gestionar los pasos realizados desde la aplicación Java proponiendo la introducción del año.

Abra la clase FCuadroDeMandos en modo Design y seleccione el botón Pedidos.

Añada una acción a este botón con el código siguiente:

private class AccionPedidos extends AbstractAction {


private static final long serialVersionUID = 1L;

public AccionPedidos() {
putValue(NAME, "Pedidos");
putValue(SHORT_DESCRIPTION,
"Mostrar las estadísticas de pedidos");
}

public void actionPerformed(ActionEvent e) {


String anio = JOptionPane.showInputDialog(null,
"Introduzca un año: ");
FEstadisticas estadisticas = new FEstadisticas
(FCuadroDeMando.this, conexion, anio);

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 5/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

estadisticas.setModal(true);
estadisticas.setLocationRelativeTo
(estadisticas.getParent());
estadisticas.setVisible(true);
}
}

El método showInputDialog abre un cuadro de diálogo que permite introducir texto simple.

Descargue el código de la ventana FEstadisticas si no lo ha hecho aún y complételo.

public FEstadisticas(Window parent,


Conexion conexion, String anio) {
super(parent);
this.anio = anio;

consulta = "SELECT MONTH(date) AS mes"


+ ", COUNT(*) AS numero "
+ "FROM pedido "
+ "WHERE YEAR(fecha) = ’"
+ anio + "’ "
+ "GROUP BY MONTH(fecha)";
darAspectoVentana();

new Worker(conexion).execute();
}

El constructor define la consulta a realizar para recuperar las estadísticas desde la base de datos.

Después instancia un Worker, que es una instancia de SwingWorker, para recuperar los resultados en segundo
plano.

private final class Worker


extends SwingWorker<Collection<Estadistica>,
Estadistica> {
private final Conexion conexion;

public Worker(Conexion conexion) {


this.conexion = conexion;
}

@Override
protected Collection<Estadistica> doInBackground()
throws Exception {

List<?> resultados = conexion.buscar(


(administrador) ‐> {
Query query =
administrador.createNativeQuery(consulta);
return query.getResultList();
});
estadisticas = new ArrayList<>(resultados.size());

for (Object obj : resultados) {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 6/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

// creación de los datos


Estadistica estadistica = new Estadistica(obj);
estadisticas.add(estadistica);
publish(estadistica);
}
return estadisticas;
}

protected void process(List<Estadistica> chunks) {


for (Estadistica estadistica : chunks) {
// inyección de datos en la tabla
data.setValue(estadistica.getMes(),
estadistica.getContador());
}
}

protected void done() {


if (estadisticas.isEmpty()) {
JOptionPane.showMessageDialog(null,
"No se ha encontrado ningún dato para este año");
} else {
print.setEnabled(true);
}
}
}

Se almacenan los datos en dos atributos de la clase principal para más comodidad: uno para el gráfico, otro para la
impresión.

public class FEstadisticas extends JDialog {


...
private final DefaultPieDataset data = new DefaultPieDataset();
private List<Estadistica> estadisticas;
...
}

Se crea después el componente gráfico circular:

private JPanel getPanel() {


if (panel == null) {
panel = generaGraphicStats();
panel.setLayout(null);
}
return panel;
}

private JPanel generaGraphicStats() {


JPanel panel = null;
try {
// clase para la creación e importación de datos
// para generar el gráfico
JFreeChart chart = ChartFactory.createPieChart(
// 1er param: título del gráfico
// 2ndo param: datos

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 7/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

"Pedidos " + anio, data,


// 3er: toolTip
// 4to: url
true, true, false);
// ChartPanel : componente que transforma
// el gráfico en JPanel
ChartPanel chartPane = new ChartPanel(chart);
chartPane.setBorder(new LineBorder(Color.BLACK,
1, true));
panel = chartPane;
} catch (Exception e) {
panel = new JPanel();
JOptionPane.showMessageDialog(null,
"Se ha producido un error al "
+ "generar el gráfico",
"Error", JOptionPane.ERROR_MESSAGE);
}
return panel;
}

Y se configura el botón de impresión:

private JButton getPrint() {


if (print == null) {
print = new JButton("Imprimir el gráfico");
print.setEnabled(false);
print.addActionListener(e ‐>{
JasperFacade.vistaPrevia("estadisticas.jrxml",
estadisticas);
});
UI.quitarAspectoBoton(print, "Printer");
print.setForeground(Color.BLACK);
}
return print;
}

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 8/8
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Creación del ejecutable


Llegados a este punto, es momento de probar la aplicación de manera independiente de Eclipse.

Hay que crear un ejecutable (consulte el capítulo Toma de contacto de Eclipse, sección Primer ejecutable).

Para simplificar la vida respecto a todas las librerías a importar, se utiliza el proyecto Maven para recuperar todas las
dependencias, es decir todos los archivos jar necesarios para que la aplicación funcione.

En efecto un proyecto desarrollado en Eclipse se beneficia del soporte de la aplicación Eclipse en sí misma: las
librerías necesarias y ausentes se añaden de forma transparente. ¡Pero los usuarios de sus aplicaciones futuras no
conocerán tal vez nunca Eclipse! El objetivo es darles una aplicación que sea auto suficiente: uno o dos archivos
únicamente deberían bastar para ejecutar o instalar esta aplicación..

Cree un archivo llamado pom.xml en la raíz del proyecto.

<project>
<modelVersion>4.0.0</modelVersion>
<name>ENI Java 8</name>

<groupId>com.eni</groupId>
<artifactId>java8</artifactId>
<version>1.0.0‐SNAPSHOT</version>

<repositories>
<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<layout>default</layout>
<url>http://repo1.maven.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>

<dependencies>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql‐connector‐java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>

<dependency>
<groupId>com.jgoodies</groupId>
<artifactId>jgoodies‐forms</artifactId>
<version>1.6.0</version>
</dependency>

<dependency>
<groupId>com.miglayout</groupId>
<artifactId>miglayout‐swing</artifactId>
<version>5.0</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j‐api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback‐classic</artifactId>
<version>1.1.3</version>
</dependency>

</dependencies>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>
UTF‐8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>
maven‐compiler‐plugin</artifactId>

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

<version>3.3</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Este archivo enumera todas las dependencias que necesita la aplicación Luna para funcionar sola.

Seleccione el proyecto y, en el menú contextual, elija la opción Configure ­ Convert to Maven Project.

Al cabo de un momento, una M pequeña decora el icono del proyecto en el explorador de packages y un
nuevo elemento aparece en la lista del proyecto: Maven Dependencies.

Suprima todas las librerías que añadió en el Build Path: ahora están presentes en el proyecto gracias a Maven.

Efectúe un nuevo clic derecho en su proyecto y elija la opción Export....

En el cuadro de diálogo que se abre, seleccione Runnable JAR file.

Haga clic en Next. En la siguiente pantalla, verifique que la opción Extract required libraries into generated
JAR está seleccionada.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Sitúe el archivo generado en una carpeta que puede llamar por ejemplo Luna.

Copie en esta carpeta la carpeta jasper presente en el proyecto Eclipse.

Para finalizar, cierre Eclipse y pruebe la aplicación jar verificando que los informes Jasper funcionan.

¿Por qué separar los informes y no incorporarlos en el jar? Porque será más sencillo para hacer modificaciones sobre
los informes en este caso: no necesitará redistribuir la aplicación completa.

Los informes Jasper para los clientes y los artículos están disponibles para la descarga desde la página Información.
Llegados a este punto, se deja a su entender integrarlos en el proyecto Luna.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Vaya más lejos


La aplicación Luna es ahora funcional para un usuario.

Quedan sin embargo muchos ejes de trabajo posibles:

Debe ser posible que varios usuarios trabajen simultáneamente sobre los mismos datos.

Las interfaces gráficas de los usuarios deben actualizarse cuando alguien modifica un dato.

Un usuario debe poder crear, editar y eliminar categorías y proveedores.

¡La recompensa principal de una aplicación bella y agradable de utilizar es aún más trabajo, ya que proporciona
ideas a los usuarios! Espere a recibir feedback de los usuarios...

Con respecto al primer punto descrito, la aplicación debe poder gestionar varios usuarios que puedan encontrarse
en cualquier punto del planeta.

Más allá de la modificación consistente en autenticarse con combinaciones de usuario/contraseña diferentes


(mediante una tabla dedicada, un directorio LDAP,...), es importante que cada usuario recupere la información
correcta, sobre todo aquella que sea temporal.

¡Sin embargo, un usuario situado en Buenos Aires o Katmandú no verá las mismas fechas de pedido que un
usuario situado en Madrid!

Esto es debido al hecho de que la información temporal almacenada en la base de datos no tiene en cuenta el huso
horario. La fecha del pedido se almacena con el formato 2015­05­25 21:26:33. ¡Y al recuperar los datos por JPS,
esta fecha está combinada con el huso horario local del usuario!

¿Cómo resolver este problema? Existen varias alternativas posibles:

Cambiar en la base de datos el formato de la fecha y hora por un dato de tipo long que representará el número de
milisegundos (o de segundos) transcurridos desde el 1 de Enero de 1970 a medianoche UTC.

Esta solución no es muy conveniente ya que entonces será imposible crear de manera sencilla el gráfico de
estadísticas. Para esto hay que guardar las fechas con el formato SQL TIMESTAMP.

Almacenar la información de huso horario en una columna adicional y combinar ambas informaciones para obtener
una fecha uniforme.

Esta solución tampoco es conveniente ya que impone también una gimnástica algorítmica al generar el informe de
estadísticas.

Llegados a este punto, se puede constatar que almacenar las fechas en base de datos es en realidad un
LocalDateTime según Java 8: una información temporal sin huso horario. Es por lo tanto posible evolucionar la
clase Cliente para sacar partido de esta situación y almacenar la fecha del pedido teniendo en cuenta el huso
horario UTC (ya que de todas maneras hay que elegir un huso horario, mejor coger el que esté normalizado).

Transforme el atributo fecha de la clase Pedido:

...
private LocalDateTime fecha;
...
public Instant getInstant() {
return ZonedDateTime.of(fecha,
ZoneId.of("UTC")).toInstant();
}

void setFecha(LocalDateTime fecha) {


this.fecha = fecha;
}

public void inicializarFecha() {

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 1/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

setFecha(LocalDateTime.now(ZoneId.of("UTC")));
}
...

JPA no permite de manera nativa convertir un objeto de tipo LocalDateTime. Debe usar un convertidor.

Cree la clase ConvertidorLocalDateTime en el control.utilidades:

package control.utilidades;

import java.sql.Timestamp;
import java.time.LocalDateTime;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class ConvertidorLocalDateTime implements
AttributeConverter<LocalDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(
LocalDateTime entityValue) {
return Timestamp.valueOf(entityValue);
}

@Override
public LocalDateTime convertToEntityAttribute(
Timestamp databaseValue) {
return databaseValue.toLocalDateTime();
}
}

Este convertidor implementa la interfaz javax.persistence.AttributeConverter. Esta interfaz contiene dos


métodos: convertToDatabaseColumn convierte un atributo en un formato comprensible para almacenarlo en la
base de datos, convertToEntityAttribute realiza la operación inversa y permite encontrar el valor del atributo tras
recuperar la información de la base de datos.

Después se anota esta clase con @Converter para que JPA lo tenga en cuenta. El parámetro autoApply permite
usar este convertidor para todos los atributos de tipo LocalDateTime en la aplicación.

Para acabar, se añade la clase del convertidor en el archivo persistence.xml.

...
<class>entidad.Linea</class>
<class>control.utilidades.ConvertidorLocalDateTime</class>
...

Si no es posible convertir automáticamente todos los tipos, las propiedades pueden anotarse con @Convert.

Por ejemplo, la propiedad fecha de la clase Pedido se puede anotar de la siguiente manera:

@Convert(converter=ConvertidorLocalDateTime.class)
private LocalDateTime fecha;

Las clases Articulo y Cliente deben también modificarse para que el atributo de fecha sea de tipo LocalDate.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 2/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

Modifique la clase Articulo:

...
private LocalDate fecha;
...
public Instant getFecha() {
return ZonedDateTime.of(
fecha.atStartOfDay(),
ZoneId.of("UTC"))
.toInstant();
}
...
public void setFecha(Instant fecha) {
this.fecha = LocalDateTime.ofInstant(fecha,
ZoneId.of("UTC"))
.toLocalDate();
}
...
@Deprecated
public Date getCreacion() {
return Date.from(getFechaCreacion());
}

Modifique la clase Cliente:

...
private LocalDate fecha;
...
public Instant getFechaCreacion() {
return ZonedDateTime.of(
fecha.atStartOfDay(),
ZoneId.of("UTC"))
.toInstant();
}
...
public void setFechaCreacion(Instant fecha) {
this.fecha = LocalDateTime.ofInstant(fecha,
ZoneId.of("UTC"))
.toLocalDate();
}
...
@Deprecated
public Date getCreacion() {
return Date.from(getFechaCreacion());
}

También es necesario crear un convertidor y referenciarlo en el archivo persistence.xml.

Cree la clase ConvertidorLocalDate en el package control.utilidades:

package control.utilidades;

import java.sql.Date;
import java.time.LocalDate;

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 3/4
16/12/2016 www.eni­training.com/client_net/pdfexport.aspx?exporttype=1

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class ConvertidorLocalDate implements
AttributeConverter<LocalDate, Date> {
@Override
public Date convertToDatabaseColumn(LocalDate entityValue) {
return Date.valueOf(entityValue);
}

@Override
public LocalDate convertToEntityAttribute(Date databaseValue) {
return databaseValue.toLocalDate();
}
}

Referencie la clase ConvertidorLocalDate en el archivo persistence.xml:

...
<class>entidad.Linea</class>
<class>control.utilidades.ConvertidorLocalDate</class>
...

Esta iteración para crear una nueva funcionalidad (o corregir un bug, según se mire) ha necesitado la modificación
de tres clases y la creación de dos nuevas clases. El resto del código de la aplicación ha quedado como estaba y esto
gracias a la elección de un diseño adecuado al crear el código fuente.

http://www.eni­training.com/client_net/pdfexport.aspx?exporttype=1 4/4

También podría gustarte