朱皮特的博客 自由的飞翔

springboot极简教程007-工程建议

2018-10-28
朱皮特
阅读量:

组织结构

实体类

建议把实体类统一放在某个包下,包名建议取名:

  • model(模型,也就是MCV模式中的M,Django也是取的该值)
  • entity(Java有些封装的函数参数就是用的这个,也比较推荐)
  • bean(java中的bean就是模型层的类,意即model)
  • pojo(Plain Ordinary Java Object的缩写;不含业务逻辑的java简单对象;完全POJO的系统也称为轻量级系统(lightweight);是相对于EJB而提出的概念。)

实体操作类

用来提供数据库操作接口:

  • JPA中带Repository后缀,如UserRepository
  • MyBatis中带Mapper后缀,如UserMapper

对于通用的操作接口,也可以让实体操作类统一继承一个自己封装的中间层(封装一些常用的接口函数):

  • JPA的做法是继承JpaRepository
  • MyBatis的做法是Mapper、MySqlMapper

例如使用MyBatis时,可以提供一个封装后的通用类CommonMapper:

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

import java.util.List;
import java.util.Map;

//FIXME 特别注意,该接口不能被扫描到,否则会出错
public interface CommonMapper<T> extends Mapper<T>, MySqlMapper<T> {
    List<T> queryList(Map<String, Object> map);
}
 
public interface CourseMapper extends CommonMapper<Course> {
}

意味着实体操作类承诺提供一个queryList的接口

服务接口

一开始很不理解这种模式,原本业务逻辑层可以直接使用实体操作类就完成的事情,硬是弄出许多个类(算上实体类,实体操作类,服务接口,服务实现一共四个了),但是后来发现这种方式还是有好处的,慢慢习惯就好。

服务接口相当于是提供了一个缓冲地带,隔离了业务逻辑层直接与实体操作类的接触,使得后期调整操作接口或者优化会比较方便。

graph LR
A(实体类)-->B(实体操作类)
B-->C(服务接口 缓冲隔离)
C-->D(业务控制层)

服务接口主要用于暴露给逻辑控制层的,等于告诉逻辑控制层:我有这些接口可以使用,可以在业务中尽情使用。实际应用发现,业务层需要的接口可能无非就是那几个,如果每次创建服务都去写一遍接口,有点重复,那么怎么办呢?

好办,把重复使用的那些接口封装出去,提到一个基类接口中管理,我们把这个基类服务接口叫做BaseService,例如最基本的增删改查这几个接口很多业务都需要,就把这些接口复用起来,让基类来统一实现和管理,接口如下:

import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 通用接口
 */
@Service
public interface BaseService<T> {

    T selectByKey(Object key);

    int save(T entity);

    int delete(Object key);

    int update(T entity);

    //TODO 其他...
}
import com.example.dao.model.Course;
import com.example.util.Page;

import java.util.List;
import java.util.Map;


public interface CourseService extends BaseService<Course> {
    int deleteByIds(String[] ids);
    int deleteByIds(Long[] ids);
    List<Course> queryList(Map<String, Object> params);
    List<Course> queryList(Page<?> page);
}

其实现代码BaseServiceImpl如下:


import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.common.Mapper;

import java.util.List;

/**
 * 通用Service
 *
 * @param <T>
 */
public abstract class BaseServiceImpl<T> implements BaseService<T> {

    @Autowired
    protected Mapper<T> mapper;

    public Mapper<T> getMapper() {
        return mapper;
    }

    @Override
    public T selectByKey(Object key) {
        //说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
        return mapper.selectByPrimaryKey(key);
    }

    @Override
    public int save(T entity) {
        //说明:保存一个实体,null的属性也会保存,不会使用数据库默认值
        return mapper.insert(entity);
    }

    @Override
    public int delete(Object key) {
        //说明:根据主键字段进行删除,方法参数必须包含完整的主键属性
        return mapper.deleteByPrimaryKey(key);
    }

    @Override
    public int update(T entity) {
        //根据主键更新属性不为null的值
        return mapper.updateByPrimaryKeySelective(entity);
    }
}

以后再为实体操作类封装服务接口时,可以这么做:继承BaseService,例如CourseService:

import com.example.dao.model.Course;
import com.example.util.Page;

import java.util.List;
import java.util.Map;


public interface CourseService extends BaseService<Course> {
    int deleteByIds(String[] ids);
    int deleteByIds(Long[] ids);
    List<Course> queryList(Map<String, Object> params);
    List<Course> queryList(Page<?> page);
}

该服务承诺:除了提供增删改查基础接口功能之外,还提供了批量删除、分页查询的接口,而且是多种参数形式的接口,比较灵活。

然后再创建一个服务接口的实现类CourseServiceImpl:


import com.example.dao.mapper.CourseMapper;
import com.example.dao.model.Course;
import com.example.util.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 */
@Service
public class CourseServiceImpl extends BaseServiceImpl<Course> implements CourseService {

    @Autowired
    CourseMapper courseMapper;

    @Override
    public int deleteByIds(String[] ids) {
        return this.courseMapper.deleteByIds(ids);
    }

    @Override
    public int deleteByIds(Long[] ids) {
        String[] string_ids = new String[ids.length];

        for (int i = 0; i < ids.length; i++) {
            string_ids[i] = String.valueOf(ids[i]);
        }
        return this.courseMapper.deleteByIds(string_ids);
    }

    @Override
    public List<Course> queryList(Map<String,Object> params) {
        PageHelper.startPage(Integer.parseInt(params.get("page").toString()), Integer.parseInt(params.get("rows").toString()));
        return this.courseMapper.queryList(params);
    }

    @Override
    public List<Course> queryList(Page<?> page) {
        return courseMapper.queryList(page.getCondition());
    }
}

可以发现,CourseServiceImpl无非是代理了CourseMapper(该类是实体操作类),CourseMapper可能只是实现了一些基本的功能,更灵活和多变的功能只不过是由服务层包装了一层而已。

控制层

建议把所有的Controller的类统一放到名为controller的包名下,建议路由映射采用模块化的方式来注册映射,具体可以参考:springboot极简教程005-URL映射

其他推荐

  • 配置文件建议使用application.yml,这也是官方的推荐做法
  • MyBatis的注解与XML方式,推荐使用注解方式
  • 推荐使用gradle编译而不是Maven,即使Maven占据了主流

Comments

Content