一篇SSM框架整合友好的文章(二)

上一篇講述了DAO 層,mybatis實(shí)現(xiàn)數(shù)據(jù)庫的連接,DAO層接口設(shè)計(jì),以及mybtis和spring的整合。DAO層采用接口設(shè)計(jì)方式實(shí)現(xiàn),接口和SQL實(shí)現(xiàn)的分離,方便維護(hù)。DAO層所負(fù)責(zé)的僅僅是接口的設(shè)計(jì)和實(shí)現(xiàn),而負(fù)責(zé)的邏輯即一個(gè)或多個(gè)DAO層接口的拼接是在Sevice層中完成。這篇文章接上篇文章,主要講述Service層的實(shí)現(xiàn)、和Spring的整合以及聲明如何聲明事物。

一、Service層接口設(shè)計(jì)

業(yè)務(wù)接口設(shè)計(jì)應(yīng)當(dāng)站在“使用者”角度設(shè)計(jì)接口,應(yīng)遵循三個(gè)規(guī)范:合理的命令,明確的參數(shù),返回結(jié)果(正常接口/異常結(jié)果)。本例子采用的Java高并發(fā)的秒殺API系列課程的例子,創(chuàng)建設(shè)計(jì)的業(yè)務(wù)邏輯接口如下:


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

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

   
    /**
     * 秒殺開啟時(shí)輸出秒殺接口地址,
     * 否則輸出系統(tǒng)時(shí)間和秒殺時(shí)間
     * @param seckillId
     */
    Exposer exportSeckillUrl(long seckillId);
   
    
    /**
      *執(zhí)行秒殺接口
      */
   SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,RepeatKillException,SeckillCloseException;
  
     

二、Service接口的實(shí)現(xiàn)

直接上代碼了,在這里講下秒殺業(yè)務(wù)的邏輯:首先是獲取秒殺列表,點(diǎn)擊列表進(jìn)入秒殺詳情頁,這時(shí)獲取系統(tǒng)時(shí)間,如果秒殺開始,獲取秒殺地址,點(diǎn)擊秒殺,執(zhí)行秒殺。所以業(yè)務(wù)邏輯也只設(shè)計(jì)了這相關(guān)的4個(gè)業(yè)務(wù)邏輯。其中使用了dto層去傳遞響應(yīng)數(shù)據(jù),以及自定義異常,所有的異常都繼承運(yùn)行異常,這是為了方便spring自動(dòng)回滾,這兩個(gè)知識(shí)點(diǎn),自行看源碼。

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();
        //系統(tǒng)當(dāng)前時(shí)間
        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
    /**
     *使用注解控制事務(wù)方法的優(yōu)點(diǎn)
     * 1:開發(fā)團(tuán)隊(duì)達(dá)成一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格
     * 2:保證事務(wù)方法的執(zhí)行時(shí)間盡可能短,不要穿插其他網(wǎng)絡(luò)請(qǐng)求,RPC/HTTP請(qǐng)求或者剝離到事務(wù)方法外
     * 3:不是所有的方法都需要事務(wù),如只有一條修改操作,只讀操作不需要事務(wù)控制
     */
    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");
        }
        //執(zhí)行秒殺邏輯:減庫存+記錄購買行為
        Date nowTime=new Date();
        try {
            //記錄購買行為
            int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
            //唯一:seckillId,userphone
            if(insertCount<=0){
                //重復(fù)秒殺
                throw new RepeatKillException("seckill repeated");
            }else{
                //減庫存,熱點(diǎn)商品競爭
                int updateCount=seckillDao.reduceNumber(seckillId,nowTime);
                if(updateCount<=0){
                    //沒有更新到記錄,秒殺結(jié)束 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);
            //所有的編譯期異常,轉(zhuǎn)化為運(yùn)行期異常(運(yùn)行時(shí)異常,spring可以做rollback操作)
            throw new SeckillException("seckill inner error:"+e.getMessage());
        }

    }
    //拋出異常是為了告訴spring是否rollback,此處使用存儲(chǔ)過程的話,就不需要拋異常了
    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);
        //執(zhí)行存儲(chǔ)過程,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的實(shí)現(xiàn)類

和上一篇文章使用spring托管dao接口一樣,這里也需要用 spring 托管service. spring ioc 使用對(duì)象工程模式,對(duì)所有的注入的依賴進(jìn)行了管理,暴露出了一致性的訪問接口,當(dāng)我們需要某個(gè)對(duì)象時(shí),直接從spring ioc中取就行了,不需要new,也不需要對(duì)它們的生命周期進(jìn)行管理。更為重要的是spring 自動(dòng)組裝依賴,比如最終的接口controller依賴service,而service依賴dao,dao依賴sessionfactory,而sessionfactory依賴datasource,這些層層依賴是通過spring管理并層層組裝,只要我們簡單配置和注解就可以方便的使用,代碼的分層和編程的藝術(shù)在spring框架中展現(xiàn)得淋漓盡至。

本項(xiàng)目采用spring ioc :

1.xml配置

2.包掃描

3.annotation注解。

創(chuàng)建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 注解聲明是一個(gè)service, @Autowired注入service 所需依賴。

@Service//聲明是一個(gè)service
public class SeckillServiceImpl implements SeckillService{

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

}

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

四、spring聲明式事物

在秒殺案例中,我們需要采用事物來防止數(shù)據(jù)的正確性,防止重復(fù)秒殺,防止庫存不足、庫存剩余等情況。一般使用事物需要開啟事物/經(jīng)常一些列的操作,提交或者回滾。spring聲明式事物,就是將事物的開啟、提交等托管給spring管理,我們只需要注重如何修改數(shù)據(jù)。

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

 <!--配置事務(wù)管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入數(shù)據(jù)庫連接池-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置基于注解的聲明式事務(wù)
        默認(rèn)使用注解來管理事務(wù)行為
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

在需要事物的業(yè)務(wù)邏輯下加 @Transactional注解。
比如在開啟秒殺方法:

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

注意:

1開發(fā)團(tuán)隊(duì)達(dá)成一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格

2:保證事務(wù)方法的執(zhí)行時(shí)間盡可能短,不要穿插其他網(wǎng)絡(luò)請(qǐng)求,RPC/HTTP請(qǐng)求或者剝離到事務(wù)方法外

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

五、單元測(cè)試

需要配置:

@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);
       //執(zhí)行結(jié)果[Seckill{seckillId=1000, name='1000元秒殺iphone6'..... 省略。。。]


    }

    @Test
    public void getById() throws Exception {
        long id=1000;
        Seckill seckill=seckillService.getById(id);
        System.out.println(seckill);
        //執(zhí)行結(jié)果: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業(yè)務(wù)接口的編寫和實(shí)現(xiàn),以及采用xml和注解方式講service 注入到spring ioc,以及聲明式事物,不得不感嘆spring 的強(qiáng)大。下一篇文章講講述 web層的開發(fā),spring mvc的相關(guān)配置。感謝大家,再接再厲,晚安。_。

關(guān)注我:

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,261評(píng)論 6 342
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,852評(píng)論 0 3
  • SSM框架詳細(xì)整合 介紹 Spring + SpringMVC + Mybatis是現(xiàn)在輕量級(jí)J2EE框架方案中,...
    2MuchT閱讀 5,319評(píng)論 3 14
  • 這世間有一百種的東西,初始擁有的時(shí)候,并沒有多大的感覺,可一旦丟失,便會(huì)有莫大的苦楚。是一百種嗎?大概是吧,...
    一條無尾的魚閱讀 441評(píng)論 0 0

友情鏈接更多精彩內(nèi)容