Interpretation and design of general Service and Controller based on general Mapper source code in SpringBoot

Posted by peteraub on Mon, 27 Dec 2021 21:02:00 +0100

1. General Service extraction

Through the idea of general Mapper, that is to extract some duplicate code and make it into a framework or tool. Then, when a Dao needs to be used, it can be solved by directly inheriting this.

Analyze how this general Mapper is made.

It can be seen that Mapper in our Dao layer does not define any methods, but can be used in the Service layer summary, such as:

This is what general Mapper does for us. How does it do it? Take a closer look

We can use the SelectAll method as an example to see that the general Mapper uses reflection and other technologies to build Sql statements for operation

Let's take a look at the string splicing of the selectAll method:

Then let's take a look at how to make a general Service

From the method defined by our Service layer:

Except that our POJO s are different, all other methods are the same. This is our breakthrough

We can write an abstract class and an implementation class to implement these general methods. Then we can learn from the general mapper and inherit some CURDP interfaces. In this way, our Service only needs to inherit our CoreService, which inherits all CURDP interfaces. In this way, our specific implementation class not only references our interface, Then inherit one of our abstract classes, so that we can complete the writing of this general Service.

The specific structure of the code is as follows:

Code of CoreService:

package com.yxinmiracle.core.service;

public interface CoreService<T> extends
        DeleteService<T>,
        InsertService<T>,
        PagingService<T>,
        SelectService<T>,
        UpdateService<T> {

}

Abstract class code:

public abstract class CoreServiceImpl<T> implements CoreService<T> {
    //General mapepr
    protected Mapper<T> baseMapper;
    //Entity class of operation
    protected Class<T> clazz;

    public CoreServiceImpl(Mapper<T> baseMapper, Class<T> clazz) {
        this.baseMapper = baseMapper;
        this.clazz = clazz;
    }

    @Override
    public int delete(T record) {
        return baseMapper.delete(record);
    }

    @Override
    public int deleteById(Object id) {
        return baseMapper.deleteByPrimaryKey(id);
    }

    @Override
    public int insert(T record) {
        return baseMapper.insertSelective(record);
    }

    @Override
    public List<T> selectAll() {
        return baseMapper.selectAll();
    }

    @Override
    public T selectByPrimaryKey(Object id) {
        return baseMapper.selectByPrimaryKey(id);
    }

    @Override
    public List<T> select(T record) {
        return baseMapper.select(record);
    }

    @Override
    public int updateByPrimaryKey(T record) {
        return baseMapper.updateByPrimaryKeySelective(record);
    }

    @Override
    public PageInfo<T> findByPage(Integer pageNo, Integer pageSize) {
        PageHelper.startPage(pageNo, pageSize);
        List<T> list = baseMapper.selectAll();
        PageInfo<T> pageInfo = new PageInfo<T>(list);
        return pageInfo;
    }

    @Override
    public PageInfo<T> findByPage(Integer pageNo, Integer pageSize, T record) {
        Example example = new Example(clazz);
        Example.Criteria criteria = example.createCriteria();
        Field[] declaredFields = record.getClass().getDeclaredFields();

        for (Field declaredField : declaredFields) {
            try {
                //In case of id annotation and transient annotation, you do not need to set the value and skip directly.
                if (declaredField.isAnnotationPresent(Transient.class) || declaredField.isAnnotationPresent(Id.class)) {
                    //encounter
                    continue;
                }
                //Attribute descriptor record getClass()
                PropertyDescriptor propDesc = new PropertyDescriptor(declaredField.getName(), record.getClass());
                //To get this value, first get the method object of the read method, and call to get the value inside
                Object value = propDesc.getReadMethod().invoke(record);
                //Object value = propDesc.getValue(declaredField.getName());
                //If string
                if (value != null && value.getClass().getName().equals("java.lang.String")) {
                    Column columnAnnotation = declaredField.getAnnotation(Column.class);
                    //Judge if the length is 1, the execution = sign
                    int length = columnAnnotation.length();
                    if (length == 1) {
                        criteria.andEqualTo(declaredField.getName(), value);
                    } else {
                        criteria.andLike(declaredField.getName(), "%" + value + "%");
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
      PageHelper.startPage(pageNo, pageSize);
        List<T> ts = baseMapper.selectByExample(example);
        PageInfo<T> info = new PageInfo<T>(ts);
        return info;
    }

    @Override
    public PageInfo<T> findByPageExample(Integer pageNo, Integer pageSize, Object example) {
        PageHelper.startPage(pageNo, pageSize);
        List<T> list = baseMapper.selectByExample(example);
        PageInfo<T> info = new PageInfo<T>(list);
        return info;
    }
    
}

In this way, we can complete the development of some basic functions without writing a line of code in our service interface and service implementation class. For example, CURD in a single table can be completed in one minute as long as the hand speed is fast. As before, a Mapper needs to write an xml and an interface, and then the service layer needs to call the DAO layer, Now these codes don't need to be written:

  • Why use abstract classes?

Because abstract classes cannot be instantiated, they cannot be managed by the spring container, causing errors in multiple objects of the same type.

  • How do we know whose Mapper is in the abstract class?

We can't assign values by using @ Autowired annotation. Firstly, we don't know what type Mapper is. Secondly, the general framework we write ourselves doesn't use third-party things as much as possible. We still use Java Native things. Since it is abstract, we can use constructors and subclasses to assign values to the parent class.

  • The interface inherited in the abstract class does not need to rewrite all the methods in the interface, which is also conducive to the scalability of our framework

2. General Controller extraction

The same way as general Service

Code structure:

ICoreController Code:

package com.yxinmiracle.core;


public interface ICoreController<T> extends
        ISelectController<T>,
        IInsertController<T>,
        IPagingController<T>,
        IDeleteController<T>,
        IUpdateController<T> {
}

AbstractCoreController Code:

public abstract class AbstractCoreController<T> implements ICoreController<T> {

    //Caller's service
    protected CoreService<T> coreService;
    //Type of caller
    protected Class<T> clazz;

    public AbstractCoreController(CoreService<T> coreService, Class<T> clazz) {
        this.coreService = coreService;
        this.clazz = clazz;
    }

    /**
     * Delete record
     *
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    @Override
    public Result deleteById(@PathVariable(name = "id") Object id) {
        coreService.deleteById(id);
        return new Result(true, StatusCode.OK, "Delete succeeded");
    }

    /**
     * Add record
     *
     * @param record
     * @return
     */
    @PostMapping
    @Override
    public Result insert(@RequestBody T record) {
        coreService.insert(record);
        return new Result(true, StatusCode.OK, "Added successfully");
    }

    /**
     * Paging query record
     *
     * @param pageNo
     * @param pageSize
     * @return
     */
    @GetMapping(value = "/search/{page}/{size}")
    @Override
    public Result<PageInfo<T>> findByPage(@PathVariable(name = "page") Integer pageNo,
                                          @PathVariable(name = "size") Integer pageSize) {
        PageInfo<T> pageInfo = coreService.findByPage(pageNo, pageSize);
        return new Result<PageInfo<T>>(true, StatusCode.OK, "Paging query succeeded", pageInfo);
    }

    @PostMapping(value = "/search/{page}/{size}")
    @Override
    public Result<PageInfo<T>> findByPage(@PathVariable(name = "page") Integer pageNo,
                                          @PathVariable(name = "size") Integer pageSize,
                                          @RequestBody T record) {
        PageInfo<T> pageInfo = coreService.findByPage(pageNo, pageSize, record);
        return new Result<PageInfo<T>>(true, StatusCode.OK, "Conditional paging query succeeded", pageInfo);
    }

    @Override
    @GetMapping("/{id}")
    public Result<T> findById(@PathVariable(name = "id") Object id) {
        T t = coreService.selectByPrimaryKey(id);
        return new Result<T>(true, StatusCode.OK, "Query single data successfully", t);
    }

    @Override
    @GetMapping
    public Result<List<T>> findAll() {
        List<T> list = coreService.selectAll();
        return new Result<List<T>>(true, StatusCode.OK, "Query all data successfully", list);
    }

    //Update data
    @Override
    @PutMapping
    public Result updateByPrimaryKey(@RequestBody T record) {
        coreService.updateByPrimaryKey(record);
        return new Result(true, StatusCode.OK, "Update succeeded");
    }
}

Use:

@RestController
@RequestMapping("/brand")
public class BrandController extends AbstractCoreController<Brand> {
    @Autowired
    private BrandService brandService;

    @Autowired
    public BrandController(BrandService brandService) {
        super(brandService, Brand.class);
    }
}

In this way, there is no need to knock on the controller layer.

Topics: Java Spring Back-end