Custom orm of 30 classes handwritten spring core principles (Part 1) (6)

Tom bomb architecture 2022-01-26 13:57:25 阅读数:122

custom orm classes handwritten spring
This article is excerpted from 《Spring 5 The core principle 》

1 Overview of implementation ideas

1.1 from ResultSet Speaking of

Speaking of ResultSet, Yes Java Development experience “ buddy ” Nature is the most familiar , But I believe for most people, it's also “ The most familiar stranger ”. from ResultSet Value taking operation, everyone will , such as :


private static List<Member> select(String sql) {
List<Member> result = new ArrayList<>();
Connection con = null;
PreparedStatement pstm = null;
ResultSet rs = null;
try {
//1. Load driver class
Class.forName("com.mysql.jdbc.Driver");
//2. Establishing a connection
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo", "root","123456");
//3. Create a statement set
pstm = con.prepareStatement(sql);
//4. Execution statement set
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. Get the result set
}catch (Exception e){
e.printStackTrace();
}
//6. Close result set 、 Close statement set 、 Close the connection
finally {
try {
rs.close();
pstm.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result;
}

The above is our routine operation before using the framework . With the increase of business and development , In the data persistence layer, such repeated code occurs very frequently . therefore , We thought of separating non functional code from business code . The first thing we think about is ResultSet Code logic separation of encapsulated data , Add one more mapperRow() Method , Specialized in the encapsulation of results , The code is as follows :


private static List<Member> select(String sql) {
List<Member> result = new ArrayList<>();
Connection con = null;
PreparedStatement pstm = null;
ResultSet rs = null;
try {
//1. Load driver class
Class.forName("com.mysql.jdbc.Driver");
//2. Establishing a connection
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo", "root","123456");
//3. Create a statement set
pstm = con.prepareStatement(sql);
//4. Execution statement set
rs = pstm.executeQuery();
while (rs.next()){
Member instance = mapperRow(rs,rs.getRow());
result.add(instance);
}
//5. Get the result set
}catch (Exception e){
e.printStackTrace();
}
//6. Close result set 、 Close statement set 、 Close the connection
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;
}

But in a real business scenario , Such code logic repetition rate is too high , The above transformation can only be applied Member class , Another entity class has to be re encapsulated , A smart programmer certainly won't write one for every entity class through pure manual labor mapperRow() Method , You will think of a code reuse scheme . We might as well do such a transformation .
First create Member class :


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 +
'}';
}
}

Optimize JDBC operation :


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. Load driver class
Class.forName("com.mysql.jdbc.Driver");
//2. Establishing a connection
con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo? characterEncoding=UTF-8&rewriteBatchedStatements=true","root","123456");
// Find the attribute name according to the class name
Map<String,String> columnMapper = new HashMap<String,String>();
// Find the field name according to the attribute name
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 {
// The default is the field name 、 Consistent property names
columnMapper.put(fieldName, fieldName);
fieldMapper.put(fieldName,fieldName);
}
}
//3. Create a statement set
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 + "");
}
// Others are not listed here , We'll write it later ORM The framework will be improved
}
}
System.out.println(sql + where.toString());
pstm = con.prepareStatement(sql + where.toString());
//4. Execution statement set
rs = pstm.executeQuery();
// Metadata ?
// All additional information other than processing real values is saved
int columnCounts = rs.getMetaData().getColumnCount();
while (rs.next()){
Object instance = entityClass.newInstance();
for (int i = 1; i <= columnCounts; i++) {
// Entity class property name , The field name of the corresponding database table
// You can get all the fields of the entity class through the reflection mechanism
// from rs Get the class name under the current cursor
String columnName = rs.getMetaData().getColumnName(i);
// It could be private
Field field = entityClass.getDeclaredField(columnMapper.get(columnName));
field.setAccessible(true);
field.set(instance,rs.getObject(columnName));
}
result.add(instance);
}
//5. Get the result set
}catch (Exception e){
e.printStackTrace();
}
//6. Close result set 、 Close statement set 、 Close the connection
finally {
try {
rs.close();
pstm.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result;
}

The above skillfully uses the reflection mechanism to read Class Information and Annotation Information , Associatively map and assign values to the columns in the database table and the fields in the class , To reduce duplicate code .

1.2 Why ORM frame

Through the previous explanation , We already know ORM The basic implementation principle of the framework .ORM Refers to object relational mapping (Object Relation Mapping), Mapping is not just object values , And the relationship between objects , For example, one to many 、 Many to many 、 A one-to-one table relationship . Now on the market ORM There are also many frameworks , There are well-known Hibernate、Spring JDBC、MyBatis、JPA etc. . Here's a brief summary , As shown in the following table .

name features describe
Hibernate Fully automatic ( block ) You don't need to write a sentence SQL
MyBatis semi-automatic ( block ) Hands are one , Support simple mapping , Complex relationships need to be written by yourself SQL
Spring JDBC Pure manual ( block ) be-all SQL Write it yourself , It helps us design a standard process

Since there are so many choices on the market , Why should I write it myself ORM What about the framework ?
This has to start with my experience as an architect in an airborne . The biggest problem faced by airborne is how to get the team “ friends ” The trust of the . at that time , The team has a total of 8 people , Everyone's level is uneven , Some people haven't even touched MySQL, Such as Redis Wait for caching middleware, let alone . Basically only use Hibernate Of CRUD, And it has affected the system performance . Due to the tight schedule , No time and energy to do systematic training for the team , Also in order to give consideration to controllability , So there is self research ORM The idea of a framework . I made such a top-level design , To reduce team “ friends ” The cost of saving interest , Unified parameters of top-level interface 、 Uniform return value , As follows .

(1) The interface model of the specified query method is :


/**
* To obtain a list of
* @param queryRule Query criteria
* @return
*/
List<T> select(QueryRule queryRule) throws Exception;
/**
* Get paging results
* @param queryRule Query criteria
* @param pageNo Page number
* @param pageSize Number of entries per page
* @return
*/
Page<?> select(QueryRule queryRule,int pageNo,int pageSize) throws Exception;
/**
* according to SQL To obtain a list of
* @param sql SQL sentence
* @param args Parameters
* @return
*/
List<Map<String,Object>> selectBySql(String sql, Object... args) throws Exception;
/**
* according to SQL Get paging
* @param sql SQL sentence
* @param pageNo Page number
* @param pageSize Number of entries per page
* @return
*/
Page<Map<String,Object>> selectBySqlToPage(String sql, Object [] param, int pageNo, int pageSize) throws Exception;

(2) The interface model that specifies the deletion method is :


/**
* Delete a record
* @param entity entity Medium ID Can't be empty , If ID It's empty , Other conditions cannot be empty , All are empty and will not be executed
* @return
*/
boolean delete(T entity) throws Exception;
/**
* Batch deletion
* @param list
* @return Returns the number of affected rows
* @throws Exception
*/
int deleteAll(List<T> list) throws Exception;

(3) The interface model that specifies the insertion method is :


/**
* Insert a record and return the inserted ID
* @param entity as long as entity It's not equal to null, Just insert
* @return
*/
PK insertAndReturnId(T entity) throws Exception;
/**
* Insert a record and add it automatically ID
* @param entity
* @return
* @throws Exception
*/
boolean insert(T entity) throws Exception;
/**
* Batch insert
* @param list
* @return Returns the number of affected rows
* @throws Exception
*/
int insertAll(List<T> list) throws Exception;

(4) The interface model specifying the modification method is :


/**
* Modify a record
* @param entity entity Medium ID Can't be empty , If ID It's empty , Other conditions cannot be empty , All are empty and will not be executed
* @return
* @throws Exception
*/
boolean update(T entity) throws Exception;

Take advantage of this basic API, Later, I based on Redis、MongoDB、ElasticSearch、Hive、HBase Each package has a set , So as to reduce the learning cost of the team , It also greatly improves the controllability of the program , More convenient for unified monitoring .

2 Build the infrastructure

2.1 Page

Definition Page The main purpose of class is to provide top-level support for the unified return results of later paging queries , Its main functions include the encapsulation of paging logic 、 Paging data .


package javax.core.common;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Paging object , Contains the current page data and paging information , Such as the total number of records
* Able to support and JQuery EasyUI Direct docking , Able to support and BootStrap Table Direct docking
*/
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; // Records per page
private long start; // The first data on the current page is in List Position in , from 0 Start
private List<T> rows; // Records stored in the current page , The type is generally List
private long total; // Total number of records
/**
* Construction method , Construct only empty pages
*/
public Page() {
this(0, 0, DEFAULT_PAGE_SIZE, new ArrayList<T>());
}
/**
* Default constructor
*
* @param start The starting position of the data on this page in the database
* @param totalSize Total number of records in the database
* @param pageSize Capacity of this page
* @param rows This page contains data
*/
public Page(long start, long totalSize, int pageSize, List<T> rows) {
this.pageSize = pageSize;
this.start = start;
this.total = totalSize;
this.rows = rows;
}
/**
* Take the total number of records
*/
public long getTotal() {
return this.total;
}
public void setTotal(long total) {
this.total = total;
}
/**
* Take the total number of pages
*/
public long getTotalPageCount() {
if (total % pageSize == 0){
return total / pageSize;
}else{
return total / pageSize + 1;
}
}
/**
* Take the data capacity of each page
*/
public int getPageSize() {
return pageSize;
}
/**
* Take the record in the current page
*/
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
/**
* Take the current page number of this page , Page number from 1 Start
*/
public long getPageNo() {
return start / pageSize + 1;
}
/**
* Does this page have a next page
*/
public boolean hasNextPage() {
return this.getPageNo() < this.getTotalPageCount() - 1;
}
/**
* Whether this page has a previous page
*/
public boolean hasPreviousPage() {
return this.getPageNo() > 1;
}
/**
* Get the position of the first data on any page in the dataset , The number of entries per page uses the default value
*
* @see #getStartOfPage(int,int)
*/
protected static int getStartOfPage(int pageNo) {
return getStartOfPage(pageNo, DEFAULT_PAGE_SIZE);
}
/**
* Get the position of the first data on any page in the dataset
*
* @param pageNo from 1 Start page number
* @param pageSize Number of records per page
* @return The first data on this page
*/
public static int getStartOfPage(int pageNo, int pageSize) {
return (pageNo - 1) * pageSize;
}
}

2.2 ResultMsg

ResultMsg Class is mainly the top-level design for unified return results , It mainly includes the status code 、 Result description and return data .


package javax.core.common;
import java.io.Serializable;
// Bottom design
public class ResultMsg<T> implements Serializable {
private static final long serialVersionUID = 2635002588308355785L;
private int status; // Status code , The return code of the system
private String msg; // Explanation of status code
private T data; // Put any result
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

As all BaseDao The top-level interface of the persistence framework , The main definition is 、 Delete 、 Change 、 Check the unified parameter list and return value .


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> {
/**
* To obtain a list of
* @param queryRule Query criteria
* @return
*/
List<T> select(QueryRule queryRule) throws Exception;
/**
* Get paging results
* @param queryRule Query criteria
* @param pageNo Page number
* @param pageSize Number of entries per page
* @return
*/
Page<?> select(QueryRule queryRule,int pageNo,int pageSize) throws Exception;
/**
* according to SQL To obtain a list of
* @param sql SQL sentence
* @param args Parameters
* @return
*/
List<Map<String,Object>> selectBySql(String sql, Object... args) throws Exception;
/**
* according to SQL Get paging
* @param sql SQL sentence
* @param pageNo Page number
* @param pageSize Number of entries per page
* @return
*/
Page<Map<String,Object>> selectBySqlToPage(String sql, Object [] param, int pageNo, int pageSize) throws Exception;
/**
* Delete a record
* @param entity entity Medium ID Can't be empty , If ID It's empty , Other conditions cannot be empty , If it is empty, it will not be executed
* @return
*/
boolean delete(T entity) throws Exception;
/**
* Batch deletion
* @param list
* @return Returns the number of affected rows
* @throws Exception
*/
int deleteAll(List<T> list) throws Exception;
/**
* Insert a record and return the inserted ID
* @param entity as long as entity It's not equal to null, Just perform the insertion operation
* @return
*/
PK insertAndReturnId(T entity) throws Exception;
/**
* Insert a record and add it automatically ID
* @param entity
* @return
* @throws Exception
*/
boolean insert(T entity) throws Exception;
/**
* Batch insert
* @param list
* @return Returns the number of affected rows
* @throws Exception
*/
int insertAll(List<T> list) throws Exception;
/**
* Modify a record
* @param entity entity Medium ID Can't be empty , If ID It's empty , Other conditions cannot be empty , If it is empty, it will not be executed
* @return
* @throws Exception
*/
boolean update(T entity) throws Exception;
}

2.4 QueryRule

If you use QueryRule Class to build query criteria , Users do not need to write when making conditional queries SQL, Implement business code and SQL decoupling .


package com.gupaoedu.vip.orm.framework;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* QueryRule, The main function is used to construct query conditions
*/
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();
}
/**
* Add ascending rules
* @param propertyName
* @return
*/
public QueryRule addAscOrder(String propertyName) {
this.ruleList.add(new Rule(ASC_ORDER, propertyName));
return this;
}
/**
* Add descending rule
* @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; // Type of rule
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 Class is mainly used to encapsulate sorting rules , The code is as follows :


package com.gupaoedu.vip.orm.framework;
/**
* SQL Sort components
*/
public class Order {
private boolean ascending; // Ascending or descending
private String propertyName; // Which field is in ascending order , Which field is in descending order
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);
}
}

Because of the length , Specific operation classes continue in the next article .

Pay attention to WeChat public number 『 Tom Bomb architecture 』 reply “Spring” The complete source code is available .

This paper is about “Tom Bomb architecture ” original , Reprint please indicate the source . Technology is about sharing , I share my happiness ! If you have any suggestions, you can also leave comments or private letters , Your support is the driving force for me to adhere to my creation . Pay attention to WeChat public number 『 Tom Bomb architecture 』 More technical dry goods are available !

Originality is not easy. , Persistence is cool , You can see it here , Little buddy, remember to praise 、 Collection 、 Looking at , One click three times plus attention ! If you think the content is too dry , You can share and forward it to friends !

copyright:author[Tom bomb architecture],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/01/202201261357196490.html