这篇文章主要介绍“SpringBoot如何集成MaxCompute”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringBoot如何集成MaxCompute”文章能帮助大家解决问题。
1、SDK方式集成
1.1、依赖引入odps-sdk-core
<properties> <java.version>1.8</java.version> <!--maxCompute sdk 版本号--> <max-compute-sdk.version>0.40.8-public</max-compute-sdk.version> </properties> <dependencies> <!--max compute sdk--> <dependency> <groupId>com.aliyun.odps</groupId> <artifactId>odps-sdk-core</artifactId> <version>${max-compute-sdk.version}</version> </dependency> </dependencies>
1.2、编写连接工具类
编写MaxComputeSdkUtil以SDK方式连接MaxCompute
1.2.1、重要类和方法说明
1、连接参数类:
@Data public class MaxComputeSdkConnParam { /**阿里云accessId 相当于用户名 */ private String aliyunAccessId; /**阿里云accessKey 相当于密码 */ private String aliyunAccessKey; /**阿里云maxCompute服务接口地址 默认是http://service.odps.aliyun.com/api*/ private String maxComputeEndpoint; /**项目名称*/ private String projectName; }
2、查询表元数据信息实体
主要是字段:tableName, comment。还可以自己添加其他字段
@Data @NoArgsConstructor @AllArgsConstructor public class TableMetaInfo { /**表名称*/ private String tableName; /**表注释*/ private String comment; }
3、公共方法(初始化)
/**默认的odps接口地址 在Odps中也可以看到该变量*/ private static final String defaultEndpoint = "http://service.odps.aliyun.com/api"; /**开启全表扫描的配置*/ private static final String FULL_SCAN_CONFIG = "odps.sql.allow.fullscan"; /**分页查询sql模板*/ private static final String PAGE_SELECT_TEMPLATE_SQL = "select z.* from (%s) z limit %s, %s;"; /**分页查询统计数量模板SQL*/ private static final String PAGE_COUNT_TEMPLATE_SQL = "select count(1) from (%s) z;"; /**sdk的odps客户端*/ private final Odps odps; /**odps连接参数*/ private final MaxComputeSdkConnParam connParam; public MaxComputeSdkUtil(MaxComputeSdkConnParam param){ this.connParam = param; // 构建odps客户端 this.odps = buildOdps(); } /** * 构建odps客户端 用于执行sql等操作 * @return odps客户端 */ private Odps buildOdps() { // 阿里云账号密码 AccessId 和 AccessKey final String aliyunAccessId = connParam.getAliyunAccessId(); final String aliyunAccessKey = connParam.getAliyunAccessKey(); // 创建阿里云账户 final AliyunAccount aliyunAccount = new AliyunAccount(aliyunAccessId, aliyunAccessKey); // 使用阿里云账户创建odps客户端 final Odps odps = new Odps(aliyunAccount); // 传入了的话就是用传入的 没有传入使用默认的 final String endpoint = connParam.getMaxComputeEndpoint(); try { odps.setEndpoint(ObjectUtils.isEmpty(endpoint) ? defaultEndpoint : endpoint); } catch (Exception e) { // 端点格式不正确 throw new BizException(ResultCode.MAX_COMPUTE_ENDPOINT_ERR); } // 设置项目 odps.setDefaultProject(connParam.getProjectName()); return odps; }
4、查询表信息
/** * 获取表信息 */ public List<TableMetaInfo> getTableInfos(){ final Tables tables = odps.tables(); List<TableMetaInfo> resultTables = new ArrayList<>(); try { for (Table table : tables) { // tableName final String name = table.getName(); // 描述 final String comment = table.getComment(); final TableMetaInfo info = new TableMetaInfo(name, comment); resultTables.add(info); } } catch (Exception e) { e.printStackTrace(); final String errMsg = ObjectUtils.isEmpty(e.getMessage()) ? "" : e.getMessage(); if (errMsg.contains("ODPS-0410051:Invalid credentials")){ throw new BizException(ResultCode.MAX_COMPUTE_UNAME_ERR); } if (errMsg.contains("ODPS-0410042:Invalid signature value")){ throw new BizException(ResultCode.MAX_COMPUTE_PWD_ERR); } if (errMsg.contains("ODPS-0420095: Access Denied")){ throw new BizException(ResultCode.MAX_COMPUTE_PROJECT_ERR); } } return resultTables; }
5、执行SQL封装
/** * 执行sql查询 * @param querySql 查询sql * @param fullScan 是否开启全表扫描 如果查询多个分区数据,需要开启全表扫描 * @return List<Map<String, Object>> */ public List<Map<String, Object>> queryData(String querySql, boolean fullScan){ try { // 配置全表扫描吗 configFullScan(fullScan); // 使用任务执行SQL final Instance instance = SQLTask.run(odps, querySql); // 等待执行成功 instance.waitForSuccess(); // 封装返回结果 List<Record> records = SQLTask.getResult(instance); // 结果转换为Map return buildMapByRecords(records); } catch (OdpsException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_SQL_EXEC_ERR); } } /** * 开启和移除全表扫描配置 * @param fullScan 是否全表扫描 */ private void configFullScan(boolean fullScan) { if (fullScan){ // 开启全表扫描配置 Map<String, String> config = new HashMap<>(); log.info("===>>开启全表扫描, 查询多个分区数据"); config.put(FULL_SCAN_CONFIG, "true"); odps.setGlobalSettings(config); }else { // 移除全表扫描配置 odps.getGlobalSettings().remove(FULL_SCAN_CONFIG); } } /** * 将List<Record>准换为List<Map></> * @param records sql查询结果 * @return 返回结果 */ private List<Map<String, Object>> buildMapByRecords(List<Record> records) { List<Map<String, Object>> listMap = new ArrayList<>(); for (Record record : records) { Column[] columns = record.getColumns(); Map<String, Object> map = new LinkedHashMap<>(); for (Column column : columns) { String name = column.getName(); Object value = record.get(name); // maxCompute里面的空返回的是使用 if ("\N".equalsIgnoreCase(String.valueOf(value))) { map.put(name, ""); } else { map.put(name, value); } } listMap.add(map); } return listMap; }
6、分页查询分装
/** * 执行sql查询【分页查询】 * @param querySql 查询sql * @param page 页码 从1开始 第n页传n * @param size 每页记录数 * @param fullScan 是否开启全表扫描 如果查询多个分区数据,需要开启全表扫描 * @return List<Map<String, Object>> */ public List<Map<String, Object>> queryData(String querySql, Integer page, Integer size, boolean fullScan){ // 重写SQl,添加limit offset, limit // 1、替换分号 querySql = querySql.replaceAll(";", ""); // 2、格式化SQL Integer offset = (page - 1 ) * size; // 得到执行sql final String execSql = String.format(PAGE_SELECT_TEMPLATE_SQL, querySql, offset, size); log.info("=======>>>执行分页sql为:{}", execSql); // 调用执行SQL数据 return queryData(execSql, fullScan); } /** * 执行分页查询 * @param querySql 分页查询sql * @param page 页码 从1开始 第n页传n * @param size 每页记录数 * @return 分页查询结果 */ public PageResult<Map<String, Object>> pageQueryMap(String querySql, Integer page, Integer size){ // 1、替换分号 querySql = querySql.replaceAll(";", ""); String countSql = String.format(PAGE_COUNT_TEMPLATE_SQL, querySql); log.info("=======>>>执行分页统计总数sql为:{}", countSql); // 查询总数 final List<Map<String, Object>> countMap = queryData(countSql, false); if (CollectionUtils.isEmpty(countMap)){ return new PageResult<>(0L, new ArrayList<>()); } long count = 0L; for (Object value : countMap.get(0).values()) { count = Long.parseLong(String.valueOf(value)); } if (count == 0){ return new PageResult<>(0L, new ArrayList<>()); } // 执行分页查询 开启全表扫描 final List<Map<String, Object>> resultList = queryData(querySql, page, size, true); return new PageResult<>(count, resultList); } /** * 执行分页查询 * @param querySql 分页查询sql * @param page 页码 从1开始 第n页传n * @param size 每页记录数 * @return 分页查询结果 */ public <T>PageResult<T> pageQuery(String querySql, Integer page, Integer size, Class<T> clazz){ final PageResult<Map<String, Object>> result = pageQueryMap(querySql, page, size); List<T> rows = new ArrayList<>(); for (Map<String, Object> row : result.getRows()) { final T t = JSONObject.parseObject(JSONObject.toJSONString(row), clazz); rows.add(t); } return new PageResult<>(result.getTotal(), rows); }
1.2.2 工具类测试
使用测试数据测试工具类
public static void main(String[] args) { // 构建连接参数 final MaxComputeSdkConnParam connParam = new MaxComputeSdkConnParam(); connParam.setAliyunAccessId("您的阿里云账号accessId"); connParam.setAliyunAccessKey("您的阿里云账号accessKey"); connParam.setProjectName("项目名"); // 实例化工具类 final MaxComputeSdkUtil sdkUtil = new MaxComputeSdkUtil(connParam); // 查询所有表 final List<TableMetaInfo> tableInfos = sdkUtil.getTableInfos(); for (TableMetaInfo tableInfo : tableInfos) { System.out.println(tableInfo.getTableName()); } // 分页查询数据 final PageResult<Map<String, Object>> page = sdkUtil.pageQueryMap("select * from ods_cust;", 2, 10); System.out.println(page.getTotal()); for (Map<String, Object> map : page.getRows()) { System.out.println(JSONObject.toJSONString(map)); } }
1.2.3 为什么要开启全表扫描
maxCompute存在使用限制如下:
当使用select语句时,屏显最多只能显示10000行结果。当select语句作为子句时则无此限制,select子句会将全部结果返回给上层查询。
select语句查询分区表时默认禁止全表扫描。
自2018年1月10日20:00:00后,在新创建的项目上执行SQL语句时,默认情况下,针对该项目里的分区表不允许执行全表扫描操作。在查询分区表数据时必须指定分区,由此减少SQL的不必要I/O,从而减少计算资源的浪费以及按量计费模式下不必要的计算费用。
如果您需要对分区表进行全表扫描,可以在全表扫描的SQL语句前加上命令set odps.sql.allow.fullscan=true;,并和SQL语句一起提交执行。假设sale_detail表为分区表,需要同时执行如下语句进行全表查询:
2、JDBC方式集成
使用odps-jdbc集成, 官方文档地址MaxCompute Java JDBC介绍
<properties> <java.version>1.8</java.version> <!--maxCompute-jdbc-版本号--> <max-compute-jdbc.version>3.0.1</max-compute-jdbc.version> </properties> <dependencies> <!--max compute jdbc--> <dependency> <groupId>com.aliyun.odps</groupId> <artifactId>odps-jdbc</artifactId> <version>${max-compute-jdbc.version}</version> <classifier>jar-with-dependencies</classifier> </dependency> </dependencies>
2.2、编写连接工具类
编写MaxComputeSdkUtil以JDBC方式连接MaxCompute
2.2.1、重要类和方法说明
1、连接参数类:
@Data public class MaxComputeJdbcConnParam { /**阿里云accessId 相当于用户名 */ private String aliyunAccessId; /**阿里云accessKey 相当于密码 */ private String aliyunAccessKey; /** maxcompute_endpoint */ private String endpoint; /**项目名称*/ private String projectName; }
2、公共方法(初始化)
/**JDBC 驱动名称*/ private static final String DRIVER_NAME = "com.aliyun.odps.jdbc.OdpsDriver"; private static final String SELECT_ALL_TABLE_SQL = "select table_name, table_comment from Information_Schema.TABLES"; private static final String SELECT_FIELD_BY_TABLE_SQL = "select column_name, column_comment from Information_Schema.COLUMNS where table_name = '%s'"; /**分页查询sql模板*/ private static final String PAGE_SELECT_TEMPLATE_SQL = "select z.* from (%s) z limit %s, %s;"; /**分页查询统计数量模板SQL*/ private static final String PAGE_COUNT_TEMPLATE_SQL = "select count(1) from (%s) z;"; /**连接*/ private final Connection conn; /** * 连接参数 */ private final MaxComputeJdbcConnParam connParam; public MaxComputeJdbcUtil(MaxComputeJdbcConnParam connParam) { this.connParam = connParam; this.conn = buildConn(); } /** * 创建连接 * @return 数据库连接 */ private Connection buildConn() { try { Class.forName(DRIVER_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_JDBC_DRIVE_LOAD_ERR); } try { // JDBCURL连接模板 String jdbcUrlTemplate = "jdbc:odps:%s?project=%s&useProjectTimeZone=true"; // 使用驱动管理器连接获取连接 return DriverManager.getConnection( String.format(jdbcUrlTemplate, connParam.getEndpoint(), connParam.getProjectName()), connParam.getAliyunAccessId(), connParam.getAliyunAccessKey()); } catch (SQLException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_JDBC_DRIVE_LOAD_ERR); } }
3、查询表信息
/** * 获取表信息 * @return 表信息列表 */ public List<TableMetaInfo> getTableInfos(){ List<TableMetaInfo> resultList = new ArrayList<>(); Statement statement = null; ResultSet resultSet = null; try { // 创建statement 使用SQL直接查询 statement = conn.createStatement(); // 执行查询语句 resultSet = statement.executeQuery(SELECT_ALL_TABLE_SQL); while (resultSet.next()){ final String tableName = resultSet.getString("table_name"); final String tableComment = resultSet.getString("table_comment"); final TableMetaInfo info = new TableMetaInfo(tableName, tableComment); resultList.add(info); } return resultList; } catch (SQLException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_SQL_EXEC_ERR); } finally { // 关闭resultSet closeResultSet(resultSet); // 关闭statement closeStatement(statement); } }
4、执行SQL封装
/** * 执行sql查询 * @param querySql 查询sql * @return List<Map<String, Object>> */ public List<Map<String, Object>> queryData(String querySql){ List<Map<String, Object>> resultList = new ArrayList<>(); Statement statement = null; ResultSet resultSet = null; try { // 创建statement statement = conn.createStatement(); // 执行查询语句 resultSet = statement.executeQuery(querySql); // 构建结果返回 buildMapByRs(resultList, resultSet); return resultList; } catch (SQLException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_SQL_EXEC_ERR); } finally { // 关闭resultSet closeResultSet(resultSet); // 关闭statement closeStatement(statement); } } /** * 将ResultSet转换为List<Map<String, Object>> * @param resultList 转换的集合 * @param resultSet ResultSet * @throws SQLException e */ private void buildMapByRs(List<Map<String, Object>> resultList, ResultSet resultSet) throws SQLException { // 获取元数据 ResultSetMetaData metaData = resultSet.getMetaData(); while (resultSet.next()) { // 获取列数 int columnCount = metaData.getColumnCount(); Map<String, Object> map = new HashMap<>(); for (int i = 0; i < columnCount; i++) { String columnName = metaData.getColumnName(i + 1); Object object = resultSet.getObject(columnName); // maxCompute里面的空返回的是使用 if ("\N".equalsIgnoreCase(String.valueOf(object))) { map.put(columnName, ""); } else { map.put(columnName, object); } } resultList.add(map); } } private void closeStatement(Statement statement){ if (statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } private void closeResultSet(ResultSet resultSet){ if (resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } }
5、分页查询分装
/** * 执行sql查询 * @param querySql 查询sql * @return List<Map<String, Object>> */ public List<Map<String, Object>> queryData(String querySql, Integer page, Integer size){ List<Map<String, Object>> resultList = new ArrayList<>(); Statement statement = null; ResultSet resultSet = null; try { // 1、替换分号 querySql = querySql.replaceAll(";", ""); // 创建statement statement = conn.createStatement(); // 2、格式化SQL int offset = (page - 1 ) * size; final String execSql = String.format(PAGE_SELECT_TEMPLATE_SQL, querySql, offset, size); log.info("=======>>>执行分页sql为:{}", execSql); // 执行查询语句 resultSet = statement.executeQuery(execSql); // 构建结果返回 buildMapByRs(resultList, resultSet); return resultList; } catch (SQLException e) { e.printStackTrace(); throw new BizException(ResultCode.MAX_COMPUTE_SQL_EXEC_ERR); } finally { // 关闭resultSet closeResultSet(resultSet); // 关闭statement closeStatement(statement); } } /** * 执行分页查询 * @param querySql 分页查询sql * @param page 页码 从1开始 第n页传n * @param size 每页记录数 * @return 分页查询结果 */ public PageResult<Map<String, Object>> pageQueryMap(String querySql, Integer page, Integer size){ // 1、替换分号 querySql = querySql.replaceAll(";", ""); String countSql = String.format(PAGE_COUNT_TEMPLATE_SQL, querySql); log.info("=======>>>执行分页统计总数sql为:{}", countSql); // 查询总数 final List<Map<String, Object>> countMap = queryData(countSql); if (CollectionUtils.isEmpty(countMap)){ return new PageResult<>(0L, new ArrayList<>()); } long count = 0L; for (Object value : countMap.get(0).values()) { count = Long.parseLong(String.valueOf(value)); } if (count == 0){ return new PageResult<>(0L, new ArrayList<>()); } // 执行分页查询 开启全表扫描 final List<Map<String, Object>> resultList = queryData(querySql, page, size); return new PageResult<>(count, resultList); } /** * 执行分页查询 * @param querySql 分页查询sql * @param page 页码 从1开始 第n页传n * @param size 每页记录数 * @return 分页查询结果 */ public <T>PageResult<T> pageQuery(String querySql, Integer page, Integer size, Class<T> clazz){ final PageResult<Map<String, Object>> result = pageQueryMap(querySql, page, size); List<T> rows = new ArrayList<>(); for (Map<String, Object> row : result.getRows()) { final T t = JSONObject.parseObject(JSONObject.toJSONString(row), clazz); rows.add(t); } return new PageResult<>(result.getTotal(), rows); }
2.2.2 工具类测试
使用测试数据测试工具类
public static void main(String[] args) { final MaxComputeJdbcConnParam connParam = new MaxComputeJdbcConnParam(); connParam.setAliyunAccessId("您的阿里云账号accessId"); connParam.setAliyunAccessKey("您的阿里云账号accessKey"); connParam.setProjectName("项目名"); connParam.setEndpoint("http://service.cn-hangzhou.maxcompute.aliyun.com/api"); final MaxComputeJdbcUtil jdbcUtil = new MaxComputeJdbcUtil(connParam); // 获取表信息 final List<TableMetaInfo> tableInfos = jdbcUtil.getTableInfos(); for (TableMetaInfo tableInfo : tableInfos) { System.out.println(tableInfo); } // 获取字段信息 final String tableName = tableInfos.get(new Random().nextInt(tableInfos.size())).getTableName(); final List<TableColumnMetaInfo> fields = jdbcUtil.getFieldByTableName(tableName); for (TableColumnMetaInfo field : fields) { System.out.println(field.getFieldName() + "-" + field.getComment()); } // 执行查询 final List<Map<String, Object>> list = jdbcUtil.queryData("select * from ods_cust;"); for (Map<String, Object> map : list) { System.out.println(JSONObject.toJSONString(map)); } // 执行分页查询 final List<Map<String, Object>> list2 = jdbcUtil.queryData("select * from ods_cust;", 2, 10); for (Map<String, Object> map : list2) { System.out.println(JSONObject.toJSONString(map)); } // 执行分页查询 并返回count final PageResult<Map<String, Object>> list3 = jdbcUtil.pageQueryMap("select * from ods_cust;", 2, 10); System.out.println(list3.getTotal()); for (Map<String, Object> map : list3.getRows()) { System.out.println(JSONObject.toJSONString(map)); } jdbcUtil.close(); }