您的位置:首页 > 编程语言 > Java开发

SSM实战——秒杀系统之Service层接口设计与实现、Spring托管、声明式事务

2017-06-01 20:03 666 查看
一:Service层接口设计

准备工作:新建三个包:service包、exception包、dto包,分别用来存放业务接口、自定义异常类、dto类。



1:定义接口

package org.myseckill.service;

import java.util.List;

import org.myseckill.dto.Exposer;
import org.myseckill.dto.SeckillExecution;
import org.myseckill.entity.Seckill;
import org.myseckill.exception.RepeatKillException;
import org.myseckill.exception.SeckillClosedException;
import org.myseckill.exception.SeckillException;

public interface SeckillService {

//查询所有
List<Seckill> getSeckillList();
//根据ID查询
Seckill getById(long seckillId);
//暴露秒杀网页地址
Exposer exportSeckillUrl(long seckillId);

//md5用于与内部md5做比较,防止用于篡改url进行秒杀
SeckillExecution executeSeckill(long seckillId,long userPhone,String md5) throws SeckillException,RepeatKillException,SeckillClosedException;

}


2:定义接口中用到的相关dto:

package org.myseckill.dto;

//封装暴露信息
public class Exposer {
//是否开启秒杀
private boolean exposed;

private String md5;

private long seckillId;

private long now;

private long start;

private long end;

public Exposer(boolean exposed, String md5, long seckillId) {
super();
this.exposed = exposed;
this.md5 = md5;
this.seckillId = seckillId;
}

public Exposer(long now, long start, long end) {
super();
this.now = now;
this.start = start;
this.end = end;
}

public Exposer(boolean exposed, long seckillId) {
super();
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;
}

}


package org.myseckill.dto;

import org.myseckill.entity.SuccessKilled;

//封装秒杀后信息
public class SeckillExecution {

private long seckillId;
private int state;
private String stateInfo;
private SuccessKilled successKilled;
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;
}
//秒杀成功的构造函数
public SeckillExecution(long seckillId, int state, String stateInfo,
SuccessKilled successKilled) {
super();
this.seckillId = seckillId;
this.state = state;
this.stateInfo = stateInfo;
this.successKilled = successKilled;
}
//秒杀失败的构造函数
public SeckillExecution(long seckillId, int state, String stateInfo) {
super();
this.seckillId = seckillId;
this.state = state;
this.stateInfo = stateInfo;
}

}


3:定义接口中用到的自定义异常

package org.myseckill.exception;

//秒杀相关通用异常
public class SeckillException extends RuntimeException {

public SeckillException() {
super();
// TODO Auto-generated constructor stub
}

public SeckillException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}

public SeckillException(String message) {
super(message);
// TODO Auto-generated constructor stub
}

public SeckillException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}

}


package org.myseckill.exception;

//重复秒杀异常
public class RepeatKillException extends SeckillException {

public RepeatKillException(String message, Throwable cause) {
super(message, cause);
}

public RepeatKillException(String message) {
super(message);
}

public RepeatKillException(Throwable cause) {
super(cause);
}

}


package org.myseckill.exception;

//秒杀已关闭异常
public class SeckillClosedException extends SeckillException {

public SeckillClosedException() {
super();

}

public SeckillClosedException(String message, Throwable cause) {
super(message, cause);

}

public SeckillClosedException(String message) {
super(message);

}

public SeckillClosedException(Throwable cause) {
super(cause);

}

}


二:Service层接口实现

1:在Service包下,新建一个Impl包,用于存放接口的实现类。



2:定义接口实现类SeckillServiceImpl

package org.myseckill.service.Impl;

import java.util.Date;
import java.util.List;

import org.myseckill.dao.SeckillDao;
import org.myseckill.dao.SuccessKilledDao;
import org.myseckill.dto.Exposer;
import org.myseckill.dto.SeckillExecution;
import org.myseckill.entity.Seckill;
import org.myseckill.entity.SuccessKilled;
import org.myseckill.exception.RepeatKillException;
import org.myseckill.exception.SeckillClosedException;
import org.myseckill.exception.SeckillException;
import org.myseckill.service.SeckillService;
import org.myseckill.enums.SeckillStateEnum;
import org.myseckill.exception.RepeatKillException;
import org.myseckill.exception.SeckillClosedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;

public class SeckillServiceImpl implements SeckillService {

//使用slf4j日志
private Logger logger=LoggerFactory.getLogger(this.getClass());

private SeckillDao seckillDao;

private SuccessKilledDao successKilledDao;

//用于加密的混淆字符串,随机串
private final String slat="asfdfadsf45qa@$E#iudkgj15=sdf=daf5";

//本例只用了4跳记录,实际项目中可以再定义一个selectall的SQL语句查询所有
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}

@Override
public Seckill getById(long seckillId) {

return seckillDao.queryById(seckillId);
}

@Override
public Exposer exportSeckillUrl(long seckillId) {
Seckill seckill=seckillDao.queryById(seckillId);
if(seckill==null){//没有这个产品的秒杀记录,不进行暴露
return new Exposer(false, seckillId);
}

Date now=new Date();
Date start=seckill.getStartTime();
Date end=seckill.getEndTime();
//若时间非法,不秒杀
if(now.getTime()<start.getTime() || now.getTime()>end.getTime()){
return new Exposer(false, seckillId, now.getTime(), start.getTime(), end.getTime());
}
//否则,进行秒杀网址暴露
String md5=getMD5(seckillId);
return new Exposer(true, md5, seckillId);
}

//用md5加密
private String getMD5(long seckillId){
String base=seckillId+"/"+slat;
String md5=DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
@Override
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillException {
if(md5==null||!md5.equals(getMD5(seckillId))){
throw new SeckillException("seckill data rewrite");
}
//执行秒杀逻辑:减库存+记录购买行为
try {
//记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
if(insertCount <= 0 ){
//重复秒杀
throw new RepeatKillException("seckill repeated");
}else{
//减库存,热点商品竞争(高并发点)
int updateCount = seckillDao.reduceNumber(seckillId, new Date());
if(updateCount<=0){
//没有更新到记录,秒杀结束,rollback
throw new SeckillClosedException("seckill is closed");
}else{
//秒杀成功,commit
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS,successKilled);
}
}

} catch(SeckillClosedException e1){
throw e1;
} catch(RepeatKillException e2){
throw e2;
}catch (Exception e) {
logger.error(e.getMessage(),e);
//所有异常转化为运行期异常
throw new SeckillException("seckill inner error:"+e.getMessage());
}
}

}


3:把状态与状态信息常量,封装成枚举常量

在org.myseckill包下新建enums包,包中新建一个枚举类SeckillStateEnum:

package org.myseckill.enums;

//在实际开发中,全局常用的常量们用枚举常量存储
public enum SeckillStateEnum {
//定义一系列枚举常量
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATA_REWRITE(-3,"数据篡改");

private int state;

private String stateInfo;

SeckillStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}

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 static SeckillStateEnum stateOf(int index) {
//迭代枚举常量,返回state值等于index的常量
for (SeckillStateEnum stateEnum : values()) {
if (stateEnum.getState() == index) {
return stateEnum;
}
}
return null;
}

}


4:修改SeckillExecution类的构造函数:

//秒杀成功的构造函数
public SeckillExecution(long seckillId, SeckillStateEnum success,SuccessKilled successKilled) {
super();
this.seckillId = seckillId;
this.state = success.getState();
this.stateInfo = success.getStateInfo();
this.successKilled = successKilled;
}
//秒杀失败的构造函数
public SeckillExecution(long seckillId, SeckillStateEnum success) {
super();
this.seckillId = seckillId;
this.state = success.getState();
this.stateInfo = success.getStateInfo();
}


三:Spring托管Service层的类

SpringIOC中,注入主要分为两种场景:

1:第三方类库提供的类,如:datasource等。使用XML中ref来注入;

2:自己定义的类,则在代码中,使用注解来注入;

1:在resource.spring包下,新建一个spring-service.xml,用于负责托管service层bean以及进行事务管理。



2:托管bean:

如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用上述注解对分层中的类进行注释。

@Service用于标注业务层组件

@Controller用于标注控制层组件(如struts中的action)

@Repository用于标注数据访问组件,即DAO组件

@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

component-scan标签默认情况下自动扫描指定路径下的包(含所有子包),将带有@Component、@Repository、@Service、@Controller标签的类自动注册到spring容器。

托管bean分两步:

首先在配置文件开启包扫描:

<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 包扫描,找到包下使用注解来依赖注入的属性,进行托管 -->
<context:component-scan base-package="org.myseckill.service"/>

</beans>


然后,在bean类加注解:为类加组件注解,类中需要注入属性的加注入注解

@Service
public class SeckillServiceImpl implements SeckillService {

@Autowired
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;

......
}


3:声明式事务管理

事务管理主要有两种:

1:XML配置,使用tx:advice标签配置aop进行事务管理。好处是一次配置处处生效(可以自己配置切入点),坏处是,阅读代码时不知道哪个方法是被事务管理的,需要频繁查阅xml。

2:@Transactional注解:使用该注解注释的方法,表示把该方法交给spring进行事务管理。推荐使用这个方法,一是简便,二是在阅读代码时清晰地看到该方法被事务托管。

事务托管:

首先,在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-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- 扫描service包下所有的类型 -->
<context:component-scan base-package="org.myseckill.service"></context:component-scan>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置基于注解的声明式事务
默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>


然后,在bean中,需要事务托管的方法前面,加@Transactional注解:

@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillExeception, SeckillException{}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐