Interacción con HBase mediante SQL y JDBC utilizando Apache Phoenix

Apache Phoenix permite utilizar un motor de base de datos relacional sobre Apache HBase, facilitando el uso de consultas SQL estándar y conectividad JDBC para gestionar datos masivos de forma eficiente. Esta capa de abstracción transforma las consultas SQL en escaneos nativos de HBase, optimizando el rendimiento sin perder la escalabilidad de NoSQL.

Configuración de dependencias en Maven

Para integrar Apache Phoenix en un proyecto basado en Spring Boot 3+, es crucial gestionar las exclusiones de dependencias para evitar conflictos con el contenedor de servlets, especialmenet con Tomcat 10 y versiones superiores de Jakarta EE. Una solución común es sustituir Tomcat por Jetty.

<dependencies>
    <!-- Spring Boot Web con exclusión de Tomcat -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- Implementación de Jetty como servidor embebido -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>

    <!-- Dependencia de Apache Phoenix -->
    <dependency>
        <groupId>org.apache.phoenix</groupId>
        <artifactId>phoenix-core</artifactId>
        <version>5.0.0-HBase-2.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- Cliente Hadoop Common -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.9.2</version>
    </dependency>
</dependencies>

Implementación de operaciones CRUD con JDBC

La siguiente clase muestra cómo gestionar una conexión Phoenix para realizar operaciones de definición de datos (DDL) y manipulación de datos (DML) utilizando una tabla denominada USUARIOS.

package com.infra.storage;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.*;
import java.util.*;

@RestController
public class PhoenixManagerController {

    private static final String DRIVER_CLASS = "org.apache.phoenix.jdbc.PhoenixDriver";
    private static final String CONNECTION_URL = "jdbc:phoenix:node1:2181";

    @RequestMapping("/execute-hbase")
    public String runWorkflow() {
        try {
            initSchema();
            insertRecords();
            fetchData("USUARIOS");
            removeRecords();
            return "Proceso completado con éxito";
        } catch (Exception e) {
            return "Error en la ejecución: " + e.getMessage();
        }
    }

    private Connection createConnection() throws Exception {
        Class.forName(DRIVER_CLASS);
        return DriverManager.getConnection(CONNECTION_URL);
    }

    public void initSchema() throws Exception {
        try (Connection conn = createConnection();
             Statement stmt = conn.createStatement()) {
            String ddl = "CREATE TABLE IF NOT EXISTS \"USUARIOS\" (" +
                         "USR_ID VARCHAR PRIMARY KEY, " +
                         "NOMBRE VARCHAR, " +
                         "DEPARTAMENTO VARCHAR)";
            stmt.execute(ddl);
        }
    }

    public void insertRecords() throws Exception {
        try (Connection conn = createConnection()) {
            String upsertSql = "UPSERT INTO \"USUARIOS\" (USR_ID, NOMBRE, DEPARTAMENTO) VALUES (?, ?, ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(upsertSql)) {
                // Registro 1
                pstmt.setString(1, "ID_001");
                pstmt.setString(2, "Carlos Ruiz");
                pstmt.setString(3, "IT");
                pstmt.executeUpdate();

                // Registro 2
                pstmt.setString(1, "ID_002");
                pstmt.setString(2, "Ana Gomez");
                pstmt.setString(3, "RRHH");
                pstmt.executeUpdate();
            }
            conn.commit();
        }
    }

    public List<Map<String, Object>> fetchData(String table) throws Exception {
        List<Map<String, Object>> dataList = new ArrayList<>();
        try (Connection conn = createConnection();
             PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM \"" + table + "\"");
             ResultSet rs = pstmt.executeQuery()) {
            
            ResultSetMetaData meta = rs.getMetaData();
            int cols = meta.getColumnCount();
            
            while (rs.next()) {
                Map<String, Object> row = new HashMap<>();
                for (int i = 1; i <= cols; i++) {
                    row.put(meta.getColumnName(i), rs.getObject(i));
                }
                dataList.add(row);
            }
        }
        return dataList;
    }

    public void removeRecords() throws Exception {
        try (Connection conn = createConnection();
             Statement stmt = conn.createStatement()) {
            stmt.execute("DELETE FROM \"USUARIOS\" WHERE USR_ID = 'ID_002'");
            conn.commit();
        }
    }
}

Resolución de errores comunes

Inconsistencia en el mapeo de Namsepaces

Si se encuentra con el error ERROR 726 (43M10): Inconsistent namespace mapping properties, se debe a una discrepancia entre la configuración del cliente y el servidor Phoenix respecto a los espacios de nombres de HBase. Para resolverlo, asegúrese de que la propiedad phoenix.schema.isNamespaceMappingEnabled coincida en ambos lados (generalmente desactivándola en el archivo de configuración hbase-site.xml si no se requiere segmentación por esquemas).

Conflictos de Servlets

Al utilizar Spring Boot 3 con librerías heredadas de Hadoop/Phoenix, es probable que ocurra una ClassCastException relacionada con jakarta.servlet.Servlet vs javax.servlet.Servlet. Esto ocurre porque Phoenix 5.x todavía depende de las APIs antiguas de Java EE (javax), mientras que Spring Boot 3 utiliza Jakarta EE. La exclusión manual de los artefactos de Tomcat y el uso de Jetty suele mitigar este problema de carga de clases.

Etiquetas: Apache Phoenix HBase JDBC java Spring Boot

Publicado el 7-22 01:57