The world's martial arts, only fast is not broken: how can back-end Java people improve their development efficiency?

For a Java back-end programmer, MyBatis, Hibernate, Data Jdbc, etc. are all our commonly used ORM frameworks. They work well sometimes, like simple CRUD, and the transaction support is great. But sometimes it is very cumbersome to use, such as a common development requirement that we will talk about next, and for this kind of requirement, this article will give a method that will improve the development efficiency by at least 100 times compared to directly using these ORMs (absolutely no exaggeration).

First the database has two tables

User table (user): (for simplicity, assume only 4 fields)

field name

type

meaning

id

bitint

User ID

name

varchar(45)

username

age

int

age

role_id

int

role id

Role table (role): (for simplicity, assuming only 2 fields)

field name

type

meaning

id

int

role id

name

varchar(45)

character name

Next, we need to implement a user query function

This query is a bit complicated and its requirements are as follows:

  • It can be queried by the username field, and the requirements are: Exact match (equal to a certain value) Full fuzzy match (including a given value) Post-fuzzy query (starting with ...) Pre-fuzzy query (end with ..) You can specify whether the above four matches can ignore case
  • It can be queried by age field, and the requirements are: Exact match (equal to a certain age) Greater than match (greater than a certain value) Less than match (less than a certain value) Interval match (a certain interval range)
  • Can be queried by role ID, requirement: exact match
  • It can be queried by user ID, requirements: the same age field
  • You can specify which columns to output only (for example, only query ID and username columns)
  • Support pagination (after each query, the page must display the total number of users who meet the conditions)
  • When querying, you can choose to sort by any field such as ID, username, age, etc.

How to write the backend interface?

Just imagine, for this kind of query, if the code in the back-end interface is written directly with MyBatis, Hibernate, and Data Jdbc, can it be implemented in 100 lines of code ?

Anyway, I don't have this confidence. Forget it, I'll directly confess, how to deal with this kind of demand backend with only one line of code .

Hands-on: only one line of code to achieve the above requirements

First of all, the key characters appear: Bean Searcher , it is a read-only ORM that focuses on advanced queries. For this kind of list retrieval, no matter it is simple or complex, it can be done with a single line of code! And it's also very lightweight and has no 3rd party dependencies (can be used in the same project as any other ORM).

Suppose the framework used in our project is Spring Boot (of course, Bean Searcher has no requirements for the framework, but it is more convenient to use in Spring Boot)

add dependencies

Maven :

<dependency>
    <groupId>com.ejlchina</groupId>
    <artifactId>bean-searcher-boot-starter</artifactId>
    <version>3.6.0</version>
</dependency>
复制代码

Gradle :

implementation 'com.ejlchina:bean-searcher-boot-starter:3.6.0'
复制代码

Then write an entity class to carry the results of the query

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u") 
public class User {

    private Long id;		// 用户ID(u.id)
    private String name;	// 用户名(u.name)
    private int age;		// 年龄(u.age)
    private int roleId;		// 角色ID(u.role_id)
    @DbField("r.name")		// 指明这个属性来自 role 表的 name 字段
    private String role;        // 角色名(r.name)

    // Getter and Setter ...
}
复制代码

Then you can write the user query interface

The interface path is called /user/index:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private MapSearcher mapSearcher;  // 注入检索器(由 bean-searcher-boot-starter 提供)

    @GetMapping("/index")
    public SearchResult<Map<String, Object>> index(HttpServletRequest request) {
    	// 这里咱们只写一行代码
        return mapSearcher.search(User.class, MapUtils.flat(request.getParameterMap()));
    }

}
复制代码

The MapUtils in the above code is a tool class provided by Bean Searcher. MapUtils.flat(request.getParameterMap()) is just to collect the request parameters from the front end uniformly, and then hand over the rest to the MapSearcher retriever. .

Is this the end? Then let's test this interface and see the effect.

(1) No parameter request

  • GET /user/index
  • Return result:
{
    "dataList": [           // 用户列表,默认返回第 0 页,默认分页大小为 15 (可配置)
        { "id": 1, "name": "Jack", "age": 25, "roleId": 1, "role": "普通用户" },
        { "id": 2, "name": "Tom", "age": 26, "roleId": 1, "role": "普通用户" },
        ...
    ],
    "totalCount": 100       // 用户总数
}
复制代码

(2) Paging request (page | size)

  • GET /user/index? page=2 & size=10
  • Return result: The structure is the same as (1) (only 10 items per page, return to page 2)

The parameter name size and page can be customized, page starts from 0 by default, can also be customized, and can be used in combination with other parameters

(3) Data sorting (sort | order)

  • GET /user/index? sort=age & order=desc
  • Return result: The structure is the same as (1) (only the dataList data list is output in descending order of age field)

The parameter names sort and order can be customized and used in combination with other parameters

(4) Specify (exclude) fields (onlySelect | selectExclude)

  • GET /user/index? onlySelect=id,name,role
  • GET /user/index? selectExclude=age,roleId
  • Return result: (The list only contains three fields: id, name and role)
{
    "dataList": [           // 用户列表,默认返回第 0 页(只包含 id,name,role 字段)
        { "id": 1, "name": "Jack", "role": "普通用户" },
        { "id": 2, "name": "Tom", "role": "普通用户" },
        ...
    ],
    "totalCount": 100       // 用户总数
}
复制代码

The parameter name onlySelect and selectExclude can be customized and used in combination with other parameters

(5) Field filtering (op = eq)

  • GET /user/index? age=20
  • GET /user/index? age=20 & age-op=eq
  • Return result: The structure is the same as (1) (but only the data of age = 20 is returned)

The parameter age-op = eq indicates that the field is eq (abbreviation of Equal), indicating that the relationship between the parameter age and the parameter value 20 is Equal. Since Equal is a default relationship, age-op = eq can also be used omit

The suffix -op of the parameter name age-op can be customized, and can be used in combination with other field parameters and the parameters listed above (paging, sorting, and specifying fields). The field parameters listed below are also the same and will not be repeated.

(6) Field filtering (op = ne)

  • GET /user/index? age=20 & age-op=ne
  • Return result: The structure is the same as (1) (but only the data of age != 20 is returned, ne is the abbreviation of NotEqual)

(7) Field filtering (op = ge)

  • GET /user/index? age=20 & age-op=ge
  • Return result: The structure is the same as (1) (but only the data of age >= 20 is returned, ge is the abbreviation of GreateEqual)

(8) Field filtering (op = le)

  • GET /user/index? age=20 & age-op=le
  • Return result: The structure is the same as (1) (but only the data of age <= 20 is returned, le is the abbreviation of LessEqual)

(9) Field filtering (op = gt)

  • GET /user/index? age=20 & age-op=gt
  • Return result: The structure is the same as (1) (but only data with age > 20 is returned, gt is the abbreviation of GreateThan)

(10) Field filtering (op = lt)

  • GET /user/index? age=20 & age-op=lt
  • Return result: The structure is the same as (1) (but only the data of age < 20 is returned, lt is the abbreviation of LessThan)

(11) Field filtering (op = bt)

  • GET /user/index? age-0=20 & age-1=30 & age-op=bt
  • GET /user/index?age=[20,30] & age-op=bt ( simplified version , [20,30] requires UrlEncode, see below)
  • Return result: The structure is the same as (1) (but only the data of 20 <= age <= 30 is returned, bt is the abbreviation of Between)

The parameter age-0 = 20 means that the 0th parameter value of age is 20. The age = 20 mentioned above is actually a shorthand for age-0 = 20. Another: the hyphen in the parameter name age-0 and age-1 - can be customized.

(12) Field filtering (op = il)

  • GET /user/index? age-0=20 & age-1=30 & age-2=40 & age-op=il
  • GET /user/index? age=[20,30,40] & age-op=il ( simplified version , [20,30,40] requires UrlEncode, see below)
  • Return result: The structure is the same as (1) (but only returns the data of age in (20, 30, 40), il is the abbreviation of InList, which means there are multiple values)

(13) Field filtering (op = ct)

  • GET /user/index? name=Jack & name-op=ct
  • Return result: The structure is the same as (1) (but only the data whose name contains Jack is returned, and ct is the abbreviation of Contain)

(14) Field filtering (op = sw)

  • GET /user/index? name=Jack & name-op=sw
  • Return result: The structure is the same as (1) (but only the data whose name starts with Jack, sw is the abbreviation of StartWith)

(15) Field filtering (op = ew)

  • GET /user/index? name=Jack & name-op=ew
  • Return result: The structure is the same as (1) (but only the data whose name ends with Jack, ew is the abbreviation of EndWith)

(16) Ignore case (ic = true)

  • GET /user/index? name=Jack & name-ic=true
  • Return result: the same structure as (1) (but only return data whose name is equal to Jack (ignoring case), ic is the abbreviation of IgnoreCase)

The suffix -ic in the parameter name name-ic can be customized. This parameter can be used in combination with other parameters. For example, when the name is equal to Jack, the case is ignored, but it is also applicable to the search when the name starts or ends with Jack. upper and lower case.

It also supports more search methods. I will not give examples here. For more information, please go here: searcher.ejlchina.com/guide/lates…

Of course, all of the above conditions can be combined, such as

The query name starts with Jack (ignoring case), and roleId = 1, the results are sorted by the id field, 10 items are loaded per page, and the query is on page 2:

  • GET /user/index? name=Jack & name-op=sw & name-ic=true & roleId=1 & sort=id & size=10 & page=2
  • Return result: the structure is the same as (1)

In fact, Bean Searcher also supports more retrieval methods (even customizable), so I won't list them all here.

OK, after reading the effect, we really only wrote one line of code in the /user/index interface, and it can support so many retrieval methods. Do you think that now you can do a hundred lines of others with just one line of code ? ?

Bean Searcher

In this example, we only use one retrieval method of the MapSearcher retriever provided by Bean Searcher, in fact, it has many retrieval methods.

retrieval method

  • searchCount(Class<T> beanClass, Map<String, Object> params) Query the total
  • searchSum(Class<T> beanClass, Map<String, Object> params, String field) Query the statistical value of a
  • searchSum(Class<T> beanClass, Map<String, Object> params, String[] fields) Query the statistical value of multiple
  • search(Class<T> beanClass, Map<String, Object> params) paging Query the data list and total number of data under specified conditions
  • search(Class<T> beanClass, Map<String, Object> params, String[] summaryFields) Same as above + multi-field statistics
  • searchFirst(Class<T> beanClass, Map<String, Object> params) Query the first data under the specified conditions
  • searchList(Class<T> beanClass, Map<String, Object> params) paging Query data list
  • searchAll(Class<T> beanClass, Map<String, Object> params) Query all data list under specified conditions

MapSearcher 与 BeanSearcher

In addition, Bean Searcher not only provides MapSearcher retriever, but also provides BeanSearcher retriever, which also has all the methods of MapSearcher, but the single piece of data it returns is not a Map, but a generic object.

Parameter build tool

In addition, if you use Bean Searcher in Service, it may not be very elegant to use parameters of type Map<String, Object> directly. For this reason, Bean Searcher specially provides a parameter construction tool.

For example, the same query name starts with Jack (ignoring case), and roleId = 1, the results are sorted by the id field, 10 items are loaded per page, and the second page is loaded. Using the parameter builder, the code can be written as follows:

Map<String, Object> params = MapUtils.builder()
        .field(User::getName, "Jack").op(Operator.StartWith).ic()
        .field(User::getRoleId, 1)
        .orderBy(User::getId, "asc")
        .page(2, 10)
        .build()
List<User> users = beanSearcher.searchList(User.class, params);
复制代码

Here is the BeanSearcher retriever, and its searchList(Class<T> beanClass, Map<String, Object> params) method.

operator constraints

As we saw above, Bean Searcher directly supports many retrieval methods for each field in the entity class.

But a classmate: Oops! There are too many retrieval methods, and I don’t need so many. My data volume is billions, and the pre-fuzzy query method of the user name field cannot use the index. What if my database crashes?

Easy to handle , Bean Searcher supports operator constraints, and the username field of the entity class only needs to be annotated:

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u") 
public class User {

    @DbField(onlyOn = {Equal.class, StartWith.class})
    private String name;

    // 为减少篇幅,省略其它字段...
}
复制代码

As above, through the onlyOn attribute of the @DbField annotation, specifying that this username can only be applied to exact match and post -fuzzy queries , and other retrieval methods will be ignored directly.

The above code restricts the name to only two retrieval methods. If it is stricter and only allows exact matching , there are actually two ways to write it.

(1) Or use operator constraints:

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u") 
public class User {

    @DbField(onlyOn = Equal.class)
    private String name;

    // 为减少篇幅,省略其它字段...
}
复制代码

(2) Override the operator parameter in the interface method of the Controller:

@GetMapping("/index")
public SearchResult<Map<String, Object>> index(HttpServletRequest request) {
    Map<String, Object> params = MapUtils.flatBuilder(request.getParameterMap())
        .field(User::getName).op(Operator.Equal)   // 把 name 字段的运算符直接覆盖为 Equal
        .build()
    return mapSearcher.search(User.class, params);
}
复制代码

Conditional constraints

The student again: Oops! The amount of my data is still very large, and the age field has no index. I don't want it to participate in the where condition, otherwise there may be slow SQL!

Don't worry , Bean Searcher also supports conditional constraints, so that this field cannot be used as a condition directly:

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u") 
public class User {

    @DbField(conditional = false)
    private int age;

    // 为减少篇幅,省略其它字段...
}
复制代码

As above, through the conditional attribute of the @DbField annotation, the age field is not allowed to participate in the condition. No matter how the front-end passes the parameters, the Bean Searcher will ignore it.

parameter filter

The classmate still: Oops! oops...

Don't be afraid! Bean Searcher also supports configuring global parameter filters, which can customize any parameter filtering rules. In a Spring Boot project, only one Bean needs to be configured:

@Bean
public ParamFilter myParamFilter() {
    return new ParamFilter() {
        @Override
        public <T> Map<String, Object> doFilter(BeanMeta<T> beanMeta, Map<String, Object> paraMap) {
            // beanMeta 是正在检索的实体类的元信息, paraMap 是当前的检索参数
            // TODO: 这里可以写一些自定义的参数过滤规则
            return paraMap;      // 返回过滤后的检索参数
        }
    };
}
复制代码

a student asked

Why are the parameters so strange, there are so many, do you have any hatred with the front end?

  1. Whether the parameter name is strange or not depends on personal preference. If you don't like dash-, op and ic suffixes, you can completely customize them. Refer to this document:

searcher.ejlchina.com/guide/lates…

  1. The number of parameters is actually related to the complexity of the requirements. If the requirements are very simple, then many parameters do not need to be passed from the front end, and the back end is just plugged in directly. For example: name only requires post-fuzzy matching, age only requires interval matching, you can:
@GetMapping("/index")
public SearchResult<Map<String, Object>> index(HttpServletRequest request) {
    Map<String, Object> params = MapUtils.flatBuilder(request.getParameterMap())
        .field(User::getName).op(Operator.StartWith)
        .field(User::getAge).op(Operator.Between)
        .build()
    return mapSearcher.search(User.class, params);
}
复制代码

In this way, the front end does not need to pass the two parameters name-op and age-op.

In fact, there is a simpler method, which is operator constraint (when the constraint exists, the operator defaults to the first value specified in the onlyOn attribute, and the front end can omit it and not pass it):

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u") 
public class User {

    @DbField(onlyOn = Operator.StartWith)
    private String name;
    @DbField(onlyOn = Operator.Between)
    private String age;

    // 为减少篇幅,省略其它字段...
}
复制代码
  1. For multi-valued parameter passing with op=bt/il , parameters can indeed be simplified, for example:
  • Simplify age-0=20 & age-1=30 & age-op=bt to age=[20,30] & age-op=bt,
  • Simplify age-0=20 & age-1=30 & age-2=40 & age-op=il to age=[20,30,40] & age-op=il,

Simplified method: just configure a ParamFilter (parameter filter), the specific code can refer to here:

github.com/ejlchina/be…

The input parameter is request. My swagger document is not easy to render.

In fact, the retriever of Bean Searcher only needs a parameter of type Map<String, Object>. As for how this parameter comes, it is not directly related to Bean Searcher. The reason why the previous article is taken from the request is just because the code looks concise. If you like to declare parameters, you can write the code like this:

@GetMapping("/index")
public SearchResult<Map<String, Object>> index(Integer page, Integer size, 
            String sort, String order, String name, Integer roleId,
            @RequestParam(value = "name-op", required = false) String name_op,
            @RequestParam(value = "name-ic", required = false) Boolean name_ic,
            @RequestParam(value = "age-0", required = false) Integer age_0,
            @RequestParam(value = "age-1", required = false) Integer age_1,
            @RequestParam(value = "age-op", required = false) String age_op) {
    Map<String, Object> params = MapUtils.builder()
        .field(Employee::getName, name).op(name_op).ic(name_ic)
        .field(Employee::getAge, age_0, age_1).op(age_op)
        .field(Employee::getRoleId, roleId)
        .orderBy(sort, order)
        .page(page, size)
        .build();
    return mapSearcher.search(User.class, params);
}
复制代码

The relationship between field parameters is "and", what about "or"? What about any combination of "and" "or"?

The above-mentioned field parameters are indeed "and". As for "or", although there are not many usage scenarios, Bean Searcher still supports it (and it is very convenient and powerful ). For details, you can Refer here:

searcher.ejlchina.com/guide/lates…

It will not be repeated here.

For the above parameters such as sort and onlySelect, are their values ​​the field names of the data table, and is there a risk of SQL injection?

You can rest assured that such a low-level error of SQL injection has been avoided in the framework design. The values ​​of the parameters sort and onlySelect are all the property names of the entity class (not the fields of the data table). When the value passed by the user is not a property name, the framework will automatically ignore them, and there is no injection problem.

Not only that, in order to ensure the security of your service, Bean Searcher also comes with paging protection function, which can effectively block malicious large page requests from clients.

Has development efficiency really improved 100 times?

It can be seen from this example that the degree of efficiency improvement depends on the complexity of retrieval requirements. The more complex the requirements, the more times the efficiency will be improved , and vice versa. If the requirements are super complex, it is possible to increase the efficiency by 1000 times.

But even if we don't have such complex requirements in our daily development, and the development efficiency is only improved by 5 to 10 times, isn't that also very impressive?

Epilogue

This article introduces the super capabilities of Bean Searcher in the field of complex list retrieval. The reason why it can greatly improve the research and development efficiency of such requirements is fundamentally due to its original dynamic field operator and multi- table mapping mechanism , which is not available in traditional ORM frameworks. However, due to space limitations, its characteristics cannot be described in this article. For example, it also:

  • Support aggregate query
  • Support Select|Where|From subquery
  • Supports entity class embedded parameters
  • Support parameter grouping and logic optimization
  • Support Field Converters
  • Support Sql interceptor
  • Support Database Dialect extension
  • Support multiple data sources
  • Support custom operators
  • Support custom annotations
  • and many more

To learn more, start a Star: Github , Gitee .

Bean Searcher is a small tool that I summarized and encapsulated in my work. It has been used in the company for 5 years, and has experienced 30 to 40 large and small projects, and it is continuously updated and maintained . It is only recently that I have started to improve the document and share it with you. If you like it , be sure to order a Star ^_^.

Then provide the detailed documentation of Bean Searcher: searcher.ejlchina.com/

Finally, another Demo address:

  • Using the demo in the Spring Boot framework github.com/ejlchina/be… gitee.com/ejlchina-zh…
  • Using demo in Grails framework github.com/ejlchina/be… gitee.com/ejlchina-zh…

Big brother, don't forget Star (broken voice... tears...)


Code, also like pure handmade, because this can create a real work of art.


Author: Joey sauce
Link: https://juejin.cn/post/7027733039299952676
 

Guess you like

Origin blog.csdn.net/wdjnb/article/details/124476805