324 Star 1.1K Fork 365

闲.大赋 / beetlsql

Create your Gitee Account
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
Clone or download
3.0-dbstyle.md 17.48 KB
Copy Edit Web IDE Raw Blame History
jiazhili authored 2020-09-06 12:25 . spring itegration 支持

BeetlSQL 多数据库支持

BeetlSQL的目标是提供开发高效,维护高效,运行高效的数据库访问框架,在一个系统多个库的情况下,提供一致的编写代码方式。

M4阶段继续增加了对国产数据库支持,包括神通,达梦,华为高斯,人大金仓,TD-Engine,目前支持如下数据库和大数据如下

  • 传统数据库:MySQL,MariaDB,Oralce,Postgres,DB2,SQL Server,H2,SQLite,Derby,神通,达梦,华为高斯,人大金仓等
  • 大数据:HBase,ClickHouse,Cassandar,Hive
  • 物联网时序数据库:Machbase,TD-Engine
  • SQL查询引擎:Drill,Presto,Druid

BeetlSQL提供了接口来抽象不同的数据库或者SQL查询引擎,新的数据库只要实现这些接口,便能作为插件作为BeetlSQL使用

多库之间的不同

可能你会疑惑,JDBC已经规范访问数据库的方式,为什么还需要BeetlSQL来规范。这是因为不同数据库,对JBDC的实现并不完全一样,而且,对SQL的的实现也不一定一样。在完成数据库集成的时候,需要考虑如下问题

  • 数据库的jdbc是否支持PreparedStatement,大部分数据库支持,但有的数据库只支持Statement,比如Drill,Druid,Presto,因此,需要BeetlSQL在这些情况下,使用Statement来作为底层执行接口
  • 数据库是否支持Metadata,如果支持,数据库框架可以得到数据库和表定义,大部分都支持。Drill 不支持(比如查询目标是个文件),TD-Engine是支持的,但目前版本获取Metadata会报错,也认为不支持。因此,需要BeetlSQL提供接口添加metadata信息
  • 数据库是否支持update操作,SQL查询引擎是不支持的,因此需要屏蔽内置的更新SQL语句
  • 数据库的翻页语句是否一样,大部分都不相同,都需要实现Range接口,然而,有些数据库是类似的,可以重用,比如OffsetLimitRange作为Range的实现类,可以为Mysql,大梦,TD-Engine,H2,Clickhouse,SqlLite使用
  • 数据库JDBC驱动对日期字段是否支持,由于Java的日期类型比较多,传统数据可能会兼容java.util.Date,以及JDK后的LocalDate,LocalDateTime, 但也可能不兼容,BeelSQL框架提供了TypeHandler来负责实现这种转化
  • 数据库JDBC对特殊字段是否支持,比如JSON,XML等,由于着俩种类型并不是java规范,比如json实现有fastjson,jackson,因此需要TypeHandler来实现这种转化,把这些类型转化为数据库对应的类型
  • 数据库对主键支持情况。越来越应用使用uuid等程序指定的分布式id而不以来数据库,也有传统应用使用自增和序列,比如Mysql自增,DB2和Postgres或者同时兼容俩种。
  • SQL查询引擎,如Presto,不支持insert,update语句

比如有的数据库不支持PreparedStatement,有的查询引擎无法获取数据库schema,再比如,对于翻页SQL语句,不同数据库实现都不太一样,再比如如,新增记录使用insert语句,但有的数据库使用的upsert,有的干脆不支持

跨库支持实现

如果BeetlSQL 目前不支持你所用的数据库,可以自己轻松扩展实现。

首先,先看看数据库跟哪些数据库比较接近或者兼容,比如很多云数据库都有传统数据库的影子,因此,你可以尝试使用传统数据库的DBStyle,比如阿里云云数据库兼容MySQL。因此,完全可以使用MySqlStyle,华为开源高斯数据库类似Postgres。

其次,新兴的数据库都有传统数据库的影子,比如翻页,大部分都是limit ${offset},\\、${limit} , 比如mysql,因此可以用复用类OffsetLimitRange ,有的数据库则是limit ${limit} offset ${offset} ,比如apache drill,couchbase,apache ignite,还有国产TD-Engine. 这时候可以复用LimitWithOffsetRange。 有的数据库翻页类似Oralce,因此可以复用RowNumRange,比如国产数据库达梦

实现XXX数据库基本上只要是实现XXXStyle,继承AbstractDbStyle,AbstractDbStyle的一些核心方法是BeetlSQL解决不同数据库差异的主要类,这些方法将在本章后面详细简介,现在简单说明如下

@Override
public String getName() {
  return "mysql";
}

@Override
public int getDBType() {
  return DBType.DB_MYSQL;
}

getName 返回数据库名称,getDBType则返回一个唯一标识,可以返回1000以外的int

@Override
public RangeSql getRangeSql() {
return this.rangeSql;
}

返回一个翻页辅助类,这将在后面详细讲解。这也是大部分数据库的差异点

对于NOSQL或者查询引擎来说,还有需要考虑的地方,以Presto为例子

@Override
public boolean  isNoSql(){
  return true;
}


public  boolean preparedStatementSupport(){
  return false;
}

@Override
public String wrapStatementValue(Object value){
  return super.wrapStatementValue(value);
}

@Override
public SQLExecutor buildExecutor(ExecuteContext executeContext){
  return new QuerySQLExecutor(executeContext);
}

isNoSql 返回true,表示是非传统数据库。

preparedStatementSupport 返回false,表示数据库jdbc 不支持预编译,因此BeetlSQL将使用Statement而不是PreparedStatement,并会调用wrapStatementValue来动态构造sql

buildExecutor 实际上构造了BeetlSQL的执行核心,这里返回QuerySQLExecutor而不是默认的BaseSQLExecutor,因为QuerySQLExecutor只保留了查询支持

有些数据库对MetaData支持不够友好,比如某些查询数据库查询文件,因此需要代码添加对“表”的描述,DBStyle需要重载initMetadataManager

@Override
public MetadataManager initMetadataManager(ConnectionSource cs){
  metadataManager = new NoSchemaMetaDataManager();
  return metadataManager;
}

NoSchemaMetaDataManager 类提供了addBean方法用于通过POJO提供一个表描述,这样才能保证BeetlSQL的代码能执行。

DBStyle

DBStyle 是提供一致使用方式的关键,抽象类AbstractDBStyle是其子类,实现了大多数方法。不同数据库Style可以继承AbstractDBStyle,覆盖其特定实现,下面会以传统数据库Mysql和大数据库Clickhouse 为例来做说明

MySqlStyle 例子

public class MySqlStyle extends AbstractDBStyle {

  RangeSql rangeSql = null;

  public MySqlStyle() {

    rangeSql = new OffsetLimitRange(this);
  }


  @Override
  public String getName() {
    return "mysql";
  }

  @Override
  public int getDBType() {
    return DBType.DB_MYSQL;
  }

  @Override
  public RangeSql getRangeSql() {
    return this.rangeSql;
  }


  @Override
  public int getIdType(Class c,String idProperty) {
    List<Annotation> ans = BeanKit.getAllAnnotation(c, idProperty);
    int idType = DBType.ID_AUTO; //默认是自增长

    for (Annotation an : ans) {
      if (an instanceof AutoID) {
        idType = DBType.ID_AUTO;
        break;// 优先
      } else if (an instanceof SeqID) {
        //my sql not support
      } else if (an instanceof AssignID) {
        idType = DBType.ID_ASSIGN;
      }
    }

    return idType;

  }

对于传统的数据库,需要重写的方法较少,主要是

  • getName ,返回数据库名字,如mysql,sqlserver2010,sqlserver2015等
  • getDBType ,返回任意一个数字类型,默认的都在DBType类里
  • rangeSql,用来实现翻页的,输入是jdbc sql,或者是模板sql,输出是一个翻页语句,本例子实现类是OffsetLimitRange,定义如下
public class OffsetLimitRange implements RangeSql {
    AbstractDBStyle sqlStyle = null;
    public OffsetLimitRange(AbstractDBStyle style){
        this.sqlStyle = style;
    }

    @Override
    public String toRange(String jdbcSql, Object objOffset , Long limit) {
        Long offset = ((Number)objOffset).longValue();
        offset = PageParamKit.mysqlOffset(sqlStyle.offsetStartZero, offset);
        StringBuilder builder = new StringBuilder(jdbcSql);
        builder.append(" limit ").append(offset).append(" , ").append(limit);
        return builder.toString();
    }

    @Override
    public String toTemplateRange(Class mapping,String template) {
        return template + sqlStyle.getOrderBy() +
                " \nlimit " + sqlStyle.appendExpress( DBAutoGeneratedSql.OFFSET )
                + " , " + sqlStyle.appendExpress(DBAutoGeneratedSql.PAGE_SIZE);
    }

    @Override
    public void addTemplateRangeParas(Map<String, Object> paras, Object objOffset, long size) {
        Long offset = (Long)objOffset;
        paras.put(DBAutoGeneratedSql.OFFSET, offset - (sqlStyle.offsetStartZero ? 0 : 1));
        paras.put(DBAutoGeneratedSql.PAGE_SIZE, size);
    }
}
  • toRange,返回一个JDBC的翻页SQL,对于MySQL,H2等支持limit&offset的来说,非常简单,后面添加limit offsetXXX,limitXX即可
  • toTemplateRange, 针对模板sql翻页语句,类似toRange方法,但使用的是俩个变量,变量名的定义是DBAutoGeneratedSql.OFFSET,DBAutoGeneratedSql.PAGE_SIZE
  • addTemplateRangeParas, 这个是同toTemplateRange匹配,提供了DBAutoGeneratedSql.OFFSET的值,以及DBAutoGeneratedSql.PAGE_SIZE的值

ClickHouseStyle例子

public class ClickHouseStyle extends AbstractDBStyle {

    RangeSql rangeSql = null;
    public ClickHouseStyle() {
        super();
        rangeSql = new OffsetLimitRange(this);
    }

    @Override
    public int getIdType(Class c,String idProperty) {
        //只支持
        return DBType.ID_ASSIGN;
    }

    @Override
    public boolean  isNoSql(){
        return true;
    }
    @Override
    public String getName() {
        return "clickhouse";
    }

    @Override
    public int getDBType() {
        return DBType.DB_CLICKHOUSE;
    }

    @Override
    public RangeSql getRangeSql() {
        return rangeSql;
    }
    @Override
    protected void checkId(Collection colsId, Collection attrsId, String clsName) {
        // 不检测主键
        return ;
    }
    @Override
    public void config(SQLManager sqlManager){
        Map<Class, JavaSqlTypeHandler> handlerMap = sqlManager.getDefaultBeanProcessors().getHandlers();
        handlerMap.put(java.util.Date.class,new UtilDateTypeHandler() );
        
    }
}

由于Clickhouse的翻页风格类似MySQL,因此rangeSql重用了OffsetLimitRange类

  • getIdType,由于clickhouse不支持序列和自增主键,因此,这里直接使用DBType.ID_ASSIGN
  • isNoSql 返回true
  • checkId方法,不检查主键,因为clickhouse实际上并没有唯一主键的概念
  • config,此方法是初始化SQLManager的最后一步,这里需要配置一些映射关系,使得clickhouse支持把它的时间类型映射到java.util.Date或者java.sql.Date上,至于Timestamp,clickhouse驱动是支持的,UtilDateTypeHandler实现如下

public static class UtilDateTypeHandler extends JavaSqlTypeHandler {
  @Override
  public Object getValue(ReadTypeParameter typePara) throws SQLException {
    Date a = typePara.getRs().getDate(typePara.getIndex());
    return new java.util.Date(a.getTime());
  }

  @Override
  public void setParameter(WriteTypeParameter writeTypeParameter, Object obj)throws SQLException {
    java.util.Date date = (java.util.Date)obj;
    writeTypeParameter.getPs().setDate(writeTypeParameter.getIndex(),new java.sql.Date(date.getTime()));
  }

}
  • getValue方法用于从查询结果ResultSet中获得值,映射到期望的类型上
  • setParameter ,用于把java的值设置到jdbc里

可以注册任意类型的映射,比如官网演示文档就演示了如何映射Java类型的JsonNode到数据库Varchar里

HBaseStyle例子

public class HBaseStyle extends AbstractDBStyle {
    RangeSql rangeSql = null;
    public HBaseStyle() {
        super();
        rangeSql = new HbaseRange(this);
    }

    @Override
    public int getIdType(Class c,String idProperty) {
        return DBType.ID_ASSIGN;
    }

    @Override
    public boolean  isNoSql(){
        return true;
    }
    @Override
    public String getName() {
        return "hbase";
    }

    @Override
    public int getDBType() {
        return DBType.DB_HBASE;
    }

    @Override
    public RangeSql getRangeSql() {
        return rangeSql;
    }

   @Override
    protected SQLSource generalInsert(Class<?> cls,boolean template){
        SQLSource sqlSource   = super.generalInsert(cls,template);
        String upsert = sqlSource.template.replaceFirst("insert","UPSERT");
        sqlSource.template = upsert;
        return sqlSource;
    }

    @Override
    public SQLSource genUpdateById(Class<?> cls) {
       return this.generalInsert(cls,false);
    }
}
  • getIdType 跟clickhouse一样,没有自增和序列主键,因此设定为ID_ASSIGN
  • rangeSql,返回一个HbaseRange实例,Hbase翻页跟MySql类似但略有不同
  • generalInsert,此方法是根据实体生成内置insert语句,因为hbase使用upsert,而不是insert,因此修改了AbtractStyle.generalInsert返还默认的SQL
  • genUpdateById,同样根据id修改对象,也采用UPSERT方式

MetadataManager

此类定义了数据库的Metadata,类似JDBC的DatabaseMetaData。但考虑到有些数据库可能没有metadata,比如文件系统,因此

MetadataManager有如下子类

  • SchemaMetadataManager: 大部分数据库,大数据使用,这些数据库都有严格的schema
  • NoSchemaMetaDataManager,无schema,如drill使用文件系统,这时候需要调用addBean方法通过POJO定义反向得到一个模拟的Schema
  • SchemaLessMetaDataManager,综合上面俩种情况
public interface MetadataManager {
     boolean existTable(String tableName);
     TableDesc getTable(String name);
     Set<String> allTable();
     public void addTableVirtuals(String realTable,String virtual);
}
  • existTable 用于检测表是否存在
  • getTable,返回TableDesc ,表的详细描述,如主键,列,备注等
  • allTable 返回所有表名
  • addTableVirtuals, 建立一个真实不要和虚拟表的映射,因此当beetlsql 通过getTable,传入虚拟表的时候,实际得到的是真实表的TableDesc,比如在分表场景下,有user_001,user_002,但表定义都是user表

对于NoSchemaMetaDataManager,还有如下方法

  • addBean 传入一个POJO,通过POJO的定义可以反向得到表定义

比如TD-Engine的JDBC目前不支持,因此DbStyle定义如下

@Override
public MetadataManager initMetadataManager(ConnectionSource cs){
  metadataManager = new NoSchemaMetaDataManager();
  return metadataManager;
}

然后在代码里手工添加定义

NoSchemaMetaDataManager metaDataManager = (NoSchemaMetaDataManager)sqlManager.getMetaDataManager();
metaDataManager.addBean(Data.class);

//Data是一个POJO,描述了个表t,有字段ts和a
@Table(name="t")
@lombok.Data
public class Data {
    @Column("ts")
    Timestamp ts;
    @Column("a")
    Integer a;
}

BeanProcessor

BeanProcessor是非常底层一个类,紧密跟JDBC 规范打交道,因此许多个性化扩展都可以通过实现BeanProcessor的某些方法来完成,比如,在前面例子中展示的让Clickhouse的结果集能映射java.util.Date上,这是最常用的情况,BeanProcessor已经内置如下类型转化,你的数据库可以重新实现或者新增类型转化

static BigDecimalTypeHandler bigDecimalHandler = new BigDecimalTypeHandler();
static BooleanTypeHandler booleanDecimalHandler = new BooleanTypeHandler();
static ByteArrayTypeHandler byteArrayTypeHandler = new ByteArrayTypeHandler();
static ByteTypeHandler byteTypeHandler = new ByteTypeHandler();
static CharArrayTypeHandler charArrayTypeHandler = new CharArrayTypeHandler();
static DateTypeHandler dateTypeHandler = new DateTypeHandler();
static DoubleTypeHandler doubleTypeHandler = new DoubleTypeHandler();
static FloatTypeHandler floatTypeHandler = new FloatTypeHandler();
static IntegerTypeHandler integerTypeHandler = new IntegerTypeHandler();
static LongTypeHandler longTypeHandler = new LongTypeHandler();
static ShortTypeHandler shortTypeHandler = new ShortTypeHandler();
static SqlDateTypeHandler sqlDateTypeHandler = new SqlDateTypeHandler();
static SqlXMLTypeHandler sqlXMLTypeHandler = new SqlXMLTypeHandler();
static StringTypeHandler stringTypeHandler = new StringTypeHandler();
static TimestampTypeHandler timestampTypeHandler = new TimestampTypeHandler();
static TimeTypeHandler timeTypeHandler = new TimeTypeHandler();
static CLobJavaSqlTypeHandler clobTypeHandler = new CLobJavaSqlTypeHandler();
static BlobJavaSqlTypeHandler blobTypeHandler = new BlobJavaSqlTypeHandler();
static LocalDateTimeTypeHandler localDateTimeHandler = new LocalDateTimeTypeHandler();
static LocalDateTypeHandler localDateHandler = new LocalDateTypeHandler();

如果考虑到某个类的所有子类都采用指定的Handler,那需要调用addAcceptType方法,指明,比如JsonNode类都使用JsonNodeTypeHandler

 JsonNodeTypeHandler typeHandler = new JsonNodeTypeHandler(); 
sqlManager.getDefaultBeanProcessors().addAcceptType(
                new BeanProcessor.InheritedAcceptType(
                        JsonNode.class,typeHandler));

另外一个扩展方法可能是setPreparedStatementPara,这是给PreparedStatement赋值,如果有需要特殊处理逻辑,也可以扩展此处。

还有一个很少用的扩展地方是getColName方法,他是根据ResultSet结果集,返回结果集的列名称,在Hive中,就重新实现了此方法,因为Hive会把SQL的子查询的前缀也传递到Java侧,比如

select * from (select id from user) t

在JDBC返回结果中,列名是t.id,而不是id,这样会导致无法映射,因此有些情况,需要排除这个前缀

Comment ( 0 )

Sign in for post a comment

Java
1
https://gitee.com/xiandafu/beetlsql.git
git@gitee.com:xiandafu/beetlsql.git
xiandafu
beetlsql
beetlsql
3.0

Search

152606 8668e384 1899542 133635 2cd7d36e 1899542