如何从零构建一个轻量级ORM框架?

wen java案例 52

本文目录导读:

如何从零构建一个轻量级ORM框架?

  1. 第一阶段:核心设计
  2. 第二阶段:核心引擎
  3. 第三阶段:对外API(BaseMapper)
  4. 如何使用?
  5. 扩展方向(进阶)

这是一个非常经典的造轮子问题,从零构建一个轻量级ORM(对象关系映射),能让你深刻理解JDBC、反射、泛型和设计模式。

为了控制复杂度,我们构建一个仅支持单表CRUD(增删改查)基于注解无缓存的迷你ORM,目标是用类似 userMapper.findById(1)userMapper.save(user) 的方式操作数据库。

以下是完整的步骤和核心代码逻辑。


第一阶段:核心设计

定义映射注解

我们需要告诉ORM,Java类对应哪张表,字段对应哪一列。

// 表名注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    String value(); // 表名
}
// 列名注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String value(); // 列名
}
// 主键注解(特殊标记,用于更新/删除时的WHERE子句)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
}

创建实体类示例

@Table("t_user")
public class User {
    @Id
    @Column("id")
    private Long id;
    @Column("user_name")
    private String userName;
    @Column("age")
    private Integer age;
    // getter / setter(必须,反射要用)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

第二阶段:核心引擎

实体元数据解析器

解析类上的注解,缓存映射关系,避免每次操作都反射。

public class EntityMeta<T> {
    private Class<T> entityClass;
    private String tableName;
    private Map<String, Field> columnFieldMap; // 列名 -> 字段
    private Field idField; // 主键字段
    private String idColumn; // 主键列名
    public EntityMeta(Class<T> clazz) {
        this.entityClass = clazz;
        // 1. 解析表名
        Table table = clazz.getAnnotation(Table.class);
        if (table == null) throw new RuntimeException("Missing @Table annotation");
        this.tableName = table.value();
        // 2. 解析所有字段
        this.columnFieldMap = new HashMap<>();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                columnFieldMap.put(column.value(), field);
                if (field.isAnnotationPresent(Id.class)) {
                    this.idField = field;
                    this.idColumn = column.value();
                }
            }
        }
    }
    // getters...
    public String getTableName() { return tableName; }
    public Field getIdField() { return idField; }
    public String getIdColumn() { return idColumn; }
    public Map<String, Field> getColumnFieldMap() { return columnFieldMap; }
}

SQL 生成器

根据元数据动态生成SQL,防止SQL注入(使用 PreparedStatement)。

public class SqlBuilder<T> {
    private EntityMeta<T> meta;
    public SqlBuilder(EntityMeta<T> meta) {
        this.meta = meta;
    }
    // 生成 INSERT 语句:INSERT INTO t_user (id, user_name, age) VALUES (?, ?, ?)
    public String buildInsertSql() {
        StringBuilder columns = new StringBuilder("(");
        StringBuilder values = new StringBuilder("(");
        for (String col : meta.getColumnFieldMap().keySet()) {
            columns.append(col).append(",");
            values.append("?,");
        }
        columns.deleteCharAt(columns.length()-1).append(")");
        values.deleteCharAt(values.length()-1).append(")");
        return "INSERT INTO " + meta.getTableName() + " " + columns + " VALUES " + values;
    }
    // 生成 SELECT BY ID:SELECT * FROM t_user WHERE id = ?
    public String buildSelectByIdSql() {
        return "SELECT * FROM " + meta.getTableName() + " WHERE " + meta.getIdColumn() + " = ?";
    }
    // 生成 UPDATE:UPDATE t_user SET user_name=?, age=? WHERE id=?
    public String buildUpdateSql() {
        StringBuilder setClause = new StringBuilder("SET ");
        for (Map.Entry<String, Field> entry : meta.getColumnFieldMap().entrySet()) {
            String col = entry.getKey();
            if (col.equals(meta.getIdColumn())) continue; // 主键不更新
            setClause.append(col).append("=?,");
        }
        setClause.deleteCharAt(setClause.length()-1);
        return "UPDATE " + meta.getTableName() + " " + setClause + " WHERE " + meta.getIdColumn() + " = ?";
    }
    // 生成 DELETE BY ID
    public String buildDeleteByIdSql() {
        return "DELETE FROM " + meta.getTableName() + " WHERE " + meta.getIdColumn() + " = ?";
    }
    // 生成 SELECT ALL
    public String buildSelectAllSql() {
        return "SELECT * FROM " + meta.getTableName();
    }
}

执行器(核心JDBC操作)

管理数据库连接,并处理 ResultSet -> 对象 的转换。

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class SimpleExecutor {
    private DataSource dataSource; // 使用连接池,或简单的DriverManager
    public SimpleExecutor(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    // 通用查询,返回实体列表
    public <T> List<T> query(String sql, Object[] params, EntityMeta<T> meta) {
        List<T> result = new ArrayList<>();
        try (Connection conn = dataSource.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            setParameters(pstmt, params);
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    T entity = meta.getEntityClass().newInstance();
                    // 遍历所有列,通过反射赋值
                    for (Map.Entry<String, Field> entry : meta.getColumnFieldMap().entrySet()) {
                        String columnName = entry.getKey();
                        Field field = entry.getValue();
                        Object value = rs.getObject(columnName);
                        // 处理类型转换(如数据库中int到Java Integer)
                        if (value != null) {
                            field.set(entity, value);
                        }
                    }
                    result.add(entity);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Query failed", e);
        }
        return result;
    }
    // 通用更新(INSERT/UPDATE/DELETE)
    public int executeUpdate(String sql, Object[] params) {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            setParameters(pstmt, params);
            return pstmt.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("Update failed", e);
        }
    }
    // 设置参数到PreparedStatement
    private void setParameters(PreparedStatement pstmt, Object[] params) throws SQLException {
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]);
            }
        }
    }
}

第三阶段:对外API(BaseMapper)

定义通用的CRUD接口,用户只需针对实体类调用。

public class BaseMapper<T> {
    private final EntityMeta<T> meta;
    private final SqlBuilder<T> sqlBuilder;
    private final SimpleExecutor executor;
    public BaseMapper(Class<T> clazz, DataSource dataSource) {
        this.meta = new EntityMeta<>(clazz);
        this.sqlBuilder = new SqlBuilder<>(meta);
        this.executor = new SimpleExecutor(dataSource);
    }
    // --- CRUD 方法 ---
    public T findById(Object id) {
        String sql = sqlBuilder.buildSelectByIdSql();
        List<T> list = executor.query(sql, new Object[]{id}, meta);
        return list.isEmpty() ? null : list.get(0);
    }
    public List<T> findAll() {
        String sql = sqlBuilder.buildSelectAllSql();
        return executor.query(sql, null, meta);
    }
    public int save(T entity) {
        // 获取所有非主键字段的值 + 主键值(如果主键是自增的,可以忽略主键字段)
        List<Object> params = new ArrayList<>();
        for (Field field : meta.getColumnFieldMap().values()) {
            try {
                params.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        String sql = sqlBuilder.buildInsertSql();
        return executor.executeUpdate(sql, params.toArray());
    }
    public int updateById(T entity) {
        // 收集所有字段值(除主键外),最后加上主键值
        List<Object> params = new ArrayList<>();
        for (Map.Entry<String, Field> entry : meta.getColumnFieldMap().entrySet()) {
            if (entry.getKey().equals(meta.getIdColumn())) continue;
            try {
                params.add(entry.getValue().get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        // 主键值放最后
        try {
            params.add(meta.getIdField().get(entity));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        String sql = sqlBuilder.buildUpdateSql();
        return executor.executeUpdate(sql, params.toArray());
    }
    public int deleteById(Object id) {
        String sql = sqlBuilder.buildDeleteByIdSql();
        return executor.executeUpdate(sql, new Object[]{id});
    }
}

如何使用?

public class Main {
    public static void main(String[] args) {
        // 1. 配置数据源(这里假设使用HikariCP或简单连接池)
        DataSource dataSource = getDataSource();
        // 2. 创建BaseMapper实例
        BaseMapper<User> userMapper = new BaseMapper<>(User.class, dataSource);
        // 3. 使用
        // 查询
        User user = userMapper.findById(1L);
        System.out.println(user.getUserName());
        // 新增
        User newUser = new User();
        newUser.setUserName("Alice");
        newUser.setAge(30);
        userMapper.save(newUser);
        // 更新
        user.setAge(31);
        userMapper.updateById(user);
    }
    private static DataSource getDataSource() {
        // 返回一个实现了 javax.sql.DataSource 的对象
        // HikariDataSource
    }
}

组件 职责 关键技术
注解 定义映射规则 @Retention(RUNTIME)
EntityMeta 缓存类/表/字段关系 反射 + Map
SqlBuilder 预编译SQL生成 PreparedStatement
SimpleExecutor 执行JDBC,结果集转对象 ResultSet.getObject + Field.set
BaseMapper 统一CRUD接口 泛型

扩展方向(进阶)

如果你想让这个轻量级ORM更实用,可以逐步添加:

  1. 连接池集成:使用HikariCP或Druid作为DataSource。
  2. 命名策略:支持 camelCase -> snake_case 自动映射,减少注解。
  3. 懒加载:对于关联对象,只在调用getter时查询。
  4. 条件查询:实现 QueryBuilder 支持 WHERE age > ? AND name LIKE ?
  5. 事务管理:通过 Connectioncommit/rollback 实现。

这个Mini ORM的核心逻辑就在 反射(读注解、赋值)JDBC(执行SQL、映射结果集) 之间来回穿梭,掌握它,你就掌握了所有ORM框架(MyBatis、Hibernate)的底层基因。

抱歉,评论功能暂时关闭!