#yyds干货盘点#30个类手写Spring核心原理之自定义ORM(上)

博观而约取,厚积而薄发。这篇文章主要讲述#yyds干货盘点#30个类手写Spring核心原理之自定义ORM(上)相关的知识,希望能为你提供帮助。
1实现思路概述 1.1从ResultSet说起
说到ResultSet,有java开发经验的“小伙伴”自然最熟悉不过了,不过我相信对于大多数人来说也算是“最熟悉的陌生人”。从ResultSet取值操作大家都会,比如:

private static List< Member> select(String sql) List< Member> 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;

以上我们在没有使用框架以前的常规操作。随着业务和开发量的增加,在数据持久层这样的重复代码出现频次非常高。因此,我们就想到将非功能性代码和业务代码进行分离。我们首先想到将ResultSet封装数据的代码逻辑分离,增加一个mapperRow()方法,专门处理对结果的封装,代码如下:
private static List< Member> select(String sql) List< Member> 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 = mapperRow(rs,rs.getRow()); 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; private static Member mapperRow(ResultSet rs, int i) throws Exception Member instance = new Member(); instance.setId(rs.getLong("id")); instance.setName(rs.getString("name")); instance.setAge(rs.getInt("age")); instance.setAddr(rs.getString("addr")); return instance;

但在真实的业务场景中,这样的代码逻辑重复率实在太高,上面的改造只能应用Member类,换一个实体类又要重新封装,聪明的程序员肯定不会通过纯体力劳动给每一个实体类写一个mapperRow()方法,一定会想到代码复用方案。我们不妨来做这样一个改造。
先创建Member类:
package com.gupaoedu.vip.orm.demo.entity; import lombok.Data; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; @Entity @Table(name="t_member") @Data public class Member implements Serializable @Id private Long id; private String name; private String addr; private Integer age; @Override public String toString() return "Member" + "id=" + id + ", name=" + name + \\ + ", addr=" + addr + \\ + ", age=" + age + ;

【#yyds干货盘点#30个类手写Spring核心原理之自定义ORM(上)】优化JDBC操作:
public static void main(String[] args) Member condition = new Member(); condition.setName("Tom"); condition.setAge(19); List< ?> result =select(condition); System.out.println(Arrays.toString(result.toArray())); private static List< ?> select(Object condition) List< Object> 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? characterEncoding=UTF-8& rewriteBatchedStatements=true","root","123456"); //根据类名找属性名 Map< String,String> columnMapper = new HashMap< String,String> (); //根据属性名找字段名 Map< String,String> fieldMapper = new HashMap< String,String> (); 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(); columnMapper.put(columnName,fieldName); fieldMapper.put(fieldName,columnName); else //默认就是字段名、属性名一致 columnMapper.put(fieldName, fieldName); fieldMapper.put(fieldName,fieldName); //3. 创建语句集 Table table = entityClass.getAnnotation(Table.class); String sql = "select * from " + table.name(); StringBuffer where = new StringBuffer(" where 1=1 "); for (Field field : fields) Object value =https://www.songbingjia.com/android/field.get(condition); if(null != value) if(String.class == field.getType()) where.append(" and " + fieldMapper.get(field.getName()) + " = " + value + ""); else where.append(" and " + fieldMapper.get(field.getName()) + " = " + value + ""); //其他的在这里就不一一列举,后面我们手写ORM框架时会完善System.out.println(sql + where.toString()); pstm =con.prepareStatement(sql + where.toString()); //4. 执行语句集 rs = pstm.executeQuery(); //元数据? //保存了处理真正数值以外的所有附加信息 int columnCounts = rs.getMetaData().getColumnCount(); while (rs.next()) Object instance = entityClass.newInstance(); for (int i = 1; i < = columnCounts; i++) //实体类属性名,对应数据库表的字段名 //可以通过反射机制拿到实体类的所有字段//从rs中取得当前这个游标下的类名 String columnName = rs.getMetaData().getColumnName(i); //有可能是私有的 Field field = entityClass.getDeclaredField(columnMapper.get(columnName)); field.setAccessible(true); field.set(instance,rs.getObject(columnName)); 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;

上面巧妙地利用反射机制读取Class信息和Annotation信息,将数据库表中的列和类中的字段进行关联映射并赋值,以减少重复代码。
1.2为什么需要ORM框架
通过前面的讲解,我们已经了解ORM框架的基本实现原理。ORM是指对象关系映射(Object Relation Mapping),映射的不只是对象值,还有对象与对象之间的关系,例如一对多、多对多、一对一这样的表关系。现在市面上ORM框架也非常多,有大家所熟知的Hibernate、Spring JDBC、MyBatis、JPA等。在这里做一个简单的总结,如下表所示。
名称 特征 描述
Hibernate 全自动(挡) 不需要写一句SQL
MyBatis 半自动(挡) 手自一体,支持简单的映射,复杂关系需要自己写SQL
Spring JDBC 纯手动(挡) 所有的SQL都要自己写,它帮我们设计了一套标准流程
既然市面上有这么多选择,我为什么还要自己写 ORM框架呢?
这得从我的一次空降担任架构师的经验说起。空降面临最大的难题就是如何取得团队“小伙伴们”的信任。当时,团队总共就8人,每个人的水平参差不齐,甚至有些人还没接触过MySQL,诸如Redis等缓存中间件更不用说了。基本只会使用Hibernate的CRUD,而且已经影响到了系统性能。由于工期紧张,没有时间和精力给团队做系统培训,也为了兼顾可控性,于是就产生了自研ORM框架的想法。我做了这样的顶层设计,以降低团队“小伙伴们”的存息成本,顶层接口统一参数、统一返回值,具体如下。
(1)规定查询方法的接口模型为:
/** * 获取列表 * @param queryRule 查询条件 * @return */ List< T> select(QueryRule queryRule) throws Exception; /** * 获取分页结果 * @param queryRule 查询条件 * @param pageNo 页码 * @param pageSize 每页条数 * @return */ Page< ?> select(QueryRule queryRule,int pageNo,int pageSize) throws Exception; /** * 根据SQL获取列表 * @param sql SQL语句 * @param args 参数 * @return */ List< Map< String,Object> > selectBySql(String sql, Object... args) throws Exception; /** * 根据SQL获取分页 * @param sql SQL语句 * @param pageNo 页码 * @param pageSize 每页条数 * @return */ Page< Map< String,Object> > selectBySqlToPage(String sql, Object [] param, int pageNo, int pageSize) throws Exception;

(2)规定删除方法的接口模型为:
/** * 删除一条记录 * @param entity entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空不予执行 * @return */ boolean delete(T entity) throws Exception; /** * 批量删除 * @param list * @return 返回受影响的行数 * @throws Exception */ int deleteAll(List< T> list) throws Exception;

(3)规定插入方法的接口模型为:
/** * 插入一条记录并返回插入后的ID * @param entity 只要entity不等于null,就执行插入 * @return */ PK insertAndReturnId(T entity) throws Exception; /** * 插入一条记录自增ID * @param entity * @return * @throws Exception */ boolean insert(T entity) throws Exception; /** * 批量插入 * @param list * @return 返回受影响的行数 * @throws Exception */ int insertAll(List< T> list) throws Exception;

(4)规定修改方法的接口模型为:
/** *修改一条记录 * @param entity entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空不予执行 * @return * @throws Exception */ boolean update(T entity) throws Exception;

利用这套基础的API,后面我又基于Redis、MongoDB、ElasticSearch、Hive、HBase各封装了一套,以此来降低团队的学习成本,也大大提升了程序的可控性,更方便统一监控。
2搭建基础架构 2.1Page
定义Page类的主要目的是为后面的分页查询统一返回结果做顶层支持,其主要功能包括分页逻辑的封装、分页数据。
package javax.core.common; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 分页对象,包含当前页数据及分页信息,如总记录数 * 能够支持和JQuery EasyUI直接对接,能够支持和BootStrap Table直接对接 */ public class Page< T> implements Serializable private static final long serialVersionUID = 1L; private static final int DEFAULT_PAGE_SIZE = 20; private int pageSize = DEFAULT_PAGE_SIZE; //每页的记录数private long start; //当前页第一条数据在List中的位置,从0开始private List< T> rows; //当前页中存放的记录,类型一般为Listprivate long total; //总记录数/** * 构造方法,只构造空页 */ public Page() this(0, 0, DEFAULT_PAGE_SIZE, new ArrayList< T> ()); /** * 默认构造方法 * * @param start 本页数据在数据库中的起始位置 * @param totalSize 数据库中总记录条数 * @param pageSize 本页容量 * @param rows 本页包含的数据 */ public Page(long start, long totalSize, int pageSize, List< T> rows) this.pageSize = pageSize; this.start = start; this.total = totalSize; this.rows = rows; /** * 取总记录数 */ public long getTotal() return this.total; public void setTotal(long total) this.total = total; /** * 取总页数 */ public long getTotalPageCount() if (total % pageSize == 0) return total / pageSize; else return total / pageSize + 1; /** * 取每页数据容量 */ public int getPageSize() return pageSize; /** * 取当前页中的记录 */ public List< T> getRows() return rows; public void setRows(List< T> rows) this.rows = rows; /** * 取该页的当前页码,页码从1开始 */ public long getPageNo() return start / pageSize + 1; /** * 该页是否有下一页 */ public boolean hasNextPage() return this.getPageNo() < this.getTotalPageCount() - 1; /** * 该页是否有上一页 */ public boolean hasPreviousPage() return this.getPageNo() > 1; /** * 获取任意一页第一条数据在数据集中的位置,每页条数使用默认值 * * @see #getStartOfPage(int,int) */ protected static int getStartOfPage(int pageNo) return getStartOfPage(pageNo, DEFAULT_PAGE_SIZE); /** * 获取任意一页第一条数据在数据集中的位置 * * @param pageNo 从1开始的页号 * @param pageSize 每页记录条数 * @return 该页第一条数据 */ public static int getStartOfPage(int pageNo, int pageSize) return (pageNo - 1) * pageSize;

2.2ResultMsg
ResultMsg类主要是为统一返回结果做的顶层设计,主要包括状态码、结果说明内容和返回数据。
package javax.core.common; import java.io.Serializable; //底层设计 public class ResultMsg< T> implements Serializable private static final long serialVersionUID = 2635002588308355785L; private int status; //状态码,系统的返回码 private String msg; //状态码的解释 private T data; //放任意结果public ResultMsg() public ResultMsg(int status) this.status = status; public ResultMsg(int status, String msg) this.status = status; this.msg = msg; public ResultMsg(int status, T data) this.status = status; this.data = https://www.songbingjia.com/android/data; public ResultMsg(int status, String msg, T data) this.status = status; this.msg = msg; this.data = data; public int getStatus() return status; public void setStatus(int status) this.status = status; public String getMsg() return msg; public void setMsg(String msg) this.msg = msg; public T getData() return data; public void setData(T data) this.data = data;

2.3BaseDao
作为所有BaseDao持久化框架的顶层接口,主要定义增、删、改、查统一的参数列表和返回值。
package javax.core.common.jdbc; import com.gupaoedu.vip.orm.framework.QueryRule; import javax.core.common.Page; import java.util.List; import java.util.Map; public interface BaseDao< T,PK> /** * 获取列表 * @param queryRule 查询条件 * @return */ List< T> select(QueryRule queryRule) throws Exception; /** * 获取分页结果 * @param queryRule 查询条件 * @param pageNo 页码 * @param pageSize 每页条数 * @return */ Page< ?> select(QueryRule queryRule,int pageNo,int pageSize) throws Exception; /** * 根据SQL获取列表 * @param sql SQL语句 * @param args 参数 * @return */ List< Map< String,Object> > selectBySql(String sql, Object... args) throws Exception; /** * 根据SQL获取分页 * @param sql SQL语句 * @param pageNo 页码 * @param pageSize 每页条数 * @return */ Page< Map< String,Object> > selectBySqlToPage(String sql, Object [] param, int pageNo, int pageSize) throws Exception; /** * 删除一条记录 * @param entity entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空则不予执行 * @return */ boolean delete(T entity) throws Exception; /** * 批量删除 * @param list * @return 返回受影响的行数 * @throws Exception */ int deleteAll(List< T> list) throws Exception; /** * 插入一条记录并返回插入后的ID * @param entity 只要entity不等于null,就执行插入操作 * @return */ PK insertAndReturnId(T entity) throws Exception; /** * 插入一条记录自增ID * @param entity * @return * @throws Exception */ boolean insert(T entity) throws Exception; /** * 批量插入 * @param list * @return 返回受影响的行数 * @throws Exception */ int insertAll(List< T> list) throws Exception; /** *修改一条记录 * @param entity entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空则不予执行 * @return * @throws Exception */ boolean update(T entity) throws Exception;

2.4QueryRule
如果用QueryRule类来构建查询条件,用户在做条件查询时不需要手写SQL,实现业务代码与SQL解耦。
package com.gupaoedu.vip.orm.framework; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * QueryRule,主要功能用于构造查询条件 */ public final class QueryRule implements Serializableprivate static final long serialVersionUID = 1L; public static final int ASC_ORDER = 101; public static final int DESC_ORDER = 102; public static final int LIKE = 1; public static final int IN = 2; public static final int NOTIN = 3; public static final int BETWEEN = 4; public static final int EQ = 5; public static final int NOTEQ = 6; public static final int GT = 7; public static final int GE = 8; public static final int LT = 9; public static final int LE = 10; public static final int ISNULL = 11; public static final int ISNOTNULL = 12; public static final int ISEMPTY = 13; public static final int ISNOTEMPTY = 14; public static final int AND = 201; public static final int OR = 202; private List< Rule> ruleList = new ArrayList< Rule> (); private List< QueryRule> queryRuleList = new ArrayList< QueryRule> (); private String propertyName; private QueryRule() private QueryRule(String propertyName) this.propertyName = propertyName; public static QueryRule getInstance() return new QueryRule(); /** * 添加升序规则 * @param propertyName * @return */ public QueryRule addAscOrder(String propertyName) this.ruleList.add(new Rule(ASC_ORDER, propertyName)); return this; /** * 添加降序规则 * @param propertyName * @return */ public QueryRule addDescOrder(String propertyName) this.ruleList.add(new Rule(DESC_ORDER, propertyName)); return this; public QueryRule andIsNull(String propertyName) this.ruleList.add(new Rule(ISNULL, propertyName).setAndOr(AND)); return this; public QueryRule andIsNotNull(String propertyName) this.ruleList.add(new Rule(ISNOTNULL, propertyName).setAndOr(AND)); return this; public QueryRule andIsEmpty(String propertyName) this.ruleList.add(new Rule(ISEMPTY, propertyName).setAndOr(AND)); return this; public QueryRule andIsNotEmpty(String propertyName) this.ruleList.add(new Rule(ISNOTEMPTY, propertyName).setAndOr(AND)); return this; public QueryRule andLike(String propertyName, Object value) this.ruleList.add(new Rule(LIKE, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andEqual(String propertyName, Object value) this.ruleList.add(new Rule(EQ, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andBetween(String propertyName, Object... values) this.ruleList.add(new Rule(BETWEEN, propertyName, values).setAndOr(AND)); return this; public QueryRule andIn(String propertyName, List< Object> values) this.ruleList.add(new Rule(IN, propertyName, new Object[]values ).setAndOr(AND)); return this; public QueryRule andIn(String propertyName, Object... values) this.ruleList.add(new Rule(IN, propertyName, values).setAndOr(AND)); return this; public QueryRule andNotIn(String propertyName, List< Object> values) this.ruleList.add(new Rule(NOTIN, propertyName, new Object[]values ).setAndOr(AND)); return this; public QueryRule orNotIn(String propertyName, Object... values) this.ruleList.add(new Rule(NOTIN, propertyName, values).setAndOr(OR)); return this; public QueryRule andNotEqual(String propertyName, Object value) this.ruleList.add(new Rule(NOTEQ, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andGreaterThan(String propertyName, Object value) this.ruleList.add(new Rule(GT, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andGreaterEqual(String propertyName, Object value) this.ruleList.add(new Rule(GE, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andLessThan(String propertyName, Object value) this.ruleList.add(new Rule(LT, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule andLessEqual(String propertyName, Object value) this.ruleList.add(new Rule(LE, propertyName, new Object[]value ).setAndOr(AND)); return this; public QueryRule orIsNull(String propertyName) this.ruleList.add(new Rule(ISNULL, propertyName).setAndOr(OR)); return this; public QueryRule orIsNotNull(String propertyName) this.ruleList.add(new Rule(ISNOTNULL, propertyName).setAndOr(OR)); return this; public QueryRule orIsEmpty(String propertyName) this.ruleList.add(new Rule(ISEMPTY, propertyName).setAndOr(OR)); return this; public QueryRule orIsNotEmpty(String propertyName) this.ruleList.add(new Rule(ISNOTEMPTY, propertyName).setAndOr(OR)); return this; public QueryRule orLike(String propertyName, Object value) this.ruleList.add(new Rule(LIKE, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orEqual(String propertyName, Object value) this.ruleList.add(new Rule(EQ, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orBetween(String propertyName, Object... values) this.ruleList.add(new Rule(BETWEEN, propertyName, values).setAndOr(OR)); return this; public QueryRule orIn(String propertyName, List< Object> values) this.ruleList.add(new Rule(IN, propertyName, new Object[]values ).setAndOr(OR)); return this; public QueryRule orIn(String propertyName, Object... values) this.ruleList.add(new Rule(IN, propertyName, values).setAndOr(OR)); return this; public QueryRule orNotEqual(String propertyName, Object value) this.ruleList.add(new Rule(NOTEQ, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orGreaterThan(String propertyName, Object value) this.ruleList.add(new Rule(GT, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orGreaterEqual(String propertyName, Object value) this.ruleList.add(new Rule(GE, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orLessThan(String propertyName, Object value) this.ruleList.add(new Rule(LT, propertyName, new Object[]value ).setAndOr(OR)); return this; public QueryRule orLessEqual(String propertyName, Object value) this.ruleList.add(new Rule(LE, propertyName, new Object[]value ).setAndOr(OR)); return this; public List< Rule> getRuleList() return this.ruleList; public List< QueryRule> getQueryRuleList() return this.queryRuleList; public String getPropertyName() return this.propertyName; protected class Rule implements Serializable private static final long serialVersionUID = 1L; private int type; //规则的类型 private String property_name; private Object[] values; private int andOr = AND; public Rule(int paramInt, String paramString) this.property_name = paramString; this.type = paramInt; public Rule(int paramInt, String paramString, Object[] paramArrayOfObject) this.property_name = paramString; this.values = paramArrayOfObject; this.type = paramInt; public Rule setAndOr(int andOr) this.andOr = andOr; return this; public int getAndOr() return this.andOr; public Object[] getValues() return this.values; public int getType() return this.type; public String getPropertyName() return this.property_name;

2.5Order
Order类主要用于封装排序规则,代码如下:
package com.gupaoedu.vip.orm.framework; /** * SQL排序组件 */ public class Order private boolean ascending; //升序还是降序 private String propertyName; //哪个字段升序,哪个字段降序public String toString() return propertyName ++ (ascending ? "asc" : "desc"); /** * Constructor for Order. */ protected Order(String propertyName, boolean ascending) this.propertyName = propertyName; this.ascending = ascending; /** * Ascending order * * @param propertyName * @return Order */ public static Order asc(String propertyName) return new Order(propertyName, true); /** * Descending order * * @param propertyName * @return Order */ public static Order desc(String propertyName) return new Order(propertyName, false);

因篇幅原因,具体的操作类下一篇继续。
关注微信公众号『 Tom弹架构 』回复“Spring”可获取完整源码。
原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

    推荐阅读