SSM,Spring,SpringMVC,Mybatis之Service---29

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);
		}
	}

}

======================================

Spring管理Service依赖:

Spring IOC理解:

使用Spring IOC可以得到一致的访问接口,通过接口可以访问工厂任意实例

业务对象依赖图:Spring了一个完整初始化的过程,我们能从Spring中得到SeckillService完整的实例

为什么用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);
		}
	}
}






























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值