jueves, 31 de mayo de 2018

Hibernate Road 3


@ManyToMany UserDetails a Vehicle y @ManyToMany Vehicle a UserDetails


Establezcamos una relación de muchos a muchos de UserDetails a Vehicle y viceversa, en otras palabras usuario puede tener varios vehículos, y también un vehículo puede tener varios usuarios.

UserDetails.java
@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
       @Id @GeneratedValue(generator="vehicle-gen",strategy=GenerationType.AUTO)
       @GenericGenerator(name="vehicle-gen",strategy="sequence",
                                         parameters={@Parameter(name="sequence_name",value="seq_vehicle")}
                                  )
       private int userId;
       private String userName;
       @ManyToMany
       private Collection<Vehicle> vehicle = new ArrayList<Vehicle>();
setters y getters…
en Vehicle.java
@Entity
public class Vehicle {
       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       private int vehicleId;
       private String vehicleName;
       @ManyToMany
       private Collection<UserDetails> userList = new ArrayList<UserDetails>();

En HibernateTest.java cargaremos datos a user y a vehicle.
public static void main(String[] args) {
             UserDetails userDetails = new UserDetails();
                          
             Vehicle vehicle1 = new Vehicle();
             vehicle1.setVehicleName("carrito");
             Vehicle vehicle2 = new Vehicle();
             vehicle2.setVehicleName("Audi");
                          
             userDetails.setUserName("Primer Usuario");
             userDetails.getVehicle().add(vehicle1);
             userDetails.getVehicle().add(vehicle2);
            
             vehicle1.getUserList().add(userDetails);
             vehicle2.getUserList().add(userDetails);
            
             SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
             Session session = sessionFactory.openSession();
             session.beginTransaction();
             session.save(userDetails);
             session.save(vehicle1);
             session.save(vehicle2);
             session.getTransaction().commit();
             session.close();
            
             //sessionFactory.close();
       }
 Al ejecutar el programa. Hibernate reporta en la consola, la creación de dos tablas de relación.
Hibernate: drop table if exists seq_vehicle
Hibernate: drop table if exists USER_DETAILS
Hibernate: drop table if exists USER_DETAILS_Vehicle
Hibernate: drop table if exists Vehicle
Hibernate: drop table if exists Vehicle_USER_DETAILS
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: create table seq_vehicle (next_val bigint) engine=MyISAM
Hibernate: insert into seq_vehicle values ( 1 )
Hibernate: create table USER_DETAILS (userId integer not null, userName varchar(255), primary key (userId)) engine=MyISAM
Hibernate: create table USER_DETAILS_Vehicle (UserDetails_userId integer not null, vehicle_vehicleId integer not null) engine=MyISAM
Hibernate: create table Vehicle (vehicleId integer not null, vehicleName varchar(255), primary key (vehicleId)) engine=MyISAM
Hibernate: create table Vehicle_USER_DETAILS (Vehicle_vehicleId integer not null, userList_userId integer not null) engine=MyISAM
Hibernate: alter table USER_DETAILS_Vehicle add constraint FKd6kp2c4oltmpsg6g9d8u15ymw foreign key (vehicle_vehicleId) references Vehicle (vehicleId)
Hibernate: alter table USER_DETAILS_Vehicle add constraint FKbdrxqw3j8dovf9npdkrfkpc3u foreign key (UserDetails_userId) references USER_DETAILS (userId)
Hibernate: alter table Vehicle_USER_DETAILS add constraint FK8t4fr6s5yx5sg3qquujrjn2w2 foreign key (userList_userId) references USER_DETAILS (userId)
Hibernate: alter table Vehicle_USER_DETAILS add constraint FK8mnn7v8pd5lo5asfwtvuwbfp3 foreign key (Vehicle_vehicleId) references Vehicle (vehicleId)
Hibernate: select next_val as id_val from seq_vehicle for update
Hibernate: update seq_vehicle set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into USER_DETAILS (userName, userId) values (?, ?)
Hibernate: insert into Vehicle (vehicleName, vehicleId) values (?, ?)
Hibernate: insert into Vehicle (vehicleName, vehicleId) values (?, ?)
Hibernate: insert into USER_DETAILS_Vehicle (UserDetails_userId, vehicle_vehicleId) values (?, ?)
Hibernate: insert into USER_DETAILS_Vehicle (UserDetails_userId, vehicle_vehicleId) values (?, ?)
Hibernate: insert into Vehicle_USER_DETAILS (Vehicle_vehicleId, userList_userId) values (?, ?)
Hibernate: insert into Vehicle_USER_DETAILS (Vehicle_vehicleId, userList_userId) values (?, ?)

Tabla UserDetails
userId userName
1 Primer Usuario

Tabla Vehicle
vehicleId vehicleName
1 carrito
2 Audi

Tabla Vehicle_USER_DETAILS
Vehicle_vehicleId userList_userId
1 1
2 1

Tabla USER_DETAILS_Vehicle
UserDetails_userId vehicle_vehicleId
1 1
1 2

Se puede observar que es redundante una de las dos tablas de relación que creo hibernate, con una sola sería suficiente, pare ello hay que indicar en la entidad que no queremos que genere la tabla de relación redundante Vehicle con @ManyToMany(mappedBy="vehicle").


@ManyToMany(mappedBy=”…”)


En Vehicle.java, indicamos con mappedBy=”vehicle” que use la propiedad “vehicle” de UserDetails.
@Entity
public class Vehicle {
       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       private int vehicleId;
       private String vehicleName;
       @ManyToMany(mappedBy="vehicle")
       private Collection<UserDetails> userList = new ArrayList<UserDetails>();
            

Al ejecutar el programa de carga de prueba HibernateTest.java, observamos que ahora solo que crea una sola table de relación USER_DETAILS_Vehicle
Hibernate: drop table if exists hibernate_sequence
Hibernate: drop table if exists seq_vehicle
Hibernate: drop table if exists USER_DETAILS
Hibernate: drop table if exists USER_DETAILS_Vehicle
Hibernate: drop table if exists Vehicle
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: create table seq_vehicle (next_val bigint) engine=MyISAM
Hibernate: insert into seq_vehicle values ( 1 )
Hibernate: create table USER_DETAILS (userId integer not null, userName varchar(255), primary key (userId)) engine=MyISAM
Hibernate: create table USER_DETAILS_Vehicle (userList_userId integer not null, vehicle_vehicleId integer not null) engine=MyISAM
Hibernate: create table Vehicle (vehicleId integer not null, vehicleName varchar(255), primary key (vehicleId)) engine=MyISAM
Hibernate: alter table USER_DETAILS_Vehicle add constraint FKd6kp2c4oltmpsg6g9d8u15ymw foreign key (vehicle_vehicleId) references Vehicle (vehicleId)
Hibernate: alter table USER_DETAILS_Vehicle add constraint FK8a2f131hek5akrmni3vff2ivy foreign key (userList_userId) references USER_DETAILS (userId)
Hibernate: select next_val as id_val from seq_vehicle for update
Hibernate: update seq_vehicle set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into USER_DETAILS (userName, userId) values (?, ?)
Hibernate: insert into Vehicle (vehicleName, vehicleId) values (?, ?)
Hibernate: insert into Vehicle (vehicleName, vehicleId) values (?, ?)
Hibernate: insert into USER_DETAILS_Vehicle (userList_userId, vehicle_vehicleId) values (?, ?)
Hibernate: insert into USER_DETAILS_Vehicle (userList_userId, vehicle_vehicleId) values (?, ?)


@ManyToOne @NotFound(NotFoundAction.IGNORE)

Utilizado para cuando se quiere evitar una exception cuando no se encuentra la entidad.

CascadeTypes 

En acciones como guardar, actualizar sobre una entidad con entidades hijas como propiedades sobre las cuales se desea que dicha acción se aplique también a estas entidades hijas. en este ejemplo guardamos userDetails pero no vehicle, es entonces cuando se genera un error
object references an unsaved transient instance...

@OneToMany 

@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
       @Id @GeneratedValue(generator="vehicle-gen",strategy=GenerationType.AUTO)
       @GenericGenerator(name="vehicle-gen",strategy="sequence",
                                         parameters={@Parameter(name="sequence_name",value="seq_vehicle")}
                                  )
       private int userId;
       private String userName;
       @OneToMany
       @JoinTable(name="USER_VEHICLE",joinColumns=@JoinColumn(name="USER_ID"),
                    inverseJoinColumns=@JoinColumn(name="VEHICLE_ID"))
       private Collection<Vehicle> vehicle = new ArrayList<Vehicle>();

@Entity
public class Vehicle {
       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       private int vehicleId;
       private String vehicleName;

public static void main(String[] args) {
             UserDetails userDetails = new UserDetails();
                          
             Vehicle vehicle1 = new Vehicle();
             vehicle1.setVehicleName("carrito");
             Vehicle vehicle2 = new Vehicle();
             vehicle2.setVehicleName("Audi");
                          
             userDetails.setUserName("Primer Usuario");
             userDetails.getVehicle().add(vehicle1);
             userDetails.getVehicle().add(vehicle2);
            
            
             SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
             Session session = sessionFactory.openSession();
             session.beginTransaction();
             session.save(userDetails);
             //session.save(vehicle1);
             //session.save(vehicle2);
             session.getTransaction().commit();
             session.close();
            
             //sessionFactory.close();
       }

may 31, 2018 2:30:30 PM org.hibernate.internal.ExceptionMapperStandardImpl mapManagedFlushFailure
ERROR: HHH000346: Error during managed flush [org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance beforeQuery flushing: org.bext.dto.Vehicle]
Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance beforeQuery flushing: org.bext.dto.Vehicle

Agregando @OneToMany(cascade=CascadeType.PERSIST)

 Al calificar con cascade=CascadeType.PERSIST, no es necesario indicar en el código que guarde las entidades vehicle, pero si hay que indicar que se hace un persist() en lugar de un save().
@Entity
@Table (name="USER_DETAILS")
public class UserDetails {
       @Id @GeneratedValue(generator="vehicle-gen",strategy=GenerationType.AUTO)
       @GenericGenerator(name="vehicle-gen",strategy="sequence",
                                         parameters={@Parameter(name="sequence_name",value="seq_vehicle")}
                                  )
       private int userId;
       private String userName;
       @OneToMany(cascade=CascadeType.PERSIST)
       @JoinTable(name="USER_VEHICLE",joinColumns=@JoinColumn(name="USER_ID"),
                    inverseJoinColumns=@JoinColumn(name="VEHICLE_ID"))
       private Collection<Vehicle> vehicle = new ArrayList<Vehicle>();

En HibernateTest.java, cambiamos save a persist.
             SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
             Session session = sessionFactory.openSession();
             session.beginTransaction();
             session.persist(userDetails);
             //session.save(vehicle1);
             //session.save(vehicle2);
             session.getTransaction().commit();
             session.close();


Implementando Herencia

Herencia es una característica común en lenguajes OOP, pero para modelos entidad relación no es materia común. Hibernate da algunas opciones para implementar herencia.
@Entity
public class DosRuedas extends Vehicle {
       private String Manubrio;
…setters y getters…

@Entity
public class CuatroRuedas extends Vehicle {
       private String volante;
…setters y getters…

HibernateTest.java
public static void main(String[] args) {
       Vehicle vehicle = new Vehicle();
       vehicle.setVehicleName("transporte");
       DosRuedas bicicleta = new DosRuedas();
       bicicleta.setVehicleName("birula");
       bicicleta.setManubrio("Manubrio de bicicleta");
       CuatroRuedas automovil = new CuatroRuedas();
       automovil.setVehicleName("Audi");
       automovil.setVolante("volante de automovil");
                          
       SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
       Session session = sessionFactory.openSession();
       session.beginTransaction();
       session.save(vehicle);
       session.save(bicicleta);
       session.save(automovil);
       session.getTransaction().commit();
       session.close();
            
       //sessionFactory.close();
}

Hibernate crea una única table con columnas extras referentes a las propiedades de las clases hijas.

Tabla Vehicle
DTYPE vehicleId vehicleName Manubrio volante
Vehicle 1 transporte NULL NULL
DosRuedas 2 birula Manubrio de bicicleta NULL
CuatroRuedas 3 Audi NULL volante de automovil


@Inheritance(strategy=InheritanceType.SINGLE_TABLE)


Configurando valores y nombres en SINGLE_TABLE
Vehicle.java
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
             name="Tipo_Vehiculo",
             discriminatorType=DiscriminatorType.STRING)
public class Vehicle {
       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       private int vehicleId;
       private String vehicleName;
DosRuedas.java
@Entity
@DiscriminatorValue("Bicicleta")
public class DosRuedas extends Vehicle {
       private String Manubrio;
CuatroRuedas.java
@Entity
@DiscriminatorValue("Carro")
public class CuatroRuedas extends Vehicle {
       private String volante;
Con estas configuraciones, obtenemos la table Vehicle con nombre de columna de discriminación con nombre Tipo_Vehiculo y valores como “Bicicleta” y “Carro”


Tabla Vehiculo customizada
Tipo_Vehiculo vehicleId vehicleName Manubrio volante
Vehicle 1 transporte NULL NULL
Bicicleta 2 birula Manubrio de bicicleta NULL
Carro 3 Audi NULL volante de automovil


@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

La estratégia que crea una tabla por clase, es configurada con la anotación mostrada en el titulo de este párrafo. Con esto no es necesaria la anotación @DiscriminationColumn en la entidad padre y @DiscriminationValue en las entidades hijas.

Vehicle.java
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       private int vehicleId;
       private String vehicleName;

DosRuedas.java
@Entity
public class DosRuedas extends Vehicle {
       private String Manubrio;

CuatroRuedas.java
@Entity
public class CuatroRuedas extends Vehicle {
       private String volante;
Tabla Vehicle
vehicleId vehicleName
1 transporte

Tabla DosRuedas
vehicleId vehicleName Manubrio
2 birula Manubrio de bicicleta

Tabla CuatroRuedas
vehicleId vehicleName volante
3 Audi volante de automovil


@Inheritance(strategy=InheritanceType.JOINED)


Con la estratégia JOINED se crean tablas separadas por clase o entidad, y se relacionan con una columna clave, de tal forma que recuperar la información se hace por medio de un query join.
Vehicle.java
@Entity
@Inheritance
(strategy = InheritanceType.JOINED)
public class Vehiculo {
   
@Id
    @GeneratedValue
   
private int Id;
   
private String Name;

setters y getters
DosRuedas.java
@Entity

public class DosRuedas extends Vehiculo {

    private String Manubrio;


setters y getters

CuatroRuedas.java
@Entity

public class CuatroRuedas extends Vehiculo{

    private String volante;
 
setters y getters


Programa de Prueba
 
public class HibernateTest {

    public static void main(String[] args){

        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("org.hibernate.tutorial.jpa");



        Vehiculo vehiculo = new Vehiculo();

        vehiculo.setName("Transporte");



        DosRuedas dosRuedas = new DosRuedas();

        dosRuedas.setName("bicicleta");

        dosRuedas.setManubrio("Manubrio de Bicicleta");



        CuatroRuedas cuatroRuedas = new CuatroRuedas();

        cuatroRuedas.setName("Audi");

        cuatroRuedas.setVolante("Volante de Audi");



        EntityManager entityManager = entityManagerFactory.createEntityManager();



        entityManager.getTransaction().begin();

        entityManager.persist(vehiculo);

        entityManager.persist(dosRuedas);

        entityManager.persist(cuatroRuedas);

        entityManager.getTransaction().commit();



        entityManagerFactory.close();



    }

}

Hibernate muestra en la consola

Hibernate: create table CuatroRuedas (volante varchar(255), Id integer not null, primary key (Id)) engine=MyISAM
Hibernate: create table DosRuedas (Manubrio varchar(255), Id integer not null, primary key (Id)) engine=MyISAM
Hibernate: create table Vehiculo (Id integer not null, Name varchar(255), primary key (Id)) engine=MyISAM
Hibernate: alter table CuatroRuedas add constraint FKhaimfgg1r4h57bbo2js59hqq2 foreign key (Id) references Vehiculo (Id)
Hibernate: alter table DosRuedas add constraint FKmdtgstld5lhffvuc9lx69xmhk foreign key (Id) references Vehiculo (Id)
Tue Jun 05 19:10:20 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.
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into Vehiculo (Name, Id) values (?, ?)
Hibernate: insert into Vehiculo (Name, Id) values (?, ?)
Hibernate: insert into DosRuedas (Manubrio, Id) values (?, ?)
Hibernate: insert into Vehiculo (Name, Id) values (?, ?)
Hibernate: insert into CuatroRuedas (volante, Id) values (?, ?)
jun 05, 2018 7:10:20 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/wsp?zeroDateTimeBehavior=convertToNull]

tabla Vehiculo
Id Name
1 Transporte
2 bicicleta
3 Audi

Tabla DosRuedas
Manubrio Id
Manubrio de Bicicleta 2

Tabla CuatroRuedas
Manubrio Id
Manubrio de Bicicleta 2

La integración de los datos se hace con un join de las tablas

SELECT * FROM vehiculo 
JOIN cuatroruedas ON vehiculo.id = cuatroruedas.id;

Id Name volante Id
3 Audi Volante de Audi 3

SELECT * FROM vehiculo 
JOIN dosruedas ON vehiculo.id = dosruedas.id;

Id Name Manubrio Id
2 bicicleta Manubrio de Bicicleta 2


FinTexto