共计 17950 个字符,预计需要花费 45 分钟才能阅读完成。
本文节选自《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 弹架构』回复“Spring”可获取残缺源码。
本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我高兴!如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源。关注微信公众号『Tom 弹架构』可获取更多技术干货!
原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!