跨越数据孤岛!SpringBoot使用JDBC调用Calcite联邦查询实战

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

前面专门介绍了Apache Calcite动态数据源基于原生connection的使用方法。使用起来有点不太方便,能不能像JDBC一样查询数据呢?

正好前面分享了Spring 6.xJdbcClient用法,我们直接一步到位,使用JdbcClient查询。

02 最佳实践配置

Apache Calcite是一个标准SQL解析器、验证器和优化器框架,支持多种后端数据源。这里就不多介绍了,直接看用法。

2.1 依赖引入

xml 复制代码
 <!-- Apache Calcite -->
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-core</artifactId>
    <version>1.38.0</version>
</dependency>
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-csv</artifactId>
    <version>1.38.0</version>
</dependency>

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!--- 根据需要引入  -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2.2 配置数据源

要使用JDBC的客户端,就需要配置数据源。

java 复制代码
@Bean
public DataSource calciteDataSource() throws Exception {
    Properties prop = new Properties();
    prop.put("lex", "MYSQL");
    prop.put("model", "src/main/resources/calcite-model.json");
    Connection connection = DriverManager.getConnection("jdbc:calcite:", prop);

    SingleConnectionDataSource calciteDataSource = new SingleConnectionDataSource(connection, true);
    return calciteDataSource;
}

因为DataSource是一个接口,需要我们自行构建或者使用其实现类,并且可以设置connection的类。DataSourceBuilder的构建方式行不通,我们只能使用其子类。

SingleConnectionDataSource类刚好可以满足我们的条件。当然也可以自行去兼容,我们这里怎么简单怎么来。

2.3 配置文件

配置文件需要配置一个jdbcd的方言,否则调用会报错。

properties 复制代码
spring.data.jdbc.dialect=mysql

2.4 模型文件

模型文件之前已经介绍了,想要了解可以看看之前的文章:《多数据源:CSV、内存对象可以通过SQL查询,甚至联查,你敢信!》

json 复制代码
{
  "version": "1.0",
  "defaultSchema": "MEM",
  "schemas": [
    {
      "name": "MEM",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory",
      "operand": {
        "class": "com.simonking.boot.calcite.schema.UserSchema"
      }
    },
    {
      "name": "CSV",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory",
      "operand": {
        "directory": "csv",
        "flavor": "scannable"
      }
    },
    {
      "name": "MYSQL_DB",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.jdbc.JdbcSchema$Factory",
      "operand": {
        "jdbcUrl": "jdbc:mysql://127.0.0.1:3306/test",
        "jdbcUser": "root",
        "jdbcPassword": "root"
      }
    }
  ]
}

03 实战

我们分别从内存、CSVmysql这三个数据源实战,应该可以覆盖常用的场景了。

3.1 内存实战

模拟内存数据

java 复制代码
public class UserSchema {

    public final User[] users;

    public UserSchema() {
        // 模拟数据
        this.users = new User[]{
                new User(1, "zhangsan", 18),
                new User(2, "admin", 20),
                new User(3, "lisi", 22)
        };
    }

    /**
     * @Description: 字段类型
     **/
    @AllArgsConstructor
    public class User {

        /** 用户id */
        public final Integer id;
        /** 用户名 */
        public final String name;
        /** 年龄 */
        public final Integer age;
    }
}

案例

java 复制代码
@Autowired
private JdbcClient jdbcClient;

@Test
void contextLoads01() {
    UserVO userVO = jdbcClient
        .sql("select * from MEM.users where id = ?")
        .param(1)
        .query(UserVO.class)
        .single();
    System.out.println(JSON.toJSONString(userVO));
}

结果

3.2 CSV实战

模拟数据

案例

java 复制代码
@Autowired
private JdbcClient jdbcClient;

@Test
void contextLoads01() {
    OrderVO orderVO = jdbcClient
        .sql("select * from CSV.orders where user_id = :userId")
        .param("userId", 2)
        .query(OrderVO.class)
        .single();
    System.out.println(JSON.toJSONString(orderVO));
}

结果

坑点

CSV数据源查询的时候有个小问题,上面的接受的实体使用的参数类型都是String。正常来讲order_iduser_id应该为IntegerLong类型,amount应该为BigDecimal类型。

开始我就是这么定义的,结果发现执行报错:

从报错中可以看出calcite是通过StringAccessor来解析的,源码中包含了IntegerAccessor但是没有调用到。

解决方案

没有在官网中找到明确的文档,但是从官方给的example找到了答案:

直接在字段后面直接指定类型并用冒号隔开。这里使用的是基础数据类型。BigDecimal可以使用double处理。

我们看看结果吧。

3.3 Mysql实战

模拟数据

案例

java 复制代码
@Autowired
private JdbcClient jdbcClient;

@Test
void contextLoads01() {
    UserRoleVO userRoleVO = jdbcClient
        .sql("select * from MYSQL_DB.user_roles where user_id = :userId")
        .param("userId", 2)
        .query(UserRoleVO.class)
        .single();
    System.out.println(JSON.toJSONString(userRoleVO));
}

结果

3.4 联查

为了方便,我们就不定义实体了,直接使用Map接收。

java 复制代码
List<Map<String, Object>> list = jdbcClient.sql("""
                 SELECT * FROM CSV.orders o
                 INNER JOIN MEM.users u ON o.user_id = u.id
                 INNER JOIN MYSQL_DB.user_roles r 
                 ON u.id = r.user_id
                 WHERE u.id = :userId
                """)
        .param("userId", 2)
        .query()
        .listOfRows();

System.out.println(JSON.toJSONString(list));

结果

这里的查询不受定义的字段类型的影响。

04 小结

原本想着之前使用connection方式已经完成了案例,改造成JdbcClient应该很顺利,但是没有想到还需遇到的意想不到的问题,好在都解决了。后面项目中使用就可以直接使用JdbcClient愉快的干活了。

相关推荐
Java编程爱好者1 小时前
金融级数据库架构实战:MySQL Router + MGR 深度指南
后端
好家伙VCC1 小时前
# 发散创新:基于Python的TTS语音合成实战与优化策略 在人工智能加速落地的今天,**文本转
java·开发语言·人工智能·python
Java编程爱好者1 小时前
Java后端开发面试题总结(全网最全、最细、附答案)
后端
等D春C夏X2 小时前
最终版C++11/14/17学习大纲(精准核对42条条款)
java·开发语言
Cg136269159742 小时前
HTML标题标签
java
Java水解2 小时前
Spring应用事件机制实践
后端·spring
feathered-feathered2 小时前
测试实战【用例设计】自己写的项目+功能测试(1)
java·服务器·后端·功能测试·jmeter·单元测试·压力测试
Sincerelyplz2 小时前
【WebSocket】消息丢失的补偿/补发机制
后端·websocket