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

上一篇講述了DAO 層,mybatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)的連接肥缔,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 {
    /**
     * 查詢(xún)所有秒殺記錄
     * @return
     */
    List<Seckill> getSerkillList();
  

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

   
    /**
     * 秒殺開(kāi)啟時(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)入秒殺詳情頁(yè),這時(shí)獲取系統(tǒng)時(shí)間木人,如果秒殺開(kāi)始信柿,獲取秒殺地址,點(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依賴(lài)
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;
    //MD5鹽值字符串司恳,用戶(hù)混淆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:開(kāi)發(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í)行秒殺邏輯:減庫(kù)存+記錄購(gòu)買(mǎi)行為
        Date nowTime=new Date();
        try {
            //記錄購(gòu)買(mǎi)行為
            int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
            //唯一:seckillId铅鲤,userphone
            if(insertCount<=0){
                //重復(fù)秒殺
                throw new RepeatKillException("seckill repeated");
            }else{
                //減庫(kù)存划提,熱點(diǎn)商品競(jìng)爭(zhēng)
                int updateCount=seckillDao.reduceNumber(seckillId,nowTime);
                if(updateCount<=0){
                    //沒(méi)有更新到記錄,秒殺結(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ǔ)過(guò)程的話(huà),就不需要拋異常了
    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ǔ)過(guò)程,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)類(lèi)

和上一篇文章使用spring托管dao接口一樣韩容,這里也需要用 spring 托管service. spring ioc 使用對(duì)象工程模式,對(duì)所有的注入的依賴(lài)進(jìn)行了管理唐瀑,暴露出了一致性的訪(fǎng)問(wèn)接口群凶,當(dāng)我們需要某個(gè)對(duì)象時(shí),直接從spring ioc中取就行了哄辣,不需要new请梢,也不需要對(duì)它們的生命周期進(jìn)行管理。更為重要的是spring 自動(dòng)組裝依賴(lài)力穗,比如最終的接口controller依賴(lài)service,而service依賴(lài)dao,dao依賴(lài)sessionfactory,而sessionfactory依賴(lài)datasource,這些層層依賴(lài)是通過(guò)spring管理并層層組裝毅弧,只要我們簡(jiǎn)單配置和注解就可以方便的使用,代碼的分層和編程的藝術(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包下所有使用注解的類(lèi)型-->
    <context:component-scan base-package="org.forezp.service"/>

然后在org,forezp.service包下的類(lèi)采用注解崖面。比如@Service 注解聲明是一個(gè)service元咙, @Autowired注入service 所需依賴(lài)。

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

 //注入service依賴(lài)
    @Autowired
    private SeckillDao seckillDao;
    @Autowired
    private SuccessKilledDao successKilledDao;

}

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

四庶香、spring聲明式事物

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

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

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

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

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

注意:

1開(kāi)發(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ù)接口的編寫(xiě)和實(shí)現(xiàn),以及采用xml和注解方式講service 注入到spring ioc筏养,以及聲明式事物斧抱,不得不感嘆spring 的強(qiáng)大。下一篇文章講講述 web層的開(kāi)發(fā)撼玄,spring mvc的相關(guān)配置夺姑。感謝大家,再接再厲掌猛,晚安。_眉睹。

關(guān)注我:

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荔茬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竹海,更是在濱河造成了極大的恐慌慕蔚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斋配,死亡現(xiàn)場(chǎng)離奇詭異孔飒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)艰争,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)坏瞄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人甩卓,你說(shuō)我怎么就攤上這事鸠匀。” “怎么了逾柿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵缀棍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我机错,道長(zhǎng)爬范,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任弱匪,我火速辦了婚禮青瀑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己狱窘,他們只是感情好杜顺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蘸炸,像睡著了一般躬络。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搭儒,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天穷当,我揣著相機(jī)與錄音,去河邊找鬼淹禾。 笑死馁菜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铃岔。 我是一名探鬼主播汪疮,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼毁习!你這毒婦竟也來(lái)了智嚷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纺且,失蹤者是張志新(化名)和其女友劉穎盏道,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體载碌,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猜嘱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫁艇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朗伶。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖裳仆,靈堂內(nèi)的尸體忽然破棺而出腕让,到底是詐尸還是另有隱情,我是刑警寧澤歧斟,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布纯丸,位于F島的核電站,受9級(jí)特大地震影響静袖,放射性物質(zhì)發(fā)生泄漏觉鼻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一队橙、第九天 我趴在偏房一處隱蔽的房頂上張望坠陈。 院中可真熱鬧萨惑,春花似錦、人聲如沸仇矾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贮匕。三九已至姐仅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刻盐,已是汗流浹背掏膏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敦锌,地道東北人馒疹。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乙墙,于是被迫代替她去往敵國(guó)和親颖变。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

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