Insertar muchos valores (con FK) en la base de datos usando LiquiBase y Spring

Estoy tratando de agregar muchos registros (actualmente ubicados en un archivo de Excel) en mi base de datos usando Liquibase (para saber cómo hacerlo para futuros cambios en la base de datos)

Mi idea era leer el archivo de Excel usando Java y luego completar los ChangeLogParameters de mi clase de inicialización de Spring de esta manera:

SpringLiquibase liqui = new SpringLiquibase();
liqui.setBeanName("liquibaseBean");
liqui.setDataSource(dataSource());
liqui.setChangeLog("classpath:changelog.xml");

HashMap<String, String> values = new HashMap<String, String>();
values.put("line1col1", ExcelValue1);
values.put("line1col2", ExcelValue2);
values.put("line1col3", ExcelValue3);
values.put("line2col1", ExcelValue4);
values.put("line2col2", ExcelValue5);
values.put("line2col3", ExcelValue6);
...
liqui.setChangeLogParameters(values);

El problema con este enfoque es que mi changelog.xml sería muy extraño (y no productivo)

<changeSet author="gcardoso" id="2012082707">
    <insert tableName="t_user">
        <column name="login" value="${ExcelValue1}"/>
        <column name="name" value="${ExcelValue2}}"/>
        <column name="password" value="${ExcelValue3}"/>
    </insert>
    <insert tableName="t_user">
        <column name="login" value="${ExcelValue4}"/>
        <column name="name" value="${ExcelValue5}}"/>
        <column name="password" value="${ExcelValue6}"/>
    </insert>
    ...
</changeSet>

¿Hay alguna manera de que pueda hacer algo como esto:

HashMap<String, ArrayList<String>> values = new HashMap<String, ArrayList<String>>();
values.put("col1", Column1);
values.put("col2", Column2);
values.put("col3", Column3);
liqui.setChangeLogParameters(values);

<changeSet author="gcardoso" id="2012082707">
    <insert tableName="t_user">
        <column name="login" value="${Column1}"/>
        <column name="name" value="${Column2}}"/>
        <column name="password" value="${Column3}"/>
    </insert>
</changeSet>

o hay otra manera?

EDIT: Mi opción actual es convertir Excel en un archivo CSV e importar los datos usando

<changeSet author="gcardoso" id="InitialImport2" runOnChange="true">

    <loadData tableName="T_ENTITY" file="com/exictos/dbUpdate/entity.csv">
        <column header="SHORTNAME" name="SHORTNAME" />
        <column header="DESCRIPTION" name="DESCRIPTION" />
    </loadData>


    <loadData tableName="T_CLIENT" file="com/exictos/dbUpdate/client.csv">
        <column header="fdbhdf" name="ENTITYID" defaultValueComputed="(SELECT ID FROM T_ENTITY WHERE SHORTNAME = ENTITY_REFERENCE"/>
        <column header="DESCRIPTION" name="DESCRIPTION" />
    </loadData>


</changeSet>

con estos archivos CSV:

entidad.csv

SHORTNAME,DESCRIPTION
nome1,descricao1
nome2,descricao2

cliente.csv

DESCRIPTION,ENTITY_REFERENCE
descricaoCliente1,nome1
descricaoCliente2,nome2

Pero me sale este error:

liquibase.exception.DatabaseException: Error executing SQL INSERT INTO `T_CLIENT` (`DESCRIPTION`, `ENTITY_REFERENCE`) VALUES ('descricaoCliente1', 'nome1'): Unknown column 'ENTITY_REFERENCE' in 'field list'

Si cambio el encabezado de mi client.csv a DESCRIPTION,ENTITYID, obtengo este error:

liquibase.exception.DatabaseException: Error executing SQL INSERT INTO `T_CLIENT` (`DESCRIPTION`, `ENTITYID`) VALUES ('descricaoCliente1', 'nome1'): Incorrect integer value: 'nome1' for column 'entityid' at row 1

En cualquiera de estos casos, parece que defaultValueComputed no funciona de la misma manera que valueComputed en el siguiente ejemplo

<changeSet author="gcardoso" id="InitialImport1">

    <insert tableName="T_ENTITY">
        <column name="SHORTNAME">nome1</column>
        <column name="DESCRIPTION">descricao1</column>
    </insert>

    <insert tableName="T_CLIENT">
        <column name="ENTITYID" valueComputed="(SELECT ID FROM T_ENTITY WHERE SHORTNAME = 'nome1')"/>
        <column name="DESCRIPTION">descricaoCliente</column>
    </insert>

</changeSet>

¿Es este el comportamiento esperado? ¿Error de LiquiBase? ¿O simplemente yo estoy haciendo algo mal (lo más probable)?

¿O hay alguna otra forma de importar una gran cantidad de datos? Pero siempre usando LiquiBase y/o Spring.

EDIT2: Mi problema es que no puedo insertar los datos en la segunda tabla con la clave externa correcta

preguntado el 27 de agosto de 12 a las 15:08

Se agregaron nuevas alternativas y resultados de prueba:

¿Podría aclarar para qué se van a utilizar los datos iniciales? ¿Son datos de referencia que normalmente no van a cambiar, pero que son necesarios para que se inicie la aplicación? ¿Se insertará solo una vez? -

Son datos iniciales para que la aplicación funcione, pero también necesito una solución que funcione para cualquier actualización de la base de datos, porque habrá muchas actualizaciones que requerirán que se agregue mucha información. -

2 Respuestas

Diría que Liquibase no es la herramienta ideal para lo que quieres lograr. Liquibase es adecuado para administrar la estructura de la base de datos, no los datos de la base de datos.

Si aún desea utilizar Liquibase para administrar los datos, tiene un par de opciones (consulte aquí) -

  1. Registre sus declaraciones de inserción como SQL y consúltelas desde changelog.xml de esta manera:

    <sqlFile path="/path/to/file.sql"/>

  2. Utilizar Clase de refactorización personalizada al que se refiere desde changelog.xml de esta manera:

    <customChange class="com.example.YourJavaClass" csvFile="/path/to/file.csv"/>

    YourJavaClass leería los registros del archivo CSV y los aplicaría a la base de datos, implementando este método:

    void execute(Database database) throws CustomChangeException;

Tenga en cuenta que una vez que haya cargado estos datos a través de Liquibase, no debe modificar los datos en el archivo, ya que esos cambios no se volverán a aplicar. Si desea realizar cambios en él, deberá hacerlo en conjuntos de cambios posteriores. Entonces, después de un tiempo, puede terminar con muchos archivos CSV/conjuntos de cambios de liquibase diferentes, todos operando con los mismos/datos similares (esto depende de cómo vaya a usar estos datos, ¿alguna vez cambiará una vez insertado?).

Recomendaría mirar usando unidad DB para la gestión de sus datos de referencia. Es una herramienta utilizada principalmente en pruebas unitarias, pero es muy madura, diría que adecuada para su uso en producción. Puede almacenar información en CSV o XML. Sugeriría usar un Spring 'InitializingBean' para cargar el conjunto de datos desde el classpath y realizar una operación de 'actualización' de DBUnit, que, desde el documentos:

Esta operación actualiza literalmente el contenido del conjunto de datos en la base de datos. Esto significa que los datos de las filas existentes se actualizan y se insertan las filas que no existen. Las filas que existen en la base de datos pero no en el conjunto de datos no se ven afectadas.

De esta manera, puede mantener sus datos de referencia en un solo lugar y agregarlos con el tiempo para que solo haya una fuente de información y no se divida en varios conjuntos de cambios de Liquibase. Mantener sus conjuntos de datos de DBUnit en el control de versiones proporcionaría capacidad de rastreo y, como beneficio adicional, los conjuntos de datos de DBUnit son portátiles entre bases de datos y pueden administrar cosas como el orden de inserción para evitar violaciones de claves externas para usted.

contestado el 23 de mayo de 17 a las 13:05

Estoy de acuerdo en que DBUnit es un buen enfoque para un enfoque Java+UnitTest para cargar datos. Desafortunadamente, no escalará si los requisitos son para cantidades "masivas" de datos. - Brad

Utilizo DBUnit en las pruebas de integración para cargar un conjunto de datos de 4 MB que abarca > 100 tablas, y tarda < 1 s contra HSQLDB y 10 s contra MySQL. Dudo que Liquibase pueda cargar los datos mucho más rápido, porque al final del día también usa JDBC. El OP está buscando una solución "siempre usando LiquiBase y/o Spring", aunque estoy de acuerdo en que para> 1 GB, las herramientas nativas serían mejores. - barry pitman

Mi problema es que no puedo insertar las claves foráneas en mi tabla (y por lo que descubrí que DBUnit tiene el mismo problema), y la solución para ambos casos se ve así: inserte un registro en la tabla con un FK falso, luego actualice su FK al correcto. PD: Mi archivo de importación inicial es de alrededor de 200 MB, y mis futuras actualizaciones serán de alrededor de 50 MB - gonzalo cardoso

No debería tener problemas para "insertar" fk con DBUnit, ya que lo admite siempre que su base de datos lo permita. He tenido problemas con fk usando CLEAN_INSERT. Vea mi publicación para una posible solución. Entonces, el problema era "eliminar", no "insertar". - Brad

Mi problema es que necesito insertar en una "confirmación única" dos tablas diferentes donde la segunda necesita agregar una clave externa a la primera: gonzalo cardoso

Depende de su base de datos de destino. Si estás usando Sybase or MSSQL servidor entonces puede usar el herramienta BCP que viene junto con su cliente + controlador instalado. Es la forma más rápida de mover grandes cantidades de datos dentro y fuera de estas bases de datos.

Googleando también encontré estos enlaces...

Oracle tiene el SQL*CARGADOR del IRS

MySQL tiene la CARGAR DATOS INFILE comando

Esperaría que cada proveedor de bases de datos proporcione una herramienta de alguna descripción para la carga masiva de datos.

Respondido 30 ago 12, 17:08

¿Dónde interviene LiquiBase o Spring en esa respuesta? - gonzalo cardoso

Estaba respondiendo a su declaración al final de su pregunta ... "¿O hay alguna otra forma de importar una gran cantidad de datos?". Supuse que no estabas recibiendo ninguna alegría con ninguna respuesta y que esto podría ser útil. - Brad

Está bien. Mi otra forma de preguntar es sobre LiquiBase y/o Spring;) Acabo de editar la publicación original para aclararla: gonzalo cardoso

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.