杰瑞科技汇

Java ResultSet如何高效转换对象?

下面我将从最基础的手动转换使用第三方库的高级自动化转换,全面地为你讲解 ResultSet 的转换方法,并提供详细的代码示例。

Java ResultSet如何高效转换对象?-图1
(图片来源网络,侵删)

手动转换

这是最基础也是最核心的方法,理解它有助于你掌握转换的原理,并且在处理复杂逻辑或特殊需求时非常灵活。

将单行 ResultSet 转换为一个 Java 对象

假设我们有一个数据库表 users

id name email created_at
1 Alice alice@example.com 2025-01-15 10:00:00
2 Bob bob@example.com 2025-01-16 11:30:00

我们创建一个对应的 Java 实体类 User

// User.java
import java.time.LocalDateTime;
public class User {
    private int id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
    // 构造方法、Getter 和 Setter
    public User() {}
    public User(int id, String name, String email, LocalDateTime createdAt) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.createdAt = createdAt;
    }
    // Getters and Setters...
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", createdAt=" + createdAt +
                '}';
    }
}

我们编写一个转换方法:

Java ResultSet如何高效转换对象?-图2
(图片来源网络,侵删)
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
public class UserMapper {
    /**
     * 将 ResultSet 的当前行映射为一个 User 对象
     * @param rs ResultSet 对象,指针必须在目标行上
     * @return User 对象
     * @throws SQLException 如果数据库访问出错
     */
    public static User mapRow(ResultSet rs) throws SQLException {
        // 1. 从 rs 中获取每一列的值
        int id = rs.getInt("id");
        String name = rs.getString("name");
        String email = rs.getString("email");
        // 注意:如果数据库是 TIMESTAMP,可以使用 getObject 并指定类型
        LocalDateTime createdAt = rs.getObject("created_at", LocalDateTime.class);
        // 2. 创建并填充 User 对象
        User user = new User();
        user.setId(id);
        user.setName(name);
        user.setEmail(email);
        user.setCreatedAt(createdAt);
        // 3. 返回对象
        return user;
    }
}

使用方法:

import java.sql.*;
public class Main {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String user = "your_username";
        String password = "your_password";
        String sql = "SELECT id, name, email, created_at FROM users WHERE id = ?";
        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, 1); // 查询 id=1 的用户
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) { // 将指针移动到第一行
                User userObj = UserMapper.mapRow(rs);
                System.out.println(userObj);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

将多行 ResultSet 转换为一个 List<User>

这非常常见,我们只需要在场景一的基础上增加一个循环即可。

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class UserRepository {
    public List<User> findAllUsers() {
        String sql = "SELECT id, name, email, created_at FROM users";
        List<User> users = new ArrayList<>();
        try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/your_database", "user", "password");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            // 遍历 ResultSet 的每一行
            while (rs.next()) {
                // 对每一行都调用映射方法
                User user = UserMapper.mapRow(rs);
                users.add(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return users;
    }
}

使用方法:

public class Main {
    public static void main(String[] args) {
        UserRepository repo = new UserRepository();
        List<User> allUsers = repo.findAllUsers();
        allUsers.forEach(System.out::println);
    }
}

使用第三方库(推荐)

手动转换虽然灵活,但存在以下问题:

Java ResultSet如何高效转换对象?-图3
(图片来源网络,侵删)
  • 样板代码多rs.getXXX()obj.setXXX() 重复性高。
  • 容易出错:列名和属性名拼写错误、类型不匹配等问题难以发现。
  • 维护成本高:数据库表结构或 Java 类结构一旦变动,修改起来很麻烦。

为了解决这些问题,优秀的 ORM(Object-Relational Mapping)框架应运而生,它们可以自动完成 ResultSet 到 Java 对象的映射。

推荐库

  1. JDBI:轻量级、无依赖的 SQL 查询和对象映射库。
  2. Spring JDBC Template / Spring Data JPA:Spring 生态的一部分,提供了强大的数据访问抽象。
  3. MyBatis:非常流行的持久层框架,灵活性高。
  4. jOOQ:类型安全的 SQL 查询构建器,代码生成,功能强大。

这里我们以 JDBI 为例,因为它非常直观地展示了自动化映射的威力。

使用 JDBI

  1. 添加依赖 (Maven):

    <dependency>
        <groupId>org.jdbi</groupId>
        <artifactId>jdbi3-core</artifactId>
        <version>3.41.0</version> <!-- 请使用最新版本 -->
    </dependency>
  2. 定义接口:JDBI 通过接口定义 SQL 查询和结果映射。

    import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
    import org.jdbi.v3.sqlobject.customizer.Bind;
    import org.jdbi.v3.sqlobject.statement.SqlQuery;
    import java.util.List;
    // 告诉 JDBI 将 User 类的实例映射到查询结果
    @RegisterBeanMapper(User.class)
    public interface UserDao {
        // 直接返回一个 List<User>,JDBI 会自动处理映射
        @SqlQuery("SELECT id, name, email, created_at FROM users")
        List<User> findAll();
        // 通过参数绑定查询
        @SqlQuery("SELECT id, name, email, created_at FROM users WHERE id = :id")
        User findById(@Bind("id") int id);
    }
  3. 使用 JDBI

    import org.jdbi.v3.core.Jdbi;
    public class Main {
        public static void main(String[] args) {
            String url = "jdbc:mysql://localhost:3306/your_database";
            // 创建 JDBI 实例
            Jdbi jdbi = Jdbi.create(url, "user", "password");
            // 获取 DAO 接口的代理实现
            UserDao userDao = jdbi.onDemand(UserDao.class);
            // 调用方法,无需手动处理 ResultSet
            User user = userDao.findById(1);
            System.out.println("Found by ID: " + user);
            List<User> allUsers = userDao.findAll();
            System.out.println("\nAll Users:");
            allUsers.forEach(System.out::println);
        }
    }

可以看到,使用 JDBI 后,代码变得极其简洁,你只需要关注 SQL 语句和业务逻辑,繁琐的映射工作都由框架完成了。


其他常见转换

转换为 Map<String, Object>

当你不想创建对应的 Java 类,或者需要处理动态表结构时,将 ResultSet 转换为 Map 会很方便。

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
public class ResultSetToMap {
    public static Map<String, Object> convertRowToMap(ResultSet rs) throws SQLException {
        Map<String, Object> row = new LinkedHashMap<>(); // LinkedHashMap 保持列顺序
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
            String columnName = metaData.getColumnLabel(i); // 使用 getColumnLabel 可以获取别名
            Object value = rs.getObject(i);
            row.put(columnName, value);
        }
        return row;
    }
}

使用方法:

// ... 在 try-with-resources 块中
while (rs.next()) {
    Map<String, Object> rowMap = ResultSetToMap.convertRowToMap(rs);
    System.out.println(rowMap);
    // 输出: {id=1, name=Alice, email=alice@example.com, created_at=2025-01-15T10:00}
}

转换为 List<Map<String, Object>>

同样,只需增加一个循环即可。

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ResultSetToListOfMaps {
    public static List<Map<String, Object>> convertToList(ResultSet rs) throws SQLException {
        List<Map<String, Object>> result = new ArrayList<>();
        while (rs.next()) {
            result.add(convertRowToMap(rs)); // 复用上面的方法
        }
        return result;
    }
    // ... convertRowToMap 方法同上 ...
}

总结与建议

方法 优点 缺点 适用场景
手动转换 无需额外依赖
灵活性最高,可处理复杂逻辑
原理清晰,易于学习
样板代码多
极易出错
维护成本高
学习 JDBC 基础
项目非常小,不想引入依赖
有非常特殊的、框架无法满足的映射需求
第三方库 (如 JDBI) 代码简洁,可读性强
减少错误,类型安全
维护成本低,表/类结构变动影响小
提供了高级功能(如延迟加载、缓存等)
引入外部依赖
需要学习框架 API
绝大多数 Java 项目,是现代 Java 开发的最佳实践
转换为 Map/List 非常灵活,无需定义 Java 类
适合处理动态或未知的表结构
类型不安全,运行时才能发现错误
IDE 无法提供代码提示
无法利用面向对象的特性(如方法)
报表、数据导出等场景
处理元数据或系统表
快速原型开发

最终建议:

对于任何生产级的 Java 应用,强烈推荐使用像 JDBI、MyBatis 或 Spring Data JPA 这样的持久层框架,它们能极大地提升开发效率、代码质量和可维护性,手动转换方式仅适合在学习或极特殊情况下使用。

分享:
扫描分享到社交APP
上一篇
下一篇