安装
<!-- 支持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();
}
}