当前位置:Java -> 高效地将JDBC查询结果转换为JSON

高效地将JDBC查询结果转换为JSON

许多企业数据存储在关系型数据库中,并通过SQL查询进行访问。许多Web服务实际上只是围绕这样的查询构建的基于HTTP的封装。

例如,下面的服务方法(使用开源Kilo框架构建)从employees表中检索所有行,并将结果作为JSON返回给调用者:

@RequestMethod("GET")
public List<Employee> getEmployees() throws SQLException {
    var queryBuilder = QueryBuilder.select(
        EMPLOYEE_NUMBER.as("employeeNumber"),
        FIRST_NAME.as("firstName"),
        LAST_NAME.as("lastName"),
        GENDER,
        BIRTH_DATE.as("birthDate"),
        HIRE_DATE.as("hireDate")
    ).from(Employee.Schema.class);

    try (var connection = getConnection();
        var statement = queryBuilder.prepare(connection);
        var results = new ResultSetAdapter(queryBuilder.executeQuery(statement))) {
        return results.stream().map(result -> BeanAdapter.coerce(result, Employee.class)).toList();
    }
}


不幸的是,虽然这个解决方案很简单,但并不高效。整个数据集必须被遍历两次 - 一次用于从数据库中读取,另一次用于将其写入输出流。此外,调用者在所有行都被读取之前将不会开始接收响应,并且在所有结果被写入之前,没有任何元素会变得可回收。

对于小的结果集来说,这种方法带来的延迟和内存影响可能是可以接受的。然而,对于更大的数据量,下面的另一种方式可能更可取。查询在后台线程上执行,并且转换后的结果通过管道流回调用者:

@RequestMethod("GET")
@ResourcePath("stream")
public List<Employee> getEmployeesStream() {
    var queryBuilder = QueryBuilder.select(
        EMPLOYEE_NUMBER.as("employeeNumber"),
        FIRST_NAME.as("firstName"),
        LAST_NAME.as("lastName"),
        GENDER,
        BIRTH_DATE.as("birthDate"),
        HIRE_DATE.as("hireDate")
    ).from(Employee.Schema.class);

    var pipe = new Pipe<Employee>(4096, 15000);

    executorService.submit(() -> {
        try (var connection = getConnection();
            var statement = queryBuilder.prepare(connection);
            var results = new ResultSetAdapter(queryBuilder.executeQuery(statement))) {
            pipe.accept(results.stream().map(result -> BeanAdapter.coerce(result, Employee.class)));
        } catch (SQLException exception) {
            throw new RuntimeException(exception);
        }
    });

    return pipe;
}


从Kilo 文档中:

Pipe类提供了一个机制,通过它,生产者线程可以提交一个元素序列以供消费者线程检索。它实现了List接口,并产生一个迭代器,根据需要阻塞地返回值。

这个例子中的管道配置为4K元素的容量和15秒的超时。限制容量确保生产者不会在消费者未检索所有数据的情况下做更多的工作。同样,指定超时确保消费者在生产者停止提交数据时不会无限期地等待。

这种方法的实现略比第一种方法更冗长。但是,由于不需要中间缓冲,结果更快地对调用者可用,且减少了CPU和内存负载

QueryBuilder和ResultSet适配器类来处理结果,但是同样的方法也可以用于ORM框架,比如Hibernate:
@RequestMethod("GET")
@ResourcePath("hibernate")
public List<Employee> getEmployeesHibernate() throws SQLException {
    var configuration = new Configuration();

    configuration.addAnnotatedClass(HibernateEmployee.class);

    try (var connection = getConnection();
        var sessionFactory = configuration.configure().buildSessionFactory();
        var session = sessionFactory.withOptions().connection(connection).openSession()) {
        var criteriaQuery = session.getCriteriaBuilder().createQuery(Employee.class);
        var query = session.createQuery(criteriaQuery.select(criteriaQuery.from(HibernateEmployee.class)));

        return query.list();
    }
}


@RequestMethod("GET")
@ResourcePath("hibernate-stream")
public List<Employee> getEmployeesHibernateStream() {
    var pipe = new Pipe<Employee>(4096, 15000);

    executorService.submit(() -> {
        var configuration = new Configuration();

        configuration.addAnnotatedClass(HibernateEmployee.class);

        try (var connection = getConnection();
            var sessionFactory = configuration.configure().buildSessionFactory();
            var session = sessionFactory.withOptions().connection(connection).openSession()) {
            var criteriaQuery = session.getCriteriaBuilder().createQuery(Employee.class);
            var query = session.createQuery(criteriaQuery.select(criteriaQuery.from(HibernateEmployee.class)));

            try (var stream = query.stream()) {
                pipe.accept(stream);
            }
        } catch (SQLException exception) {
            throw new RuntimeException(exception);
        }
    });

    return pipe;
}


此处找到。

推荐阅读: 百度面经(24)

本文链接: 高效地将JDBC查询结果转换为JSON