Springboot架構設計(二)封裝

這時候數據庫還沒有準備好,接口需求也沒有定下來,我們可以做一些早期的封裝。早期封裝的好,儘量實現低耦合,就和實現快速開發,而且還能應對各種不確定的變化。

一般的接口需求,以獲取數據爲主。獲取數據有些是單一數據類型,有的卻是多種數據多種結構組合在一起。比如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,纔會成功修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章