一篇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的相关配置。感谢大家,再接再厉,晚安。_

关注我:

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,783评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,396评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,834评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,036评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,035评论 5 362
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,242评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,727评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,376评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,415评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,463评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,140评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,734评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,809评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,028评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,521评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,119评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,714评论 6 342
  • spring官方文档:http://docs.spring.io/spring/docs/current/spri...
    牛马风情阅读 1,642评论 0 3
  • SSM框架详细整合 介绍 Spring + SpringMVC + Mybatis是现在轻量级J2EE框架方案中,...
    2MuchT阅读 5,212评论 3 14
  • 这世间有一百种的东西,初始拥有的时候,并没有多大的感觉,可一旦丢失,便会有莫大的苦楚。是一百种吗?大概是吧,...
    一条无尾的鱼阅读 371评论 0 0