• 一篇SSM框架整合友好的文章(二)


    上一篇讲述了DAO 层,mybatis实现数据库的连接,DAO层接口设计,以及mybtis和spring的整合。DAO层采用接口设计方式实现,接口和SQL实现的分离,方便维护。DAO层所负责的仅仅是接口的设计和实现,而负责的逻辑即一个或多个DAO层接口的拼接是在Sevice层中完成。这篇文章接上篇文章,主要讲述Service层的实现、和Spring的整合以及声明如何声明事物。

    ###一、Service层接口设计
    业务接口设计应当站在“使用者”角度设计接口,应遵循三个规范:合理的命令,明确的参数,返回结果(正常接口/异常结果)。本例子采用的Java高并发的秒杀API系列课程的例子,创建设计的业务逻辑接口如下:

    
    public interface SeckillService {
        /**
         * 查询所有秒杀记录
         * @return
         */
        List<Seckill> getSerkillList();
      
    
        /**
         * 查询单个秒杀记录
         * @param seckillId
         * @return
         */
        Seckill getById(long seckillId);
    
       
        /**
         * 秒杀开启时输出秒杀接口地址,
         * 否则输出系统时间和秒杀时间
         * @param seckillId
         */
        Exposer exportSeckillUrl(long seckillId);
       
    	
    	/**
    	  *执行秒杀接口
    	  */
       SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException,RepeatKillException,SeckillCloseException;
      
         
    

    二、Service接口的实现

    直接上代码了,在这里讲下秒杀业务的逻辑:首先是获取秒杀列表,点击列表进入秒杀详情页,这时获取系统时间,如果秒杀开始,获取秒杀地址,点击秒杀,执行秒杀。所以业务逻辑也只设计了这相关的4个业务逻辑。其中使用了dto层去传递响应数据,以及自定义异常,所有的异常都继承运行异常,这是为了方便spring自动回滚,这两个知识点,自行看源码。

    package org.forezp.service.impl;
    
    @Service
    public class SeckillServiceImpl implements SeckillService{
        private Logger logger= LoggerFactory.getLogger(this.getClass());
        //注入service依赖
        @Autowired
        private SeckillDao seckillDao;
        @Autowired
        private SuccessKilledDao successKilledDao;
        //MD5盐值字符串,用户混淆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();
            //系统当前时间
            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
        /**
         *使用注解控制事务方法的优点
         * 1:开发团队达成一致约定,明确标注事务方法的编程风格
         * 2:保证事务方法的执行时间尽可能短,不要穿插其他网络请求,RPC/HTTP请求或者剥离到事务方法外
         * 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
         */
        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");
            }
            //执行秒杀逻辑:减库存+记录购买行为
            Date nowTime=new Date();
            try {
                //记录购买行为
                int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
                //唯一:seckillId,userphone
                if(insertCount<=0){
                    //重复秒杀
                    throw new RepeatKillException("seckill repeated");
                }else{
                    //减库存,热点商品竞争
                    int updateCount=seckillDao.reduceNumber(seckillId,nowTime);
                    if(updateCount<=0){
                        //没有更新到记录,秒杀结束 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);
                //所有的编译期异常,转化为运行期异常(运行时异常,spring可以做rollback操作)
                throw new SeckillException("seckill inner error:"+e.getMessage());
            }
    
        }
        //抛出异常是为了告诉spring是否rollback,此处使用存储过程的话,就不需要抛异常了
        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);
            //执行存储过程,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的实现类

    和上一篇文章使用spring托管dao接口一样,这里也需要用 spring 托管service. spring ioc 使用对象工程模式,对所有的注入的依赖进行了管理,暴露出了一致性的访问接口,当我们需要某个对象时,直接从spring ioc中取就行了,不需要new,也不需要对它们的生命周期进行管理。更为重要的是spring 自动组装依赖,比如最终的接口controller依赖service,而service依赖dao,dao依赖sessionfactory,而sessionfactory依赖datasource,这些层层依赖是通过spring管理并层层组装,只要我们简单配置和注解就可以方便的使用,代码的分层和编程的艺术在spring框架中展现得淋漓尽至。

    本项目采用spring ioc :

    1.xml配置

    2.包扫描

    3.annotation注解。

    创建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包下所有使用注解的类型-->
        <context:component-scan base-package="org.forezp.service"/>
    
    

    然后在org,forezp.service包下的类采用注解。比如@Service 注解声明是一个service, @Autowired注入service 所需依赖。

    @Service//声明是一个service
    public class SeckillServiceImpl implements SeckillService{
    
     //注入service依赖
        @Autowired
        private SeckillDao seckillDao;
        @Autowired
        private SuccessKilledDao successKilledDao;
    
    }
    

    只需要一个包扫描和几个简单的注解就可以将service注解到spring ioc容器中。

    四、spring声明式事物

    在秒杀案例中,我们需要采用事物来防止数据的正确性,防止重复秒杀,防止库存不足、库存剩余等情况。一般使用事物需要开启事物/经常一些列的操作,提交或者回滚。spring声明式事物,就是将事物的开启、提交等托管给spring管理,我们只需要注重如何修改数据。

    配置spring 声明式事物
    在spring-service.xml中配置:

     <!--配置事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据库连接池-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
        <!--配置基于注解的声明式事务
            默认使用注解来管理事务行为
        -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    

    在需要事物的业务逻辑下加 @Transactional注解。
    比如在开启秒杀方法:

    @Transactional
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {
            if(md5==null ||!md5.equals(getMD5(seckillId))){
            }
    
    

    注意:

    1开发团队达成一致约定,明确标注事务方法的编程风格

    2:保证事务方法的执行时间尽可能短,不要穿插其他网络请求,RPC/HTTP请求或者剥离到事务方法外

    3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制

    五、单元测试

    需要配置:

    @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);
           //执行结果[Seckill{seckillId=1000, name='1000元秒杀iphone6'..... 省略。。。]
    
    
        }
    
        @Test
        public void getById() throws Exception {
            long id=1000;
            Seckill seckill=seckillService.getById(id);
            System.out.println(seckill);
            //执行结果: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业务接口的编写和实现,以及采用xml和注解方式讲service 注入到spring ioc,以及声明式事物,不得不感叹spring 的强大。下一篇文章讲讲述 web层的开发,spring mvc的相关配置。感谢大家,再接再厉,晚安。_


    扫码关注公众号有惊喜

    (转载本站文章请注明作者和出处 方志朋的博客

  • 相关阅读:
    axios实现跨域及突破host和referer的限制
    视频测试URL地址
    微信小程序 自定义导航栏 自动获取高度 写法
    解决flex布局宽度超出时,子元素被压缩的问题
    子组件向父组件通信与父组件向子组件通信
    时间截止器
    arguments
    改变this指向&闭包特性
    ES6扩展——箭头函数
    ES6扩展——函数扩展之剩余函数
  • 原文地址:https://www.cnblogs.com/forezp/p/9852186.html
Copyright © 2020-2023  润新知