Cómo configurar la conexión a SQLServer mediante JDBC (NetBeans, Java 8, SQLServer Express 2016, Windows 10…)

Standard

Nunca antes, luego de haber aprendido cómo conectarme a distintas bases de datos mediante JDBC, me había costado tanto una como lo fue con SQLServer (casi 15 minutos). Gracias a Google, Stack Overflow y conocimiento previo salió medianamente rápido, pero vamos, que a esta gente le gusta complicar a veces las cosas.

Archivo de propiedades de conexión (config.properties)

Ya sabemos que es de muy mala educación, y prohibido además, hard-codear valores o atributos que pueden cambiar en el tiempo de una computadora, una red, etc… a otra; entre ellos las propiedades de conexión a una base de datos. Es por eso, que lo ideal es colocarlo en un archivo de propiedades como los que estamos acostumbrados a hacer:

#Ubicado en src\res\config.properties
db.url=jdbc:sqlserver://servidor\\SQLEXPRESS;databaseName=nombre_bd;integratedSecurity=true
db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
db.user=usuario
db.password=clave

Respecto a los valores db.url y db.driver, recordemos que cada manejador de base datos  (o DBMS, como quieran llamarlo) tiene su forma de establecer estos valores, los que les indico funcionan con SQLServer Express 2016; acá mucha mas información respecto a cómo formar la url.

Y una clase en Java sencilla que lo lea:

package th.control.db;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

/**
 *
 * @author jescobar
 */
public final class DBConnection {
 static InputStream inputStream;
 static Connection conn;
 
 public static void connect() throws Exception {
 inputStream = Class.forName("th.control.db.DBConnection").getClassLoader().getResourceAsStream("config.properties");
 
 Properties prop = new Properties();
 
 prop.load(inputStream);
 
 
 Class.forName(prop.getProperty("db.driver"));
 conn = DriverManager.getConnection(
 prop.getProperty("db.url"), 
 prop.getProperty("db.user"), 
 prop.getProperty("db.password"));
 
 System.out.println("Me conecté!!!");
 }
}

Todo bien, compila al pelo, pero al ejecutar un indeseado java.lang.NullPointerException te aparece en la línea donde intentas cargar el archivo de propiedades mediante el método prop.load(inputStream).

run:
Exception in thread "main" java.lang.NullPointerException
 at java.util.Properties$LineReader.readLine(Properties.java:434)
 at java.util.Properties.load0(Properties.java:353)
 at java.util.Properties.load(Properties.java:341)
 at th.control.db.DBConnection.connect(DBConnection.java:26)

El punto es que ni NetBeans ni Eclipse te van a decir dónde colocar el archivo para que sea leído, debes especificar en cada IDE que rutas incluir en el classpath. En NetBeans una solución sencilla es moverlo al directorio src.

#Ubicado en src
db.url=jdbc:sqlserver://servidor\\SQLEXPRESS;databaseName=nombre_bd;integratedSecurity=true
db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
db.user=usuario
db.password=clave

Ahora tu problema va a ser otro…

Colocando donde es el Driver de conexión

Yo estoy partiendo de que ya te habías descargado el driver JDBC de Microsoft, bien sea el sqljdbc41.jar o el sqljdbc42.jar (clic acá si no lo has hecho) para conectarte con su BD, creaste un directorio lib en el proyecto, y ahí lo metiste. Pero al tratar de ejecutar, recibes otro mensaje de que no vas…

run:
Exception in thread "main" java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver
 at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:264)
 at th.control.db.DBConnection.connect(DBConnection.java:29)

Y es que tienes que decirle a NetBeans donde estas colocando tus librerías externas. Algo así logras presionando sobre el botón “Add JAR/Folder” y especificando la ruta que visualizas en la imagen.

002

Y con lo anterior, al tratar de ejecutar… Cambiamos de problema! Uno mas de no sabemos cuantos mas… Pero lo que si sabemos, que como todo, es que en algún momento ya no habrán.

Ahora la librería que colocamos nos está diciendo que no logra conectarse con el servidor SQLServer…

run:
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: No se pudo realizar la conexión TCP/IP al host localhost, puerto 1433. Error: "Connection refused: connect. Verifique las propiedades de conexión, compruebe que hay una instancia de SQL Server ejecutándose en el host y aceptando las conexiones TCP/IP en el puerto y compruebe que no hay ningún firewall bloqueando las conexiones TCP en el puerto.".
 at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:191)
 at com.microsoft.sqlserver.jdbc.SQLServerException.ConvertConnectExceptionToSQLServerException(SQLServerException.java:242)
 at com.microsoft.sqlserver.jdbc.SocketFinder.findSocket(IOBuffer.java:2369)
 at com.microsoft.sqlserver.jdbc.TDSChannel.open(IOBuffer.java:551)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.java:1963)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.java:1628)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.java:1459)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.java:773)
 at com.microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.java:1168)
 at java.sql.DriverManager.getConnection(DriverManager.java:664)
 at java.sql.DriverManager.getConnection(DriverManager.java:247)
 at th.control.db.DBConnection.connect(DBConnection.java:30)

Acomodando las cosas para que SQLServer escuche

No tiene sentido que si usas el Microsoft SQL Server Management Studio, y ahí puedes hacer lo que te plazca (creates, updates, inserts, tanto de datos como esquemas, bases de datos, etc…) si tratas de conectarte desde otra aplicación, estén las puertas cerradas. Para abrirlas hay que hacer lo siguiente:

En una línea de comandos (cmd), o desde el comando ejecutar (Tecla Win + R), ejecuta los siguiente:

 C:\Windows\SysWOW64\SQLServerManager13.msc

Se te abrirá una ventana como la siguiente (yo ni sabía que existía). Es el SQL Server Configuration Manager.

001

Y tal como muestra la imagen siguiente, habilita las comunicación TCP/IP dentro de Protocolos de SQLEXPRESS.

003

El SQL Server Configuration Manager te va a decir que necesitas reiniciar el servicio para que los cambios tomen efecto, la siguiente imagen te indica cómo hacerlo.

004

Le damos play a la clase y…. Otra excepción… Lo bueno es que esta nos dice explícitamente que hacer!

run:
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: No se pudo realizar la conexión con el host localhost, instancia con nombre sqlexpress. Error: "java.net.SocketTimeoutException: Receive timed out". Verifique los nombres del servidor y de instancia, compruebe que no hay ningún firewall bloqueando el tráfico UDP al puerto 1434. Para SQL Server 2005 o posterior, verifique que el servicio SQL Server Browser se está ejecutando en el host.
 at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:191)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.getInstancePort(SQLServerConnection.java:5247)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.primaryPermissionCheck(SQLServerConnection.java:1871)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.java:1608)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.java:1459)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.java:773)
 at com.microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.java:1168)
 at java.sql.DriverManager.getConnection(DriverManager.java:664)
 at java.sql.DriverManager.getConnection(DriverManager.java:247)
 at th.control.db.DBConnection.connect(DBConnection.java:30)

Nos dice que verifiquemos que un fulano servicio llamado SQL Server Browser debe estar iniciado. Y si revisamos la última imagen, hay algo ahí llamado igual. No hace falta imagen para esto sino sentido común: Clic derecho -> Iniciar… Play de nuevo!

run:
feb 05, 2017 9:59:38 AM com.microsoft.sqlserver.jdbc.AuthenticationJNI <clinit>
ADVERTENCIA: Failed to load the sqljdbc_auth.dll cause : no sqljdbc_auth in java.library.path
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: Este controlador no está configurado para la autenticación integrada. ClientConnectionId:056b6f27-b750-4a8d-bda7-188d11f2aae9
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.terminate(SQLServerConnection.java:2400)
 at com.microsoft.sqlserver.jdbc.AuthenticationJNI.<init>(AuthenticationJNI.java:68)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.logon(SQLServerConnection.java:3132)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.access$100(SQLServerConnection.java:43)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection$LogonCommand.doExecute(SQLServerConnection.java:3123)
 at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7505)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2445)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.java:1981)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.java:1628)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.java:1459)
 at com.microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.java:773)
 at com.microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.java:1168)
 at java.sql.DriverManager.getConnection(DriverManager.java:664)
 at java.sql.DriverManager.getConnection(DriverManager.java:247)
 at th.control.db.DBConnection.connect(DBConnection.java:30)

Ok, esto sigue descontrolado! Y ahora qué?

Bueno, resulta que Microsoft permite dos maneras de conectarnos con su base de datos, y eso se lo decimos en el archivo de configuración. El parámetro integratedSecurity (integratedSecurity=true) indica si se usan las credenciales de Windows para conectarnos o si por el contrario, debemos especificar un usuario o contraseña (integratedSecurity=false), tal cual estamos haciendo. En otras palabras, SQLServer se esta pasando por el forro nuestro usuario y clave porque está usando nuestras credenciales de Windows para iniciar sesión…

Hay dos soluciones para esto… La que puedes inferir en el párrafo anterior (coloca en false el valor de integratedSecurity), o la otra, localizar el bendito sqljdbc_auth.dll y pegarlo en la carpeta System32 de Windows. Ambas funcionan, y el fulano .dll está en el mismo archivo donde bajaste los drivers JDBC de SQL Server hace unos parrafos mas arriba.

Cuando ejecutes de nuevo tu clase, este será el mensaje que obtendrás:

run:
Me conecté!!!

Saludos!

Problemas de desempeño mas comunes en Java… Y en cuáles he caído desarrollando en plataformas móviles: El N+1

Standard

Recientemente me hice con un entretenido e-book de AppDynamics donde se presentan un conjunto de 1o problemas con los cuales estoy seguro todos nos hemos enfrentado cuando estamos realizando estas maravillosas y extensas aplicaciones en Java que nos toma meses construir y las mismas una vez que las ‘inauguramos’ empiezan a caer, colgarse y molestar a nuestros usuarios.

¡Javier, esto es muy lento! Dime de una vez si me regreso a mi formulario en papel…

Mi mayor experiencia en Java empezó refactorizando una aplicación de ventas que se ejecuta sobre la máquina virtual de Java de IBM (J9) en la plataforma Windows Mobile. Para aquella entonces, los dispositivos basados en procesadores de 600 Mhz y apenas 256 MB de Ram de los AS400 que empleábamos daban la batalla en durabilidad para las manos pesadas y descuidos a los que estaban sometidos por la fuerza de venta; sin embargo, debido a sus limitados recursos, el código tenía que ser mas que eficiente en uso de memoria y el tiempo de respuesta de la aplicación era crítico para perder o ganar una venta.

Posterior a eso, la era de Android. Equipos doble núcleo, gigas de memoria, ¡mucho poder! Pero en un mercado emergente y socialmente peligroso como en el cual se desenvuelve nuestra fuerza de ventas, el vendedor no puede andar si quiera con un Galaxy S5 sin estar expuesto a un asalto y tampoco es viable para la empresa equipar a mas de 900 ‘asesores comerciales’ con un smartphone cada mes por situaciones como estas.

La aplicación debe ser rápida y ejecutarse en cualquier ‘aparatico’ que no pase de 70USD

Muchos de los problemas de desempeño a veces los solucionamos empleando sentido común, pero no descarto también golpes de suerte probando eso o aquello conseguido en Internet.

El e-book que les comparto distribuye en cuatro categorías los problemas de desempeño en desarrollos basados en Java: base de datos, memoria, concurrencia, y ‘descuido’. Este último no se llama así como tal, pero lo he tropicalizado. Los mismos están medidos por la magnitud del impacto que pueden causar en el desempeño de la aplicación, muestra patrones que permiten identificarlos y lineamentos para resolverlos. Son muchos como para estar en un solo post así que habrá mas de una publicación de este tema en este blog.

Problema ‘Muerte por 1000 Cortes’. El problema N+1 de Base de datos

Confieso que el nombre me llamó la atención y lo que leí en Wikipedia me dejó un mal sabor del café que me tomaba mientras les redactaba esto.

Ocurre cuando nuestra aplicación se vuelve lenta por el uso excesivo de la base de datos a través de accesos individuales. Es mas común en procesos transaccionales y tiene lugar cuando utilizas un número elevado de consultas cuando lo mismo lo puedes hacer con solo dos.

Por ejemplo, el vendedor con su dispositivo puede atender alrededor de 60 clientes al mes que tiene asignado. Un vendedor puede tomar en promedio al día cerca de 10 pedidos de unas 25 líneas a clientes distintos. En un mes puede hacer en promedio 5.000 líneas de pedido; y en total, los 900 vendedores de la empresa pueden hacer 4.500.000 líneas al mes.

Obtener las ventas realizadas a un cliente entonces, durante toda su vida pudiese hacerse ‘de mal manera’ así:

select id_pedido from pedido where id_cliente = $el_cliente

select * from pedido_detalle where id_pedido = 1

select * from pedido_detalle where id_pedido = 2

...

select * from pedido_detalle where id_pedido = n

La solución a este problema es sencilla, de hecho, seguramente la mayoría de quienes lean esto preguntarán ¿por qué no se hace así?:

select id_pedido from pedido where id_cliente = $el_cliente

select * from pedido_detalle where id_pedido in (1, 2, ..., n)

¿Cómo identifico que esto está sucediendo?

  • La aplicación responde lentamente obteniendo información.
  • Si cuentas el número de consultas que haces a la base de datos y lo comparas con el número de ítems del negocio siendo procesados, encontrarás una relación cercana de 1 a 1.

¿Qué me pasó a mi?

Cuando desarrollas para plataformas móviles, siempre debes tener en cuenta también algo: memoria.

Hice que la aplicación que carga lentamente los pedidos muestre un mensaje de “Espere por favor”para que el usuario’pierda un poco menos’ la paciencia mientras se analizaba el problema. El problema N+1 estaba en el código obteniendo todos los pedidos con una consulta a la base de datos pero instanciando las líneas de cada pedido con una consulta a la vez. Se solucionó la lentitud con una sola consulta, Join de pedidos y detalle de pedidos, la respuesta era inmediata pero mas temprano que tarde un java.lang.OutOfMemoryError aparecía de vez en cuando.

Esto no lo entendía puesto que la forma anterior debería ocasionar el mismo problema. Esto no era así porque en otra sección del código, luego de recuperar el pedido deseado, los detalles no utilizados eran descartados (se liberaba memoria) pero vueltos a cargar al volver a la pantalla anterior (lentitud de nuevo… pero funciona).

Cuando trabajas con poca memoria se debe analizar el impacto de la solución. La selección de únicamente los id del pedido que se necesitaban para instanciar los detalles y la consulta de únicamente las columnas necesarias para la instanciación de los detalles solucionó el problema. Eso, y por supuesto, no descartar las líneas de los demás salvo cuando se cambie de cliente.

El problema recién descrito parece ser trivial, pero no se asombren de si de vez en cuando pecan de hacerlo porque fue la solución rápida, la máquina donde se ejecuta tiene el poder para eso, o simplemente llevamos días sin dormir y el trabajo tenía que terminarse como fuese.

En un próximo post seguimos con dos problemas mas de desempeño relacionados con bases de datos  y Java.