# Orm框架 **Repository Path**: laomaodu/orm-framework ## Basic Information - **Project Name**: Orm框架 - **Description**: 一个专注于简化对象关系映射的开源项目,支持多种数据库,提供高效、灵活的数据操作接口,助力开发者快速构建应用。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-01-27 - **Last Updated**: 2026-01-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 0介绍 **ORM = 用面向对象的方式操作关系型数据库** - 开发者操作的是 **对象(Object)** - ORM 框架负责把对象 **自动映射** 为: - SQL - 表(Table) - 行(Row) - 列(Column) 目标: - 减少手写 SQL - 降低业务代码与数据库的耦合 - 提升开发效率与可维护性 | ORM 框架 | 自动化程度 | 核心特点 | 适合场景 | | -------------------- | ---------- | ----------------------------------------------- | -------------------------------- | | Hibernate / JPA | 全自动 | 强对象模型,关系通过注解/配置描述,几乎不写 SQL | 领域模型清晰,复杂对象关系 | | MyBatis (iBatis) | 半自动 | SQL 手写,结果映射成对象,更可控 | SQL 复杂,对性能和查询精度要求高 | | Spring JDBC Template | 弱 ORM | 只封装 JDBC,RowMapper 手动映射 | 简单场景,对 ORM 抽象不敏感 | 互联网 高并发 高性能 高可用 为什么要自己手写?为什么要重复造轮子 各有各的需求。学习还是为了解思想 ## 第一章:实现思路概述 **第一版本** ```java // private static List select(String sql) { // List result = new ArrayList<>(); // Connection con = null; //连接对象 // PreparedStatement pstm = null; //语句集 // ResultSet rs = null; //结果集 // try { // //1、加载驱动类,千万不要忘记了 // Class.forName("com.mysql.jdbc.Driver"); // //2、建立连接 // con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo","root","123456"); // //3、创建语句集 // pstm = con.prepareStatement(sql); // //4、执行语句集 // rs = pstm.executeQuery(); // while (rs.next()){ // //纯粹的硬编码 // Member instance = new Member(); // instance.setId(rs.getLong("id")); // instance.setName(rs.getString("name")); // instance.setAge(rs.getInt("age")); // instance.setAddr(rs.getString("addr")); // result.add(instance); // } // //5、获取结果集 // }catch (Exception e){ // e.printStackTrace(); // } // //6、关闭结果集、关闭语句集、关闭连接 // finally { // try { // rs.close(); // pstm.close(); // con.close(); // }catch (Exception e){ // e.printStackTrace(); // } // } // return result; // } ``` **SQL 生成方式** 直接传入固定 SQL 字符串(如`select * from t_member`),查询条件写死在 SQL 里。 **实体映射逻辑**针对`Member`类硬编码字段映射,只适配单一实体: ``` instance.setId(rs.getLong("id")); instance.setName(rs.getString("name")); ``` **表名 / 列名绑定**表名、列名直接写死(如`t_member`、`id`、`name`),换表 / 换列必须改代码。 **扩展性**仅支持`Member`类,新增`Order`类需要重写`select`方法和`mapperRow`映射逻辑。 **维护成本**每新增实体 / 修改字段,都要修改 SQL 和映射代码,维护成本高。 **代码复用性**复用性极低,不同实体的查询逻辑无法共用。 **第2版本** 先来想想思路,软编码,代表着要动态计算表名和字段名字 先来约定 -自行猜测 开写代码 ```java Member condition = new Member(); condition.setName("TomCat"); condition.setAge(2); //"select * from t_member where name = 'Tom' and age = 19" List result = select(condition); System.out.println(Arrays.toString(result.toArray())); ``` ```java public static List select(Object condition) { List result = new ArrayList<>(); Class entityClass = condition.getClass(); Connection con = null; //连接对象 PreparedStatement pstm = null; //语句集 ResultSet rs = null; //结果集 try { //1、加载驱动类,千万不要忘记了 Class.forName("com.mysql.jdbc.Driver"); //2、建立连接 con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo","root","123456"); //数据库列名和Java 实体字段名的双向映射表: 键 = 数据库列名,值 = Java 实体类的字段名(通过列名找字段名) Map getFieldNameByColumn = new HashMap(); //键 = Java 实体类的字段名,值 = 数据库列名(通过字段名找列名) Map getColumnByFieldName = new HashMap(); Field[] fields = entityClass.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); String fieldName = field.getName(); if(field.isAnnotationPresent(Column.class)){ Column column = field.getAnnotation(Column.class); String columnName = column.name(); getFieldNameByColumn.put(columnName,fieldName); getColumnByFieldName.put(fieldName,columnName); }else{ //默认属性名就是列名 getFieldNameByColumn.put(fieldName,fieldName); getColumnByFieldName.put(fieldName,fieldName); } } StringBuffer sql = new StringBuffer(); //3、创建语句集 Table table = entityClass.getAnnotation(Table.class); //条件拼接 sql.append("select * from " + table.name() + " where 1=1 "); sql.append("select * from " + table.name() + " where 1=1 "); for (Field field : fields) { Object value = field.get(condition); if(null != value){ if(String.class == field.getType()){ sql.append(" and " + getColumnByFieldName.get(field.getName()) + " = '" + value + "'"); }else{ sql.append(" and " + getColumnByFieldName.get(field.getName()) + " = " + value); } //其他依次类推 } } pstm = con.prepareStatement(sql.toString()); //4、执行,获取结果集 rs = pstm.executeQuery(); //结果映射 int columnCounts = rs.getMetaData().getColumnCount(); while (rs.next()){ Object instance = entityClass.newInstance(); for (int i = 1; i <= columnCounts; i++) { String columnName = rs.getMetaData().getColumnName(i); Field field = entityClass.getDeclaredField(getFieldNameByColumn.get(columnName)); field.setAccessible(true); field.set(instance,rs.getObject(columnName)); } result.add(instance); } } ``` 如上,这个sql,就完成了 适配性和软编码 ## 第二章:搭建基础架构 本ORM框架基于Spring JDBC实现,目标是提供一个轻量级、高性能的数据持久化解决方案,核心特性包括: - 自动SQL生成 - 实体与数据库表的自动映射 - 支持复杂查询条件构建 - 动态数据源切换 - 读写分离支持 作为一个spring Orm框架 ```java gupaoedu-vip-spring-orm/ ├── src/main/java/ │ ├── com.gupaoedu.vip.orm.framework/ # 框架核心包 │ │ ├── BaseDaoSupport.java # DAO基础支持类(核心) │ │ ├── EntityOperation.java # 实体操作类 │ │ ├── QueryRule.java # 查询规则构造器 │ │ ├── QueryRuleSqlBuilder.java # SQL构建器 │ │ ├── ClassMappings.java # 类映射工具 │ │ └── Order.java # 排序规则 │ │ │ ├── javax.core.common.jdbc/ # JDBC通用包 │ │ ├── BaseDao.java # DAO接口定义 │ │ └── datasource/ # 数据源包 │ │ ├── DynamicDataSource.java # 动态数据源 │ │ └── DynamicDataSourceEntry.java # 数据源上下文 │ │ │ ├── javax.core.common/ # 通用工具包 │ │ ├── Page.java # 分页对象 │ │ ├── ResultMsg.java # 结果消息 │ │ └── utils/ # 工具类 │ │ │ └── com.gupaoedu.vip.orm.demo/ # 示例代码 │ ├── entity/ # 实体类 │ │ ├── Member.java │ │ └── Order.java │ └── dao/ # DAO实现 │ ├── MemberDao.java │ └── OrderDao.java │ └── pom.xml # Maven配置 ``` ```java 1. 定义实体类(使用JPA注解) ↓ 2. 创建DAO类继承BaseDaoSupport ↓ 3. 注入数据源 ↓ 4. 使用QueryRule构建查询条件 ↓ 5. 调用父类方法执行CRUD操作 ``` ```java org.springframework spring-jdbc 5.0.2.RELEASE javax.persistence persistence-api 1.0 com.alibaba druid 1.0.9 mysql mysql-connector-java 5.1.14 ``` | 文件名 | 作用 | 关键功能 | | ---------------------------- | ---------------------------- | ------------------------------------- | | **BaseDaoSupport.java** | DAO基础支持类,所有DAO的父类 | 提供CRUD、分页、批量操作等通用方法 | | **EntityOperation.java** | 实体反射操作类 | 实体与数据库字段映射、ResultSet转对象 | | **QueryRule.java** | 查询规则构造器 | 构建复杂查询条件(AND/OR/LIKE/IN等) | | **QueryRuleSqlBuilder.java** | SQL语句构建器 | 根据QueryRule生成WHERE和ORDER BY子句 | | **ClassMappings.java** | 类映射工具 | 扫描实体类的getter/setter方法和字段 | #### 2.2.2 数据源层 | 文件名 | 作用 | 关键功能 | | ------------------------------- | ------------ | --------------------------------------------- | | **DynamicDataSource.java** | 动态数据源 | 继承AbstractRoutingDataSource,实现数据源路由 | | **DynamicDataSourceEntry.java** | 数据源上下文 | 使用ThreadLocal存储当前线程的数据源标识 | #### 2.2.3 接口定义层 | 文件名 | 作用 | 关键功能 | | ---------------- | -------- | -------------------- | | **BaseDao.java** | DAO接口 | 定义标准CRUD接口方法 | | **Page.java** | 分页对象 | 封装分页查询结果 | ## 第三章:实现关键功能 ##### BaseDaoSupport - DAO基础支持类 ``` @Autowired private OrderDao orderDao; public class OrderDao extends BaseDaoSupport { 先研究baseDaoSupport public abstract class BaseDaoSupport implements BaseDao { ``` - 使用泛型``支持任意实体类型 - 通过反射获取实体类信息,自动生成SQL - 封装JdbcTemplate,简化数据库操作 - 支持读写分离(jdbcTemplateWrite/jdbcTemplateReadOnly) ```java private String tableName; // 表名 private JdbcTemplate jdbcTemplateWrite; // 写库JdbcTemplate private JdbcTemplate jdbcTemplateReadOnly; // 读库JdbcTemplate private EntityOperation op; // 实体操作对象 ``` 约定大于配置 ``` public interface BaseDao {} ``` 约定了需要实现的接口 ``` List select(QueryRule queryRule) throws Exception; ``` ``` BaseDaoSupport 来实现增删查改 ``` 建造者模式 -责任链 ##### 条件构造QueryRule 我们来约定 select(T and) 我们要支持多数据库 T太过于宽广了,我们不妨想一下mybatis Lam.eq这样的写法,解耦 ```java protected T selectUnique(String propertyName,Object value) throws Exception { QueryRule queryRule = QueryRule.getInstance(); queryRule.andEqual(propertyName, value); queryRule.andEqual("deleted", false) return this.selectUnique(queryRule); } ``` 条件有多个,最后是在sql语言增加内容 怎么实现的呢 **条件构造QueryRule** ``` public QueryRule andIsNull(String propertyName) { this.ruleList.add(new Rule(ISNULL, propertyName).setAndOr(AND)); return this; } private List ruleList = new ArrayList(); ``` 构造了一个Rule规则链 到时再动态构造 QueryRuleSqlBuilder ```java public List select(QueryRule queryRule) throws Exception{ QueryRuleSqlBuilder bulider = new QueryRuleSqlBuilder(queryRule); String ws = removeFirstAnd(bulider.getWhereSql()); String whereSql = ("".equals(ws) ? ws : (" where " + ws)); String sql = "select " + op.allColumn + " from " + getTableName() + whereSql; Object [] values = bulider.getValues(); String orderSql = bulider.getOrderSql(); orderSql = (StringUtils.isEmpty(orderSql) ? " " : (" order by " + orderSql)); sql += orderSql; log.debug(sql); return (List) this.jdbcTemplateReadOnly().query(sql, this.op.rowMapper, values); } ``` ```java for (Rule rule : queryRule.getRuleList()) { switch (rule.getType()) { case QueryRule.BETWEEN: processBetween(rule); break; case QueryRule.EQ: processEqual(rule); break; case QueryRule.LIKE: processLike(rule); break; case QueryRule.NOTEQ: processNotEqual(rule); break; ``` ```java private void processEqual(Rule rule) { if (ArrayUtils.isEmpty(rule.getValues())) { return; } add(rule.getAndOr(),rule.getPropertyName(),"=",rule.getValues()[0]); } ``` ```java /** * 加入到sql查询规则队列 * @param andOr and 或则 or * @param key 列名 * @param split 列名与值之间的间隔 * @param prefix 值前缀 * @param value 值 * @param suffix 值后缀 */ private void add(int andOr,String key,String split ,String prefix,Object value,String suffix){ String andOrStr = (0 == andOr ? "" :(QueryRule.AND == andOr ? " and " : " or ")); properties.add(CURR_INDEX, andOrStr + key + " " + split + prefix + (null != value ? " ? " : " ") + suffix); if(null != value){ values.add(CURR_INDEX,value); CURR_INDEX ++; } } ``` ```java @Nullable public T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (this.logger.isDebugEnabled()) { String sql = getSql(psc); this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } Connection con = DataSourceUtils.getConnection(this.obtainDataSource()); PreparedStatement ps = null; Object var13; try { ps = psc.createPreparedStatement(con); this.applyStatementSettings(ps); T result = (T)action.doInPreparedStatement(ps); this.handleWarnings((Statement)ps); var13 = result; } catch (SQLException ex) { if (psc instanceof ParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } String sql = getSql(psc); JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, this.getDataSource()); con = null; throw this.translateException("PreparedStatementCallback", sql, ex); } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, this.getDataSource()); } return (T)var13; } ``` ##### 字段映射 EntityOperation ![image-20260127120112620](https://raw.githubusercontent.com/Xlan-cell/tupian/master/20260127120119765.png) ``` return (List) this.jdbcTemplateReadOnly().query(sql, this.op.rowMapper, values); ``` ```java public EntityOperation(Class clazz,String pk) throws Exception{ if(!clazz.isAnnotationPresent(Entity.class)){ throw new Exception("在" + clazz.getName() + "中没有找到Entity注解,不能做ORM映射"); } this.entityClass = clazz; Table table = entityClass.getAnnotation(Table.class); if (table != null) { this.tableName = table.name(); } else { this.tableName = entityClass.getSimpleName(); } Map getters = ClassMappings.findPublicGetters(entityClass); Map setters = ClassMappings.findPublicSetters(entityClass); Field[] fields = ClassMappings.findFields(entityClass); fillPkFieldAndAllColumn(pk,fields); this.mappings = getPropertyMappings(getters, setters, fields); this.allColumn = this.mappings.keySet().toString().replace("[", "").replace("]","").replaceAll(" ",""); this.rowMapper = createRowMapper(); } ``` ```java RowMapper createRowMapper() { return new RowMapper() { public T mapRow(ResultSet rs, int rowNum) throws SQLException { try { T t = entityClass.newInstance(); ResultSetMetaData meta = rs.getMetaData(); int columns = meta.getColumnCount(); String columnName; for (int i = 1; i <= columns; i++) { Object value = rs.getObject(i); columnName = meta.getColumnName(i); fillBeanFieldValue(t,columnName,value); } return t; }catch (Exception e) { throw new RuntimeException(e); } } }; } ``` ``` static { //只要这里写了的,默认支持自动类型转换 Class[] classes = { boolean.class, Boolean.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class, String.class, Date.class, Timestamp.class, BigDecimal.class }; SUPPORTED_SQL_OBJECTS.addAll(Arrays.asList(classes)); } ``` ```java void set(Object target, Object value) throws Exception { if (enumClass != null && value != null) { value = Enum.valueOf(enumClass, (String) value); } //BeanUtils.setProperty(target, fieldName, value); try { if(value != null){ setter.invoke(target, setter.getParameterTypes()[0].cast(value)); } } catch (Exception e) { e.printStackTrace(); /** * 出错原因如果是boolean字段 mysql字段类型 设置tinyint(1) */ System.err.println(fieldName + "--" + value); } } ``` ##### 读写分离 ```java @Resource(name="dynamicDataSource") public void setDataSource(DataSource dataSource) { this.dataSource = (DynamicDataSource)dataSource; this.setDataSourceReadOnly(dataSource); this.setDataSourceWrite(dataSource); } ``` ## 第四章:动态数据源切换底层原理 动态数据源切换是实现读写分离、分库分表的核心技术。本框架基于Spring的`AbstractRoutingDataSource`实现。 **核心原理:** 1. Spring在执行SQL前会调用`determineCurrentLookupKey()`获取数据源key 2. 使用ThreadLocal存储当前线程的数据源标识 3. 通过AOP在方法执行前切换数据源 #### DynamicDataSource动态数据源 public class DynamicDataSource extends AbstractRoutingDataSource { ```java // 数据源上下文 private DynamicDataSourceEntry dataSourceEntry; /** * 决定使用哪个数据源 * Spring在获取Connection前会调用此方法 */ @Override protected Object determineCurrentLookupKey() { // 从ThreadLocal中获取数据源标识 return this.dataSourceEntry.get(); } public void setDataSourceEntry(DynamicDataSourceEntry dataSourceEntry) { this.dataSourceEntry = dataSourceEntry; } public DynamicDataSourceEntry getDataSourceEntry(){ return this.dataSourceEntry; } ``` 1. 应用启动时,配置多个数据源到targetDataSources 2. 请求到来时,通过dataSourceEntry.set()设置数据源key 3. Spring执行SQL前调用determineCurrentLookupKey() 4. 根据返回的key从targetDataSources中获取对应的DataSource 5. 使用该DataSource获取Connection执行SQL #### DynamicDataSourceEntry - 数据源上下文 public class DynamicDataSourceEntry { // 默认数据源 public final static String DEFAULT_SOURCE = null; // 使用ThreadLocal保证线程安全 private final static ThreadLocal local = new ThreadLocal(); /** * 获取当前线程的数据源标识 */ public String get() { return local.get(); } /** * 设置当前线程的数据源标识 */ public void set(String source) { local.set(source); } /** * 根据年份动态设置数据源(分库场景) */ public void set(int year) { local.set("DB_" + year); } /** * 还原为默认数据源 */ public void restore() { local.set(DEFAULT_SOURCE); } /** * 清空数据源标识 */ public void clear() { local.remove(); } - 每个线程都有独立的数据源标识 - 不同线程之间互不影响 - 避免并发问题 Spring配置文件 ```java ``` ```java #sysbase database mysql config #mysql.jdbc.driverClassName=com.mysql.jdbc.Driver #mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo?characterEncoding=UTF-8&rewriteBatchedStatements=true #mysql.jdbc.username=root #mysql.jdbc.password=123456 db2019.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver db2019.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2019?characterEncoding=UTF-8&rewriteBatchedStatements=true db2019.mysql.jdbc.username=root db2019.mysql.jdbc.password=123456 db2020.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver db2020.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2020?characterEncoding=UTF-8&rewriteBatchedStatements=true db2020.mysql.jdbc.username=root db2020.mysql.jdbc.password=123456 #alibaba druid config dbPool.initialSize=1 dbPool.minIdle=1 dbPool.maxActive=200 dbPool.maxWait=60000 dbPool.timeBetweenEvictionRunsMillis=60000 dbPool.minEvictableIdleTimeMillis=300000 dbPool.validationQuery=SELECT 'x' dbPool.testWhileIdle=true dbPool.testOnBorrow=false dbPool.testOnReturn=false dbPool.poolPreparedStatements=false dbPool.maxPoolPreparedStatementPerConnectionSize=20 dbPool.filters=stat,log4j,wall ``` ```java public boolean insertOne(Order order) throws Exception{ //约定优于配置 Date date = null; if(order.getCreateTime() == null){ date = new Date(); order.setCreateTime(date.getTime()); }else { date = new Date(order.getCreateTime()); } Integer dbRouter = Integer.valueOf(yearFormat.format(date)); System.out.println("自动分配到【DB_" + dbRouter + "】数据源"); this.dataSource.getDataSourceEntry().set(dbRouter); order.setCreateTimeFmt(fullDataFormat.format(date)); ///动态表 super.setTableName("t_order_" + dbRouter); Long orderId = super.insertAndReturnId(order); order.setId(orderId); return orderId > 0; } ```