miércoles, 30 de mayo de 2018

Hibernate Road 1

Descarga de Hibernate de su página

- Se crea un projecto
  - Se crea una librería que integre los archivos jar descargados de hibernate.
  - Se agrega en la librería el conector hacia la base de datos que se utilizará.

Se crea Entidad UserDetails en UserDetails.java y HibernateTest para crear la sessión y persistir la entidad con transacción.

Estructura de archivos en el projecto
src
   org
       bext
            dto
                UserDetails.java
            hibernate
                HibernateTest.java
   hibernate.cfg.xml

Para crear el archivo de configuración  de hibernate, un camino práctico es tomar un archivo existente de los archivos descargados de hibernate y elegir alguno que este más completo en cuanto a sus propiedades, y a partir de ahí modificarlo a nuestras necesidades.

hibernate.cfg.xml adaptado

<?xml version='1.0' encoding='utf-8'?>
<!--
  ~ Hibernate, Relational Persistence for Idiomatic Java
  ~
  ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
  ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
  -->
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.gjt.mm.mysql.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/wsp?zeroDateTimeBehavior=convertToNull</property>
        <property name="connection.username">root</property>
        <property name="connection.password">XXXXXXX</property>
        <property name="hibernate.default_schema">wsp</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create</property>

        <!-- Names the annotated entity class -->
        <mapping class="org.bext.dto.UserDetails"/>

    </session-factory>

</hibernate-configuration>


UserDetails.java define la entidad y un id con anotaciones

package org.bext.dto;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class UserDetails {
    @Id
    private int userId;
    private String userName;
 
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}

HibernateTest.java

package org.bext.hibernate;

import org.bext.dto.UserDetails;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateTest {

    public static void main(String[] args) {
        UserDetails userDetails = new UserDetails();
        userDetails.setUserId(1);
        userDetails.setUserName("Primer Usuario");
       
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.getTransaction().commit();
        session.close();
    }
}


Al ejecutar el programa en la consola obtendermos comentarios que arroja hibernate y dan pistas sobre lo que hace a nivel de base de datos.

may 28, 2018 1:26:24 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.10.Final}
may 28, 2018 1:26:24 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
may 28, 2018 1:26:25 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
may 28, 2018 1:26:25 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
may 28, 2018 1:26:25 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [org.gjt.mm.mysql.Driver] at URL [jdbc:mysql://localhost:3306/wsp?zeroDateTimeBehavior=convertToNull]
may 28, 2018 1:26:25 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
may 28, 2018 1:26:25 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
may 28, 2018 1:26:25 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 1 (min=1)
Mon May 28 13:26:26 CDT 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
may 28, 2018 1:26:26 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
Hibernate: drop table if exists UserDetails
may 28, 2018 1:26:28 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@512535ff] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: create table UserDetails (userId integer not null, userName varchar(255), primary key (userId)) engine=MyISAM
may 28, 2018 1:26:29 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@22875539] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
may 28, 2018 1:26:29 PM org.hibernate.tool.schema.internal.SchemaCreatorImpl applyImportSources
INFO: HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@43f82e78'
Hibernate: insert into UserDetails (userName, userId) values (?, ?)

Y nos crea la tabla con valores default según los nombres del POJO
userid[INT]  userName[VARCHAR255]
1                   Primer Usuario

Modificando característicadas de la Tabla creada con @Column

En el POJO UserDetails.java

@Entity (name="USER_DETAILS")
public class UserDetails {
    @Id
    @Column(name="USER_ID")
    private int userId;
    @Column(name="USER_NAME")
    private String userName;
...

En el archivo HibernateTest.java modificamos para que guarde dos registros.

    public static void main(String[] args) {
        UserDetails userDetails = new UserDetails();
        UserDetails userDetails2 = new UserDetails();
        userDetails.setUserId(1);
        userDetails.setUserName("Primer Usuario");
       
        userDetails2.setUserId(2);
        userDetails2.setUserName("Segundo Usuario");
       
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.save(userDetails2);
        session.getTransaction().commit();
        session.close();
        sessionFactory.close();
    }


Al ejecutarlo tenemos que hibernate nos arroja a la consola
...
Hibernate: drop table if exists USER_DETAILS
...
Hibernate: create table USER_DETAILS (USER_ID integer not null, USER_NAME varchar(255), primary key (USER_ID)) engine=MyISAM
Hibernate: insert into USER_DETAILS (USER_NAME, USER_ID) values (?, ?)
Hibernate: insert into USER_DETAILS (USER_NAME, USER_ID) values (?, ?)
...


Veremos que la tabla ahora se llama USER_DETAILS y sus columnas USER_ID, USER_NAME
USER_ID[INT]  USER_NAME[VARCHAR255]
1                          Primer Usuario
2                          Segundo Usuario

Uso del Getter para asignar valores al valor del campo


...
    @Column(name="USER_NAME")
    public String getUserName() {
        return userName + " [esta parte agregada en getter]";

...

El resultado es que en la tabla agrega el dato a el registro.

USER_ID[INT]  USER_NAME[VARCHAR255]
1                          Primer Usuario [esta parte agregada en getter]
2                          Segundo Usuario [esta parte agregada en getter]

Agregando más campos o atributos a la Entidad

Se agragan atributos al POJO UserDetails

@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @Id
    private int userId;
    private String userName;
    private Date joinedDate;
    private String address;
    private String description;
... setters y getters...


y en HibernateTest.java se asignan estos campos

public class HibernateTest {

    public static void main(String[] args) {
        UserDetails userDetails = new UserDetails();
        UserDetails userDetails2 = new UserDetails();
        userDetails.setUserId(1);
        userDetails.setUserName("Primer Usuario");
        userDetails.setAddress("address Primer Usuario");
        userDetails.setDescription("Primer Usuario Descripcion");
       
        userDetails2.setUserId(2);
        userDetails2.setUserName("Segundo Usuario");
       
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.save(userDetails2);
        session.getTransaction().commit();
        //session.close();
        //sessionFactory.close();
    }


Al ejecutarlo, la consola reporta que hibernate crea los campos con los tipos siguientes
...
Hibernate: create table USER_DETAILS (userId integer not null, address varchar(255), description varchar(255), joinedDate datetime, userName varchar(255), primary key (userId)) engine=MyISAM
...

Y se insertan datos para el primer registro. notese que el valor de la llave se asigna manualmente.

userid address                           description                           userName               joinedDate
1         address Primer UsuarioPrimer Usuario Descripcion
Primer Usuario
2


Segundo Usuario

@Temporal y @Lob Anotaciones


@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @Id
    private int userId;
    private String userName;
    @Temporal (TemporalType.DATE)
    private Date joinedDate;
    private String address;
    @Lob
    private String description;
... getters y setters...


Al ejecutar el programa, el la consola Hibernate crea la tabla con los siguientes campos

Hibernate: create table USER_DETAILS (userId integer not null, address varchar(255), description longtext, joinedDate date, userName varchar(255), primary key (userId)) engine=MyISAM

El programa de prueba carga de la siguiente manera los datos a la entidad. notese el Date y Description.
    public static void main(String[] args) {
        UserDetails userDetails = new UserDetails();
        UserDetails userDetails2 = new UserDetails();
        userDetails.setUserId(1);
        userDetails.setUserName("Primer Usuario");
        userDetails.setJoinedDate(new Date());
        userDetails.setAddress("address Primer Usuario");
        userDetails.setDescription("Primer Usuario Descripcion");
...


userid address                           description                           userName               joinedDate
1         address Primer UsuarioPrimer Usuario Descripcion
Primer Usuario    2018-02-23
2


Segundo Usuario

Recuperando Objetos con session.get(Class<T> t, serializable LlavePrimaria)

Se recupera un registro de la tabla con la llave primaria, en este caso userid = 2.

HibernateTest.java
       ...
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.save(userDetails2);
        session.getTransaction().commit();
        session.close();
       
        userDetails = null;
       
        session = sessionFactory.openSession();
        session.beginTransaction();
        userDetails = session.get(UserDetails.class, 2);
        System.out.println("El usuario obtenido es:" + userDetails.getUserName());
        sessionFactory.close();


Generación automática de valores de llave Primaria

@Id @GeneratedValue(strategy=GenerationType.AUTO)


Se agrega la anotación @GeneratedValue(...) al la propiedad del POJO cuya secuencia se desea sea generada automáticamente.

El POJO queda en sus atribbutos de la siguiente manera

@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private int userId;
    private String userName;
    @Temporal (TemporalType.DATE)
    private Date joinedDate;
    private String address;
    @Lob
    private String description;

... setters y getters...

En el programa de prueba HibernateTest.java comentamos las lineas que asignan los valores a userId, ya que no sera necesario.
En la consola Hibernate Reporta una tabla llamada hibernate_sequence, que es donde se almacena la secuencia de userId.
...
Hibernate: drop table if exists hibernate_sequence
...

Hibernate: drop table if exists USER_DETAILS
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
...

Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: create table USER_DETAILS (userId integer not null, address varchar(255), description longtext, joinedDate date, userName varchar(255), primary key (userId)) engine=MyISAM
...


@Embeddable califica una Entidad para ser Embedida

@Embedded Propiedad calificada a ser una Entidad Embebida


Se creara Address como una entidad, y se integrará a UserDetails, el efecto será que las propiedades de Address (campos) se agragarán a la tabla de userDetails como columnas.

Address.java
 import javax.persistence.Embeddable;

@Embeddable
public class Address {
    private String street;
    private String city;
    private String state;
    private String pinCode;

...setters y getters...

Y en UserDetails.java

@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private int userId;
    private String userName;
    @Temporal (TemporalType.DATE)
    private Date joinedDate;
    @Embedded
    private Address address;
    @Lob
    private String description;
...setters y getters...


hibernateTest.java

public class HibernateTest {

    public static void main(String[] args) {
        Address addr1 = new Address();
        addr1.setStreet("calle 10");
        addr1.setCity("Mexico");
        addr1.setState("DF");
        addr1.setPinCode("32123");
       
        Address addr2 = new Address();
        addr2.setStreet("Miguel Hidalgo");
        addr2.setCity("San Miguel de Allende");
        addr2.setState("Guanajuato");
        addr2.setPinCode("00011");
               
        UserDetails userDetails = new UserDetails();
        UserDetails userDetails2 = new UserDetails();
       
        userDetails.setUserName("Primer Usuario");
        userDetails.setJoinedDate(new Date());
        userDetails.setDescription("Primer Usuario Descripcion");
        userDetails.setAddress(addr1);
       
        userDetails2.setUserName("Segundo Usuario");
        userDetails2.setAddress(addr2);
       
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.save(userDetails2);
        session.getTransaction().commit();
        session.close();
        //sessionFactory.close();
    }


Al ejecutar el hibernateTest.java, en la consola hibernate reporta que crea la tabla user_details, más con las coumnas que aparecen en addres.
...
Hibernate: create table USER_DETAILS (userId integer not null, city varchar(255), pinCode varchar(255), state varchar(255), street varchar(255), description longtext, joinedDate date, userName varchar(255), primary key (userId)) engine=MyISAM
...

Dos Campos Address, evitar duplicar nombres de columnas en tabla @Embedded, @AttributeOverride


Se crea la entidad Address a la cual se le configuran nombres de columnas en la entidad Address, y se agregaran dos campos address a la entidad UserDetails, pero los nombres de las columnas de las dos adderess se duplicarian en la tabla, así que se necesitan sobreescribir estos nombres de columnas. esto se hace con @AttibuteOverrides({
@AttributeOverride (name="street", column=@Column(name="OFF_STREET_NAME")),
@AttributeOverride (name="city", column=@Column(name="OFF_CITY_NAME")),...})

Address.java

@Embeddable
public class Address {
    @Column (name="STREET_NAME")
    private String street;
    @Column (name="CITY_NAME")
    private String city;
    @Column (name="STATE_NAME")
    private String state;
    @Column (name="PIN_CODE")
    private String pinCode;

... setters y gettes...

En UserDetails.java se agregan dos campos address y a uno de ellos se le sobreescriben las características de los campos.

UserDetails.java

@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private int userId;
    private String userName;
    @Temporal (TemporalType.DATE)
    private Date joinedDate;
    @Embedded
    private Address homeaddress;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride (name="street", column=@Column(name="OFF_STREET_NAME")),
        @AttributeOverride (name="city", column=@Column(name="OFF_CITY_NAME")),
        @AttributeOverride (name="state", column=@Column(name="OFF_STATE_NAME")),
        @AttributeOverride (name="pinCode", column=@Column(name="OFF_PIN_CODE"))
    })
    private Address officeaddress;

    @Lob
    private String description;
...setters y getters...


Al ejecutar HibernateTest.java, Hibernate reporta en la consola que crea la tabla de la siguiente forma
...
Hibernate: create table USER_DETAILS (userId integer not null, description longtext, CITY_NAME varchar(255), PIN_CODE varchar(255), STATE_NAME varchar(255), STREET_NAME varchar(255), joinedDate date, OFF_CITY_NAME varchar(255), OFF_PIN_CODE varchar(255), OFF_STATE_NAME varchar(255), OFF_STREET_NAME varchar(255), userName varchar(255), primary key (userId)) engine=MyISAM
...

Donde se observa que se crean los campos de dos Address con nombres de columnas diferenciados.


userId description CITY_NAME PIN_CODE STATE_NAME STREET_NAME joinedDate OFF_CITY_NAME OFF_PIN_CODE OFF_STATE_NAME OFF_STREET_NAME userName
1 Primer Usuario Descripcion Mexico 32123 DF calle 10 2018-05-28 Mexico 01066 D.F. Paseo de la Reforma Primer Usuario
2 NULL San Miguel de Allende 00011 Guanajuato Miguel Hidalgo NULL Mexico 01066 D.F. Paseo de la Reforma Segundo Usuario

@EmbeddedId PrimaryKey Compuesta


Para generar una tabla con una llave primaria compuesta, podemos usar una clase embeddable cuyos attributos sean los campos que compondran la llave. Así generaremos la Clase LoginName.java que tendra dos atributos que corresponderan a la llave primaria compuesta para la entidad UserDetails.
estos campos de llave primaria serán loginId de tipo Long, y loginName de tipo String.

LoginName.java

@Embeddable
public class LoginName implements Serializable{
   
    /**
     *
     */
    private static final long serialVersionUID = -4755541708740964848L;
    private Long loginId;
    private String loginName;
   
    public LoginName() {
        super();
    }
   
    public LoginName(Long loginId, String loginName) {
        super();
        this.loginId = loginId;
        this.loginName = loginName;
    }

    public Long getLoginId() {
        return loginId;
    }
    public void setLoginId(Long loginId) {
        this.loginId = loginId;
    }
    public String getLoginName() {
        return loginName;
    }
    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((loginId == null) ? 0 : loginId.hashCode());
        result = prime * result + ((loginName == null) ? 0 : loginName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        LoginName other = (LoginName) obj;
        if (loginId == null) {
            if (other.loginId != null)
                return false;
        } else if (!loginId.equals(other.loginId))
            return false;
        if (loginName == null) {
            if (other.loginName != null)
                return false;
        } else if (!loginName.equals(other.loginName))
            return false;
        return true;
    }
}


En la clase UserDetails.java se integra con la anotacion @EmbeddedId

...
@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
    @EmbeddedId
    private LoginName loginName;
    private String userName;
    @Temporal (TemporalType.DATE)
    private Date joinedDate;
    @Embedded
    private Address homeAddress;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="street",column=@Column(name="OFFICE_STREET_NAME")),
        @AttributeOverride(name="city",column=@Column(name="OFFICE_CITY_NAME")),
        @AttributeOverride(name="state",column=@Column(name="OFFICE_STATE_NAME")),
        @AttributeOverride(name="pinCode",column=@Column(name="OFFICE_PIN_CODEE"))
    })
    private Address officeAddress;
    @Lob
    private String description;

...

HibernateTest.java
...
public class HibernateTest {

    public static void main(String[] args) {
        Address addr1 = new Address();
        addr1.setStreet("calle 10");
        addr1.setCity("Mexico");
        addr1.setState("DF");
        addr1.setPinCode("32123");
       
        Address addr2 = new Address();
        addr2.setStreet("Miguel Hidalgo");
        addr2.setCity("San Miguel de Allende");
        addr2.setState("Guanajuato");
        addr2.setPinCode("00011");
       
        Address officeAddr = new Address();
        officeAddr.setStreet("Paseo de la Reforma");
        officeAddr.setCity("Mexico");
        officeAddr.setState("D.F");
        officeAddr.setPinCode("01223");
               
        UserDetails userDetails = new UserDetails();
        UserDetails userDetails2 = new UserDetails();
        LoginName loginName1 = new LoginName(1L,"unoLogin");
        LoginName loginName2 = new LoginName(2L,"dosLogin");
       
        userDetails.setLoginName(loginName1);
        userDetails.setUserName("Primer Usuario");
        userDetails.setJoinedDate(new Date());
        userDetails.setDescription("Primer Usuario Descripcion");
        userDetails.setHomeAddress(addr1);
        userDetails.setOfficeAddress(officeAddr);

        userDetails2.setLoginName(loginName2);
        userDetails2.setUserName("Segundo Usuario");
        userDetails2.setHomeAddress(addr2);
        userDetails2.setOfficeAddress(officeAddr);
       
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(userDetails);
        session.save(userDetails2);
        session.getTransaction().commit();
        session.close();
        //sessionFactory.close();
    }

La ejecución del programa HibernateTest.java, en la consola revela que crea la tabla User_Detail de la siguiente manera

...
Hibernate: create table USER_DETAILS (loginId bigint not null, loginName varchar(255) not null, description longtext, city varchar(255), pinCode varchar(255), state varchar(255), street varchar(255), joinedDate date, OFFICE_CITY_NAME varchar(255), OFFICE_PIN_CODEE varchar(255), OFFICE_STATE_NAME varchar(255), OFFICE_STREET_NAME varchar(255), userName varchar(255), primary key (loginId, loginName)) engine=MyISAM
...

 La tabla USER_DETAILS tendría lo siguiente.

loginId loginName description city pinCode state street joinedDate OFFICE_CITY_NAME OFFICE_PIN_CODEE OFFICE_STATE_NAME OFFICE_STREET_NAME userName
1 unoLogin Primer Usuario Descripcion Mexico 32123 DF calle 10 2018-05-28 Mexico 01223 D.F Paseo de la Reforma Primer Usuario
2 dosLogin NULL San Miguel de Allende 00011 Guanajuato Miguel Hidalgo NULL Mexico 01223 D.F Paseo de la Reforma Segundo Usuario



fin de texto.

No hay comentarios:

Publicar un comentario