-
通用 Mapper:提供通用的 CRUD 方法。
-
条件构造器:支持链式调用,构建查询条件。
-
分页查询:支持 Oracle 的分页查询。
-
连表查询:支持 JOIN 查询。
-
指定字段查询:支持动态选择查询字段。
-
注解支持:通过注解配置实体类和数据库表的映射关系。
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 | 支持动态映射,代码复用性高 | 需要处理反射和类型转换,性能略低 |