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

来源:互联网 发布:淘宝卖家下架的宝贝在哪里 编辑:程序博客网 时间:2024/06/05 10:17

上一篇讲述了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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

二、Service接口的实现

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

package org.forezp.service.impl;@Servicepublic 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);        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121

三、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"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

@Service//声明是一个servicepublic class SeckillServiceImpl implements SeckillService{ //注入service依赖    @Autowired    private SeckillDao seckillDao;    @Autowired    private SuccessKilledDao successKilledDao;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

只需要一个包扫描和几个简单的注解就可以将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"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

@Transactionalpublic SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {        if(md5==null ||!md5.equals(getMD5(seckillId))){        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

注意:

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);    }   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

这篇文章主要讲了service业务接口的编写和实现,以及采用xml和注解方式讲service 注入到spring ioc,以及声明式事物,不得不感叹spring 的强大。下一篇文章讲讲述 web层的开发,spring mvc的相关配置。感谢大家,再接再厉,晚安。^_^。

0 0