SSM框架整合(二)

目錄(?)[+]

上一篇講述了DAO 層,mybatis實現數據庫的連接,DAO層接口設計,以及mybtis和spring的整合。DAO層採用接口設計方式實現,接口和SQL實現的分離,方便維護。DAO層所負責的僅僅是接口的設計和實現,而負責的邏輯即一個或多個DAO層接口的拼接是在Sevice層中完成。這篇文章接上篇文章,主要講述Service層的實現、和Spring的整合以及聲明如何聲明事物。

一、Service層接口設計

業務接口設計應當站在“使用者”角度設計接口,應遵循三個規範:合理的命令,明確的參數,返回結果(正常接口/異常結果)。本例子採用的Java高併發的秒殺API系列課程的例子,創建設計的業務邏輯接口如下:


public interface SeckillService {
    /**
     * 查詢所有秒殺記錄
     * @return
     */
    List<Seckill> getSerkillList();


    /**
     * 查詢單個秒殺記錄
     * @param seckillId
     * @return
     */
    Seckill getById(long seckillId);


    /**
     * 秒殺開啓時輸出秒殺接口地址,
     * 否則輸出系統時間和秒殺時間
     * @param seckillId
     */
    Exposer exportSeckillUrl(long seckillId);


    /**
      *執行秒殺接口
      */
   SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,RepeatKillException,SeckillCloseException;

二、Service接口的實現

直接上代碼了,在這裏講下秒殺業務的邏輯:首先是獲取秒殺列表,點擊列表進入秒殺詳情頁,這時獲取系統時間,如果秒殺開始,獲取秒殺地址,點擊秒殺,執行秒殺。所以業務邏輯也只設計了這相關的4個業務邏輯。其中使用了dto層去傳遞響應數據,以及自定義異常,所有的異常都繼承運行異常,這是爲了方便spring自動回滾,這兩個知識點,自行看源碼。

package org.forezp.service.impl;

@Service
public class SeckillServiceImpl implements SeckillService{
    private Logger logger= LoggerFactory.getLogger(this.getClass());
    //注入service依賴
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;
    //MD5鹽值字符串,用戶混淆MD5
    private final String slat="sfsa=32q4r23234215ERWERT^**%^SDF";



    public List<Seckill> getSerkillList() {
        return seckillDao.queryAll(0,4);
    }



    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }



    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill =seckillDao.queryById(seckillId);
        if(seckill==null){
            return new Exposer(false,seckillId);
        }
        Date startTime=seckill.getStartTime();
        Date endTime=seckill.getEndTime();
        //系統當前時間
        Date nowTime=new Date();
        if(nowTime.getTime()<startTime.getTime()||nowTime.getTime()>endTime.getTime()){
            return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
        }
        String md5=getMD5(seckillId);
        return new Exposer(true,md5,seckillId);
    }
    private String getMD5(long seckillId){
        String base=seckillId+"/"+slat;
        String md5= DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }


    @Transactional
    /**
     *使用註解控制事務方法的優點
     * 1:開發團隊達成一致約定,明確標註事務方法的編程風格
     * 2:保證事務方法的執行時間儘可能短,不要穿插其他網絡請求,RPC/HTTP請求或者剝離到事務方法外
     * 3:不是所有的方法都需要事務,如只有一條修改操作,只讀操作不需要事務控制
     */
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
        if(md5==null ||!md5.equals(getMD5(seckillId))){
            throw new SeckillException("seckill data rewrite");
        }
        //執行秒殺邏輯:減庫存+記錄購買行爲
        Date nowTime=new Date();
        try {
            //記錄購買行爲
            int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
            //唯一:seckillId,userphone
            if(insertCount<=0){
                //重複秒殺
                throw new RepeatKillException("seckill repeated");
            }else{
                //減庫存,熱點商品競爭
                int updateCount=seckillDao.reduceNumber(seckillId,nowTime);
                if(updateCount<=0){
                    //沒有更新到記錄,秒殺結束 rollback
                    throw new SeckillCloseException("seckill is closed");
                }else{
                    //秒殺成功 commit
                    SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
                    return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,successKilled);
                }
            }

        }catch(SeckillCloseException e1){
            throw e1;
        } catch (RepeatKillException e2){
            throw e2;
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            //所有的編譯期異常,轉化爲運行期異常(運行時異常,spring可以做rollback操作)
            throw new SeckillException("seckill inner error:"+e.getMessage());
        }

    }
    //拋出異常是爲了告訴spring是否rollback,此處使用存儲過程的話,就不需要拋異常了
    public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
        if(md5 ==null || !md5.equals(getMD5(seckillId))){
            return new SeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);
        }
        Date killTime=new Date();
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("seckillId",seckillId);
        map.put("phone",userPhone);
        map.put("killTime",killTime);
        map.put("result",null);
        //執行存儲過程,result被賦值
        try {
            seckillDao.killByProcedure(map);
            int result=(Integer) map.get("result");
            if(result==1){
                SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
                return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,successKilled);
            }else{
                return new SeckillExecution(seckillId,SeckillStatEnum.stateof(result));
            }
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
            return new SeckillExecution(seckillId,SeckillStatEnum.INNER_ERROE);
        }
    }
}

三、Sping託管 service的實現類

和上一篇文章使用spring託管dao接口一樣,這裏也需要用 spring 託管service. spring ioc 使用對象工程模式,對所有的注入的依賴進行了管理,暴露出了一致性的訪問接口,當我們需要某個對象時,直接從spring ioc中取就行了,不需要new,也不需要對它們的生命週期進行管理。更爲重要的是spring 自動組裝依賴,比如最終的接口controller依賴service,而service依賴dao,dao依賴sessionfactory,而sessionfactory依賴datasource,這些層層依賴是通過spring管理並層層組裝,只要我們簡單配置和註解就可以方便的使用,代碼的分層和編程的藝術在spring框架中展現得淋漓盡至。

本項目採用spring ioc :

1.xml配置

2.包掃描

3.annotation註解。

創建sping-service.xml

採用包掃描+註解方式,首先在xml中聲明包掃描:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd"
>
    <!--掃描service包下所有使用註解的類型-->
    <context:component-scan base-package="org.forezp.service"/>

然後在org,forezp.service包下的類採用註解。比如@Service 註解聲明是一個service, @Autowired注入service 所需依賴。

@Service//聲明是一個service
public class SeckillServiceImpl implements SeckillService{

 //注入service依賴
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;

}

只需要一個包掃描和幾個簡單的註解就可以將service註解到spring ioc容器中。

四、spring聲明式事物

在秒殺案例中,我們需要採用事物來防止數據的正確性,防止重複秒殺,防止庫存不足、庫存剩餘等情況。一般使用事物需要開啓事物/經常一些列的操作,提交或者回滾。spring聲明式事物,就是將事物的開啓、提交等託管給spring管理,我們只需要注重如何修改數據。

配置spring 聲明式事物
在spring-service.xml中配置:

 <!--配置事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入數據庫連接池-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置基於註解的聲明式事務
        默認使用註解來管理事務行爲
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

在需要事物的業務邏輯下加 @Transactional註解。
比如在開啓秒殺方法:

@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
        if(md5==null ||!md5.equals(getMD5(seckillId))){
        }

注意:

1開發團隊達成一致約定,明確標註事務方法的編程風格

2:保證事務方法的執行時間儘可能短,不要穿插其他網絡請求,RPC/HTTP請求或者剝離到事務方法外

3:不是所有的方法都需要事務,如只有一條修改操作,只讀操作不需要事務控制

五、單元測試

需要配置:

@ContextConfiguration({
“classpath:spring/spring-dao.xml”,
“classpath:spring/spring-service.xml”
})
直接上代碼:



@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
        "classpath:spring/spring-dao.xml",
        "classpath:spring/spring-service.xml"
})
public class SeckillServiceTest {
    private final Logger logger=LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SeckillService seckillService;

    @Test
    public void getSerkillList() throws Exception {
        List<Seckill> list=seckillService.getSerkillList();
       System.out.println(list);
       //執行結果[Seckill{seckillId=1000, name='1000元秒殺iphone6'..... 省略。。。]


    }

    @Test
    public void getById() throws Exception {
        long id=1000;
        Seckill seckill=seckillService.getById(id);
        System.out.println(seckill);
        //執行結果:Seckill{seckillId=1000, name='1000元秒殺iphone6', number=100, startTime=Sun Nov 01 00:00:00 CST 2015,。。。。}
    }

    @Test
    public void exportSeckillUrl() throws Exception {
        long id=1000;
        Exposer exposer=seckillService.exportSeckillUrl(id);
        System.out.println(exposer);

    }

    @Test
    public void executeSeckill() throws Exception {
        long id=1000;
        long phone=13502171122L;
        String md5="e83eef2cc6b033ca0848878afc20e80d";
        SeckillExecution execution=seckillService.executeSeckill(id,phone,md5);
        System.out.println(execution);
    }
   }

這篇文章主要講了service業務接口的編寫和實現,以及採用xml和註解方式講service 注入到spring ioc,以及聲明式事物,不得不感嘆spring 的強大。下一篇文章講講述 web層的開發,spring mvc的相關配置。

發佈了84 篇原創文章 · 獲贊 27 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章