Para trabajar con operaciones de lectura en MyBatis, es fundamental comprender cómo mapear consultas SQL a objetos Java y cómo manejar parámetros de forma segura. A continuación, se detalla la implementación de una consulta exacta por identificador y una búsqueda aproximada (LIKE) por nombre, asegurando la prevención de inyecciones SQL.
- Esquema de la Base de Datos
En primer lugar, definimos la tabla customers en una base de datos relacional (en este ejemplo, MySQL) e insertamos algunos registros de prueba.
CREATE TABLE customers (
client_id INT PRIMARY KEY AUTO_INCREMENT,
full_name VARCHAR(50) NOT NULL,
occupation VARCHAR(50),
contact_number VARCHAR(20)
);
INSERT INTO customers (full_name, occupation, contact_number) VALUES
('Alice Smith', 'Engineer', '555-0101'),
('Bob Johnson', 'Designer', '555-0102'),
('Charlie Brown', 'Developer', '555-0103'),
('David Jones', 'Manager', '555-0104'),
('Eve Adams', 'Analyst', '555-0105');
- Clase de Entidad (POJO)
Creamos la clase Java que representará los registros de la tabla. Se utilizan nombres de variables descriptivos y se implementan los métodos getters, setters y toString.
package com.example.mybatis.entity;
public class Client {
private Integer clientId;
private String fullName;
private String occupation;
private String contactNumber;
public Integer getClientId() { return clientId; }
public void setClientId(Integer clientId) { this.clientId = clientId; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getOccupation() { return occupation; }
public void setOccupation(String occupation) { this.occupation = occupation; }
public String getContactNumber() { return contactNumber; }
public void setContactNumber(String contactNumber) { this.contactNumber = contactNumber; }
@Override
public String toString() {
return "Client{" +
"clientId=" + clientId +
", fullName='" + fullName + '\'' +
", occupation='" + occupation + '\'' +
", contactNumber='" + contactNumber + '\'' +
'}';
}
}
- Archivo de Mapeo (Mapper XML)
El archivo XML define las sentencias SQL. Para la búsqueda exacta se utiliza el marcador de posición #{}. Para la búsqueda aproximada, se emplea la función CONCAT junto con #{} para evitar vulnerabilidades de seguridad.
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.example.mybatis.mapper.ClientMapper">
<select id="getClientById" parameterType="int" resultType="com.example.mybatis.entity.Client">
SELECT client_id AS clientId, full_name AS fullName, occupation, contact_number AS contactNumber
FROM customers
WHERE client_id = #{id}
</select>
<select id="searchClientsByName" parameterType="string" resultType="com.example.mybatis.entity.Client">
SELECT client_id AS clientId, full_name AS fullName, occupation, contact_number AS contactNumber
FROM customers
WHERE full_name LIKE CONCAT('%', #{searchTerm}, '%')
</select>
</mapper>
- Configuración del Entorno de MyBatis
El archivo mybatis-config.xml gestiona la conexión a la base de datos, el administrador de transacciones y la ubicación de los archivos de mapeo.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mybatis/mapper/ClientMapper.xml"/>
</mappers>
</configuration>
- Configuración de Registro (Log4j)
Para depurar las consultas SQL generadas, configuramos Log4j en el archivo log4j.properties.
log4j.rootLogger=DEBUG, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
- Clase de Pruebas Unitarias
La clase de pruebas utiliza JUnit 5 y la estructura try-with-resources para garantizar que la sesión de SQL se cierre automáticamente después de cada ejecución, optimizando la gestión de recursos.
package com.example.mybatis.test;
import com.example.mybatis.entity.Client;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.util.List;
public class ClientMapperTest {
private SqlSessionFactory buildSessionFactory() throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testGetClientById() throws Exception {
try (SqlSession session = buildSessionFactory().openSession()) {
Client client = session.selectOne("com.example.mybatis.mapper.ClientMapper.getClientById", 1);
System.out.println("Resultado por ID: " + client);
}
}
@Test
public void testSearchClientsByName() throws Exception {
try (SqlSession session = buildSessionFactory().openSession()) {
List<Client> clients = session.selectList("com.example.mybatis.mapper.ClientMapper.searchClientsByName", "a");
System.out.println("Resultados de búsqueda aproximada:");
clients.forEach(System.out::println);
}
}
}
Diferencias entre #{} y ${} en MyBatis
Es crucial entender cómo MyBatis procesa los parámetros para mantener la integridad y seguridad de la base de datos:
#{}(Marcador de posición seguro): UtilizaPreparedStatementde JDBC. Los valores se tratan como parámetros vinculados, lo que previene automáticamente la inyección SQL. El motor de base de datos se encarga de agregar las comillas simples necesarias si el parámetro es una cadena de texto.${}(Concatenación directa): Realiza una sustitución de cadena literal antes de que la sentencia SQL se envíe a la base de datos. Esto no previene la inyección SQL y debe evitarse para valores proporcionados por el usuario. Su uso debe limitarse estrictamente a elementos estructurales dinámicos, como nombres de tablas o columnas que no pueden ser parametrizados.
Cuando se requiere una búsqueda aproximada (LIKE), una práctica común pero insegura es usar ${} para concatenar los comodines (ej. LIKE '%${value}%'). La alternativa segura y recomendada es utilizar las funciones de concatenación nativas de la base de datos junto con el marcador seguro #{}, como se demostró en el mapeo XML mediante CONCAT('%', #{searchTerm}, '%'). En bases de datos como Oracle o PostgreSQL, esto también puede lograrse utilizando el operador de concatenación estándar: LIKE '%' || #{searchTerm} || '%'.