本文节选自《Spring 5外围原理》
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")@Datapublic 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 + '}'; }}
优化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 =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.1 Page
定义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; //当前页中寄存的记录,类型个别为List private 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.2 ResultMsg
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 = 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.3 BaseDao
作为所有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.4 QueryRule
如果用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 Serializable{ private 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.5 Order
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”可获取残缺源码。
本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『 Tom弹架构 』可获取更多技术干货!
原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!