【Phoenix教程】SpringBoot-MybatisPlus-HBase-Phoenix 整合

安装

  1. 官网下载 解压。

  2. 将 phone目录下的 phoenix-server-hbase-2.4-5.1.2.jar 拷贝到 hbase\lib 目录下。

  3. 修改 hbase-site.xml。

<!-- 支持HBase命名空间映射 phoenix内的Schema就是hbase的namespace-->
<property>
    <name>phoenix.schema.isNamespaceMappingEnabled</name>
    <value>true</value>
</property>
<!-- 支持索引预写日期编辑 -->
<property>
    <name>hbase.regionserver.wal.codec</name>
    <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
4. 重启 Hbase
hbase/bin/stop-hbase.sh
hbase/bin/start-hbase.sh

常用命令

创建 schema

phoenix 内的schema相当于hbase内的namespace,配置文件内必须开启才有效。

--查看schema
!schema
--创建schema
create schema if not exists test1;
--删除schema
drop schema test1;
--使用schema
use schema

创建表

-- 查看所有表
show tables;
!tables
--创建表
--phoenix 内"表示引用表或字段, '表示字符串 如果创建表时不加" 列会变成大写
create table if not exists person(
	id varchar primary key,
    "info"."name" varchar, -- info为列簇,name为列名
    "info"."age" integer,
    "info"."money" decimal(10,2)
) 
split on ("3|", "5|","7|" ) 
version=3 -- version指定版本
compression="GZ" -- 指定压缩方式 
--创建表时指定 split 表示预分区 这里设置了4个region [0,3),[3,5),[5,7),[7,无穷)
-- 还可以指定盐的方式分区  create table sale_test (host varchar primary key) SALT_BUCKETS=6;

--删除表: 
drop table person;

修改表

-- 新增列
alter table person add salary double(10,2);
-- 删除列
alter table person drop column salary;

增删改查

-- 插入|修改数据
upsert into person values('a001','zs',23,99.99);
--分页查询 如下表示第二页(分页大小为5) 
select * from person limit 5 offset 5

视图

-- 如果hbase已经建立了表,可以通过视图的方式将hbase表和phoenix映射
--注意列簇和字段名称必须一样
create view "namespace"."hbase_table_view"(
 "id" varchar primary key,
 "info"."name" varchar   
);
-- 通过查询语句创建视图
create view my_view as select id,name from person;

索引

全局索引

适用于读多写少的场景,修改数据时会同时更新索引表的数据开销较大,索引数据分布在各个region内。

如果select 了其他非索引列 会使索引失效。

create index idx_age on 表名(列名1,列名2,...);

本地索引

使用与写操作频繁,读相对较少的业务。

查询数据时,phoenix会自动选择是否使用本地索引。

本地索引的数据存储在同一个服务器上,避免 写入期间的网络开销。

4.8.0之前 本地索引保存在单独的表中,4.8之后本地索引数据保持在一个影子列簇内。

本地索引查询时引用了非索引的字段也会引用索引。

create local index idx_name on student(列名1,...);

覆盖索引

将其他列的数据一起存放到索引数内

create index idx_include on student(c1,c2) include(c3,c4)

函数索引

对使用了函数的索引进行操作

create index idx_upper_name on student( upper("name") );
-- 查询时会使用索引
select * from studen where upper("name") = 'zs';

Mybatis Plus 整合 Phoneix

1 引入依赖

<!-- MybatisPlus依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<!-- SpringJDBC依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- hbaseClient依赖 -->
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.4.8</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- phoenix依赖 -->
<dependency>
    <groupId>org.apache.phoenix</groupId>
    <artifactId>phoenix-core</artifactId>
    <version>5.1.2</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2 将相关源码引入

phoenix高版本有些源码没有包含在jar包内需要手动引用, 下载phoneix源码包 phoenix-5.1.2-src.tar.gz

解压后将 org\apache\phoenix\compat\hbase包下的所有文件放入项目的java目录下。

3 编写配置类

application.yml

spring:
  datasource:
    phoenix:
      jdbc-url: jdbc:phoenix:hadoop204,hadoop205,hadoop206:2181
      driver-class-name: org.apache.phoenix.jdbc.PhoenixDriver

Phoenix配置类

@MapperScan(value = "org.gjw.mapper.phoenix",sqlSessionTemplateRef = "phoenixSqlSessionTemplate",sqlSessionFactoryRef = "phoenixSqlSessionFactory")
@Configuration
public class PhoenixConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.phoenix")
    public DataSource phoenixDataSource(){
        return new HikariDataSource();
    }

    @Bean
    public SqlSessionFactory phoenixSqlSessionFactory( @Qualifier("phoenixDataSource") @Autowired DataSource phoenixDataSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource( phoenixDataSource() );

        sqlSessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:/phoenixMapper/**/*.xml"));
		//这里注入自己写的MybatisPlus SQL拦截器 可以不写
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor( new PhoenixMPPlugin() );

        sqlSessionFactoryBean.setPlugins( interceptor );

        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        mybatisConfiguration.setMapUnderscoreToCamelCase(true);
        mybatisConfiguration.setLogImpl(StdOutImpl.class);

        sqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate phoenixSqlSessionTemplate( @Qualifier("phoenixSqlSessionFactory") @Autowired SqlSessionFactory phoenixSqlSessionFactory){
        return new SqlSessionTemplate( phoenixSqlSessionFactory );
    }

    @Bean
    public DataSourceTransactionManager phoenixDataSourceTransactionManager(@Qualifier("phoenixDataSource") @Autowired DataSource phoenixDataSource){
        return new DataSourceTransactionManager(phoenixDataSource);
    }

}

4 编写bean和mapper接口

entity

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("test1.student")
public class Student {
	//标注id
    @TableId(type = IdType.INPUT,value = "ID")
    private String id;
    //注意必须写规范,不加"代表全大写
    @TableField("\"INFO\".\"name\"")
    private String name;
    @TableField("INFO.age")
    private Integer age;
    @TableField("birthday")
    private Date birthday;
    @TableField("\"money\"")
    private Double money;
}

mapper

@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}

扩展: mybatisPlus 插件

phoenix 的语法和mySQL不太一样,需要我们在bean内通过@TableField申明列名,如果我们不想老是申明,可以通过插件的方式简化。

public class PhoenixMPPlugin extends JsqlParserSupport implements InnerInterceptor {

    /**
     * 查询时处理逻辑
     */
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        //通过 JSqlParser工具修改sql后执行
        mpBs.sql(parserSingle(mpBs.sql(), null));
    }

    /**
     * 增删改时 处理逻辑
     */
    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();

        //增删改调用 JSqlParser工具修改sql后执行
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), null));
        }

    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        System.out.println( "新增前操作" );
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        System.out.println( "删除前操作" );
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        System.out.println("修改操作");
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {

        //此处处理select逻辑 将字符串拼接上 双引号
        SelectBody selectBody = select.getSelectBody();

        if(selectBody instanceof PlainSelect) reformatPlainSelect((PlainSelect) selectBody);

    }

    /**
     * 处理查询字段
     */
    private List<SelectItem> disposeSelectColumn(List<SelectItem> selectItems){
        return selectItems.stream().map( this::resetSelectItem ).collect(Collectors.toList());
    }

    private SelectItem resetSelectItem( SelectItem selectItem ){
        //如果不符合直接返回
        if( !(selectItem instanceof SelectExpressionItem) ) return selectItem;

        SelectExpressionItem item = (SelectExpressionItem)selectItem;

        //如果是列
        if( item.getExpression() instanceof Column ){
            Column columnExp = (Column)item.getExpression();
            return new SelectExpressionItem( reFormatSelectColumn( columnExp,item.getAlias() ) );
        }

        //如果是函数
        if( item.getExpression() instanceof Function){
            Function function = (Function) item.getExpression();
            return new SelectExpressionItem( reFormatFunction( function ) );
        }

         return item;
    }

    /**
     * 重新格式化 查询语句
     * @param plainSelect 查询语句
     * @return 格式化的查询语句
     */
    public void reformatPlainSelect(PlainSelect plainSelect){

        //处理要查询的字段
        List<SelectItem> selectItems = plainSelect.getSelectItems();

        //处理查询条件
        plainSelect.setSelectItems( disposeSelectColumn( selectItems ) );

        //处理 where 条件
        plainSelect.setWhere( disposeSelectWhere( plainSelect.getWhere() )  );

    }

    /**
     * 重新格式化列
     * @param columnExp 列
     * @param alias 列别名
     * @return 格式化的列
     */
    private Column reFormatSelectColumn( Column columnExp,Alias alias ){
        if( columnExp == null ) return columnExp;

        //表名和列簇名会在一起
        String tableAndCFName= columnExp.getTable() == null ? "" : columnExp.getTable().toString();
        //字段名
        String columnName= columnExp.getColumnName();

        //根据 `.` 分隔方便处理表名和列簇名
        String[] tableAndCFInfo = tableAndCFName.split("\\.");

        // 可能会出现很多情况 列名  列簇.列名  表名.列簇.列名 表名.列名
        String tableName = tableAndCFInfo[0];
        String cf        = tableAndCFInfo[tableAndCFInfo.length - 1];

        //如果表名和字段名相等,只有3种情况: 列名  表名.列名  列簇.列名
        if( StrUtil.equals(tableName,cf)  && StrUtil.isNotBlank(tableName) ){
            //判断前缀是表名还是列名  要求列簇必须全大写 表名不能全大写
            //如果全大写这是列簇名
            if( StrUtil.equals(cf.toUpperCase(),cf) ) {
                tableName = "";
            }else cf = ""; //否则是表名
        }

        StringBuilder finalName = new StringBuilder();

        //如果表名不为空 拼接表名
        if( StrUtil.isNotBlank( tableName ) )   finalName.append( tableName ).append( "." );
        //如果列簇名不为空 拼接列簇名
        if( StrUtil.isNotBlank( cf ) ) finalName.append( appendPrefixAndSuffix(cf) ).append(".");
        //拼接字段名
        finalName.append( appendPrefixAndSuffix(columnName) );
        //拼接别名: as xxx
        if( alias !=null ) finalName.append(" ").append( alias.getName() );

        //重新格式化列名 封装返回
        return new Column( finalName.toString() );
    }

    /**
     * 重新格式化查询函数
     * @param function 函数
     * @return 格式化的函数
     */
    private Function reFormatFunction( Function function ){

        List<Expression> expressions = function.getParameters().getExpressions();

        //对于是列的参数进行格式化
        expressions = expressions.stream().map(exp -> {
            if (exp instanceof Column) return reFormatSelectColumn((Column) exp, null);
            return exp;
        }).collect(Collectors.toList());

        //重新设置回去
        function.getParameters().setExpressions(expressions);

        return function;
    }

    /**
     * 重新格式化子查询
     * @param subSelect 子查询
     * @return 格式化的函数
     */
    private SubSelect reFormatSubSelect( SubSelect subSelect ){

        if( subSelect.getSelectBody() instanceof PlainSelect ){
            reformatPlainSelect( (PlainSelect)subSelect.getSelectBody() );
        }

        return subSelect;
    }

    public Expression disposeSelectWhere(Expression expression){

        if( !(expression instanceof BinaryExpression) ) return expression;

        BinaryExpression binaryExpression =(BinaryExpression)expression;

        //如果左边还是多条件的
        if( binaryExpression.getLeftExpression() instanceof BinaryExpression){
            disposeSelectWhere( binaryExpression.getLeftExpression() );
        }

        //如果右边还是多条件的
        if( binaryExpression.getRightExpression() instanceof BinaryExpression){
            disposeSelectWhere( binaryExpression.getRightExpression() );
        }

        //如果左边表达式是列信息 格式化
        if(  binaryExpression.getLeftExpression() instanceof Column ){
            Column newColumn = reFormatSelectColumn((Column) binaryExpression.getLeftExpression(), null);
            binaryExpression.setLeftExpression( newColumn );
        }

        //如果左边表达式是 子查询 processPlainSelect
        if(binaryExpression.getLeftExpression() instanceof SubSelect){
            SubSelect subSelect = (SubSelect)binaryExpression.getLeftExpression();
            if( subSelect.getSelectBody() instanceof PlainSelect ){
                reformatPlainSelect( (PlainSelect)subSelect.getSelectBody() );
            }
        }

        //如果右边是列信息 格式化
        if(  binaryExpression.getRightExpression() instanceof Column ){
            Column newColumn = reFormatSelectColumn((Column) binaryExpression.getLeftExpression(), null);
            binaryExpression.setRightExpression( newColumn );
        }

        //如果右边表达式是 子查询 processPlainSelect
        if( binaryExpression.getRightExpression() instanceof SubSelect){
            SubSelect subSelect = (SubSelect)binaryExpression.getRightExpression();
            reFormatSubSelect( subSelect );
        }

        return binaryExpression;
    }

    private String appendPrefixAndSuffix(String str){

        final String PREFIX = "\"";
        final String SUFFIX = "\"";

        //如果已经有前缀了直接返回
        if( str.contains(PREFIX) ) return str;

        //拼接前缀和后缀
        return new StringBuilder().append(PREFIX).append(str).append(SUFFIX).toString();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值