本文节选自《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")
@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 +
'}';
}
}

优化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弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

30个类手写Spring核心原理之自定义ORM(上)(6)的更多相关文章

  1. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

  2. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

  3. 30个类手写Spring核心原理之依赖注入功能(3)

    本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...

  4. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  5. 30个类手写Spring核心原理之Ioc顶层架构设计(2)

    本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...

  6. 30个类手写Spring核心原理之MVC映射功能(4)

    本文节选自<Spring 5核心原理> 接下来我们来完成MVC模块的功能,应该不需要再做说明.Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成 ...

  7. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  8. 手写Spring MVC

    闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...

  9. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

随机推荐

  1. vue + cesium开发(5) 搭建 vue + cesium开发环境(2)

    上vue+cesium开发(1)中,没有进行配置webpack,而是使用了插件进行代替,在使用过程中出现了一些未知BUG,影响体验,因此参考了官方文档对项目进行重新配置,使用了 copy-webpac ...

  2. 第十五章---JSON

    目录: (一)介绍 (二)Python 编码为 JSON 类型转换对应表 (三)JSON 解码为 Python 类型转换对应表 (四)实例 正文: (一)介绍 JSON (JavaScript Obj ...

  3. <C#任务导引教程>练习七

    //55,类的声明示例using System;class Date{    public int year;    public int month;    public int day;    p ...

  4. 【JavaSE】字符编码和存储编码

    字符编码和存储编码 2019-07-15  22:34:51  by冲冲 1. 英文字母和中文汉字在不同字符集编码下的字节数不同. 英文字母 字节数 : 1; 编码:GB2312 字节数 : 1; 编 ...

  5. html+css第十篇-命名

    命名:根据每块元素的主题 或者功能.在页面上的位置 php 每个单词中间以"_"隔开 #main_left_box{} 驼峰命名 从第二个单词开始每个单词的首字母大写 #mainL ...

  6. Swift-技巧(九)CGImage To CVPixelBuffer

    摘要 iOS 中图像的表现形式不只是 Image,还有更加底层的方式,比如 CVPixelBuffer 像素缓存形式,那么 CGImage 就可以转换为像素缓存的方式也是需要了解的. CGImage ...

  7. 彻底搞清楚 JavaScript 的原型和原型链

    JavaScript真的挺无语的,怪不得看了那么多的介绍文章还是一头雾水,直到自己终于弄懂了一点点之后才深有体会: 先从整体说起吧,发现没有基础做依据,那都是空中楼阁: 先从基础开始介绍吧,又发现基础 ...

  8. Atcoder Grand Contest 024 E - Sequence Growing Hard(dp+思维)

    题目传送门 典型的 Atcoder 风格的计数 dp. 题目可以转化为每次在序列中插入一个 \([1,k]\) 的数,共操作 \(n\) 次,满足后一个序列的字典序严格大于前一个序列,问有多少种操作序 ...

  9. Atcoder Grand Contest 020 E - Encoding Subsets(记忆化搜索+复杂度分析)

    Atcoder 题面传送门 & 洛谷题面传送门 首先先考虑如果没有什么子集的限制怎样计算方案数.明显就是一个区间 \(dp\),这个恰好一年前就做过类似的题目了.我们设 \(f_{l,r}\) ...

  10. Atcoder Grand Contest 031 D - A Sequence of Permutations(置换+猜结论)

    Atcoder 题面传送门 & 洛谷题面传送门 猜结论神题. 首先考虑探究题目中 \(f\) 函数的性质,\(f(p,q)_{p_i}=q_i\leftarrow f(p,q)\circ p= ...