1. 程式人生 > >Springboot架構設計(二)封裝

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,才會成功修改。