Exposición Web Service con NetBeans 6.5.1 y Glassfish 2.1

domingo, 24 de mayo de 2009
Tiempo atrás hice un tutorial bastante detallado sobre como crear y consumir web services usando NetBeans y Glassfish. Hoy traigo lo mismo pero con un ejemplo mucho mas sencillo y fácil de entender y digerir.

Esta entrada trata sobre la exposición de un Web Service, en otra entrada explicaré cómo consumirlo.

El ejemplo expone dos métodos a través de Web Service para validar la existencia de un usuario (getUsuario) y para modificar sus datos (setUsuario).

Los datos del usuario están en un archivo XML para mantener el ejemplo sencillo y simple. La estructura del XML es la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<raiz>
<usuario id="1" user="metalklesk" passwd="metalklesk" activo="true" />
<usuario id="2" user="klesk" passwd="klesk" activo="false" />
</raiz>

como se puede ver a continuación:


El archivo se llama usuarios.xml y debe crearse (o copiarse) en el directorio home del usuario (en el caso de Windows creo que es el directorio del usuario dentro de Mis Documentos, corríjanme si me equivoco y en el caso de Linux es /home/usuario).

Una vez listo ya podemos crear el Web Service. Para esta tarea utilizaremos un contenedor EJB.

Abrimos NetBeans 6.5.1 -> New project -> Java EE -> EJB module


como nombre ingresamos EJBModuleWebService y seleccionamos Glassfish como servidor de aplicaciones.

Una vez listo creamos una clase java a la cual llamaremos Usuario y la agregamos al package org.modelo (como no existe, se creará al ingresarlo en la misma pantalla de creación de la clase). La clase Usuario tendrá el siguiente código:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package org.modelo;

/**
*
* @author metalklesk
*/
public class Usuario {

private int id;
private String user;
private String passwd;
private boolean activo;

public boolean isActivo() {
return activo;
}

public void setActivo(boolean activo) {
this.activo = activo;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getPasswd() {
return passwd;
}

public void setPasswd(String passwd) {
this.passwd = passwd;
}

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}
}

Ahora crearemos la clase AccesoDatos en el mismo package. Esta clase tendrá el siguiente código:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package org.modelo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
*
* @author metalklesk
*/
public class AccesoDatos {

private Document documento = null;
private String archivo = System.getProperty("user.home") + "/usuarios.xml";
private static AccesoDatos datos = new AccesoDatos();

private AccesoDatos() {
try {
documento = (Document) (((DocumentBuilderFactory.newInstance()).newDocumentBuilder()).parse(new File(archivo)));
} catch (SAXException ex) {
Logger.getLogger(AccesoDatos.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(AccesoDatos.class.getName()).log(Level.SEVERE, null, ex);
} catch (ParserConfigurationException ex) {
Logger.getLogger(AccesoDatos.class.getName()).log(Level.SEVERE, null, ex);
}
}

public static AccesoDatos getInstance() {
return datos;
}

public Usuario getUsuario(String user, String passwd) {
Element raiz = (Element) documento.getDocumentElement();
NodeList hijos = raiz.getElementsByTagName("usuario");
Usuario usuario = null;

for(int i=0; i<hijos.getLength(); i++) {
Element elemento = (Element) hijos.item(i);
String x = elemento.getAttribute("user");
String y = elemento.getAttribute("passwd");

if(x.equalsIgnoreCase(user) && y.equalsIgnoreCase(passwd)) {
usuario = new Usuario();
usuario.setId(Integer.parseInt(elemento.getAttribute("id")));
usuario.setUser(x);
usuario.setPasswd(y);
usuario.setActivo(Boolean.parseBoolean(elemento.getAttribute("activo")));

break;
}
}

return usuario;
}

public boolean setUsuario(Usuario usuario) {
Element raiz = (Element) documento.getDocumentElement();
NodeList hijos = raiz.getElementsByTagName("usuario");
boolean respuesta = false;

for(int i=0; i<hijos.getLength(); i++) {
Element elemento = (Element) hijos.item(i);
int id = Integer.parseInt(elemento.getAttribute("id"));

if(id == usuario.getId()) {
elemento.setAttribute("id", String.valueOf(id));
elemento.setAttribute("user", usuario.getUser());
elemento.setAttribute("passwd", usuario.getPasswd());
elemento.setAttribute("activo", String.valueOf(usuario.isActivo()));

respuesta = actualizarUsuarios();
break;
}
}

return respuesta;
}

private boolean actualizarUsuarios() {
documento.getDocumentElement().normalize();

try {
DOMSource source = new DOMSource(documento);
StreamResult result = new StreamResult(new FileOutputStream(archivo));

TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();
transformer.transform(source, result);

return true;
} catch (TransformerException ex) {
Logger.getLogger(AccesoDatos.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (FileNotFoundException ex) {
Logger.getLogger(AccesoDatos.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
}

Una vez listo ya podemos crear el Web Service, para eso hacemos click derecho en el proyecto -> New -> Web service


Ingresamos como nombre UsuarioWebService y lo agregamos al package org.modelo.


Una vez listo, ingresamos el siguiente código con los métodos a exponer en el Web Service:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package org.modelo;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.ejb.Stateless;

/**
*
* @author metalklesk
*/
@WebService()
@Stateless()
public class UsuarioWebService {

/**
* Web service getUsuario
* @param user
* @param passwd
* @return
*/
@WebMethod(operationName = "getUsuario")
public Usuario getUsuario(@WebParam(name = "user")
String user, @WebParam(name = "passwd")
String passwd) {
AccesoDatos datos = AccesoDatos.getInstance();

return datos.getUsuario(user, passwd);
}

/**
* Web service getUsuario
* @param usuario
* @return
*/
@WebMethod(operationName = "setUsuario")
public Boolean setUsuario(@WebParam(name = "usuario")
Usuario usuario) {
AccesoDatos datos = AccesoDatos.getInstance();

return datos.setUsuario(usuario);
}

}

Ya con eso estamos listos con la exposición del Web Service, ahora para probarlo podemos ejecutar la aplicación haciendo click derecho -> Run y luego expandimos la carpeta Web Services, damos click derecho el web service UsuarioWebService y pinchamos la opción Test Web service, lo cual abrirá una ventana del navegador web predeterminado en NetBeans.

Espero les sea de utilidad y como siempre me pueden dejar un comentario con su dirección de correo y les reboto el proyecto NetBeans comprimido.

Saludos !!!

Gráficos JMaki-Dojo, Servlets y Java Beans

viernes, 22 de mayo de 2009
Hace unos días estuve "jugando" con Java y JMaki para crear gráficos (charts). Buscando en la red encontré el proyecto jmaki-charting, el cual permite generar gráficos Dojo de tipo Area, Linea, Barra y Torta.

Desgraciadamente, los ejemplos de gráficos Dojo están malos, si entran en la página de ejemplos y pinchan un enlace de gráficos Dojo verán lo siguiente:


Asi es que me propuse crear un ejemplo que si funcione y de eso se trata esta entrada.

La gracia del ejemplo que explicaré a continuación es que se hace uso de clases java para la creación de los gráficos. De ésta manera se pueden crear gráficos a partir de datos obtenidos dinámicamente de una base de datos o cualquier fuente que se quiera. Si bien el ejemplo usa datos "duros" fácilmente se podrían modificar las clases y traer los datos de una base de datos.

También se muestra en el ejemplo la carga de los gráficos sin la necesidad de cargar una página jsp nueva, simplemente se refresca el área correspondiente y lo demás sigue igual lo que impacta positivamente en la velocidad de carga y experiencia del usuario.

Manos a la obra. Lo primero que necesitamos es NetBeans 6.5.1 con el JDK 1.6 update 13 (con ésta versión realicé el ejemplo). Una vez instalado hay que descargar los módulos de JMaki, para eso ir a Tools -> Plugins y seleccionar los dos plugines de JMaki.

Una vez reiniciado NetBeans hay que descargar el módulo jmaki-charting, abrir NetBeans e ir a Tools -> Palette -> Add jmaki library y seleccionar el módulo previamente descargado. Una vez listo ya se pueden utilizar los gráficos Dojo en NetBeans a través de la paleta.

La aplicación es web y explicaré la lógica que hay detrás de todo. Lo primero es entender como funciona la aplicación.

Básicamente se tiene un menú donde puedes elegir el tipo de gráfico. Éste menú es un combobox Dojo, el cual se arrastra desde la paleta al index.jsp. Cuando se arrastra un combobox dojo se genera con valores por default, pero eso no es muy útil, lo mejor es cargar los datos desde un Java Bean. El java Bean utilizado es el siguiente:

package org.beans;

import org.modelo.Parametros;

/**
*
* @author metalklesk
*/
public class BeanListaCharts {

public String getValues() {
StringBuffer buffer = new StringBuffer();

buffer.append("[");
buffer.append("{label:'Area', value:'" + Parametros.AREA + "'},");
buffer.append("{label:'Barra', value:'" + Parametros.BAR + "'},");
buffer.append("{label:'Linea', value:'" + Parametros.LINE + "'},");
buffer.append("{label:'Torta', value:'" + Parametros.PIE + "'}");
buffer.append("]");

return buffer.toString();
}
}

Luego hay que ingresar la siguiente línea en el jsp para hacer uso del Bean:

<jsp:useBean id="BeanListaCharts" class="org.beans.BeanListaCharts"/>

Y luego hay que pasar del siguiente código (que viene por default al arrastrar un combobox Dojo)

<a:widget name="dojo.combobox"
value="[
{label : 'Alabama', value : 'AL'},
{label : 'California', value : 'CA'},
{label : 'New York', value : 'NY', selected : true},
{label : 'Texas', value : 'TX'}
]" />

a la siguiente forma:

<a:widget name="dojo.combobox" value="${BeanListaCharts.values}" publish="/miCombo" />

Si se fijan, el value lo rellenamos con el método getValues del Java Bean. Eventualmente esos datos podrían provenir de una base de datos.

El publish es para publicar un evento asociado al combobox. Lo que sigue es programar el evento a nuestro gusto. Esto se hace en el archivo glue.js utilizando un subscribe, en ese archivo agregamos lo siguiente (al final):

jmaki.subscribe("/miCombo/onSelect", function(args) {
/*
*Se debe cargar el grafico desde una pagina (o servlet) externa pero dentro del dominio
*e insertarla mediante el injector.
**/
var url_ = jmaki.webRoot + '/ServletGeneradorCharts?tipoChart=' + args.value;
var injector = new jmaki.Injector();
injector.inject({url:url_, injectionPoint:"chart"});
});


Aquí utilizamos un Injector para agregar código html dinámico en una parte específica de la página, sin tener que recargar todo de nuevo en una página nueva. El Injector recibe como parámetros la dirección de la página a cargar y el lugar de la página actual en la cual será insertada.

En este caso llamamos a un servlet al cual le pasamos como parámetro el valor recogido de la selección en el combobox.

El método processRequest del servlet luce de la siguiente manera:

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

String contexto = request.getContextPath();

String seleccion = request.getParameter("tipoChart");
String chart = "No existe ese tipo de gráfico.";

if(seleccion.equalsIgnoreCase(Parametros.PIE)) {
PieChartDojoJMaki pie = new PieChartDojoJMaki();
//si no le pasamos los parámetros requeridos, se genera un pie de ejemplo
//con valores predeterminados.
chart = pie.getValor();

out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/base.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/excanvas.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/PlotKit_Packed.js"></script>");
out.println("<link rel="stylesheet" type="text/css" href="" + contexto + "/resources/jmaki/charting/resources/jmaki-charting.css" />");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/pie/component.js"></script>");
out.println("<script type="text/javascript">jmaki.addWidget({widgetDir:'" + contexto + "/resources/jmaki/charting/pie',name:'jmaki.charting.pie',value:" + chart + ",uuid:'jmaki_charting_pie'});</script>");
out.println("<div id="jmaki_charting_pie" class="jmCPie">");
out.println("<canvas id="jmaki_charting_pie_chart" class="jmCPie"></canvas>");
out.println("</div>");
out.close();
} else if(seleccion.equalsIgnoreCase(Parametros.AREA)) {
AreaChartDojoJMaki area = new AreaChartDojoJMaki();
//si no le pasamos los parámetros requeridos, se genera un pie de ejemplo
//con valores predeterminados.
chart = area.getValor();

out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/base.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/excanvas.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/PlotKit_Packed.js"></script>");
out.println("<link rel="stylesheet" type="text/css" href="" + contexto + "/resources/jmaki/charting/resources/jmaki-charting.css" />");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/area/component.js"></script>");
out.println("<script type="text/javascript">jmaki.addWidget({widgetDir:'" + contexto + "/resources/jmaki/charting/area',name:'jmaki.charting.area',value:" + chart + ",uuid:'jmaki_charting_area2'});</script>");
out.println("<div id="jmaki_charting_area2" class="jmCPie">");
out.println("<canvas id="jmaki_charting_area2_chart" class="jmCPie"></canvas>");
out.println("</div>");
out.close();
} else if(seleccion.equalsIgnoreCase(Parametros.BAR)) {
BarChartDojoJMaki bar = new BarChartDojoJMaki();
//si no le pasamos los parámetros requeridos, se genera un pie de ejemplo
//con valores predeterminados.
chart = bar.getValor();

out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/base.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/excanvas.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/PlotKit_Packed.js"></script>");
out.println("<link rel="stylesheet" type="text/css" href="" + contexto + "/resources/jmaki/charting/resources/jmaki-charting.css" />");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/bar/component.js"></script>");
out.println("<script type="text/javascript">jmaki.addWidget({widgetDir:'" + contexto + "/resources/jmaki/charting/bar',name:'jmaki.charting.bar',value:" + chart + ",uuid:'jmaki_charting_bar'});</script>");
out.println("<div id="jmaki_charting_bar" class="jmCPie">");
out.println("<canvas id="jmaki_charting_bar_chart" class="jmCPie"></canvas>");
out.println("</div>");
out.close();
} else if(seleccion.equalsIgnoreCase(Parametros.LINE)) {
LineChartDojoJMaki line = new LineChartDojoJMaki();
//si no le pasamos los parámetros requeridos, se genera un pie de ejemplo
//con valores predeterminados.
chart = line.getValor();

out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/base.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/excanvas.js"></script>");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/resources/plotkit/PlotKit_Packed.js"></script>");
out.println("<link rel="stylesheet" type="text/css" href="" + contexto + "/resources/jmaki/charting/resources/jmaki-charting.css" />");
out.println("<script type="text/javascript" src="" + contexto + "/resources/jmaki/charting/line/component.js"></script>");
out.println("<script type="text/javascript">jmaki.addWidget({widgetDir:'" + contexto + "/resources/jmaki/charting/line',name:'jmaki.charting.line',value:" + chart + ",uuid:'jmaki_charting_line'});</script>");
out.println("<div id="jmaki_charting_line" class="jmCPie">");
out.println("<canvas id="jmaki_charting_line_chart" class="jmCPie"></canvas>");
out.println("</div>");
out.close();
} else {
//si el tipo de gráfico no corresponde, notifico al usuario.
out.println("<font color="#f00">" + chart + "</font>");
out.close();
}
}

El servlet recibe el parámetro seleccionado por el usuario a través del request.getParameter y dependiendo del valor construye un gráfico. Para obtener los valores del widget se hace uso de las clases

  • AreaChartDojoJMaki
  • BarChartDojoJMaki
  • LineChartDojoJMaki
  • PieChartDojoJMaki
Cada una de éstas clases se especializa en construir un gráfico en particular, utilizando la notación definida por JMaki (Dojo en este caso). En el ejemplo se utilizan datos duros, pero al igual que el caso del combobox, fácilmente se pueden adaptar para hacer uso de una base de datos.

A continuación se puede ver el código de la clase que define un gráfico de area:

package org.modelo;

import java.util.ArrayList;

/**
*
* @author metalklesk
*/
public class AreaChartDojoJMaki {

private ArrayList<String> xEtiquetas;
private ArrayList<String> yEtiquetas;
private ArrayList<String> datos;

/**
* Constructor sin parámetros. Inicia valores predeterminados para construir un
* gráfico de ejemplo.
*/
public AreaChartDojoJMaki() {
xEtiquetas = new ArrayList<String>();
xEtiquetas.add("{label : 'Enero'}, ");
xEtiquetas.add("{label : 'Febrero'}, ");
xEtiquetas.add("{label : 'Marzo'}, ");
xEtiquetas.add("{label : 'Abril'}, ");
xEtiquetas.add("{label : 'Mayo'}, ");
xEtiquetas.add("{label : 'Junio'}, ");
xEtiquetas.add("{label : 'Julio'}, ");
xEtiquetas.add("{label : 'Agosto'}, ");
xEtiquetas.add("{label : 'Septiembre'}, ");
xEtiquetas.add("{label : 'Octubre'}, ");
xEtiquetas.add("{label : 'Noviembre'}, ");
xEtiquetas.add("{label : 'Diciembre'}");

yEtiquetas = new ArrayList<String>();
yEtiquetas.add("{ label : '0', value : 0}");
yEtiquetas.add("{ label : '10s', value : 10}");
yEtiquetas.add("{ label : '20s', value : 20}");
yEtiquetas.add("{ label : '30s', value : 30}");
yEtiquetas.add("{ label : '40s', value : 40}");
yEtiquetas.add("{ label : '50s', value : 50}");

datos = new ArrayList<String>();
datos.add("{id : 'marcador', label : 'Dataset 1', values : [25, 45, 25, 45, 50, 25, 35, 25, 25, 20, 35, 45] }");
datos.add("{label : 'Dataset 2', values : [20, 40, 30, 35, 45, 20, 25, 15, 20, 25, 30, 40] }");
datos.add("{label : 'Dataset 3', values : [15, 35, 15, 40, 30, 15, 20, 10, 15, 20, 30, 35] }");
datos.add("{label : 'Dataset 4', values : [10, 25, 10, 5, 20, 5, 15, 5, 10, 15, 25, 30] }");
}

/**
* Recibe los datos que construyen el gráfico. Se pueden ingresar varios conjuntos de datos (datasets) y la forma
* en que se ingresa cada uno es de la siguiente manera:
* "{label : '$titulo_dataset', values : [$valor1, $valor2, ..., $valorN]}", sin las comillas.
* @param datos
*/
public void setDatos(ArrayList<String> datos) {
this.datos = datos;
}

/**
* Recibe las etiquetas para la coordenada X. El formato de cada etiqueta debe ser de la siguiente forma:
* "{label: '$valor'},", sin las comillas.
* @param xEtiquetas
*/
public void setXEtiquetas(ArrayList<String> xEtiquetas) {
this.xEtiquetas = xEtiquetas;
}

/**
* Recine las etiquetas para la coordenada Y. El formato de cada etiqueta debe ser de la siguiente forma:
* "{label : '$titulo', value : $valor}"
* @param yEtiquetas
*/
public void setYEtiquetas(ArrayList<String> yEtiquetas) {
this.yEtiquetas = yEtiquetas;
}

/**
* Retorna el contenido de la etiqueta value del componente jmaki.charting.area.
* @return
*/
public String getValor() {
StringBuffer buffer = new StringBuffer();

buffer.append("{");
//valores de la coordenada x
buffer.append("xAxis : { labels : [ ");
for(int i=0; i< xEtiquetas.size(); i++) {
buffer.append(xEtiquetas.get(i));
}
buffer.append("]},");
//valores de la coordenada y
buffer.append("yAxis : { labels : [");
for(int i=0; i< yEtiquetas.size(); i++) {
buffer.append(yEtiquetas.get(i) + ", ");
}
buffer.append("]},");
//aqui agrego marcadores en algunos puntos.
buffer.append("markers : [{ targetId : 'marcador', label : '{value}', " +
"index : 5}, { targetId : 'marcador', label : '{value}', index : 6}, " +
"{ targetId : 'marcador', label : '{value}', index : 8}],");
//datos que construyen el grafico
buffer.append("data : [ ");
for(int i=0; i<datos.size(); i++) {
buffer.append(datos.get(i) + ", ");
}
buffer.append("]");

buffer.append("}");

return buffer.toString();
}
}

Como se pude ver, no es necesario utilizar objetos JSON, un simple String con la estructura adecuada basta para generar un chart o widget JMaki en general.

A continuación se pueden ver imágenes del ejemplo funcionando.



Como siempre pueden dejar un comentario con su dirección de correo y les envío el ejemplo completo (proyecto en NetBeans 6.5.1 comprimido) para que lo corran y vean en sus equipos.

Saludos !!!

    Convertir datos de latin1 a utf8 en MySQL 5.1

    domingo, 17 de mayo de 2009
    Hace poco tuve que migrar el sitio web de la empresa en la cual trabajo a otro servidor para lo cual fue necesario pedir, al proveedor del servicio de hosting, el respaldo del sitio junto a la base de datos.

    El problema apareció cuando los datos de la base de datos perdieron la codificación utf8, los acentos (tíldes), las ñ's y otros caracteres fueron reemplazados por caracteres extraños, por ejemplo Consultoría en vez de Consultoría.

    Cuando cargué la base de datos (utilizando PhpMyAdmin) me dí cuenta que las tablas quedaron con el charset latin1 en vez de utf8. Averiguando por Internet supe que el problema está en la instalación inicial de MySQL, la cual deja todo el sistema con latin1_swedish_ci y por lo tanto al exportar la base de datos, se pierden los caracteres especiales.

    En Internet abundan pequeñas guías que solo solucionan parcialmente el problema pero no del todo. En la mayoría de las páginas encontré que se debía cambiar el charset de la base de datos, tablas y campos (varchar y text), pero eso solo sirve para los datos nuevos, pero que pasa con los que ya están ingresados y que presentan el problema ?

    La receta para solucionar el problema completo es seguir los siguientes pasos (en orden):

    1. Convertir los datos de latin1 a binario y de binario a utf8 para cada campo de tipo varchar y text de cada tabla de la base de datos.
    2. Cambiar el charset del campo de latin1 a utf8.
    3. Cambiar el charset de las tablas de latin1 a utf8.
    4. Cambiar el charset y el collation de la base de datos de latin1 a utf8.

    Para convertir los datos de un charset a otro se utiliza la función convert de MySQL, la cual se usa de la siguiente manera:

    update tabla set campo = convert( convert(campo using binary) using utf8);


    Para cambiar el charset de la tabla se hace de la siguiente manera:

    alter table tabla charset = utf8;


    Para cambiar el charset de la base de datos se hace de la siguiente manera:

    alter database basedatos charset = utf8;


    Y con eso se soluciona el problema.

    Para evitar que vuelva a suceder el problema en el futuro, recomiendo que sean precavidos al crear una base de datos y siempre seleccionen el charset y collation a utf8 (_unicode_ci).

    Espero les sirva como a mi.

    Saludos !!!