SSM之Service---29
Spring/SpringMVC/Mybatis,日志
Service文件夹:
exception:异常包,创建公版异常(SeckillException),其他异常进行继承
dao:主要面向与业务无关的,和如Hibernate、MyBatis相关的事务操作。dto(数据传输),类似entity,侧重于存放跟web交互的实体类,(做为service的返回类型使用)
service放与业务逻辑相关的事务操作(接口);
Service代码(包括service(接口),dto,exception):
service文件(接口):
@SeckillService.java
/**
* 业务接口:站在“使用者”角度设计接口
* 三个方面:方法定义的粒度(明确使用者要使用的方法),
* 参数(简单直接,少用Map),
* 返回类型(return 类型简单,少用Map /异常)
*/
public interface SeckillService {
/**
* 查询所有秒杀记录
* @return
*/
List<Seckill> getSeckillList();
/**
* 查询单个秒杀记录
* @param seckillId
* @return
*/
Seckill getSeckillById(long seckillId);
/**
* 秒杀开启时输出渺少接口地址,
* 否则输出系统时间和秒杀时间
* @param seckillId
* @return Exposer
*/
Exposer exportSeckillUrl(long seckillId);
/**
* 执行秒杀操作
* @param seckillId
* @param userPhone
* @param md5
* @return SeckillExecution
*/
SeckillExecution executeSeckill(long seckillId,long userPhone,String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
}
dto文件(包含Service实现所需要的属性):
@Exposer.java
/*** 暴露秒杀地址DTO
* @author THHINK
*
*/
public class Exposer {
//是否开启秒杀
private boolean exposed;
//秒杀ID
private long seckillId;
//一种加密措施
private String md5;
//系统当前时间(毫秒)
private long now;
//开始时间
private long start;
//结束时间
private long end;
public Exposer(boolean exposed, long seckillId, String md5) {
super();
this.exposed = exposed;
this.seckillId = seckillId;
this.md5 = md5;
}
public Exposer(boolean exposed, long now, long start, long end) {
super();
this.exposed = exposed;
this.now = now;
this.start = start;
this.end = end;
}
public Exposer(boolean exposed, long seckillId) {
super();
this.exposed = exposed;
this.seckillId = seckillId;
}
@SeckillException.java
/**
* 封装秒杀执行后的结果
* @author THHINK
*
*/
public class SeckillExecution {
private long seckillId;
//秒杀执行结果状态
private int state;
//状态表示
private String stateInfo;
//秒杀成功对象
private 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;
}
exception文件(各种异常,一个公版,其他继承);
@SeckillException.java
/**
* 秒杀相关业务异常
*/
public class SeckillException extends RuntimeException {
public SeckillException(String message){
super(message);
}
public SeckillException(String message,Throwable cause){
super(message,cause);
}
@SeckillCloseException.java
/**
* 秒杀关闭异常
*/
public class SeckillCloseException extends SeckillException {
public SeckillCloseException(String message){
super(message);
}
public SeckillCloseException(String message,Throwable cause){
super(message,cause);
}
}
@RepeatKillException.java
/**
* 重复秒杀异常(运行期异常--不需要手动try Catch)
* spring的事务只接收 运行期异常,并进行回滚。
*/
public class RepeatKillException extends SeckillException {
public RepeatKillException(String message){
super(message);
}
public RepeatKillException(String message,Throwable cause){
super(message,cause);
}
===================================================================================
使用枚举表示数据常量字段,方便维护
定义枚举格式
/**
*使用枚举 表述常量数据字段
*/
public enum SeckillStatEnum {
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),
INNER_ERRPR(-2,"系统异常"),
Data_REWEITE(-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 String getStateInfo() {
return stateInfo;
}
public static SeckillStatEnum stateOf(int index){
for (SeckillStatEnum state : values()){
if (state.getState() == index){
return state;
}
}
return null;
}
}
实现SeckillService接口---SeckillServiceImpl实现类。
@SeckillServiceImpl.java
@Service("seckillService")
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//注入Service依赖
@Autowired//@Resource,@Inject
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
@Autowired
private RedisDao redisDao;
@Resource
private SeckillUtil seckillUtil;
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}
public Seckill getSeckillById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
//优化点:缓存优化:超时的基础上维护一致性(数据不变的情况,多并发)
//1:访问redis
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
//2:访问数据库
seckill = seckillDao.queryById(seckillId);
if(seckill != null){
//3:放入redis
String result = redisDao.putSeckill(seckill);
System.out.println("result"+result);
seckill = redisDao.getSeckill(seckillId);
System.out.println("seckill="+seckill);
}else{
return new Exposer(false, seckillId);
}
}
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 = seckillUtil.getMD5(seckillId);
return new Exposer(true, seckillId, md5);
}
/**
* 使用注解控制事务方法的优点:
* 1.开发团队达成一致约定,明确标注事务方法的编程风格。
*2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部
*3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
*/
@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
System.out.println("执行executeSeckill。。。");
if (md5 == null || !md5.equals(seckillUtil.getMD5(seckillId))) {
throw new SeckillException("seckill data rewrite");
}
Date killTime = new Date();
// 执行秒杀逻辑:减库存+记录秒杀行为(一个事务(运行期),出现问题,回滚)
try {
// 记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
// 唯一验证:seckillId,userPhone
// 重复秒杀
if (insertCount <= 0){
throw new RepeatKillException("seckill repeated");
} else {
// 减库存 ,热门商品竞争
System.out.println("减库存。。。");
int updateCount = seckillDao.reduceNumber(seckillId, killTime);
System.out.println("减库存完成");
if (updateCount <= 0) {
//没有更新到记录,秒杀结束,rollback
throw new SeckillCloseException("seckill is closed");
} else {
// 秒杀成功 commit
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS, successKilled);
}
}
} catch (SeckillCloseException e1){
throw e1;
} catch (RepeatKillException e2){
throw e2;
}catch (Exception e) {
logger.error(e.getMessage(), e);
// 所有编译期异常 转化为运行期异常
throw new SeckillException("seckill inner error" + e.getMessage());
}
}
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(seckillUtil.getMD5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStateEnum.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);
// 执行存储过程,result被赋值
try {
seckillDao.killByProcedure(map);
//commons-collections, 获取result
int result = MapUtils.getInteger(map, "result", -2);
if (result == 1) {
SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk);
} else {
return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
}
}
}
@Service("seckillService")
public class SeckillServiceImpl implements SeckillService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//注入Service依赖
@Autowired//@Resource,@Inject
private SeckillDao seckillDao;
@Autowired
private SuccessKilledDao successKilledDao;
@Autowired
private RedisDao redisDao;
@Resource
private SeckillUtil seckillUtil;
public List<Seckill> getSeckillList() {
return seckillDao.queryAll(0, 4);
}
public Seckill getSeckillById(long seckillId) {
return seckillDao.queryById(seckillId);
}
public Exposer exportSeckillUrl(long seckillId) {
//优化点:缓存优化:超时的基础上维护一致性(数据不变的情况,多并发)
//1:访问redis
Seckill seckill = redisDao.getSeckill(seckillId);
if (seckill == null) {
//2:访问数据库
seckill = seckillDao.queryById(seckillId);
if(seckill != null){
//3:放入redis
String result = redisDao.putSeckill(seckill);
System.out.println("result"+result);
seckill = redisDao.getSeckill(seckillId);
System.out.println("seckill="+seckill);
}else{
return new Exposer(false, seckillId);
}
}
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 = seckillUtil.getMD5(seckillId);
return new Exposer(true, seckillId, md5);
}
/**
* 使用注解控制事务方法的优点:
* 1.开发团队达成一致约定,明确标注事务方法的编程风格。
*2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部
*3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
*/
@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
System.out.println("执行executeSeckill。。。");
if (md5 == null || !md5.equals(seckillUtil.getMD5(seckillId))) {
throw new SeckillException("seckill data rewrite");
}
Date killTime = new Date();
// 执行秒杀逻辑:减库存+记录秒杀行为(一个事务(运行期),出现问题,回滚)
try {
// 记录购买行为
int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
// 唯一验证:seckillId,userPhone
// 重复秒杀
if (insertCount <= 0){
throw new RepeatKillException("seckill repeated");
} else {
// 减库存 ,热门商品竞争
System.out.println("减库存。。。");
int updateCount = seckillDao.reduceNumber(seckillId, killTime);
System.out.println("减库存完成");
if (updateCount <= 0) {
//没有更新到记录,秒杀结束,rollback
throw new SeckillCloseException("seckill is closed");
} else {
// 秒杀成功 commit
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS, successKilled);
}
}
} catch (SeckillCloseException e1){
throw e1;
} catch (RepeatKillException e2){
throw e2;
}catch (Exception e) {
logger.error(e.getMessage(), e);
// 所有编译期异常 转化为运行期异常
throw new SeckillException("seckill inner error" + e.getMessage());
}
}
public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
if (md5 == null || !md5.equals(seckillUtil.getMD5(seckillId))) {
return new SeckillExecution(seckillId, SeckillStateEnum.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);
// 执行存储过程,result被赋值
try {
seckillDao.killByProcedure(map);
//commons-collections, 获取result
int result = MapUtils.getInteger(map, "result", -2);
if (result == 1) {
SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, sk);
} else {
return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
}
}
}
======================================
Spring管理Service依赖:
Spring IOC理解:
使用Spring IOC可以得到一致的访问接口,通过接口可以访问工厂中任意实例。
业务对象依赖图:Spring了一个完整初始化的过程,我们能从Spring中得到SeckillService完整的实例。
为什么用IOC:
1:对象创建统一托管
2:规范的生命周期管理
3:灵活的依赖注入
4:一致的获取对象
为什么用IOC:
1:对象创建统一托管 2:规范的生命周期管理 3:灵活的依赖注入 4:一致的获取对象深刻理解IOC的依赖注入以及注入场景使用
IOC配置流程:XML配置 -> package-scan(包扫描) -> Annotation 注解
声明式事务:
声明式事务目的在于解脱繁琐的关于事务的代码,
在实际的开发中,不需要关心事务的开启、提交、回滚、关闭等等,而是直接交由第三方框架托管,比如spring。
第二中方法:tx:advice+aop命名空间(一次配置永久生效)
第三中方法:@Transactional(注解控制)
本例推荐使用第三种基于注解的声明式事务的方法,这种方式的优点在于,当看到@Transcation注解的时候知道这是一个与事务有关的方法,此时就会自觉遵守一些关于事务开发的规范,有利于程序的进一步维护。
Spring在抛出运行期异常时会回滚事务,两点注意:
1 非运行期异常时要注意,防止出现部分成功部分失败的情况(所以自己封装异常时,在需要的地方要implements RuntimeException)。 2 小心使用try-catch:被try-catch块包裹起来的异常Spring也是感觉不到的。
Spring 管理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:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd "> <!--扫描service包下所有使用注解的类型Service,Controller --> <context:component-scan base-package="org.seckill.service"/> <!-- 配置事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/><!--注入数据连接池--> </bean> <!-- 配置基于注解的声明式事务 默认使用注解管理事务行为 @Transactional --> <tx:annotation-driven transaction-manager="txManager" /> </beans>
编写测试类
配置日志:
logback官方配置网页http://logback.qos.ch/manual/configuration.html
@logback.xml <?xml version="1.0" encoding="UTF-8" ?> <configuration debug="true"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are by default assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder --> <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>
@SeckillServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
//告诉Junit spring配置文件
@ContextConfiguration(locations={
"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() {
List<Seckill> seckills= seckillService.getSeckillList();
logger.info("seckills={}",seckills);
}
@Test
public void testGetSeckillById() {
long seckillId = 1002;
Seckill seckill = seckillService.getSeckillById(seckillId);
logger.info("seckill={}", seckill);
}
@Test
public void testExportSeckillUrl() {
long seckillId = 1000;
Exposer exposer =seckillService.exportSeckillUrl(seckillId);
logger.info("exposer={}", exposer);
}
@Test
public void testExecuteSeckill() {
long seckillId = 1000;
long userPhone = 13022211112L;
String md5="018bd79f607030689c3b18cd4c03f7e3";
try {
SeckillExecution seckillExecution =seckillService.executeSeckill(seckillId, userPhone, md5);
logger.info("seckillExecution={}", seckillExecution);
} catch (RepeatKillException e) {
e.printStackTrace();
} catch (SeckillCloseException e) {
e.printStackTrace();
}
}
//集成测试代码完整逻辑,注意可重复执行
@Test
public void testSeckillLogic() {
long seckillId = 1000;
Exposer exposer =seckillService.exportSeckillUrl(seckillId);
if(exposer.isExposed()){
logger.info("exposer={}", exposer);
long userPhone = 13022211113L;
String md5=exposer.getMd5();
try {
SeckillExecution seckillExecution =seckillService.executeSeckill(seckillId, userPhone, md5);
logger.info("seckillExecution={}", seckillExecution);
} catch (RepeatKillException e) {
e.printStackTrace();
} catch (SeckillCloseException e) {
e.printStackTrace();
}
}else{
//秒杀未开启
logger.warn("exposer={}",exposer);
}
}
}