当前位置: 首页 > news >正文

手写ibatis

  1. 通用 Mapper:提供通用的 CRUD 方法。

  2. 条件构造器:支持链式调用,构建查询条件。

  3. 分页查询:支持 Oracle 的分页查询。

  4. 连表查询:支持 JOIN 查询。

  5. 指定字段查询:支持动态选择查询字段。

  6. 注解支持:通过注解配置实体类和数据库表的映射关系。


1. 项目结构

src/main/java
├── com.example.orm
│   ├── annotation
│   │   ├── Table.java
│   │   └── Column.java
│   ├── core
│   │   ├── QueryWrapper.java
│   │   ├── Page.java
│   │   └── SqlHelper.java
│   ├── mapper
│   │   └── BaseMapper.java
│   ├── util
│   │   └── ReflectionUtil.java
│   └── OrmApplication.java

2. 完整代码

(1) 注解类

Table.java
package com.example.orm.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {String value(); // 表名
}
Column.java
package com.example.orm.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {String value() default ""; // 列名,默认为字段名boolean isPrimaryKey() default false; // 是否为主键
}

(2) 核心工具类

ReflectionUtil.java
package com.example.orm.util;import com.example.orm.annotation.Column;
import com.example.orm.annotation.Table;import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class ReflectionUtil {/*** 获取表名*/public static String getTableName(Class<?> clazz) {Table table = clazz.getAnnotation(Table.class);if (table != null) {return table.value();}return clazz.getSimpleName().toLowerCase();}/*** 获取字段名和列名的映射*/public static Map<String, String> getColumnMappings(Class<?> clazz) {Map<String, String> mappings = new HashMap<>();for (Field field : clazz.getDeclaredFields()) {Column column = field.getAnnotation(Column.class);if (column != null) {String columnName = column.value().isEmpty() ? field.getName() : column.value();mappings.put(field.getName(), columnName);}}return mappings;}/*** 获取主键字段名*/public static String getPrimaryKeyField(Class<?> clazz) {for (Field field : clazz.getDeclaredFields()) {Column column = field.getAnnotation(Column.class);if (column != null && column.isPrimaryKey()) {return field.getName();}}throw new RuntimeException("No primary key found in class: " + clazz.getName());}/*** 获取字段值*/public static Object getFieldValue(Object obj, String fieldName) {try {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);return field.get(obj);} catch (Exception e) {throw new RuntimeException("Failed to get field value: " + fieldName, e);}}
}

(3) 条件构造器

QueryWrapper.java
package com.example.orm.core;import java.util.ArrayList;
import java.util.List;public class QueryWrapper<T> {private final Class<T> entityClass;private final List<String> conditions = new ArrayList<>();private final List<String> joinClauses = new ArrayList<>();private final List<String> selectFields = new ArrayList<>();private String orderByClause;private Integer limit;private Integer offset;private String mainTableAlias; // 主表别名public QueryWrapper(Class<T> entityClass) {this.entityClass = entityClass;this.mainTableAlias = "t1"; // 默认主表别名}/*** 设置主表别名*/public QueryWrapper<T> alias(String alias) {this.mainTableAlias = alias;return this;}/*** 添加等值条件*/public QueryWrapper<T> eq(String column, Object value) {conditions.add(mainTableAlias + "." + column + " = '" + value + "'");return this;}/*** 添加模糊查询条件*/public QueryWrapper<T> like(String column, String value) {conditions.add(mainTableAlias + "." + column + " LIKE '%" + value + "%'");return this;}/*** 添加 JOIN 子句*/public QueryWrapper<T> join(JoinType joinType, String table, String alias, String onClause) {joinClauses.add(joinType.getValue() + " " + table + " " + alias + " ON " + onClause);return this;}/*** 添加排序条件*/public QueryWrapper<T> orderBy(String orderByClause) {this.orderByClause = orderByClause;return this;}/*** 设置分页参数*/public QueryWrapper<T> page(int offset, int limit) {this.offset = offset;this.limit = limit;return this;}/*** 添加需要查询的字段*/public QueryWrapper<T> select(String... fields) {for (String field : fields) {selectFields.add(field);}return this;}/*** 构建 SQL 查询语句*/public String build() {StringBuilder sql = new StringBuilder("SELECT ");// 添加查询字段if (selectFields.isEmpty()) {sql.append(mainTableAlias + ".*"); // 默认查询主表所有字段} else {sql.append(String.join(", ", selectFields));}sql.append(" FROM ").append(ReflectionUtil.getTableName(entityClass)).append(" ").append(mainTableAlias);// 添加 JOIN 子句if (!joinClauses.isEmpty()) {sql.append(" ").append(String.join(" ", joinClauses));}// 添加 WHERE 条件if (!conditions.isEmpty()) {sql.append(" WHERE ").append(String.join(" AND ", conditions));}// 添加排序条件if (orderByClause != null) {sql.append(" ORDER BY ").append(orderByClause);}// 添加分页条件if (limit != null && offset != null) {sql = new StringBuilder("SELECT * FROM (").append("SELECT t.*, ROWNUM AS rn FROM (").append(sql).append(") t WHERE ROWNUM <= ").append(offset + limit).append(") WHERE rn >= ").append(offset + 1);}return sql.toString();}
}

(4) 分页工具

Page.java
package com.example.orm.core;import java.util.List;public class Page<T> {private List<T> records; // 当前页数据private long total;      // 总记录数private long size;       // 每页大小private long current;    // 当前页码// Getters and Setterspublic List<T> getRecords() {return records;}public void setRecords(List<T> records) {this.records = records;}public long getTotal() {return total;}public void setTotal(long total) {this.total = total;}public long getSize() {return size;}public void setSize(long size) {this.size = size;}public long getCurrent() {return current;}public void setCurrent(long current) {this.current = current;}
}

(5) SQL 工具类

SqlHelper.java
package com.example.orm.core;import com.example.orm.util.ReflectionUtil;import java.util.Map;public class SqlHelper {/*** 生成插入 SQL*/public static <T> String buildInsertSql(T entity) {Class<?> clazz = entity.getClass();String tableName = ReflectionUtil.getTableName(clazz);Map<String, String> columnMappings = ReflectionUtil.getColumnMappings(clazz);StringBuilder columns = new StringBuilder();StringBuilder values = new StringBuilder();for (Map.Entry<String, String> entry : columnMappings.entrySet()) {String fieldName = entry.getKey();String columnName = entry.getValue();Object value = ReflectionUtil.getFieldValue(entity, fieldName);columns.append(columnName).append(",");values.append("'").append(value).append("',");}// 去掉最后一个逗号columns.deleteCharAt(columns.length() - 1);values.deleteCharAt(values.length() - 1);return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ")";}/*** 生成更新 SQL*/public static <T> String buildUpdateSql(T entity) {Class<?> clazz = entity.getClass();String tableName = ReflectionUtil.getTableName(clazz);Map<String, String> columnMappings = ReflectionUtil.getColumnMappings(clazz);String primaryKeyField = ReflectionUtil.getPrimaryKeyField(clazz);String primaryKeyColumn = columnMappings.get(primaryKeyField);Object primaryKeyValue = ReflectionUtil.getFieldValue(entity, primaryKeyField);StringBuilder setClause = new StringBuilder();for (Map.Entry<String, String> entry : columnMappings.entrySet()) {String fieldName = entry.getKey();String columnName = entry.getValue();Object value = ReflectionUtil.getFieldValue(entity, fieldName);setClause.append(columnName).append(" = '").append(value).append("',");}// 去掉最后一个逗号setClause.deleteCharAt(setClause.length() - 1);return "UPDATE " + tableName + " SET " + setClause + " WHERE " + primaryKeyColumn + " = '" + primaryKeyValue + "'";}/*** 生成删除 SQL*/public static <T> String buildDeleteSql(T entity) {Class<?> clazz = entity.getClass();String tableName = ReflectionUtil.getTableName(clazz);String primaryKeyField = ReflectionUtil.getPrimaryKeyField(clazz);Map<String, String> columnMappings = ReflectionUtil.getColumnMappings(clazz);String primaryKeyColumn = columnMappings.get(primaryKeyField);Object primaryKeyValue = ReflectionUtil.getFieldValue(entity, primaryKeyField);return "DELETE FROM " + tableName + " WHERE " + primaryKeyColumn + " = '" + primaryKeyValue + "'";}
}

(6) 通用 Mapper

BaseMapper.java
package com.example.orm.mapper;import com.example.orm.core.Page;
import com.example.orm.core.QueryWrapper;
import com.example.orm.core.SqlHelper;import java.util.List;public interface BaseMapper<T> {/*** 插入记录*/default int insert(T entity) {String sql = SqlHelper.buildInsertSql(entity);return executeUpdate(sql);}/*** 更新记录*/default int update(T entity) {String sql = SqlHelper.buildUpdateSql(entity);return executeUpdate(sql);}/*** 删除记录*/default int delete(T entity) {String sql = SqlHelper.buildDeleteSql(entity);return executeUpdate(sql);}/*** 查询记录*/List<T> selectList(QueryWrapper<T> queryWrapper);/*** 分页查询*/Page<T> selectPage(Page<T> page, QueryWrapper<T> queryWrapper);/*** 执行更新操作*/int executeUpdate(String sql);/*** 执行查询操作*/List<T> executeQuery(String sql);
}

3. 使用示例

(1) 实体类

@Table("user")
public class User {@Column(isPrimaryKey = true)private Long id;@Column("username")private String name;@Columnprivate Integer age;// Getters and Setters
}

(2) Mapper 实现

public class UserMapper implements BaseMapper<User> {@Overridepublic List<User> selectList(QueryWrapper<User> queryWrapper) {String sql = queryWrapper.build();return executeQuery(sql);}@Overridepublic Page<User> selectPage(Page<User> page, QueryWrapper<User> queryWrapper) {String sql = queryWrapper.build();List<User> records = executeQuery(sql);page.setRecords(records);return page;}@Overridepublic int executeUpdate(String sql) {// 实现 JDBC 更新逻辑return 0;}@Overridepublic List<User> executeQuery(String sql) {// 实现 JDBC 查询逻辑return null;}
}

4. 总结

  • 支持通用 CRUD 操作。

  • 支持动态条件构造、分页查询、连表查询和指定字段查询。

  • 通过注解配置实体类和数据库表的映射关系。

在 iBatis 中,resultClass 用于指定 SQL 查询结果的映射类型。要支持不同的 resultClass,可以通过以下方式实现通用性:


1. 使用 java.util.Map 作为通用结果类型

resultClass 设置为 java.util.Map,这样查询结果会以 Map<String, Object> 的形式返回,其中键是列名,值是列值。

(1) XML 映射文件

<select id="executeSql" parameterClass="java.util.Map" resultClass="java.util.HashMap">${sql}
</select>

(2) 代码调用

Map<String, String> params = new HashMap<>();
params.put("sql", "SELECT id, name, age FROM user WHERE age = 25");List<Map<String, Object>> result = sqlMapClient.queryForList("executeSql", params);for (Map<String, Object> row : result) {System.out.println("ID: " + row.get("ID") + ", Name: " + row.get("NAME") + ", Age: " + row.get("AGE"));
}

优点

  • 无需定义实体类,适合动态查询。

  • 结果以 Map 形式返回,灵活通用。

缺点

  • 需要手动处理列名和类型转换。

  • 代码可读性较差。


2. 使用反射动态映射结果

通过反射将查询结果动态映射到不同的实体类中。可以在 XML 映射文件中使用 resultClass="java.lang.Object",然后在代码中手动处理映射。

(1) XML 映射文件

<select id="executeSql" parameterClass="java.util.Map" resultClass="java.lang.Object">${sql}
</select>

(2) 代码调用

Map<String, String> params = new HashMap<>();
params.put("sql", "SELECT id, name, age FROM user WHERE age = 25");List<Object> result = sqlMapClient.queryForList("executeSql", params);for (Object row : result) {if (row instanceof Map) {Map<String, Object> map = (Map<String, Object>) row;System.out.println("ID: " + map.get("ID") + ", Name: " + map.get("NAME") + ", Age: " + map.get("AGE"));} else {// 处理其他类型的映射}
}

优点

  • 支持动态映射到不同的实体类。

  • 灵活性较高。

缺点

  • 需要手动处理类型转换。

  • 代码复杂度较高。


3. 使用泛型和反射实现通用 Mapper

通过泛型和反射,实现一个通用的 Mapper 类,支持动态映射到不同的实体类。

(1) 通用 Mapper 接口

public interface GenericMapper<T> {List<T> executeQuery(String sql, Class<T> resultClass);
}

(2) 通用 Mapper 实现

import com.ibatis.sqlmap.client.SqlMapClient;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class GenericMapperImpl<T> implements GenericMapper<T> {private final SqlMapClient sqlMapClient;public GenericMapperImpl(SqlMapClient sqlMapClient) {this.sqlMapClient = sqlMapClient;}@Overridepublic List<T> executeQuery(String sql, Class<T> resultClass) {try {Map<String, String> params = new HashMap<>();params.put("sql", sql);List<Map<String, Object>> result = sqlMapClient.queryForList("executeSql", params);// 将 Map 转换为实体类return mapToEntity(result, resultClass);} catch (Exception e) {throw new RuntimeException("Failed to execute query", e);}}private List<T> mapToEntity(List<Map<String, Object>> result, Class<T> resultClass) {try {List<T> entities = new ArrayList<>();for (Map<String, Object> row : result) {T entity = resultClass.getDeclaredConstructor().newInstance();for (Map.Entry<String, Object> entry : row.entrySet()) {String fieldName = entry.getKey().toLowerCase(); // 列名转小写Object value = entry.getValue();Field field = resultClass.getDeclaredField(fieldName);field.setAccessible(true);field.set(entity, value);}entities.add(entity);}return entities;} catch (Exception e) {throw new RuntimeException("Failed to map result to entity", e);}}
}

(3) 使用示例

public class Main {public static void main(String[] args) {SqlMapClient sqlMapClient = ...; // 初始化 SqlMapClientGenericMapper<User> userMapper = new GenericMapperImpl<>(sqlMapClient);String sql = "SELECT id, name, age FROM user WHERE age = 25";List<User> users = userMapper.executeQuery(sql, User.class);for (User user : users) {System.out.println("User: " + user.getName() + ", Age: " + user.getAge());}}
}

优点

  • 支持动态映射到不同的实体类。

  • 代码复用性高。

缺点

  • 需要处理反射和类型转换。

  • 性能可能略低于直接映射。


4. 总结

方法 优点 缺点
使用 java.util.Map 简单灵活,无需定义实体类 需要手动处理列名和类型转换
使用反射动态映射结果 支持动态映射到不同的实体类 需要手动处理类型转换,代码复杂度较高
使用泛型和反射实现通用 Mapper 支持动态映射,代码复用性高 需要处理反射和类型转换,性能略低
http://www.jsqmd.com/news/21488/

相关文章:

  • 国产IPD项目管理软件推荐|别再靠 Excel 推 IPD 了!帮你把IPD流程从“纸上”搬进系统
  • 【源码解读之 Mybatis】【核心篇】--第7篇:ParameterHandler参数处理机制
  • [linux] 文件夹可写权限的关闭和打开
  • 2025年教室护眼灯厂家权威推荐榜单:led教室灯/幼儿园教室灯/教室照明灯具源头厂家精选
  • 2025年自动定量灌装机厂家权威推荐榜单:称重灌装机/膏状灌装机/瓶灌装机源头厂家精选
  • 厨房电子秤芯片方案:SIC8833
  • 在MCUXpresso IDE中建立使用静态库的工程 - 指南
  • 从“天书”到源码:HarmonyOS NEXT 崩溃堆栈解析实战指南
  • 深入理解Java线程
  • Launcher 桌面源码笔记一(3D车模桌面)
  • 2025 年最新推荐!软件验收测试公司最新排行榜,揭秘具备 CMA/CNAS 资质的靠谱品牌可靠/权威/知名的软件验收测试公司推荐
  • Ollama大模型推理场景下3090和4090性能实测
  • OSI七层网络参考模型(Leo)
  • 2025 年最新推荐河道护栏源头厂家口碑榜,聚焦全流程服务与高性价比之选铝合金/绳索/不锈钢河道护栏公司推荐
  • ABP vNext 基础四层
  • 2025 年管道修补器源头厂家最新推荐排行榜:揭秘行业内具备全流程管控能力的靠谱厂商及优质产品选型指南加长/铸铁/弯头/卡箍式管道修补器公司推荐
  • 实用指南:YOLO系列——实时屏幕检测
  • 信号(Signal)、信号量(Semaphore)
  • 在 macOS 中遇到 brew 命令不存在的问题
  • 在线聊天室
  • 2025 年亚克力大型鱼缸厂家联系方式推荐:江苏金穗的全产业链服务与定制化技术优势解析
  • 2025 年海洋水族馆厂家联系方式推荐:江苏金穗亚克力定制服务与工程案例,泳池 / 鱼缸项目解决方案
  • 例3.3】三个数 ------信息奥赛高级题库
  • 详细介绍:Go 和云原生 的现状和发展前景
  • Socket 编程 TCP(准备阶段) - 指南
  • 2025 年亚克力板材厂家联系方式推荐:江苏金穗技术工艺与工程案例解析,泳池 / 鱼缸 / 海洋馆解决方案
  • 2025 年 亚克力透明泳池厂家联系方式推荐:江苏金穗的技术积淀与工程服务优势解析
  • 2025山东单招综评培训机构推荐榜:济南易升教育五星领跑,小班培养 + 高上岸率适配升学需求
  • 实用指南:npm 包构建与发布
  • 2025修护/二硫化硒去屑/香氛/控油蓬松/洗发水品牌推荐榜:MASIL 玛丝兰(西安悦己容)五星领衔,这些专研洗护品牌值得关注