有问题请联系管理员stephenking@fanruan.com

Page tree
Skip to end of metadata
Go to start of metadata

前言

我们已经支持包括MySQL,Oracle,Postgre等许多常用数据库,但某些时候,客户需要使用某些我们不支持的数据库,也就是我们没有提供对应的数据库操作的方法,好在我们提供了用于处理数据库之间差异的接口(方言),这个时候就需要开发单独的插件实现这个接口的某些方法来处理特定需求。

第一步:首先我们需要建立数据库方言对象生成接口的实现类,该接口提供了两个方法返回对应的数据库方言类,详见下图注释。

数据库方言对象生成接口

DialectCreator
package com.fr.stable.fun;

import com.fr.stable.UrlDriver;
import com.fr.stable.fun.mark.Mutable;

import java.sql.Connection;

/**
 * 数据库方言对象生成接口
 */
public interface DialectCreator extends Mutable {

    String XML_TAG = "DialectCreator";

    int CURRENT_LEVEL = 1;

    /**
     * 根据数据库驱动地址生成数据库方言
     * 如果非当前插件处理的driver, 允许return null
     *
     * @param driver 数据库驱动对象
     * @return 数据库方言
     */
    Class<?> generate(UrlDriver driver);

    /**
     * 根据数据库连接生成数据库方言,如果想使用内置的则返回null就可以
     *
     * @param connection 数据库连接
     * @return 数据库方言
     */
    Class<?> generate(Connection connection);

}

在按照规范实现编写了实现这个接口的类后,在插件配置文件中注册这个类即可

Demo
<extra-core>
    <DialectCreator class="com.fr.plugin.testDialect.DialectCreatorImpl"/>
</extra-core>

示例实现(在该示例中,我们假定在urlDriver为“com.mysql.jdbc.Driver”时返回对应的数据库方言实现类 TestDialect类,也可根据Connection来判断何时使用插件提供的数据库方言实现类)

testDialect
package com.fr.plugin.adsDialect;
import com.fr.general.ComparatorUtils;
import com.fr.stable.UrlDriver;
import com.fr.stable.fun.impl.AbstractDialectCreator;
import java.sql.Connection;
 
public class DialectCreatorImpl extends AbstractDialectCreator{
    @Override
    public int currentAPILevel() {
        return CURRENT_LEVEL;
    }
    @Override
    public Class<?> generate(UrlDriver driver) {
        if (ComparatorUtils.equals(driver.getDriver(), "com.mysql.jdbc.Driver")){
            return TestDialect.class;
        }
        //return null的话, 在外部还能继续从metadata里处理, 从driver获取和从metadata获取是顺序的关系, 不是同级的.
        //driver里获取不到, 再从metadata里找.
        return null;
    }
    @Override
    public Class<?> generate(Connection connection) {
        return null;
    }
}

第二步:实现对应的数据库方言实现类,也就是TestDialect。

数据库方言接口,也就是用于处理数据库之间差异的接口(该类型插件开发最重要的接口)

 


Dialect
package com.fr.data.core.db.dialect;

import com.fr.base.StoreProcedureParameter;
import com.fr.data.core.db.ColumnInformation;
import com.fr.data.core.db.TableProcedure;
import com.fr.data.core.db.dml.Table;
import com.fr.data.core.db.handler.SQLTypeHandlerFactory;
import com.fr.data.core.db.tableObject.Column;
import com.fr.data.dao.JDBCDataAccessObjectOperator;
import com.fr.data.dao.ObjectMappingTable;

import javax.transaction.NotSupportedException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 方言,用于处理数据库之间差异的接口
 */
public interface Dialect {

    public static final int DISABLE_FOREIGN_KEY_CHECKS = 0;
    public static final int ENABLE_FOREIGN_KEY_CHECKS = 1;

    /**
     * 初始化数据表
     *
     * @param manager  数据连接操作
     * @param cn       连接
     * @param tableObj 表信息
     * @throws Exception
     */
    public void initTables(JDBCDataAccessObjectOperator manager, Connection cn, ObjectMappingTable[] tableObj) throws Exception;


    /**
     * 数据库字段初始化是否可以允许有默认NULL
     *
     * @param columnSQL 字段sql
     * @param column    字段
     * @return columnSQL 转换之后的结果
     */
    public StringBuffer columnInit(StringBuffer columnSQL, Column column);

    /**
     * 数据库联动删除关键字的位置
     *
     * @param delCascade 是否支持串联删除
     * @param dialect    方言
     * @param buf        语句部分
     * @return 联动删除外键语句
     */
    public String cascadeDeletePosition(boolean delCascade, boolean isSupportsCascadeDelete, StringBuffer buf);


    /**
     * 批处理时一次处理的数目,处理该数目的数据时性能最优
     *
     * @return 一次处理的数目
     */
    public int getFetchSize();

    /**
     * 将列名转化为特定数据库下的名字
     *
     * @param columnName 列名
     * @return 特定数据库下的名字
     */
    public String column2SQL(String columnName);

    /**
     * 生成where条件时将列名转化为特定数据库下的名字
     *
     * @param string 列名
     * @param type   类型
     * @return 特定数据库下的名字
     */
    public String column2SQL4WhereSQL(String string, int type);

    /**
     * 将列类型转化成特定数据库的的列类型
     *
     * @param columnType 列类型
     * @param columnSize 列大小
     * @return 特定数据库列类型
     */
    public String columnType2SQL(int columnType, String columnSize);

    /**
     * 将表转化成SQL语句
     *
     * @param table 要转化的表
     * @return SQL语句
     */
    public String table2SQL(Table table);

    /**
     * 通过数据库连接获取数据库的模式
     *
     * @param conn 数据库连接
     * @return 模式
     * @throws Exception
     */
    public String[] getSchemas(Connection conn) throws Exception;

    /**
     * 根据指定的数据库连接和模式获取数据库表
     *
     * @param connection 数据库连接
     * @param schema     模式
     * @param b          oracle是否显示所有表
     * @return 数据库表组成的集合
     * @throws Exception
     * @deprecated 没用charset转码
     */
    public TableProcedure[] getTableProcedure(Connection connection, String schema, boolean b) throws Exception;

    /**
     * 根据指定的数据库连接和模式获取数据库表
     *
     * @param database   报表数据连接
     * @param connection 数据库连接
     * @param schema     模式
     * @param b          oracle是否显示所有表
     * @return 数据库表组成的集合
     * @throws Exception
     */
    public TableProcedure[] getTableProcedure(com.fr.data.impl.Connection database, Connection connection, String schema, boolean b) throws Exception;

    /**
     * 根据指定的数据库连接和模式获取存储过程表
     *
     * @param connection 数据库连接
     * @param result     结果集
     * @param catalog    包名
     * @param schema     模式
     * @return 存储过程表组
     */
    public TableProcedure[] getProcedureList(Connection connection, ResultSet result, String catalog, String schema);

    /**
     * 根据指定的数据库连接、表、列名以及列类型生成自增长SQL
     *
     * @param conn       数据库连接
     * @param tableName  表名
     * @param columnName 列名
     * @param columnType 列类型
     * @return 自增长SQL
     */
    public String createSequence(Connection conn, String tableName, String columnName, String columnType);

    /**
     * 获取一个select语句用于检索最后为某个特殊的表生成的标记
     *
     * @param table  表名
     * @param column 列
     * @param type   字段类型
     * @return select语句
     * @throws Exception
     */
    public String getIdentitySelectString(String table, String column, int type) throws Exception;

    /**
     * 设置该数据库连接下是否可以自动提交
     *
     * @param conn       数据库连接
     * @param autoCommit true表示可以自定提交,false表示不自动提交
     * @throws SQLException
     */
    public void setAutoCommit(Connection conn, boolean autoCommit) throws SQLException;

    /**
     * 是否支持限制查询
     *
     * @return 支持返回true,否则返回false
     */
    public boolean supportsLimitOffset();

    /**
     * 生成限制查询的SQL
     *
     * @param query  原始的SQL
     * @param offset 起始位置
     * @param limit  限制值
     * @return 修改过的SQL语句
     */
    public String getLimitString(String query, int offset, int limit);

    /**
     * 查询数据条数的SQL
     *
     * @param sql 需要被查询的数据
     * @return
     */
    public String getCountSql(String sql);


    /**
     * 判断是否是年类型的字段,主要针对MySQL的年类型
     *
     * @param conn       数据库连接
     * @param type       列字段类型
     * @param table      数据库表
     * @param columnName 列名
     * @return 如果是年则返回true,否则返回false
     */
    public boolean isYearData(Connection conn, int type, Table table, String columnName);

    /**
     * 返回定时器代理
     *
     * @return 定时器代理类
     */
    public String quartzDelegateClass();

    /**
     * 返回定时器代理的key
     *
     * @return 定时器代理类的key
     */
    public String quartzDelegateKey();

    /**
     * 错误时候默认的查询语句
     *
     * @param conn 数据库连接
     * @return SQL语句
     */
    public String defaultValidationQuery(Connection conn);

    /**
     * 获取存储过程的参数信息
     *
     * @param conn 数据库连接
     * @param name 数据库表名
     * @return 存储过程的参数信息组成的集合
     */
    public StoreProcedureParameter[] getStoreProcedureDeclarationParameters(Connection conn, String name, String parameterDefaultValue);

    /**
     * SQL生成工厂
     *
     * @return 返回SQL生成工厂类
     */
    public SQLTypeHandlerFactory buildSQLTypeHandlerFactory();

    /**
     * 是否支持串联删除
     *
     * @return true表示支持串联删除,false表示不支持
     */
    public boolean supportsCascadeDelete();

    /**
     * 返回建立外键的语句
     *
     * @param constraintName       约束字段名
     * @param foreignKey           列名
     * @param referencedTable      外键表名
     * @param primaryKey           主键
     * @param referencesPrimaryKey 是否引用主键
     * @return SQL语句
     */
    public String buildForeignKeyString(
            String constraintName,
            String[] foreignKey,
            String referencedTable,
            String[] primaryKey,
            boolean referencesPrimaryKey);

    /**
     * 该数据库在添加和修改表的时候是否支持添加特定的约束
     *
     * @return true表示支持,false表示不支持
     */
    public boolean supportsUniqueConstraintInCreateAlterTable();

    /**
     * 该数据库是否支持检查违反唯一约束
     *
     * @return true表示支持,false表示不支持
     */
    public boolean supportsUniqueViolationExceptionCheck();

    /**
     * SQL异常是否是检查出违反唯一约束时的异常
     *
     * @param se SQL异常
     * @return true表示是违反唯一约束异常则返回,false表示不是
     */
    public boolean isUniqueViolationException(SQLException se);

    /**
     * 存储过程的内容
     *
     * @param conn        数据库连接
     * @param name        数据库表名
     * @param charSetName 字符集
     * @return 表示存储过程的字符串
     */
    public String getStoreProcedureText(java.sql.Connection conn, String name, String charSetName);

    /**
     * 是否支持获取参数
     *
     * @return 是则返回true
     */
    public boolean isSupportFetchText();

    /**
     * 数据库中的值转换成field中的值
     *
     * @param value 值
     * @param type  值的类型
     * @return 转换后的值
     */
    public Object parseValue(Object value, int type);

    /**
     * 获取表的注释内容
     *
     * @param conn
     * @param tableName
     * @param schema
     * @return
     */
    public String getTableCommentName(java.sql.Connection conn, String tableName, String schema, String dbLink);

    /**
     * 获取表字段信息
     *
     * @param conn
     * @param tableName
     * @param schema
     * @return
     */
    public List getTableFieldsInfor(java.sql.Connection conn, String tableName, String schema, String dbLink);

    /**
     * 写在这里是为了可以在下面方法执行完毕之后释放
     *
     * @param conn 数据连接
     * @return 生成限制语句
     */
    public Statement createLimitUseStatement(java.sql.Connection conn) throws SQLException;

    /**
     * 创建指定行到指定行的游标
     * FIXME Sqlserver 等 endRow 是没有效果的
     *
     * @param database   数据连接
     * @param conn       连接
     * @param stmt       语句
     * @param fieldNames 字段名字
     * @param schema     模式
     * @param tableName  表名
     * @param dbLink     连接
     * @param startRow   开始行
     * @return 游标
     */
    public ResultSet createLimitResultSet(com.fr.data.impl.Connection database,
                                          java.sql.Connection conn, Statement stmt,
                                          String[] fieldNames, String schema,
                                          String tableName, String dbLink, long startRow) throws SQLException;

    /**
     * 创建指定行的SQL
     *
     * @param sql        sql语句
     * @param fieldNames 字段名
     * @param startRow   开始行
     * @return SQL语句
     */
    public String createLimitSQL(String sql, String[] fieldNames, long startRow) throws NotSupportedException;

    /**
     * 获取第row行的数据
     *
     * @param sql sql语句
     * @param row 行
     * @return 行数据
     */
    public String getSpecificRowSql(String sql, int row);

    /**
     * 获取从start行到row行的数据
     *
     * @param sql   sql语句
     * @param start 开始行
     * @param end   结束行
     * @return 数据
     */
    public String getRowRangeSql(String sql, int start, int end, String[] cols);

    /**
     * 返回最前面rowCount行的数据
     *
     * @param rowCount 行数
     * @param table    表名
     * @return 数据
     * @throws NotSupportedException 异常
     */
    public String getTopNRowSql(int rowCount, Table table) throws NotSupportedException;

    /**
     * 是否不支持插入操作事务时进行查询事务
     *
     * @param insertCount 插入行数
     * @return 默认支持
     */
    public boolean isSupportQueryWhileInsert(int insertCount);

    /**
     * 获取列信息
     *
     * @param connection     数据库连接
     * @param rs             结果集
     * @param oriCharsetName 原始编码
     * @param newCharsetName 结果编码
     * @return 列信息
     * @throws SQLException 执行出错则抛出此异常
     */
    public ColumnInformation[] getColumnInformation(Connection connection, ResultSet rs, String sql, String oriCharsetName, String newCharsetName) throws SQLException;

    /**
     * 设置外键约束
     *
     * @param conn      数据连接
     * @param keyChecks 外键约束
     *                  DISABLE_FOREIGN_KEY_CHECKS 表示取消, ENABLE_FOREIGN_KEY_CHECKS 表示需要
     */
    public void setForeignKeyChecks(Connection conn, int keyChecks);

    /**
     * 获取列信息的sql
     *
     * @param query 原始sql
     * @return 获取列信息的sql
     */
    String createSQL4Columns(String query);

    /**
     * 获取所有表信息
     *
     * @return 数据库表信息
     */
    public TableProcedure[] getAllTableProcedure(com.fr.data.impl.Connection database, String type);

    /**
     * 创建数据库声明
     *
     * @param connection 连接
     * @param sql        sql语句
     * @return 数据库声明
     */
    public Statement createStatement(Connection connection, String sql) throws SQLException;

    /**
     * 执行sql语句
     *
     * @param sql        数据库语句
     * @param statement  声明
     * @param connection 数据连接
     * @return 结果集
     */
    public ResultSet executeQuery(Statement statement, String sql, Connection connection) throws SQLException;

    /**
     * 判断字段值是否为空
     *
     * @param object 字段值
     * @return 字段值是否为空
     */
    public boolean isNULL(Object object);

    /**
     * 触发器状态改变
     *
     * @param cn    数据连接操作
     * @param table 表
     * @throws Exception
     */
    public void notifyTriggerChange(Connection cn, Table table, TriggerAction action);

    /**
     * RPC: 远程存储过程调用
     *
     * @param connection 数据连接
     * @param query      SQL语句
     * @return 结果集Object[2] ~ [Statement, ResultSet]
     * @date 2014-9-30-下午5:11:47
     */
    public Object[] remoteProcedureCall(Connection connection, String query) throws SQLException;

}

各个方法都给了相关注释,用于处理数据库差异的方方面面,这里以阿里云ADS上的MySQL为例(用odbc直连的时候获取不到所有表, 还不提供API获取原始数据库信息, 只能反射去拿odbc里的驱动文件名来判断不同数据库, 再写不同的sql获取表),实现该接口获取所有表的方法(一般来说,不需要直接实现com.fr.data.core.db.dialect.Dialect接口,只要继承com.fr.data.core.db.dialect.AbstractDialect抽象类即可。)。

 

AdsMysqlOdbcDialect
package com.fr.plugin.adsDialect;

import com.fr.base.FRContext;
import com.fr.data.core.db.ColumnInformation;
import com.fr.data.core.db.DBUtils;
import com.fr.data.core.db.TableProcedure;
import com.fr.data.core.db.dialect.AbstractDialect;
import com.fr.data.impl.Connection;
import com.fr.plugin.ExtraClassManager;
import com.fr.stable.fun.FunctionHelper;
import com.fr.stable.fun.FunctionProcessor;
import com.fr.stable.fun.impl.AbstractFunctionProcessor;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 阿里云ads上的mysql, 用odbc的方式连接.
 * 从metadata里获取不到所有表的信息, 但是直接写sql是可以查出来的.
 * 装了插件后, 在odbc连接方式下依然会先从默认逻辑获取所有表, 如果获取不到, 那么就走dialect获取.
 */
public class AdsMysqlOdbcDialect extends AbstractDialect {

    public static final String PLUGIN_ID = "com.fr.plugin.adsDialect.DialectCreatorImpl";
    private static final FunctionProcessor FUNCTION_RECORD = new AbstractFunctionProcessor() {
        @Override
        public int getId() {
            return FunctionHelper.generateFunctionID(PLUGIN_ID);
        }

        @Override
        public String getLocaleKey() {
            return "FR-Plugin_Ads-Dialect";
        }
    };


    /**
     * 获取列信息的sql
     *
     * @param query 原始sql
     * @return 获取列信息的sql
     */
    public String createSQL4Columns(String query) {
        return query;
    }

    /**
     * 获取所有表信息
     *
     * @return 数据库表信息
     */
    public TableProcedure[] getAllTableProcedure(Connection database, String type) {
        FunctionProcessor processor = ExtraClassManager.getInstance().getFunctionProcessor();
        if (processor != null) {
            processor.recordFunction(FUNCTION_RECORD);
        }

        java.sql.Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            List sqlTableList = new ArrayList();
            connection = database.createConnection();

            statement = connection.createStatement();
            resultSet = statement.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES");
            while (resultSet.next()) {
                sqlTableList.add(new TableProcedure(null, resultSet.getString(1), type, this));
            }
            return (TableProcedure[]) sqlTableList.toArray(new TableProcedure[sqlTableList.size()]);
        } catch (Exception e) {
            FRContext.getLogger().error(e.getMessage());
        } finally {
            release(connection, statement, resultSet);
        }

        return new TableProcedure[0];
    }

    private void release(java.sql.Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }

            if (statement != null) {
                statement.close();
            }

            if (connection != null) {
                connection.close();
            }
        } catch (Exception e) {

        }
    }

    public List getTableFieldsInfor(java.sql.Connection conn, String tableName, String schema, String dbLink){
        String query = "select * from " + (schema == null ? "" : (schema + ".")) + tableName;
        List result = new ArrayList();

        try {
            ColumnInformation[] informations = DBUtils.checkInColumnInformation(conn, this, query);
            for(ColumnInformation information : informations){
                Map field = new HashMap();
                field.put("column_name", information.getColumnName());
                field.put("column_comment", "");
                field.put("column_type", information.getColumnType());
                field.put("column_size", information.getColumnSize());
                field.put("column_key", false);
                result.add(field);
            }

        } catch (SQLException e) {
            FRContext.getLogger().error(e.getMessage());
        }

        return result;
    }

}

 

 

从上面两个接口的实现类就能够很容易明白如何处理数据库差异,自定义数据库方言来实现对某些小众数据库的支持。

 

 源码

如果你需要更清晰的了解这个接口的使用,可以参照在自定义OdbcDialect

的实现:http://cloud.finedevelop.com:2015/projects/BA/repos/plugins-free/browse/plugin-ads_odbc_dialect

  • No labels
Write a comment…