30個類手寫Spring核心原理之自定義ORM(上)(6)
本文節選自《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彈架構 』可獲取更多技術乾貨!
原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太乾,可以分享轉發給朋友滋潤滋潤!
- 使用橋接模式設計複雜的訊息系統
- 為什麼MySQL索引結構採用B 樹?
- 為什麼Netty執行緒池預設大小為CPU核數的2倍
- 談談你對深克隆和淺克隆的理解
- 什麼是代理,為什麼要用動態代理?
- 什麼是零拷貝,Netty是如何實現的?
- 3分鐘輕鬆理解單執行緒下的HashMap工作原理
- 被面試官問爛的Spring AOP原理,你是怎麼答的?
- Spring為何需要三級快取解決迴圈依賴,而不是二級快取?
- 為什麼Spring中每個Bean都要定義作用域?
- 談談你對Spring Bean的理解
- 趣談裝飾器模式,讓你一輩子不會忘
- 掌握這些招數,你也能寫出HR眼中的高分簡歷
- MongoDB高階應用之資料轉存與恢復(5)
- 圖解MongoDB叢集部署原理(3)
- 爆肝30天,肝出來史上最透徹Spring原理和27道高頻面試題總結
- Spring核心原理之IoC容器初體驗(2)
- Spring核心原理分析之MVC九大元件(1)
- 30個類手寫Spring核心原理之動態資料來源切換(8)
- 【緊急】Log4j又發新版2.17.0,只有徹底搞懂漏洞原因,才能以不變應萬變,小白也能看懂