上一篇講述了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)注我: