国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Java高并發(fā)秒殺API(二)之Service層

1. 設(shè)計前的分析

分層的必要性

  • DAO層工作演變?yōu)?接口設(shè)計+SQL編寫(不需要其他雜七雜八的功能)
  • 代碼和SQL的分離,方便review(瀏覽)
  • DAO拼接等邏輯在Service層完成(DAO只需負(fù)責(zé)SQL語句,其他都由Service層完成)

一些初學(xué)者容易出現(xiàn)的錯誤,就是喜歡在DAO層進(jìn)行邏輯的編寫,其實DAO就是數(shù)據(jù)訪問的縮寫,它只進(jìn)行數(shù)據(jù)的訪問操作。

業(yè)務(wù)接口的編寫

初學(xué)者總是關(guān)注細(xì)節(jié),關(guān)注接口如何去實現(xiàn),這樣設(shè)計出來的接口往往比較冗余。業(yè)務(wù)接口的編寫要站在“使用者”的角度定義,三個方面:方法定義的粒度、參數(shù)、返回值。

  • 方法定義粒度:關(guān)注接口的功能本身,至于這個功能需要包含哪些步驟那是具體的實現(xiàn),也就是說,功能明確而且單一。
  • 參數(shù):方法所需要的數(shù)據(jù),供使用者傳入,明確方法所需要的數(shù)據(jù),而且盡可能友好,簡練。
  • 返回值:一般情況下,entity數(shù)據(jù)不夠,需要自定義DTO,也有可能拋出異常,需要自定義異常,不管是DTO還是異常,盡可能將接口調(diào)用的信息返回給使用者,哪怕是失敗信息。

DTO與entity的區(qū)別

DTO數(shù)據(jù)傳輸層:用于Web層和Service層之間傳遞的數(shù)據(jù)封裝。

entity:用于業(yè)務(wù)數(shù)據(jù)的封裝,比如數(shù)據(jù)庫中的數(shù)據(jù)。

關(guān)于秒殺地址的暴露

  1. 需要有專門一個方法實現(xiàn)秒殺地址輸出,避免人為因素提前知道秒殺地址而出現(xiàn)漏洞。
  2. 獲取秒殺url時,如果不合法,則返回當(dāng)前時間和秒殺項目的時間;如果合法,才返回md5加密后url,以避免url被提前獲知。
  3. 使用md5將url加密、校驗,防止秒殺的url被篡改。

MD5加密

Spring提供了MD5生成工具。代碼如下:

DigestUtils.md5DigestAsHex();

MD5鹽值字符串(salt),用于混淆MD5,添加MD5反編譯難度

2. Service層的接口設(shè)計

src/main/java包下建立com.lewis.service包,用來存放Service接口;在src/main/java包下建立com.lewis.exception包,用來存放Service層出現(xiàn)的異常類:比如重復(fù)秒殺異常、秒殺已關(guān)閉異常;在src/main/java包下建立com.lewis.dto包,用來封裝Web層和Service層之間傳遞的數(shù)據(jù)。

定義SeckillService接口

/** * 業(yè)務(wù)接口:站在使用者(程序員)的角度設(shè)計接口 三個方面:1.方法定義粒度,方法定義的要非常清楚2.參數(shù),要越簡練越好 3.返回類型(return * 類型一定要友好/或者return異常,我們允許的異常) */public interface SeckillService {    /**     * 查詢?nèi)康拿霘⒂涗?    *      * @return     */    List<Seckill> getSeckillList();    /**     * 查詢單個秒殺記錄     *      * @param seckillId     * @return     */    Seckill getById(long seckillId);    // 再往下,是我們最重要的行為的一些接口    /**     * 在秒殺開啟時輸出秒殺接口的地址,否則輸出系統(tǒng)時間和秒殺時間     *      * @param seckillId 秒殺商品Id     * @return 根據(jù)對應(yīng)的狀態(tài)返回對應(yīng)的狀態(tài)實體     */    Exposer exportSeckillUrl(long seckillId);    /**     * 執(zhí)行秒殺操作,有可能失敗,有可能成功,所以要拋出我們允許的異常     *      * @param seckillId 秒殺的商品ID     * @param userPhone 手機(jī)號碼     * @param md5 md5加密值     * @return 根據(jù)不同的結(jié)果返回不同的實體信息     */    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,            RepeatKillException, SeckillCloseException;}

在dto包中創(chuàng)建Exposer.java,用于封裝秒殺的地址信息

/** * 暴露秒殺地址(接口)DTO */public class Exposer {    // 是否開啟秒殺    private boolean exposed;    // 加密措施    private String md5;    //id為seckillId的商品的秒殺地址    private long seckillId;    // 系統(tǒng)當(dāng)前時間(毫秒)    private long now;    // 秒殺的開啟時間    private long start;    // 秒殺的結(jié)束時間    private long end;    public Exposer(boolean exposed, String md5, long seckillId) {        this.exposed = exposed;        this.md5 = md5;        this.seckillId = seckillId;    }    public Exposer(boolean exposed, long seckillId, long now, long start, long end) {        this.exposed = exposed;        this.seckillId = seckillId;        this.now = now;        this.start = start;        this.end = end;    }    public Exposer(boolean exposed, long seckillId) {        this.exposed = exposed;        this.seckillId = seckillId;    }    public boolean isExposed() {        return exposed;    }    public void setExposed(boolean exposed) {        this.exposed = exposed;    }    public String getMd5() {        return md5;    }    public void setMd5(String md5) {        this.md5 = md5;    }    public long getSeckillId() {        return seckillId;    }    public void setSeckillId(long seckillId) {        this.seckillId = seckillId;    }    public long getNow() {        return now;    }    public void setNow(long now) {        this.now = now;    }    public long getStart() {        return start;    }    public void setStart(long start) {        this.start = start;    }    public long getEnd() {        return end;    }    public void setEnd(long end) {        this.end = end;    }    @Override    public String toString() {        return "Exposer{" + "exposed=" + exposed + ", md5='" + md5 + '\'' + ", seckillId=" + seckillId + ", now=" + now                + ", start=" + start + ", end=" + end + '}';    }}

在dto包中創(chuàng)建SeckillExecution.java,用于封裝秒殺是否成功的結(jié)果(該對象用來返回給頁面)

/** * 封裝執(zhí)行秒殺后的結(jié)果:是否秒殺成功 */public class SeckillExecution {    private long seckillId;    //秒殺執(zhí)行結(jié)果的狀態(tài)    private int state;    //狀態(tài)的明文標(biāo)識    private String stateInfo;    //當(dāng)秒殺成功時,需要傳遞秒殺成功的對象回去    private SuccessKilled successKilled;    //秒殺成功返回所有信息    public SeckillExecution(long seckillId, int state, String stateInfo, SuccessKilled successKilled) {        this.seckillId = seckillId;        this.state = state;        this.stateInfo = stateInfo;        this.successKilled = successKilled;    }    //秒殺失敗    public SeckillExecution(long seckillId, int state, String stateInfo) {        this.seckillId = seckillId;        this.state = state;        this.stateInfo = stateInfo;    }    public long getSeckillId() {        return seckillId;    }    public void setSeckillId(long seckillId) {        this.seckillId = seckillId;    }    public int getState() {        return state;    }    public void setState(int state) {        this.state = state;    }    public String getStateInfo() {        return stateInfo;    }    public void setStateInfo(String stateInfo) {        this.stateInfo = stateInfo;    }    public SuccessKilled getSuccessKilled() {        return successKilled;    }    public void setSuccessKilled(SuccessKilled successKilled) {        this.successKilled = successKilled;    }}

在exception包中創(chuàng)建秒殺過程中可能出現(xiàn)的異常類

定義一個基礎(chǔ)的異常類SeckillException,繼承自RuntimeException

/** * 秒殺相關(guān)的所有業(yè)務(wù)異常 */public class SeckillException extends RuntimeException {    public SeckillException(String message) {        super(message);    }    public SeckillException(String message, Throwable cause) {        super(message, cause);    }}

重復(fù)秒殺異常,繼承自SeckillException

/** * 重復(fù)秒殺異常,是一個運行期異常,不需要我們手動try catch * Mysql只支持運行期異常的回滾操作 */public class RepeatKillException extends SeckillException {    public RepeatKillException(String message) {        super(message);    }    public RepeatKillException(String message, Throwable cause) {        super(message, cause);    }}

秒殺已關(guān)閉異常,繼承自SeckillException

/** * 秒殺關(guān)閉異常,當(dāng)秒殺結(jié)束時用戶還要進(jìn)行秒殺就會出現(xiàn)這個異常 */public class SeckillCloseException extends SeckillException{    public SeckillCloseException(String message) {        super(message);    }    public SeckillCloseException(String message, Throwable cause) {        super(message, cause);    }}

3. Service層接口的實現(xiàn)

com.lewis.service包下再建立impl包,用來存放接口的實現(xiàn)類SeckillServiceImpl

public class SeckillServiceImpl implements SeckillService{    //日志對象    private Logger logger= LoggerFactory.getLogger(this.getClass());    //加入一個混淆字符串(秒殺接口)的salt,為了我避免用戶猜出我們的md5值,值任意給,越復(fù)雜越好    private final String salt="aksehiucka24sf*&%&^^#^%$";    //注入Service依賴    @Autowired //@Resource    private SeckillDao seckillDao;    @Autowired //@Resource    private SuccessKilledDao successKilledDao;    public List<Seckill> getSeckillList() {        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) //說明查不到這個秒殺產(chǎn)品的記錄        {            return new Exposer(false,seckillId);        }        //若是秒殺未開啟        Date startTime=seckill.getStartTime();        Date endTime=seckill.getEndTime();        //系統(tǒng)當(dāng)前時間        Date nowTime=new Date();        if (startTime.getTime()>nowTime.getTime() || endTime.getTime()<nowTime.getTime())        {            return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());        }        //秒殺開啟,返回秒殺商品的id、用給接口加密的md5        String md5=getMD5(seckillId);        return new Exposer(true,md5,seckillId);    }    private String getMD5(long seckillId)    {        String base=seckillId+"/"+salt;        String md5= DigestUtils.md5DigestAsHex(base.getBytes());        return md5;    }    //秒殺是否成功,成功:減庫存,增加明細(xì);失敗:拋出異常,事務(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");//秒殺數(shù)據(jù)被重寫了        }        //執(zhí)行秒殺邏輯:減庫存+增加購買明細(xì)        Date nowTime=new Date();        try{            //減庫存            int updateCount=seckillDao.reduceNumber(seckillId,nowTime);            if (updateCount<=0)            {                //沒有更新庫存記錄,說明秒殺結(jié)束                throw new SeckillCloseException("seckill is closed");            }else {                //否則更新了庫存,秒殺成功,增加明細(xì)                int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);                //看是否該明細(xì)被重復(fù)插入,即用戶是否重復(fù)秒殺                if (insertCount<=0)                {                    throw new RepeatKillException("seckill repeated");                }else {                    //秒殺成功,得到成功插入的明細(xì)記錄,并返回成功秒殺的信息                    SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);                    return new SeckillExecution(seckillId,1,"秒殺成功",successKilled);                }            }        }catch (SeckillCloseException e1)        {            throw e1;        }catch (RepeatKillException e2)        {            throw e2;        }catch (Exception e)        {            logger.error(e.getMessage(),e);            //將編譯期異常轉(zhuǎn)化為運行期異常            throw new SeckillException("seckill inner error :"+e.getMessage());        }    }}

在以上代碼中,我們捕獲了運行時異常,原因是Spring的事務(wù)默認(rèn)是發(fā)生了RuntimeException才會回滾,發(fā)生了其他異常不會回滾,所以在最后的catch塊里通過throw new SeckillException("seckill inner error :"+e.getMessage());將編譯期異常轉(zhuǎn)化為運行期異常。

另外,在代碼里還存在著硬編碼的情況,比如秒殺結(jié)果返回的state和stateInfo參數(shù)信息是輸出給前端的,這些字符串應(yīng)該考慮用常量枚舉類封裝起來,方便重復(fù)利用,也易于維護(hù)。

src/main/java包下新建一個枚舉包com.lewis.enums包,在該包下創(chuàng)建一個枚舉類型SeckillStatEnum

public enum SeckillStatEnum {    SUCCESS(1,"秒殺成功"),    END(0,"秒殺結(jié)束"),    REPEAT_KILL(-1,"重復(fù)秒殺"),    INNER_ERROR(-2,"系統(tǒng)異常"),    DATE_REWRITE(-3,"數(shù)據(jù)篡改");    private int state;    private String info;    SeckillStatEnum(int state, String info) {        this.state = state;        this.info = info;    }    public int getState() {        return state;    }    public String getInfo() {        return info;    }    public static SeckillStatEnum stateOf(int index) {        for (SeckillStatEnum state : values()) {            if (state.getState()==index) {                return state;            }        }        return null;    }}

創(chuàng)建了枚舉類型后,就需要修改之前硬編碼的地方,修改SeckillExecution涉及到state和stateInfo參數(shù)的構(gòu)造方法

//秒殺成功返回所有信息 public SeckillExecution(long seckillId, SeckillStatEnum statEnum, SuccessKilled successKilled) {     this.seckillId = seckillId;     this.state = statEnum.getState();     this.stateInfo = statEnum.getInfo();     this.successKilled = successKilled; } //秒殺失敗 public SeckillExecution(long seckillId, SeckillStatEnum statEnum) {     this.seckillId = seckillId;     this.state = statEnum.getState();     this.stateInfo = statEnum.getInfo(); }

接著把SeckillServiceImpl里返回的秒殺成功信息的return new SeckillExecution(seckillId,1,"秒殺成功",successKilled);改成return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,successKilled);

4. 使用Spring進(jìn)行Service層的配置

在之前創(chuàng)建的spring包下創(chuàng)建spring-service.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="com.lewis.service" />    <!--配置事務(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" /></beans>

事務(wù)管理器

MyBatis采用的是JDBC的事務(wù)管理器

Hibernate采用的是Hibernate的事務(wù)管理器

通過注解的方式將Service的實現(xiàn)類(注意,不是Service接口)加入到Spring IoC容器中

@Servicepublic class SeckillServiceImpl implements SeckillService;

在需要進(jìn)行事務(wù)聲明的方法上加上事務(wù)的注解@Transactional

@Transactionalpublic SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)        throws SeckillException, RepeatKillException, SeckillCloseException {}

Spring的聲明式事務(wù)管理

  • 異常捕獲機(jī)制

Java異常分編譯期異常和運行期異常,運行期異常不需要手工try-catch,Spring的的聲明式事務(wù)只接收運行期異常回滾策略,非運行期異常不會幫我們回滾。

  • 事務(wù)傳播行為

Spring一共有7個事務(wù)傳播行為,默認(rèn)的事務(wù)傳播行為是PROPAGATION_REQUIRED,詳情可以參考這篇文章

使用注解控制事務(wù)方法的優(yōu)點(對于秒殺這種對事務(wù)延遲要求高的業(yè)務(wù)場景尤為重要)

  • 1.開發(fā)團(tuán)隊達(dá)成一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格
  • 2.保證事務(wù)方法的執(zhí)行時間盡可能短,不要穿插其他網(wǎng)絡(luò)操作RPC/HTTP請求或者剝離到事務(wù)方法外部(保證事務(wù)方法里面是很干凈的/效率的)
  • 3.不是所有的方法都需要事務(wù),如只有一條修改操作、只讀操作不要事務(wù)控制(MYSQL 表級鎖、行級鎖)

為什么使用IoC(控制反轉(zhuǎn))

  1. 對象創(chuàng)建統(tǒng)一托管。
  2. 規(guī)范的生命周期管理。
  3. 靈活的依賴注入。
  4. 一致的對象獲取方式。

Spring基于注解的事務(wù)操作

  • 在Spring早期版本中是使用ProxyFactoryBean+XMl方式來配置事務(wù)。
  • 在Spring配置文件使用tx:advice+aop命名空間,好處就是一次配置永久生效,你無須去關(guān)心中間出的問題,不過出錯了你很難找出來在哪里出了問題。
  • 注解@Transactional的方式,注解可以在方法定義、接口定義、類定義、public方法上,但是不能注解在private、final、static等方法上,因為Spring的事務(wù)管理默認(rèn)是使用Cglib動態(tài)代理的:
    • private方法因為訪問權(quán)限限制,無法被子類覆蓋
    • final方法無法被子類覆蓋
    • static是類級別的方法,無法被子類覆蓋
    • protected方法可以被子類覆蓋,因此可以被動態(tài)字節(jié)碼增強(qiáng)

不能被Spring AOP事務(wù)增強(qiáng)的方法

序號 動態(tài)代理策略 不能被事務(wù)增強(qiáng)的方法
1 基于接口的動態(tài)代理 除了public以外的所有方法,并且public static的方法也不能被增強(qiáng)
2 基于Cglib的動態(tài)代理 private、static、final的方法

關(guān)于Spring的組件注解、注入注解

  • @Component:標(biāo)識一個組件,當(dāng)不知道是什么組件,或者該組件不好歸類時使用該注解
  • @Service:標(biāo)識業(yè)務(wù)層組件
  • @Repository:標(biāo)識DAO層組件
  • @Controller:標(biāo)識控制層組件

通過Spring提供的組件自動掃描機(jī)制,可以在類路徑下尋找標(biāo)注了上述注解的類,并把這些類納入進(jìn)spring容器中管理,這些注解的作用和在xml文件中使用bean節(jié)點配置組件時一樣的。

<context:component-scan base-package=”xxx.xxx.xxx”>

component-scan標(biāo)簽?zāi)J(rèn)情況下自動掃描指定路徑下的包(含所有子包),將帶有@Component、@Repository、@Service、@Controller標(biāo)簽的類自動注冊到spring容器。getBean的默認(rèn)名稱是類名(頭字母小寫),如果想自定義,可以@Service(“aaaaa”)這樣來指定。這種bean默認(rèn)是“singleton”的,如果想改變,可以使用@Scope(“prototype”)來改變。

當(dāng)使用<context:component-scan/>后,就可以將<context:annotation-config/>移除了,前者包含了后者。

另外,@Resource,@Inject 是J2EE規(guī)范的一些注解

@Autowired是Spring的注解,可以對類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注,完成自動裝配的工作。通過 @Autowired的使用來消除setter/getter方法,默認(rèn)按類型裝配,如果想使用名稱裝配可以結(jié)合@Qualifier注解進(jìn)行使用,如下:

@Autowired() @Qualifier("baseDao")     private BaseDao baseDao; 

與@Autowired類似的是@Resource,@Resource屬于J2EE規(guī)范,默認(rèn)安照名稱進(jìn)行裝配,名稱可以通過name屬性進(jìn)行指定,如果沒有指定name屬性,當(dāng)注解寫在字段上時,默認(rèn)取字段名進(jìn)行按照名稱查找,如果注解寫在setter方法上默認(rèn)取屬性名進(jìn)行裝配。當(dāng)找不到與名稱匹配的bean時才按照類型進(jìn)行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進(jìn)行裝配。

@Resource(name="baseDao")     private BaseDao baseDao; 

而@Inject與@Autowired類似,也是根據(jù)類型注入,也可以通過@Named注解來按照name注入,此時只會按照名稱進(jìn)行裝配。

@Inject @Named("baseDao")private BaseDao baseDao; 

5. 進(jìn)行Service層的集成測試

使用logback來輸出日志信息,在resources包下創(chuàng)建logback.xml

<?xml version="1.0" encoding="UTF-8"?><configuration>  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">    <!-- encoders are assigned the type         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->    <encoder>      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>    </encoder>  </appender>  <root level="debug">    <appender-ref ref="STDOUT" />  </root></configuration>

通過IDE工具快速生成Junit單元測試,然后在各個方法里寫測試代碼。

@RunWith(SpringJUnit4ClassRunner.class)//告訴junit spring的配置文件@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 testGetSeckillList() throws Exception {        List<Seckill> list=seckillService.getSeckillList();        logger.info("list={}", list);    }    @Test    public void testGetById() throws Exception {        long seckillId=1000;        Seckill seckill=seckillService.getById(seckillId);        logger.info("seckill={}", seckill);    }}

在測試通過了這兩個方法后,開始對后兩個業(yè)務(wù)邏輯方法的測試,首先測試testExportSeckillUrl()

@Testpublic void testExportSeckillUrl() throws Exception {    long seckillId=1000;    Exposer exposer=seckillService.exportSeckillUrl(seckillId);    logger.info("exposer={}", exposer);}

會發(fā)現(xiàn)沒有返回商品的秒殺地址,因為我們數(shù)據(jù)庫的秒殺時間和結(jié)束秒殺時間沒有修改,所以判斷當(dāng)前商品的秒殺已結(jié)束。將數(shù)據(jù)庫中的秒殺時間和結(jié)束秒殺時間修改成滿足我們當(dāng)前的時間的范圍,重新測試該方法,可以獲取到該商品的秒殺地址。而第四個方法的測試需要使用到該地址(md5),將該值傳入到testExecuteSeckill()中進(jìn)行測試:

@Testpublic void testExecuteSeckill() throws Exception {    long seckillId=1000;    long userPhone=13476191876L;    String md5="70b9564762568e9ff29a4a949f8f6de4";    SeckillExecution execution=seckillService.executeSeckill(seckillId,userPhone,md5);    logger.info("result={}", execution);}

需要注意的是,該方法是會產(chǎn)生異常的,比如我們重復(fù)運行該方法,會報錯,因為用戶進(jìn)行了重復(fù)秒殺,所以我們需要手動try-catch,將程序允許的異常包起來而不去向上拋給junit,更改測試代碼如下:

@Testpublic void testExecuteSeckill() throws Exception {    long seckillId=1000;    long userPhone=13476191876L;    String md5="70b9564762568e9ff29a4a949f8f6de4";    try {        SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);        logger.info("result={}", execution);    }catch (RepeatKillException e)    {        logger.error(e.getMessage());    }catch (SeckillCloseException e1)    {        logger.error(e1.getMessage());    }}

在測試過程中,第四個方法使用到了第三個方法返回的秒殺地址,在實際開發(fā)中,我們需要將第三個和第四個方法合并成一個完整邏輯的方法:

//集成測試代碼完整邏輯,注意可重復(fù)執(zhí)行@Testpublic void testSeckillLogic() throws Exception {    long seckillId=1000;    Exposer exposer=seckillService.exportSeckillUrl(seckillId);    if (exposer.isExposed())    {        logger.info("exposer={}", exposer);        long userPhone=13476191876L;        String md5=exposer.getMd5();        try {            SeckillExecution execution = seckillService.executeSeckill(seckillId, userPhone, md5);            logger.info("result={}", execution);        }catch (RepeatKillException e)        {            logger.error(e.getMessage());        }catch (SeckillCloseException e1)        {            logger.error(e1.getMessage());        }    }else {        //秒殺未開啟        logger.warn("exposer={}", exposer);    }}

我們可以在SeckillServiceTest類里面加上@Transational注解,原因是:

@Transactional注解是表明此測試類的事務(wù)啟用,這樣所有的測試方案都會自動的 rollback,即不用自己清除自己所做的任何對數(shù)據(jù)庫的變更了。

日志無法打印的問題

在pom.xml中加上

<dependency>  <groupId>ch.qos.logback</groupId>  <artifactId>logback-classic</artifactId>  <version>1.1.9</version></dependency>

存在的坑

  • 關(guān)于同類中調(diào)用事務(wù)方法的時候有個坑,同學(xué)們需要注意下AOP切不到調(diào)用事務(wù)方法。事務(wù)不會生效,解決辦法有幾種,可以搜一下,找一下適合自己的方案。本質(zhì)問題是類內(nèi)部調(diào)用時AOP不會用代理調(diào)用內(nèi)部方法。
  • 沒有引入AOP的xsd會報錯

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance    http://www.springmodules.org/schema/cache/springmodules-cache.xsd     http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd"

相關(guān)鏈接

Spring事務(wù)異?;貪L,捕獲異常不拋出就不會回滾

本節(jié)結(jié)語

至此,關(guān)于Java高并發(fā)秒殺API的Service層的開發(fā)與測試已經(jīng)完成,接下來進(jìn)行Web層的開發(fā),詳情請參考下一篇文章。

上一篇文章:Java高并發(fā)秒殺API(一)之業(yè)務(wù)分析與DAO層

下一篇文章:Java高并發(fā)秒殺API(三)之Web層

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Spring 事務(wù)失效的 8 大場景,看看你都遇到過幾個?
Spring Boot 這么火,常用注解和原理都給你整理好了
spring 事務(wù)不起效
Spring 事務(wù)原理和使用
SpringBoot持久化層操作支持(一)SQL篇
聊聊spring事務(wù)失效的12種場景,太坑了
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服