二 service层实现
1.内容
站在使用者的角度设计接口
三个方向 :方法粒度,参数,返回类型
2.代码
SeckillService
package org.seckill.service;
import java.util.List;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillException;
import org.seckill.exception.seckillCloseException;
/**
* 站在使用者角度设计接口
* 三个方面 方法粒度 参数 返回类型
*/
public interface SeckillService {
/**
*
* 查询所有秒杀记录
* @return
*/
List <Seckill> getSeckillList();
/**
*
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getById(long seckillId);
/**
* 输出秒杀接口地址 否则输出系统时间和秒杀时间
* @param seckillId
*/
Exposer exportSeckillUrl(long seckillId);
//执行秒杀操作
SeckillExecution excuteSeckill(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException;
//执行秒杀操作by存储过程
SeckillExecution excuteSeckillProcedure(long seckillId,long userPhone,String md5) throws RepeatKillException,seckillCloseException,SeckillException;
}
重复秒杀异常 RepeatKillException
package org.seckill.exception;
//重复秒杀异常(运行期异常)
public class RepeatKillException extends RuntimeException{
public RepeatKillException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public RepeatKillException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
秒杀关闭异常 seckillCloseException
package org.seckill.exception;
/**
*
* 秒杀关闭异常
* @author LCJA
*
*/
public class seckillCloseException extends RuntimeException{
public seckillCloseException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public seckillCloseException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
秒杀相关的所有业务异常 SeckillException
package org.seckill.exception;
/**
*
* 秒杀相关的所有业务异常
* @author LCJA
*
*/
public class SeckillException extends RuntimeException{
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
}
}
SeckillServiceImpl 实现类
package org.seckill.service.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dao.cache.RedisDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillException;
import org.seckill.exception.seckillCloseException;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
//@component @service @dao @conroller
@Service
public class SeckillServiceImpl implements SeckillService{
private Logger logger=LoggerFactory.getLogger(this.getClass());
// mabaits 使用mapper形式 注入spring容器中
//注入service依赖
@Autowired//@resource@inject
private SeckillDao seckilldao;
@Autowired
private SuccessKilledDao successkilleddao;
@Autowired
private RedisDao redisdao;
//用于混淆md5
private final String slat="abcd";
public List<Seckill> getSeckillList() {
// TODO Auto-generated method stub
return seckilldao.queryAll(0, 4);
}
public Seckill getById(long seckillId) {
// TODO Auto-generated method stub
return seckilldao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
//优化点 : 缓存优化
/**
* get from cache
* if null
* get db
* else
* put cache
* locgin
* 可以这样做的原因 秒杀单 一般不做修改
* 超时维护
* 一般做集群
*/
//访问redis
Seckill seckill = redisdao.getSeckill(seckillId);
if(seckill == null){
seckill=seckilldao.queryById(seckillId);
System.out.println("seckill----"+seckill);
if(seckill== null){
return new Exposer(false, seckillId);
}else{
//放入redis
redisdao.putSeckill(seckill);
}
}
Date startTime=seckill.getStartTime();
Date endTime=seckill.getEndTime();
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);
}
/**
* 使用注解控制事务有点
* 开发团队达成一致约定 明确事务方法的编程风格
* 保证事务方法的执行时间竟可能短不要穿插其他网络操作
* 不是所有的方法都需要事务 如只有一条修改操作 ,只读操作不需要控制事务
* 通过异常来告诉声明式事务 回滚还是commit
*/@Transactional
public SeckillExecution excuteSeckill(long seckillId, long userPhone, String md5)
throws RepeatKillException, seckillCloseException, SeckillException {
try{
if(md5==null||!md5.equals(getMD5(seckillId))){
throw new SeckillException("seckill data rewrite");
}
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){
//没有更新到记录,秒杀结束
throw new seckillCloseException("seckill losed");
}else{
//减库存成功 记录购买行为
//秒杀成功
SuccessKilled successKilled=successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);
}
}
}catch(Exception e){
logger.error(e.getMessage(),e);
throw new SeckillException("seckill inner error:"+e.getMessage());
}
}
private String getMD5(long seckillId){
String base=seckillId+"/"+slat;
String md5=DigestUtils.md5DigestAsHex(base.getBytes());
return md5;
}
public SeckillExecution excuteSeckillProcedure(long seckillId, long userPhone, String md5)
throws RepeatKillException, seckillCloseException, SeckillException {
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);
try {
seckilldao.killByProcedure(map);
int result = MapUtils.getInteger(map, "result",-2);
if(result==1){
SuccessKilled sk = successkilleddao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,sk);
}else{
return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage());
return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
}
}
}
SeckillStatEnum
package org.seckill.enums;
//使用枚举表示常量数据字段
public enum SeckillStatEnum {
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERROR(-2,"系统异常"),
DATA_REWRITE(-3,"数据篡改")
;
private int state;
private String stateInfo;
private SeckillStatEnum(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 SeckillStatEnum stateOf(int index){
for (SeckillStatEnum state :values()){
if(state.getState() == index){
return state;
}
}
return null;
}
}
3.spring托管service
spring会通过spring工厂创建对象
seckillservice 依赖 SeckillDao和SuccessKillDao,
SeckillDao和SuccessKillDao依赖SqlSessionFactory,
SqlSessionFactory 依赖 数据源..
原因
对象创建统一管理,
规范生命周期管理,
灵活的依赖注入,
一致获取对象。
ioc使用场景
xml | 注解 | 配置类 |
---|---|---|
1.bean实现类来自第三方类库,如DataSource等;2需要命名空间配置,如:context,aop,mvc等 | 项目中自身开发使用的类,可直接在代码中使用注解 如@Service等 | 需要通过代码控制对象创建逻辑的场景,如自定义修改依赖类库 |
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:aop="http://www.springframework.org/schema/aop"
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.seckill.service"></context:component-scan>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置注解式声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
4.spring声明式事务
抛出运行期异常时,会回滚,小心try-catch
这个是 tx:advice+aop命名空间 一次配置永久生效(看起来很美好),本篇没有采用
<!-- 配置事务事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="append*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<!-- 配置事务切点,并把切点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.jinlou.service..*Impl.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
本篇采用的是注解的形式
<!-- 配置注解式声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
使用注解控制事务的优点;
1.开发团队达成一致约定,明确标注事务方法的编程风格
2.保证事务执行时尽可能短,不要穿插其他网络操作(rpc(远程服务调用),http请求)
3.不是所有的方法都不是需要事务
5.单元测试
package org.seckill.service;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"
})
public class SeckillServiceTest {
private Logger logger=LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
@Test
public void testGetSeckillList() {
List <Seckill> list=seckillService.getSeckillList();
logger.info("list={}",list);
}
@Test
public void testGetById() {
long id=1003;
Seckill seckill=seckillService.getById(id);
logger.info("seckill={}seckill",seckill);
}
@Test
public void testExportSeckillUrl() {
long id=1000;
Exposer exposer= seckillService.exportSeckillUrl(id);
logger.info("exposer={}",exposer);
}
//exposed=true, md5=8e7add56132475b9141111ed8e961613, seckillId=1000, now=0, start=0, end=0]
@Test
public void testExcuteSeckill() {
long id=1000;
long phone=12345679;
String md5="8e7add56132475b9141111ed8e961613";
SeckillExecution seckillexcution =seckillService.excuteSeckill(id, phone, md5);
logger.info("seckillexcution={}",seckillexcution);
}
@Test
public void executeSeckillProcedure(){
long seckillId =1000;
long phone =1313131313;
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
if(exposer.isExposed()){
String md5 = exposer.getMd5();
SeckillExecution excution = seckillService.excuteSeckillProcedure(seckillId, phone, md5);
logger.info(excution.getStateInfo());
}
}
}
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>
另外可以打印sql
<bean id="druid-stat-interceptor"
class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"
scope="prototype">
<property name="patterns">
<list>
<value>com.skwx.service.impl.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
需要在pom中添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.27</version>
</dependency>