DB

DBUnit으로 이기종 간 데이터 이관하기

Developer Garam.Choi 2020. 3. 10. 09:28

이전 포스팅에 이어, 

 

현 회사에서는 이기종간 mapper dir을 별도로 관리하고있는데,

 

mapper는 분류하여 작업한다 하더라도,

 

실제 데이터( mock data )에 대해서는 해결할 방안을 찾던 중 

 

https://woowabros.github.io/experience/2019/11/06/db-unit.html

 

스프링부트에서 DbUnit 을 이용하여 DB 테스트 해보기 - 우아한형제들 기술 블로그

안녕하세요. 상품시스템팀 권순규입니다. 저희 팀에서 DB 테스트를 위해 사용하고 있는 DbUnit 의 설정 및 사용에 대해 알려드리고자 합니다.

woowabros.github.io

 

위 포스팅을 보고 데이터 이관을 DBUnit으로 할 수 있을 것으로 보여 시작하였다.

 

 

우선 DBUnit이란 xml 형태로 dataset을 가지고오는 형태이며,

 

tablename이라는 table이 있고,

column / column2 의 컬럼이 있을때 아래와 같이 조회되는 형태이다.

 

행은 DBUnit으로 조회하는 쿼리에 row 별로 뽑힌다.

<dataset>
  <tablename column="1" column2="ㅁ">
  <tablename column="2" column2="ㄴ">
  <tablename column="3" column2="ㅇ">
  <tablename column="4" column2="ㄹ">
  <tablename column="5" column2="ㅎ">
  <tablename column="6" column2="ㅗ">
</dataset> 

 

이를 이용하여, 

 

Oracle DB의 data만 dbunit으로 빼오고, 이를 다시 dbunit insert로 이기종별 insert가 가능할 것으로 판단되 실행하였다.

 

하지만 DBunit에서 export -> import 형식으로 생각한 개발자가 별로 없는지 내역이 많이 나오지 않았고,

 

실제 export 시에도, FlatXmlDataSet 처리 간 별도로 진행해야하는 사항이 있어 디컴파일하여 처리하는것이 있었다.

 

 

우선 Oracle 기준으로 DATA 추출 코드이다.

 

public class ExportDBUnit {


        public static void main(String[] args) throws Exception {

            String outputFilePath = "C:\\solution\\gitWorkSpace\\standard-common\\src\\main\\test\\exportDBUnit";
            Class driverClass = Class.forName("oracle.jdbc.driver.OracleDriver");
            String user = "dbuser";
            ResultSet rs = null;
            Connection jdbcConnection = DriverManager.getConnection("jdbc:oracle:thin:@url:1521:sid", user, "pwd");

            Statement stmt = null;

            stmt = jdbcConnection.createStatement();

            String sql = "SELECT UT.OBJECT_NAME AS TABLE_NAME\n" +
                    "            FROM   ALL_OBJECTS UT,\n" +
                    "                    ALL_TAB_COMMENTS UTC\n" +
                    "            WHERE UT.OBJECT_NAME = UTC.TABLE_NAME\n" +
                    "            AND UT.OWNER = UTC.OWNER\n" +
                    "            AND UT.OBJECT_TYPE IN ('TABLE')\n" +
                    "            AND UT.OWNER ='"+user.toUpperCase()+"'\n" +
                    "\n" +
                    "            AND UT.OBJECT_NAME NOT LIKE 'BIN$%'\n" +
                    "            ORDER BY TABLE_NAME";

            rs = stmt.executeQuery(sql);


            while(rs.next()){
                IDatabaseConnection connection = new DatabaseConnection(jdbcConnection);
                DatabaseConfig config = connection.getConfig();
                config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new Oracle10DataTypeFactory());
                config.setFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES,Boolean.TRUE);

                QueryDataSet partialDataSet = new QueryDataSet(connection);
                String tableName = rs.getString("TABLE_NAME");
                partialDataSet.addTable(tableName);

                System.out.println("TABLE NAME :"+tableName);


                    /*String originalStr = partialDataSet.getTable(tableName).getValue(0,partialDataSet.getTableMetaData(tableName).getColumns()[0].getColumnName()).toString(); // 테스트
                    String [] charSet = {"utf-8","euc-kr","ksc5601","iso-8859-1","x-windows-949"};

                    for (int i=0; i<charSet.length; i++) {
                        for (int j=0; j<charSet.length; j++) {
                            try {
                                //FlatXmlDataSet.write(partialDataSet, new FileWriter(outputFilePath+ File.separator + tableName+".xml",false) ,"MS949" );
                                FileWriter t = new FileWriter(outputFilePath+ File.separator + tableName+".xml",true);
                                t.write( new String(originalStr.getBytes(charSet[i]), charSet[j]));
                                t.flush();
                                System.out.println("[" + charSet[i] +"," + charSet[j] +"] = " + new String(originalStr.getBytes(charSet[i]), charSet[j]));
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }
                    }*/

                    FlatXmlDataSet.write(partialDataSet, new FileWriter(outputFilePath+ File.separator + tableName+".xml",false) ,"UTF-8" );

            }




            System.out.println("END");

        }

 

 

이후 다른 이기종 Import 시 

public class ImportDBUnit {




    @Test
    public void loadData() throws Exception {
        String outputFilePath = "C:\\solution\\gitWorkSpace\\standard-common\\src\\main\\test\\exportDBUnit";
        Class driverClass = Class.forName("oracle.jdbc.driver.OracleDriver");
        String user = "user";

        Connection jdbcConnection = DriverManager.getConnection("jdbc:oracle:thin:@url:1521:sid", user, "pwd");




        IDatabaseConnection connection = new DatabaseConnection(jdbcConnection,user.toUpperCase());
        DatabaseConfig config = connection.getConfig();
        config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new Oracle10DataTypeFactory());
        config.setFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, Boolean.TRUE);

        //table 존재여부 검출용도
        IDataSet databaseDataSet = connection.createDataSet();
        String[] getTableNames =databaseDataSet.getTableNames();

        List<String> importTableNames = new ArrayList<String>();

        try {

           this.deleteDBData();

            for (File info : FileUtils.listFilesAndDirs(new File(outputFilePath), TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)) {
                if(info.isDirectory()) {
                    ArrayList<File> fileList = new ArrayList<File>();

                    this.arrayDirInFileList(fileList, info);

                    for (File inDirIsFile : fileList) {
                        String tableName = FilenameUtils.getBaseName(inDirIsFile.getName());

                        if(importTableNames.contains(tableName)) continue; //이미 import된 xml이라면 다음으로.

                        System.out.println("START FILE READ TABLE NAME :"+tableName);

                        for(String getTableName : getTableNames){
                            if(getTableName.equals(tableName)){

                               /* String[] depRootTableNames = TablesDependencyHelper.getAllDependentTables( connection, getTableName );

                                //상위 테이블 검색
                                if(depRootTableNames.length > 1){

                                    *//*String[] depTableNames = new String[depRootTableNames.length];
                                    this.getDependencyTableNames(connection,depRootTableNames);*//*

                                    for(int i=depRootTableNames.length-1; i>=0; i--){

                                        String depTableName = depRootTableNames[i];
                                        File depFile = new File(outputFilePath+File.separator+depTableName+".xml");

                                        IDataSet depData = new FlatXmlDataSetBuilder().build(depFile);
                                        depData.getTable(depTableName);
                                        System.out.println("IMPORT START TABLE NAME :"+depTableName);

                                        try{
                                            DeleteAllOperation.DELETE_ALL.execute(connection, depData);
                                            DatabaseOperation.CLEAN_INSERT.execute(connection, depData);
                                        }catch (Exception e){
                                            e.printStackTrace();
                                        }


                                        importTableNames.add(depTableName);
                                    }
                                }*/
                                FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
                                builder.setColumnSensing(true);
                                IDataSet data = builder.build(inDirIsFile);
                                data.getTable(tableName);
                                try{

                                    System.out.println("IMPORT START TABLE NAME :"+tableName);

                                    /*DatabaseOperation op = new CompositeOperation(DatabaseOperation.DELETE_ALL,DatabaseOperation.INSERT);
                                    op.execute(connection, data);*/
                                    DatabaseOperation.CLEAN_INSERT.execute(connection, data);
                                    importTableNames.add(tableName);
                                }catch (Exception e){

                                    DatabaseOperation.DELETE_ALL.execute(connection, data);
                                }

                            }
                        }


                    }

                }else{
                    String tableName = FilenameUtils.getBaseName(info.getName()) == null? "" : FilenameUtils.getBaseName(info.getName());

                    System.out.println("START FILE READ TABLE NAME :"+tableName);

                    if(importTableNames.contains(tableName)) continue; //이미 import된 xml이라면 다음으로.

                    outputFilePath = outputFilePath.replaceAll(info.getName(),"");


                    for(String getTableName : getTableNames){
                        if(getTableName.equals(tableName)){
                             FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
                                builder.setColumnSensing(true);
                                IDataSet data = builder.build(info);
                                data.getTable(tableName);
                            data.getTable(tableName);
                            try{

                                System.out.println("IMPORT START TABLE NAME :"+tableName);

                                DatabaseOperation.CLEAN_INSERT.execute(connection, data);

                                importTableNames.add(tableName);
                            }catch (Exception e){

                                DatabaseOperation.DELETE_ALL.execute(connection, data);
                            }

                        }
                    }
                }


            }
        } catch (Throwable e) {
            e.printStackTrace();

        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
    }


    public ArrayList<File> arrayDirInFileList(ArrayList<File> files ,File dir){

        if(dir.isDirectory()) {
            File[] children = dir.listFiles();
            for(File f : children) {
                // 재귀 호출 사용
                // 하위 폴더 탐색 부분
                arrayDirInFileList(files,f);
            }
        } else {
            files.add(dir);
        }

        return files;
    }

    public void deleteDBData(){

        IDatabaseConnection srm9qaDBConnection = null;
        IDatabaseConnection srm9newDBConnection = null;
        Statement stmt = null;
        ResultSet rs = null;

        try{
            Connection jdbcConnection = DriverManager.getConnection("jdbc:oracle:thin:@url:1521:sid", "user", "pwd");

            Connection srm9new_jdbcConnection = DriverManager.getConnection("jdbc:oracle:thin:@url:1521:sid", "user", "pwd");
            srm9newDBConnection = new DatabaseConnection(srm9new_jdbcConnection,"user".toUpperCase());


            srm9qaDBConnection = new DatabaseConnection(jdbcConnection,"user".toUpperCase());
            DatabaseConfig srm9qaConfig = srm9qaDBConnection.getConfig();
            srm9qaConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new Oracle10DataTypeFactory());
            srm9qaConfig.setFeature(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, Boolean.TRUE);

            //table 존재여부 검출용도
            IDataSet databaseDataSet = srm9newDBConnection.createDataSet();
            String[] getTableNames =databaseDataSet.getTableNames();

            stmt = jdbcConnection.createStatement();

            String sql = "db 자식-> 부모 순으로 지울수있는 쿼리";

            rs = stmt.executeQuery(sql);

            while(rs.next()){
                QueryDataSet partialDataSet = new QueryDataSet(srm9newDBConnection);
                String tableName = rs.getString("TABLE_NAME");

                for(String getTableName : getTableNames) {
                    if (getTableName.equals(tableName)) {
                        System.out.println("DELETE TABLE NAME :"+tableName);
                        partialDataSet.addTable(tableName);
                        DatabaseOperation.DELETE_ALL.execute(srm9newDBConnection, partialDataSet);

                    }
                }
            }


        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(null != srm9qaDBConnection) {
                try{
                    srm9qaDBConnection.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

        }
    }

 

우선 import 시 초기화 -> insert 작업을 진행하려 하였는데, 

 

현 회사에서는 oracle base로 data가 들어가야하는 순서 (부모-자식테이블) 구조가 있기 때문에, 

 

초기화가 가능하도록 순차적으로 지울수있었으나, 활용하는 곳에서 지울수있는것은 미지수이기에,

 

이에 대한 방안은 고려해봐야할것으로 보인다.

 

 

또한 DBUnit을 디컴파일하여 처리하다보니, dataset xml 처리간 number / timestamp 에 대해서는 공백이 아닌 null 처리이면, column name 조차 표기하지 않아 data가 정확히 들어가지 않는 부분 있었다.

 

이는 datasetproduceradapter를 override하는것으로 해결하였다. ( 해당 부분은 xml로 데이터를 명확히 보려고하는 목적임을 서술합니다. )

public class DataSetProducerAdapter implements IDataSetProducer{

    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(DataSetProducerAdapter.class);

    private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer();

    private final ITableIterator _iterator;
    private IDataSetConsumer _consumer = EMPTY_CONSUMER;

    public DataSetProducerAdapter(ITableIterator iterator)
    {
        _iterator = iterator;
    }

    public DataSetProducerAdapter(IDataSet dataSet) throws DataSetException
    {
        _iterator = dataSet.iterator();
    }

    @Override
    public void setConsumer(IDataSetConsumer consumer) throws DataSetException {
        logger.debug("setConsumer(consumer) - start");

        _consumer = consumer;
    }

    @Override
    public void produce() throws DataSetException
    {
        logger.debug("produce() - start");

        _consumer.startDataSet();
        while(_iterator.next())
        {
            ITable table = _iterator.getTable();
            ITableMetaData metaData = table.getTableMetaData();

            _consumer.startTable(metaData);
            try
            {
                Column[] columns = metaData.getColumns();
                if (columns.length == 0)
                {
                    _consumer.endTable();
                    continue;
                }

                for (int i = 0; ; i++)
                {
                    Object[] values = new Object[columns.length];
                    for (int j = 0; j < columns.length; j++)
                    {
                        Column column = columns[j];
                        if(column.getDataType().isNumber() || column.getDataType().isDateTime()){
                            values[j] = table.getValue(i, column.getColumnName()) == null? null : table.getValue(i, column.getColumnName());
                        }else{
                            values[j] = table.getValue(i, column.getColumnName()) == null? "" : table.getValue(i, column.getColumnName());
                        }
                    }
                    _consumer.row(values);
                }
            }
            catch (RowOutOfBoundsException e)
            {
                // This exception occurs when records are exhausted
                // and we reach the end of the table.  Ignore this error
                // and close table.

                // end of table
                _consumer.endTable();
            }
        }
        _consumer.endDataSet();
    }
}

 

사용하는 방향에 따라 많은것을 진행할 수 있는 코드로 보여, 공유합니다.

 

잘쓰셨으면 좋겟습니다.