Dependencia del proyecto
<!-- Integración de Spring Boot con el framework MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
Caso: Relación uno a uno
Clases de entidad:
// Clase Persona que referencia a la clase Tarjeta
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Persona {
private String nombre;
private int id;
private int edad;
// Referencia a la clase Tarjeta
private Tarjeta tarjeta;
}
// Clase Tarjeta
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Tarjeta {
// Clave externa que referencia al id de Persona
private int usuarioId;
private String numero;
}
Interfaz del Mapper:
// Interfaz del Mapper para Persona
@Mapper
@Repository
public interface PersonaMapper {
// Definición de mapeo (para reutilización)
// Configura el Mapper y método de la Tarjeta en Persona; establece el campo "usuarioId" de Tarjeta como id de Persona
// one=@One indica que se mapea una entidad, es decir, relación uno a uno
// FetchType.EAGER: carga inmediata; al cargar una entidad, los atributos definidos con carga inmediata se obtienen de la base de datos sin demora
@Results(id="mapaPersona", value={
@Result(id=true, column="id", property="id"),
@Result(column="nombre", property="nombre"),
@Result(column="edad", property="edad"),
@Result(column="id", property="tarjeta",
one=@One(select="com.ejemplo.dao.TarjetaMapper.obtenerTarjetaPorUsuarioId", fetchType= FetchType.EAGER))
})
public Persona seleccionarUno();
@Select("select * from persona where id = #{id}")
// Referencia al mapeo definido anteriormente
@ResultMap(value="mapaPersona")
public Persona obtenerPersonaPorId(int id);
}
// Interfaz del Mapper para Tarjeta
@Mapper
@Repository
public interface TarjetaMapper {
// usuarioId es la clave externa que referencia al campo id de Persona
@Select("select * from tarjeta where usuario_id = #{usuarioId} ")
public Tarjeta obtenerTarjetaPorUsuarioId(int usuarioId);
}
Prueba unitaria:
@Test // Relación uno a uno
public void probarUnoAUno() throws IOException {
// Crear conexión de datos
SqlSessionFactory fabricaSesiones = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("configuracion-mybatis"));
SqlSession sesion = fabricaSesiones.openSession();
// Obtener la interfaz del Mapper
PersonaMapper personaMapper = sesion.getMapper(PersonaMapper.class);
// Consultar objeto Persona por id, obteniendo simultáneamente la Tarjeta asociada mediante la relación uno a uno
Persona persona = personaMapper.obtenerPersonaPorId(1);
// Imprimir resultados
System.out.println(persona);
// Verificar si la Tarjeta referenciada en Persona tiene valor
System.out.println(persona.getTarjeta().getNumero());
// Cerrar la conexión de datos
sesion.close();
}
Caso: Relación uno a muchos
Clases de entidad:
// Clase Provincia que referencia a un conjunto de la clase Ciudad
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Provincia {
private String nombre;
private int id;
private int poblacion;
// Referencia al conjunto de Ciudades
private Set<Ciudad> ciudades;
}
// Clase Ciudad
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Ciudad {
private int ciudadId;
private String nombreCiudad;
// Clave externa que referencia al id de Provincia (en una relación uno a muchos, múltiples Ciudades corresponden a una Provincia)
private int provinciaId;
}
Interfaz del Mapper:
// Interfaz del Mapper para Provincia
@Mapper
@Repository
public interface ProvinciaMapper {
// Definición de mapeo (para reutilización)
// Configura el Mapper y método de las Ciudades en Provincia; establece el campo "provinciaId" de Ciudad como id de Provincia
// many=@Many indica que se mapean múltiples entidades, es decir, relación uno a muchos o muchos a muchos
// FetchType.LAZY: carga diferida; al cargar una entidad, los atributos definidos con carga diferida no se obtienen de la base de datos inmediatamente
@Results(id="mapaProvincia", value={
@Result(id=true, column="id", property="id"),
@Result(column="nombre", property="nombre"),
@Result(column="id", property="ciudades",
many=@Many(select="com.ejemplo.dao.CiudadMapper.obtenerCiudadesPorProvinciaId", fetchType= FetchType.LAZY))
})
public Provincia seleccionarUno();
@Select("select * from provincia where id = #{id}")
// Referencia al mapeo definido anteriormente
@ResultMap(value="mapaProvincia")
public Provincia obtenerProvinciaPorId(int id);
}
// Interfaz del Mapper para Ciudad
@Mapper
@Repository
public interface CiudadMapper {
// El campo provinciaId corresponde al id de Provincia
@Select("select * from ciudad where provincia_id=#{provinciaId}")
public List<Ciudad> obtenerCiudadesPorProvinciaId(int provinciaId);
}
Prueba unitaria:
@Test // Relación uno a muchos
public void probarUnoAMuchos() throws IOException {
// Establecer sesión de conexión SqlSession
SqlSessionFactory fabricaSesiones = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("configuracion-mybatis"));
SqlSession sesion = fabricaSesiones.openSession();
// Obtener el Mapper
ProvinciaMapper provinciaMapper = sesion.getMapper(ProvinciaMapper.class);
// Invocar la interfaz
Provincia provincia = provinciaMapper.obtenerProvinciaPorId(1);
// Imprimir resultados
System.out.println(provincia.getNombre());
// Verificar si el conjunto de Ciudades tiene valores
for (Ciudad ciudad : provincia.getCiudades()) {
System.out.println(ciudad.getNombreCiudad());
}
// Cerrar la conexión
sesion.close();
}
Caso: Relación muchos a muchos
Clases de entidad:
// Clase Usuario que referencia a un conjunto de la clase Rol
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Usuario {
private int id;
private String nombre;
// Conjunto de roles
private Set<Rol> roles;
}
// Clase Rol que referencia a un conjunto de la clase Permiso
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Rol {
private int rolId;
private String nombreRol;
// Referencia al id de Usuario
private int usuarioId;
// Conjunto de permisos
private Set<Permiso> permisos;
}
// Clase Permiso
@Data
@AllArgsConstructor
@NoArgsConstructor
// Esta anotación es obligatoria; de lo contrario, se producirá un error
@JsonIgnoreProperties(value = {"handler"})
public class Permiso {
private int permisoId;
private String nombrePermiso;
// Referencia al campo rolId de Rol
private int rolId;
}
Interfaz del Mapper:
// Interfaz del Mapper para Usuario
@Mapper
@Repository
public interface UsuarioMapper {
// Consulta difusa para obtener un conjunto de usuarios
@Select("select * from usuarios where nombre like %#{nombre}%")
// Método abreviado para el mapeo
@Results({
@Result(id=true, column="id", property="id"),
@Result(column="nombre", property="nombre"),
@Result(column="id", property="roles",
many=@Many(select="com.ejemplo.dao.RolMapper.obtenerRolesPorUsuarioId", fetchType= FetchType.LAZY))
})
public List<Usuario> obtenerUsuariosPorNombre(String nombre);
}
// Interfaz del Mapper para Rol
@Mapper
@Repository
public interface RolMapper {
// Obtener conjunto de roles según el usuarioId
@Select("select * from roles where usuario_id = #{usuarioId}")
// Método abreviado para el mapeo
@Results({
@Result(id=true, column="rol_id", property="rolId"),
@Result(column="nombre_rol", property="nombreRol"),
@Result(column="usuario_id", property="usuarioId"),
@Result(column="rol_id", property="permisos",
many=@Many(select="com.ejemplo.dao.PermisoMapper.obtenerPermisosPorRolId", fetchType= FetchType.LAZY))
})
public List<Rol> obtenerRolesPorUsuarioId(int usuarioId);
}
// Interfaz del Mapper para Permiso
@Mapper
@Repository
public interface PermisoMapper {
// Obtener conjunto de permisos según el rolId
@Select("select * from permisos where rol_id = #{rolId}")
// Método abreviado para el mapeo
@Results({
@Result(id=true, column="permiso_id", property="permisoId"),
@Result(column="nombre_permiso", property="nombrePermiso"),
@Result(column="rol_id", property="rolId")
})
public List<Permiso> obtenerPermisosPorRolId(int rolId);
}
Prueba unitaria:
@Test // Relación muchos a muchos
public void probarMuchosAMuchos() throws IOException {
// Establecer sesión de conexión SqlSession
SqlSessionFactory fabricaSesiones = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("configuracion-mybatis"));
SqlSession sesion = fabricaSesiones.openSession();
// Obtener la interfaz UsuarioMapper
UsuarioMapper usuarioMapper = sesion.getMapper(UsuarioMapper.class);
// Invocar la interfaz
List<Usuario> usuarios = usuarioMapper.obtenerUsuariosPorNombre("Ejemplo");
// Imprimir resultados
for (Usuario usuario : usuarios) {
System.out.println(usuario.getNombre());
// Verificar si el conjunto de Roles no está vacío
for (Rol rol : usuario.getRoles()) {
System.out.println(rol.getNombreRol());
// Verificar si el conjunto de Permisos no está vacío
for (Permiso permiso : rol.getPermisos()) {
System.out.println(permiso.getNombrePermiso());
}
}
}
// Cerrar la conexión
sesion.close();
}
Manejo de errores
Información del error:
No se encontró un serializador parra la clase org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$EnhancedResultObjectProxyImpl. Esto ocurre al intentar convertir un objeto proxy a JSON.
Causa:
Durante consultas en cascada en MyBatis, se genera una propiedad "handler" en la estructura de datos del proxy.
Solución:
Agregar la anotación @JsonIgnoreProperties a las clases de entidad relacionadas para indicar que se ignoren ciertas propiedades al serializar a JSON.
@JsonIgnoreProperties(value = {"handler"})
Esta anotación le dice al componente de serialización JSON que ignore las propiedades en el array value, como "handler". Si se necesitan ignorar otras propiedades, como "hibernateLazyInitializer" o "fieldHandler", se pueden agregar al array.