Springboot架构设计(二)封装

原创 2017年09月16日 17:38:14

这时候数据库还没有准备好,接口需求也没有定下来,我们可以做一些早期的封装。早期封装的好,尽量实现低耦合,就和实现快速开发,而且还能应对各种不确定的变化。

一般的接口需求,以获取数据为主。获取数据有些是单一数据类型,有的却是多种数据多种结构组合在一起。比如Android的页面如果比较复杂,就需要组装一套复杂的数据提供。这就导致java后端纵向分割无法确定。

我的观点是,controller是数据提供层,分割的依据是前端提供的模块,依照前端的一级模块或者接口需求的分法确定命名空间。service和dao这两层则是数据处理层,他们是一致的,可以按照数据类型进行划分,也就是基本依照数据表。数据库表中联系紧密的几张表可以算作同一种数据类型。

因为接口需求文档还没有生成,我们不知道controller怎么处理,所以我们只好先处理单一类型的数据。操作单一类型的数据相当于操作一张表,一般有以下几种:

1.获取一条记录

2.获取所有的记录(列表)

3.获取指定分页的记录(封装在分页模型中)

4.删除一条记录(依据id)

5.删除多条数据(依据id集合)

6.获取记录总数

以上六条是确定的。

以下操作是不确定的。

增加一条记录:不确定传进来的参数,除非是用body+json传进来整个的对象,这样需要固定请求模式,但是也没有这样传的,浪费流量;

增加多条记录:同上;

修改一条记录:同上。

从增删改查角度分析,可以确定封装的基本操作有以上六种。我们就按照这六种来进行封装。

一、首先,自定义一个Repository,实现DAO层的封装。实际上,JpaRepository已经把这六种操作封装好了。只是作为程序员,要想使自己的程序够灵活,尽量不要直接用原生的,否则遇到需要修改的时候手忙脚乱。哪怕我们自定义类之后其实什么都没有做,只是路过也没有关系,我们拿到操作权,可以很方便进行维护。

在这里,我给自己加一个需求。JpaRepository中的分页操作的数据类型不合我的使用,我要把数据放在自己定义的PageModel中包起来,我决定在DAO层去实现。

1. 首先自定义一个接口,继承JpaRepository

@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
    PageModel<T> getPage(Pageable pageable);//获取自定义分页
}
2. 给上面的接口创建一个实现类,继承SimpleJpaRepository,实现类的类名就在接口名后面加上“Impl”就可以了。

public class BaseRepositoryImpl<T, ID extends Serializable>
        extends SimpleJpaRepository<T, ID>
        implements BaseRepository<T, ID> {

    private EntityManager entityManager;

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    /**
     * 获取自定义分页
     *
     * @param pageable
     * @return
     */
    @Override
    public PageModel<T> getPage(Pageable pageable) {
        PageModel<T> pageModel = new PageModel<T>(pageable.getPageNumber() + 1, pageable.getPageSize());
        pageModel.count = count();
        pageModel.hasNext = pageModel.page * pageModel.pageSize < pageModel.count;//是否有下一页
        pageModel.dataList = findAll(pageable).getContent();
        return pageModel;
    }
}

在这个实现类中,我们实现了我们自己添加的方法。

3. 创建处理类,继承JpaRepositoryFactoryBean

public class BaseRepositoryFactoryBean<JR extends JpaRepository<T, ID>, T, ID extends Serializable>
        extends JpaRepositoryFactoryBean<JR, T, ID> {
    public BaseRepositoryFactoryBean(Class<? extends JR> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new BaseRepositoryFactory(entityManager);
    }

    private static class BaseRepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory {
        private final EntityManager entityManager;

        public BaseRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
            this.entityManager = entityManager;
        }

        @Override
        protected Object getTargetRepository(RepositoryInformation information) {
            return new BaseRepositoryImpl<T, ID>((Class<T>) information.getDomainType(), entityManager);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return BaseRepositoryImpl.class;
        }
    }
}

4. 在Application上面加上一句注解,开启处理工厂

@EnableJpaRepositories(basePackages = "com.meiyue", repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)


二、自定义Service基类

ublic abstract class BaseService<T, I extends Serializable, R extends BaseRepository<T, I>> {
    @Autowired
    R dao;

    //1. 查询:一条数据
    public T getOne(I id) {
        return dao.findOne(id);
    }

    //2. 查询:数据列表 按照id升序排列
    public List<T> getList() {
        return dao.findAll(new Sort(Sort.Direction.ASC, "id"));
    }

    //3. 查询:数据分页 按照id升序排列
    public PageModel<T> getPage(int page, int pageSize) {
        //数据库分页查询起始id是从0开始的,请求的页码是从1开始的,所以处理的时候要减一
        return page > 0 ? dao.getPage(new PageRequest(page - 1, pageSize, new Sort(Sort.Direction.ASC, "id"))) : null;
    }

    //4. 增加:增加一条数据
    public boolean addOne(T t) {
        T data = dao.save(t);
        return data != null ? true : false;
    }

    //5. 增加:增添批量数据
    public boolean addList(List<T> dataList) {
        //todo 此处要做事务处理
        try {
            for (T t : dataList) {
                addOne(t);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //6. 删除:删除一条记录
    public boolean removeOne(I id) {
        try {
            dao.delete(id);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //7. 删除:批量删除
    public boolean removeList(String ids) {
        String[] idss = ids.split(",");
        //todo 此处需要事务处理
        try {
            for (String id : idss) {
                removeOne((I) id);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //8. 修改:修改一条记录 对象必须包含id
    public boolean updateOne(T t) {
        try {
            dao.save(t);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

在这里封装了增加和修改的操作,是方便子类的调用。在子类将对象构建好之后,就可以直接调用,也可以调用DAO进行操作,由于职司不同不建议在controller中构建对象进行调用,虽然这也可以。

三、自定义controller抽象类。严格地说,这个封装的用处不大,因为接口并不是按照数据类型分得这么清楚。不过也不排除有些接口就是操作某一种类型单一的数据,那就可以用上了,而且,可以快速实现。

由于封装要考虑KV请求和body请求两种方式,所封装的方法参数结构是不一样的,所以要分开封装,在这里只贴出KV请求的封装,body请求原理相同。

public abstract class BaseKvController<T, I extends Serializable, S extends BaseService> {

    public abstract S getService();//获得Service 处理不好自动装载的笨办法

    //1.获取一个数据
    @RequestMapping("/getOne")
    public NetResult<T> getOne(I id) {
        return ResultUtils.buildResult((T)(getService().getOne(id)));
    }


    //2.获取数据列表
    @RequestMapping("/getList")
    public NetResult<List<T>> getList() {
        return ResultUtils.buildResult(getService().getList());
    }

    //3.获取分页
    @RequestMapping("/getPage")
    public NetResult<PageModel<T>> getPage(int page, int pageSize) {
        return ResultUtils.buildResult(getService().getPage(page, pageSize));
    }

    //4.删除一条
    @RequestMapping("/removeOne")
    public NetResult<Boolean> removeOne(I id) {
        return ResultUtils.buildResult(getService().removeOne(id));
    }

    //5.删除一批
    @RequestMapping("/removeList")
    public NetResult<Boolean> removeList(String ids) {
        return ResultUtils.buildResult(getService().removeList(ids));
    }
}

可以看到这里有5中常见操作。


四、还有几个工具类,也一并贴出来

1.构造结果的工具类ResultUtils(见上面)

2.构造json的工具类(简易版)

public class JsonUtils {
    private static Gson gson = new Gson();

    public static String toJson(Object obj) {
        return gson.toJson(obj);
    }
}
3.打印后台日志的工具类

public class MsgUtils {
    private static Logger logger = LoggerFactory.getLogger(MsgUtils.class);

    public static void println(Object obj) {
        System.out.println(obj);
    }

    public static void print(Object obj) {
        System.out.print(obj);
    }

    public static void d(Object obj) {
        logger.debug(String.valueOf(obj));
    }

    public static void i(Object obj) {
        logger.info(String.valueOf(obj));
    }

    public static void w(Object obj) {
        logger.warn(String.valueOf(obj));
    }

    public static void e(Object obj) {
        logger.error(String.valueOf(obj));
    }

}

我们来实践一下:

首先连接数据库



在项目上右键点击,选择Add Framework Support,选中JavaEE persistence,选中hibernate,下载确定。

这是Idea左下角会有persistence窗口,在项目上点击右键,选中Gennarate Persistence Mapping->By Database schema,选择对应的表和参数,生成自带注解的实体类,每个实体类对应一张表。

我们选择其中一个TestCitiesEntity作为我们测试的数据类型来操作。

1.创建DAO

public interface CityDao extends BaseRepository<TestCitiesEntity, Integer> {
}
可以看到,一句代码都没有,就是继承了我们封装的接口。

2.创建Service

@Service
public class CityService extends BaseService<TestCitiesEntity, Integer, CityDao> {
}
同样是一句代码都没有,注解要加上。

3.创建controller

@RestController
@RequestMapping("/city")
public class CityController extends BaseKvController<TestCitiesEntity, Integer, CityService>{
    @Autowired
    CityService cityService;

    @Override
    public CityService getService() {
        return cityService;
    }
}

这个我目前没解决BaseService的自动装配问题,所以留出了一个抽象方法需要实现。只加了简单的一点代码。上面的两行注解是必须的。

我们启动测试一下。数据库有可查询的数据。

我们在PostMan里请求192.168.1.101:8080/yuedao/city/getPage?page=2&pageSize=4

看看结果:


我们一个接口都没写,但是我们已经有接口可以用了。

换做那些复杂数据接口,我们也只需要把各种需要的service注入进去,进行广泛的调用组装就可以。至于那些封装顾及不到的,特事特办,已经很少了。

修改一个地方,就是controller的封装实际上是可以封装添加数据和修改一条数据的,我刚了解到,controller可以接收一个对象,而在请求的时候只需要提供相同的字段就可以了。不过,修改调用是必须要提供id的。

把下面这部分加到BaseKvController里面:

    //6.添加一条数据
    @RequestMapping("/addOne")
    public NetResult<Boolean> addOne(T t) {
        return ResultUtils.buildResult(getService().addOne(t));
    }

    //6.修改一条数据
    @RequestMapping("/updateOne")
    public NetResult<Boolean> updateOne(T t) {
        return ResultUtils.buildResult(getService().updateOne(t));
    }

测试没有问题,添加数据和修改数据都有效。不过由于无法判断泛型是否包含id所以无法对修改数据进行验证。如果不传id,就会增加一条数据,只有参数中包含一个可用的id,才会成功修改。

版权声明:本文为博主原创文章,未经博主允许不得转载。

SpringBoot第四讲扩展和封装Spring Data JPA(一)_自定义Repository和创建自己的BaseRepository

这一讲主要介绍spring Data JPA的封装。和设计相关的东西都是仁者见仁,智者见智的事情,如果你有更好的封装方案可以和我交流,互相学习。这一讲会讲如下一些内容  - 扩展Spring Dat...

Spring Boot打包总结

环境配置信息-** JDK 1.8 -** Spring Boot 1.5.3.RELEASE -** IDE: STS 3.4Spring Boot下打包过程基于STS创建Spring ...

Spring WebSocketStompClient connect连接时间的等待--CountDownLatch

1、Spring WebSocketStompClient连接服务端时,连接所需时间往往不固定,开始用的方法是在connect后面加上Thread.sleep(等待时间),如下: L...

App架构设计

App架构设计 架构的设计原则1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以...

Android - 底部菜单架构设计及封装实现

引言 在Android的应用中,经常会见到底部菜单,例如微信的底部菜单如下所示:   而在企业级的Android应用中,也存在同样的需求,但与微信这些大众软件的区别在于企业级的Android应用...

Android架构设计---MVP模式第(二)篇,如何减少类爆炸

今天是2017年3月16日,差不多一年前,写过一篇MVP基础类型的文章Android架构设计—MVP模式第(一)篇,梳理了一下MVP怎么使用。OK,先回忆一下。一、基础知识1.1、MVP分层总共分成三...

FPGA研发之道(7)架构设计漫谈(二)

FPGA研发之道(7)架构设计漫谈(二)     敏捷开发宣言中,有一条定律是“可以工作的软件胜过面面俱到的文档”。如何定义可可以工作的,这就是需求确定后架构设计的首要问题。而大部分看这句话的同...
  • lizf477
  • lizf477
  • 2014年07月09日 23:01
  • 891

高并发订单系统架构设计(二)

高并发下单主要包括以下几个方面: 分库分表 多应用实例全局唯一订单号 数据库连接 买家查询订单 卖家查询订单 扩容问题 业务拆分 一、分库分表随着订单量的增长,数据库的发展主要经历以下几个步骤: -...

高并发高流量的大型网站架构设计(二)

4.3 硬盘级缓存  硬盘级别的缓存是指将需要动态生成的内容暂时缓存在硬盘上,在一个可接受的延迟时间范围内,同样的请求不再动态生成,以达到节约系统资源,提高网站承受能力的目的。Linux环境下硬盘级...

Android App整体架构设计的思考(二)

接上文:Android App整体架构设计的思考(一) 3 基于AOP的框架设计         AOP(Aspect-Oriented Programming, 面向切面编程),诞生于上个世纪90年...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Springboot架构设计(二)封装
举报原因:
原因补充:

(最多只允许输入30个字)