利用Druid实现Oracle语法转换成MySQL建表离线篇

数据库迁移语法改造是非常重要的一块,本文讲述利用Druid进行语法改造

Posted by Qiyibaba on February 24, 2020

Oracle建表语句修改为MySQL的建表语句,在提供Oracle连接和不使用连接的情况下实现方式略有不同:

  • 有原始环境,可以将所有需要的信息进行获取,然后拼接生成语法
  • 没有原始环境,使用Druid解析Oracle语法,并定制转换规则生成对应的MySQL语法

本文主要是对没有原始环境的Oracle导出的ddl文件进行转换,有原始环境的在线转换另开篇介绍。

​ 基本实现步骤是解析语法生成对应的statement,并对statement实现定制,本省Druid能够使用定制的Visitor来实现相关的定制操作,但是Oracle导出的ddl文件中,主键是使用单独的语句导出。而在我们转换的DDL的要求中,建表语句必须要携带主键信息,否则创建失败,就不能使用Visitor。没有这种硬性要求的小伙伴们可以尝试下使用visitor进行定制,实现会更清晰明朗。

  • 文件读取,该部分没有啥好多说的,读取文件到内存中

    try (InputStreamReader fReader = new InputStreamReader(new FileInputStream(file),
                                                     Constants.BASIC.FILE_DEFAULT_CHARSET); BufferedReader reader = new BufferedReader(fReader)) {
        String temp;
        while ((temp = reader.readLine()) != null) {
            boolean add = true;
            for (String s : blackList) {
                if (temp.startsWith(s)) {
                    add = false;
                    break;
                }
            }
            if (add) {
                sb.append(temp).append(Constants.BASIC.LINE_SPLIT);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • 使用Druid进行解析生成对应的statement列表

    List<SQLStatement> statements = SQLUtils.parseStatements(src.toUpperCase(), "oracle");
    
  • 对列表的Statement类型做判断,保留ddl语句,并且将属于同一个表的statement存放到一起

    Map<String, List<SQLStatement>> tbStmt = new HashMap<>();
    for (SQLStatement statement : statements) {
        String tbSchema;
        if (statement instanceof OracleCreateTableStatement) {
            tbSchema = ((OracleCreateTableStatement) statement).getTableSource().getName().toString();
        } else if (statement instanceof SQLAlterTableStatement) {
            String tbName = ((SQLAlterTableStatement) statement).getTableName();
            String dbname = ((SQLAlterTableStatement) statement).getSchema();
            tbSchema = (dbname + "." + tbName).replaceAll(" ", "");
        } else if (statement instanceof OracleCreateIndexStatement) {
            String tbName = ((SQLCreateIndexStatement) statement).getTableName();
            String dbname = ((SQLCreateIndexStatement) statement).getSchema();
            tbSchema = (dbname + "." + tbName).replaceAll(" ", "");
        } else if (statement instanceof SQLCommentStatement) {
            SQLPropertyExpr expr = (SQLPropertyExpr) ((SQLCommentStatement) statement).getOn().getExpr();
            if (expr.getOwner().toString().contains(".")) {
                tbSchema = expr.getOwner().toString();
            } else {
                tbSchema = expr.toString();
            }
        } else {
            continue;
        }
      
        if (null == tbStmt.get(tbSchema)) {
            List<SQLStatement> tl = new ArrayList<>();
            tl.add(statement);
            tbStmt.put(tbSchema, tl);
        } else {
            tbStmt.get(tbSchema).add(statement);
        }
    
  • 对每个表的ddl进行定制转换,并转换成对应的MySQL建表对象

    List<MySqlCreateTableStatement> mcs = new ArrayList<>();
    List<String> lostPrimaryKeyTables = new ArrayList<>();
      
    for (Map.Entry<String, List<SQLStatement>> entry : stmtMap.entrySet()) {
        MySqlCreateTableStatement mc = new MySqlCreateTableStatement();
        List<SQLTableElement> mte = new ArrayList<>();
        List<SQLTableElement> autoIncrements = new ArrayList<>();
      
        String tbName = "", dbName = "";
      
        for (SQLStatement stmt : entry.getValue()) {
            if (stmt instanceof OracleCreateTableStatement) {
                OracleCreateTableStatement db2cts = (OracleCreateTableStatement) stmt;
                List<SQLTableElement> tes = db2cts.getTableElementList();
                tbName = ((SQLPropertyExpr) db2cts.getName()).getName();
                dbName = ((SQLPropertyExpr) db2cts.getName()).getOwnernName();
                for (SQLTableElement te : tes) {
                    if (te instanceof SQLColumnDefinition) {
                        ((SQLColumnDefinition) te).setDbType(JdbcConstants.MYSQL);
                        ((SQLColumnDefinition) te)
                        .setDataType(SQLTransformUtils.transformOracleToMySql(((SQLColumnDefinition) te).getDataType()));
                        SQLIdentifierExpr name = (SQLIdentifierExpr) ((SQLColumnDefinition) te).getName();
                        name.setName(Constants.BASIC.MYSQL_ENCLOSE + name.getName() + Constants.BASIC.MYSQL_ENCLOSE);
                        mte.add(te);
                        if (((SQLColumnDefinition) te).isAutoIncrement()) {
                            autoIncrements.add(te);
                        }
                    }
                }
            } else if (stmt instanceof SQLAlterTableStatement) {
                SQLAlterTableStatement db2ats = (SQLAlterTableStatement) stmt;
                List<SQLAlterTableItem> items = db2ats.getItems();
                for (SQLAlterTableItem item : items) {
                    if (item instanceof SQLAlterTableAddConstraint) {
                        SQLAlterTableAddConstraint ac = (SQLAlterTableAddConstraint) item;
                        SQLConstraint constraint = ac.getConstraint();
                        if (constraint instanceof OraclePrimaryKey) {
                            OraclePrimaryKey pk = (OraclePrimaryKey) constraint;
                            MySqlPrimaryKey mp = new MySqlPrimaryKey();
                            List<SQLSelectOrderByItem> tt = pk.getColumns();
                            for (SQLSelectOrderByItem t : tt) {
                                SQLIdentifierExpr e = (SQLIdentifierExpr) t.getExpr();
                                e.setName(Constants.BASIC.MYSQL_ENCLOSE + e.getName() + Constants.BASIC.MYSQL_ENCLOSE);
                            }
                            mp.getColumns().addAll(pk.getColumns());
                            mte.add(mp);
                        } else if (constraint instanceof OracleUnique) {
                            OracleUnique pk = (OracleUnique) constraint;
                            MySqlPrimaryKey mp = new MySqlPrimaryKey();
                            List<SQLSelectOrderByItem> tt = pk.getColumns();
                            for (SQLSelectOrderByItem t : tt) {
                                SQLIdentifierExpr e = (SQLIdentifierExpr) t.getExpr();
                                e.setName(Constants.BASIC.MYSQL_ENCLOSE + e.getName() + Constants.BASIC.MYSQL_ENCLOSE);
                            }
                            mp.getColumns().addAll(pk.getColumns());
                            mte.add(mp);
                        } else {
                            LOG.error("Lost parse [" + constraint.getClass() + "],statement is " + constraint);
                        }
                    } else {
                        LOG.error("Lost parse [" + item.getClass() + "],statement is " + item);
                    }
                }
            } else if (stmt instanceof OracleCreateIndexStatement) {
                SQLCreateIndexStatement db2cis = (OracleCreateIndexStatement) stmt;
                List<SQLSelectOrderByItem> items = db2cis.getItems();
                for (SQLSelectOrderByItem item : items) {
                    item.setType(null);
                    SQLIdentifierExpr e = (SQLIdentifierExpr) item.getExpr();
                    e.setName(Constants.BASIC.MYSQL_ENCLOSE + e.getName() + Constants.BASIC.MYSQL_ENCLOSE);
                }
                if (null != db2cis.getType() && db2cis.getType().toLowerCase().equals("unique")) {
                    MySqlUnique unique = new MySqlUnique();
                    unique.getColumns().addAll(db2cis.getItems());
                    mte.add(unique);
                } else {
                    MySqlTableIndex index = new MySqlTableIndex();
                    index.getColumns().addAll(db2cis.getItems());
                    mte.add(index);
                }
            }
            //else if (stmt instanceof SQLCommentStatement) {
            // LOG.debug("no body");}
            else {
                LOG.error("Lost parse [" + stmt.getClass() + "],statement is " + stmt);
            }
        }
      
        mc.getTableElementList().addAll(mte);
        MySqlUserName name = new MySqlUserName();
        name.setUserName(Constants.BASIC.MYSQL_ENCLOSE + dbName.replaceAll(" ", "") + Constants.BASIC.MYSQL_ENCLOSE + '.' + Constants.BASIC.MYSQL_ENCLOSE
                         + tbName.replaceAll(" ", "") + Constants.BASIC.MYSQL_ENCLOSE);
        mc.setName(name);
        mcs.add(mc);
    }
    
  • 剩下的就是将生成的DDL输出成文件即可,该实现并未将注释纳入,需要的小伙伴自行添加

  • 关于类型转换,Druid的实现

    SQLTransformUtils.transformOracleToMySql(((SQLColumnDefinition) te).getDataType());
    

    具体的实现可以查看源码。