Commons CRUD

前言

其实这个想法来自于 spring-boot-starter-data-rest 他给我们提供了一些基础方法,

甚至于可以Query Repository 中的方法来调用 并且为一套标准的 RestFul

https://juejin.im/post/5cdd8cf2518825513d307026 参考

这文章看了不下 20 遍,依然有值得我们去深思和学习的地方.

步入

极度推荐阅读完上面文章 再往下看

https://juejin.im/post/5cdd8cf2518825513d307026

问题

总结的几个点

  1. 发现简单的 CRUD 都是有规律(其实一直都是这样)
  2. 重点是我们如何去快速解决这些重复的工作量
  3. 如何是现在代码中

思考

如何去解决重复的问题,

  1. 代码生成器 可以帮你重复的生成你想要的代码,
  2. 动态代理 不过好像不太合适 做起来有点复杂
  3. 利用继承的抽象 好像可行 这里有一部分思想来自于 Mybatis Plus

编码

  1. 有四层 Controller Service Manager Mapper
  2. Mapper 可以生成
  3. Manager 作为共用的抽象层
  4. Service 业务层
  5. Controller 控制层

在这里思考一下规范,我们先从 RestFul 的基本概念去理解说

【GET】          /users                 # 查询用户信息列表
【GET】          /users/1001            # 查看某个用户信息
【POST】         /users                 # 新建用户信息
【PUT】          /users/1001            # 更新用户信息(全部字段)
【PATCH】        /users/1001            # 更新用户信息(部分字段)
【DELETE】       /users/1001            # 删除用户信息

作者:梁桂钊
链接:https://juejin.im/post/5cdd8cf2518825513d307026
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这样就好理解很多了 有了一个这样的规范 继续去编码

BaseMapper (来源于Mybatis Generator)


import java.util.Optional;

/**
 * @author 郑查磊
 * @date 2019-08-14 09:57
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface BaseMapper<T> {

    /**
     * delete by primary key
     *
     * @param id primaryKey
     * @return deleteCount
     */
    int deleteByPrimaryKey(Long id);

    /**
     * insert record to table
     *
     * @param record the record
     * @return insert count
     */
    int insert(T record);

    /**
     * insert record to table selective
     *
     * @param record the record
     * @return insert count
     */
    int insertSelective(T record);

    /**
     * select by primary key
     *
     * @param id primary key
     * @return object by primary key
     */
    Optional<T> selectByPrimaryKey(Long id);

    /**
     * update record
     *
     * @param record the updated record
     * @return update count
     */
    int updateByPrimaryKeySelective(T record);

    /**
     * update record selective
     *
     * @param t the updated record
     * @return update count
     */
    int updateByPrimaryKey(T t);
}

这里是Mybatis Generator 自动生成的代码

Optional selectByPrimaryKey(Long id); 这里是我改的 添加了一个 Optional

其他无改动

Manager interface

import java.util.Optional;

/**
 * @author 郑查磊
 * @date 2019-08-14 11:48
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface BaseManager<T> {
    /**
     * 根据ID查询
     *
     * @param id 主键
     * @return 对象实体
     */
    Optional<T> selectByPrimaryKey(Long id);

    /**
     * 根据主键删除
     *
     * @param id primaryKey
     * @return deleteCount
     */
    int deleteByPrimaryKey(Long id);

    /**
     * 新增
     *
     * @param t the t
     * @return insert count
     */
    int insert(T t);

    /**
     * 动态新增
     *
     * @param t the t
     * @return insert count
     */
    int insertSelective(T t);

    /**
     * 动态修改
     *
     * @param t the updated t
     * @return update count
     */
    int updateByPrimaryKeySelective(T t);

    /**
     * 修改
     *
     * @param t the updated t
     * @return update count
     */
    int updateByPrimaryKey(T t);
}

定义 BaseManager 抽象基本方法

AbstractBaseManager


import cn.run.java.cherry.admin.framework.base.mapper.BaseMapper;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Optional;


/**
 * @author 郑查磊
 * @date 2019-08-14 09:58
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public abstract class AbstractBaseManager<M extends BaseMapper<T>, T> implements BaseManager<T> {

    @Autowired
    protected M mapper;

    /**
     * 根据ID查询
     *
     * @param id 主键
     * @return 对象实体
     */
    @Override
    public Optional<T> selectByPrimaryKey(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    /**
     * 根据主键删除
     *
     * @param id primaryKey
     * @return deleteCount
     */
    @Override
    public int deleteByPrimaryKey(Long id) {
        return mapper.deleteByPrimaryKey(id);
    }

    /**
     * 新增
     *
     * @param t the t
     * @return insert count
     */
    @Override
    public int insert(T t) {
        return mapper.insert(t);
    }

    /**
     * 动态新增
     *
     * @param t the t
     * @return insert count
     */
    @Override
    public int insertSelective(T t) {
        return mapper.insertSelective(t);
    }

    /**
     * 动态修改
     *
     * @param t the updated t
     * @return update count
     */
    @Override
    public int updateByPrimaryKeySelective(T t) {
        return mapper.updateByPrimaryKeySelective(t);
    }

    /**
     * 修改
     *
     * @param t the updated t
     * @return update count
     */
    @Override
    public int updateByPrimaryKey(T t) {
        return mapper.updateByPrimaryKey(t);
    }

}

定义了基本实现

Service interface


/**
 * @author 郑查磊
 * @date 2019-08-14 09:56
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface BaseService<T> {

    /**
     * 根据ID查询
     *
     * @param id 主键
     * @return 对象实体
     */
    T selectByPrimaryKey(Long id);

    /**
     * 根据主键删除
     *
     * @param id primaryKey
     * @return deleteCount
     */
    T deleteByPrimaryKey(Long id);

    /**
     * 新增
     *
     * @param t the t
     * @return insert count
     */
    T insert(T t);

    /**
     * 动态新增
     *
     * @param t the t
     * @return insert count
     */
    T insertSelective(T t);

    /**
     * 动态修改
     *
     * @param t the updated t
     * @return update count
     */
    int updateByPrimaryKeySelective(T t);

    /**
     * 修改
     *
     * @param t the updated t
     * @return update count
     */
    int updateByPrimaryKey(T t);

}

和 Manager 一样

AbstractBaseService

import cn.run.java.cherry.admin.framework.base.manager.BaseManager;
import cn.run.java.cherry.admin.framework.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;

/**
 * @author 郑查磊
 * @date 2019-08-14 12:40
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
@Slf4j
public abstract class AbstractBaseService<M extends BaseManager<T>, T extends Serializable> implements BaseService<T> {

    @Autowired
    protected M manager;

    /**
     * 根据ID查询
     *
     * @param id 主键
     * @return 对象实体
     */
    @Override
    public T selectByPrimaryKey(Long id) {
        log.info("根据ID查询 selectByPrimaryKey:{}", id);
        return manager.selectByPrimaryKey(id).orElseThrow(() -> new BusinessException("信息未找到!"));
    }

    /**
     * 根据主键删除
     *
     * @param id primaryKey
     * @return deleteCount
     */
    @Override
    public T deleteByPrimaryKey(Long id) {
        log.info("根据主键删除 primaryKey:{}", id);
        T t = selectByPrimaryKey(id);
        manager.deleteByPrimaryKey(id);
        return t;
    }

    /**
     * 新增
     *
     * @param t the t
     * @return insert count
     */
    @Override
    public T insert(T t) {
        log.info("新增 insert:{}", t);
        int insert = manager.insert(t);
        return selectByPrimaryKey((long) insert);
    }

    /**
     * 动态新增
     *
     * @param t the t
     * @return insert count
     */
    @Override
    public T insertSelective(T t) {
        log.info("动态新增 insertSelective:{}", t);
        int i = manager.insertSelective(t);
        return selectByPrimaryKey((long) i);
    }

    /**
     * 动态修改
     *
     * @param t the updated t
     * @return update count
     */
    @Override
    public int updateByPrimaryKeySelective(T t) {
        log.info("动态修改 updateByPrimaryKeySelective:{}", t);
        int i = manager.updateByPrimaryKeySelective(t);
        return i;
    }

    /**
     * 修改
     *
     * @param t the updated t
     * @return update count
     */
    @Override
    public int updateByPrimaryKey(T t) {
        log.info("修改 updateByPrimaryKey:{}", t);
        int i = manager.updateByPrimaryKeySelective(t);
        return i;
    }
}

有一点不同的是我吧 Optional 去掉了

Controller


import cn.run.java.cherry.admin.commons.R;
import cn.run.java.cherry.admin.framework.base.service.BaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author 郑查磊
 * @date 2019-08-14 09:58
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public abstract class AbstractBaseController<S extends BaseService<T>, T> {

    @Autowired
    protected S service;

    /**
     * 根据ID查询
     *
     * @param id 主键
     * @return 对象实体
     */
    @GetMapping("/{id}")
    public R<T> selectByPrimaryKey(@PathVariable Long id) {
        return R.ok(service.selectByPrimaryKey(id));
    }

    /**
     * 根据主键删除
     *
     * @param id primaryKey
     * @return deleteCount
     */
    @DeleteMapping("/{id}")
    public R<T> deleteByPrimaryKey(@PathVariable Long id) {
        return R.ok(service.deleteByPrimaryKey(id));
    }

    /**
     * 新增
     *
     * @param t the t
     * @return insert count
     */
    @PostMapping("/all")
    public R<T> insert(T t) {
        return R.ok(service.insert(t));
    }

    /**
     * 动态新增
     *
     * @param t the t
     * @return insert count
     */
    @PostMapping
    public R<T> insertSelective(T t) {
        return R.ok(service.insertSelective(t));
    }

    /**
     * 动态修改
     *
     * @param t the updated t
     * @return update count
     */
    @PatchMapping
    public R<Integer> updateByPrimaryKeySelective(T t) {
        return R.ok(service.updateByPrimaryKeySelective(t));
    }

    /**
     * 修改
     *
     * @param t the updated t
     * @return update count
     */
    @PutMapping
    public R<Integer> updateByPrimaryKey(T t) {
        return R.ok(service.updateByPrimaryKey(t));
    }

}

定义了一些基本的实现 CRUD

R


import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @author 郑查磊
 * @date 2019/4/11 19:08
 * @email <a href="mailto:stone981023@gmail.com">stone</a>
 */
@Data
@Accessors(chain = true)
public class R<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * code  默认值为0
     */
    private int code = 0;

    /**
     * msg 提示信息
     */
    private String msg = "操作成功!";

    /**
     * 数据载体
     */
    private T data = null;

    /**
     * 前段的提示信息 icon
     */
    private int icon = 1;

    /**
     * 状态 区别用于 exception 或者 ok
     * 200  正常
     * 500  服务器错误
     */
    private boolean status = true;

    private R() {
    }

    /**
     * 只返回提示信息
     *
     * @param msg
     * @return
     */
    public static R ok(String msg) {
        return new R().setMsg(msg);
    }

    /**
     * 只返回提示数据
     *
     * @param t
     * @returnt
     */
    public static <T> R<T> ok(T t) {
        return new R<T>().setData(t);
    }

    /**
     * 返回数据信息时附带 数据
     *
     * @param msg
     * @param t
     * @return
     */
    public static <T> R<T> ok(String msg, T t) {
        return new R<T>().setMsg(msg).setData(t);
    }

    /**
     * 异常时候返回信息
     *
     * @param msg
     * @return
     */
    public static <T> R<T> error(String msg) {
        return new R<T>().setMsg(msg).setIcon(2).setStatus(false);
    }

}

使用

UserMapper interface

/**
 * @author 郑查磊
 * @date 2019-08-14 09:56
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 根据用户名查询用户信息
     *
     * @param username
     * @return
     */
    Optional<User> selectUserByUsername(String username);
}

这里要理解得是 继承BaseMapper 之后, 父类的方法子类也会有 ,所以也不用担心 XML 找不到接口方法

import cn.run.java.cherry.admin.framework.base.manager.BaseManager;
import cn.run.java.cherry.admin.system.v1.domain.User;

import java.util.Optional;

/**
 * @author 郑查磊
 * @date 2019-08-14 13:41
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface UserManager extends BaseManager<User> {

    /**
     * 根据用户名查询用户信息
     *
     * @param username
     * @return
     */
    Optional<User> selectUserByUsername(String username);

}

import cn.run.java.cherry.admin.framework.base.manager.AbstractBaseManager;
import cn.run.java.cherry.admin.system.v1.domain.User;
import cn.run.java.cherry.admin.system.v1.manager.UserManager;
import cn.run.java.cherry.admin.system.v1.mapper.UserMapper;
import org.springframework.stereotype.Component;

import java.util.Optional;

/**
 * @author 郑查磊
 * @date 2019-08-14 11:25
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
@Component
public class UserManagerImpl extends AbstractBaseManager<UserMapper, User> implements UserManager {


    /**
     * 根据用户名查询用户信息
     *
     * @param username
     * @return
     */
    @Override
    public Optional<User> selectUserByUsername(String username) {
        return mapper.selectUserByUsername(username);
    }
}

/**
 * @author 郑查磊
 * @date 2019-08-14 12:58
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
public interface UserService extends BaseService<User> {

    /**
     * 根据用户名查询用户信息
     *
     * @param username
     * @return
     */
    Optional<User> selectUserByUsername(String username);

}

import cn.run.java.cherry.admin.framework.base.service.AbstractBaseService;
import cn.run.java.cherry.admin.system.v1.domain.User;
import cn.run.java.cherry.admin.system.v1.manager.UserManager;
import cn.run.java.cherry.admin.system.v1.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

/**
 * @author 郑查磊
 * @date 2019-08-14 12:58
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
@Slf4j
@Service
@Transactional(rollbackFor = RuntimeException.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public class UserServiceImpl extends AbstractBaseService<UserManager, User> implements UserService {

    /**
     * 根据用户名查询用户信息
     *
     * @param username
     * @return
     */
    @Override
    public Optional<User> selectUserByUsername(String username) {
        return manager.selectUserByUsername(username);
    }
}

/**
 * @author 郑查磊
 * @date 2019-08-14 13:25
 * @email <a href="mailto:stone981023@gmail.com">SmallStone</a>
 */
@RestController
@RequestMapping("/v1/system/users")
public class UserController extends AbstractBaseController<UserService, User> {


}

到这里 我们可以去理解一下 如果是没有 Manager 层 , 这里可以为我们减少大部分的工作量;

如果觉得不爽 可以 Override 最后去审视一下 Manager , 他为我们改变了什么.

我是如何理解得,在开发中,我们可能会遇到一个值会有 N 个新增入口 , 比如设置一些 日期, 初始化数据等等.

但是我们去写的时候 大多数情况下 都需要重新复写一遍逻辑,或者单独抽象出来一个 private 的方法

但是好像都没有那么的优雅

最后多一句嘴 在 Mybatis 中的 查询 例如:

<!-- 最优 共有的查询 在维护中 更加方便  -->
<select id="selectUserByUsername" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from sys_user where username = #{username,jdbcType=VARCHAR}
</select>

<!-- 不可控字段的返回 -->
<select id="selectUserByUsername" resultMap="BaseResultMap">
    select
    *
    from sys_user where username = #{username,jdbcType=VARCHAR}
</select>

<!-- 如果每个都是这样 在新增或者删减字段时候维护及其难 没有抽象的概念 -->
<select id="selectUserByUsername" resultMap="BaseResultMap">
    select
     user_id, user_avatar, user_email, user_password, user_phone, user_join_time, username
    from sys_user where username = #{username,jdbcType=VARCHAR}
</select>

其次就是 Mybatis 返回值的 DTO 丶 VO 丶domain 的一些思考

不写了 有机会补上吧.....