SSM整合开发(二)—Service层

SSM整合开发(二)—Service层

首先感谢慕课网的老师,讲的真的很棒,学习很多书本上学不到的实用知识。

学习课程的地址:https://www.imooc.com/learn/631

概要

Dao层只完成了针对表的相关操作包括写了接口方法和映射文件中的sql语句,并没有编写逻辑的代码,例如对多个Dao层方法的拼接,当我们用户成功秒杀商品时我们需要进行商品的减库存操作(调用SeckillDao接口)和增加用户明细(调用SuccessKilledDao接口),这些逻辑都需要在Service层完成。这也是一些初学者容易出现的错误,他们喜欢在Dao层进行逻辑的编写,其实Dao就是数据访问的缩写,它只进行数据的访问操作。

针对Service层的编写也是从接口编写开始,然后再写实现类的细节。

Service层

spring-service.xml

       首先配置spring-service.xml文件,头部依然可以在官网上找最新的。这里主要是扫描service包下注解(@Service)的bean,将其托管给spring容器,同时配置事务管理器等。

xml version="1.0" encoding="UTF-8"?>
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">

    
    <context:component-scan base-package="com.seu.service"/>

    
    id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        name="dataSource" ref="dataSource"/>
    

    
    <tx:annotation-driven transaction-manager="transactionManager"/>

 

service接口设计与实现

SecKillService.java

service接口设计,业务接口:站在使用者(程序员)的角度设计接口。三个方面:1.方法定义粒度,方法定义的要非常清楚;2.参数,要越简练越好;3.返回类型(return 类型一定要友好/或者return异常,我们允许的异常)。

/**业务接口:站在使用者(程序员)的角度设计接口
 * 三个方面:1.方法定义粒度,方法定义的要非常清楚2.参数,要越简练越好
 * 3.返回类型(return 类型一定要友好/或者return异常,我们允许的异常)
 */
public interface SeckillService {

    /**
     * 查询全部的秒杀记录
     * @return
     */
    List getSeckillList();

    /**
     *查询单个秒杀记录
     * @param seckillId
     * @return
     */
    Seckill getById(long seckillId);


    //再往下,是我们最重要的行为的一些接口
    /**
     * 在秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间
     * @param seckillId
     */
    Exposer exportSeckillUrl(long seckillId);


    /**
     * 执行秒杀操作,有可能失败,有可能成功,所以要抛出我们允许的异常
     * @param seckillId
     * @param userPhone
     * @param md5
     * @return
     */
    SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException,RepeatKillException,SeckillCloseException;
}

SecKilServiceImpl.java

       实现类一般为接口名后面加Impl。

@Service
public class SeckillServiceImpl implements SeckillService
{
    //日志对象
    private Logger logger= LoggerFactory.getLogger(this.getClass());

    //加入一个混淆字符串(秒杀接口)salt,为了我避免用户猜出我们的md5值,值任意给,越复杂越好
    private final String salt="sad$%^}{ljgb";

    //注入Service依赖
    @Autowired //@Resource
    private SeckillDao seckillDao;

    @Autowired //@Resource
    private SuccessKilledDao successKilledDao;

    @Autowired
    private RedisDao redisDao;

    public List getSeckillList() {
        return seckillDao.queryAll(0,4);
    }

    public Seckill getById(long seckillId) {
        return seckillDao.queryById(seckillId);
    }

    public Exposer exportSeckillUrl(long seckillId) {
        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 (startTime.getTime()>nowTime.getTime() || endTime.getTime()return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
        }

        //秒杀开启,返回秒杀商品的id、用给接口加密的md5
        String md5=getMD5(seckillId);
        return new Exposer(true,md5,seckillId);
    }

    private String getMD5(long seckillId)
    {
        String base=seckillId+"/"+salt;
        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);
            //看是否该明细被重复插入,即用户是否重复秒杀
            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);
            //所以编译期异常转化为运行期异常
            throw new SeckillException("seckill inner error :"+e.getMessage());
        }
    }
}

事务配置

相关配置已经体现在上述代码中,这里强调一下。

声明式事务的使用方式:

1.早期使用的方式:ProxyFactoryBean+XMl。

2.tx:advice+aop命名空间,这种配置的好处就是一次配置永久生效。

3.注解@Transactional的方式。在实际开发中,建议使用第三种对我们的事务进行控制。

使用注解控制事务方法的优点:

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

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

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

DTO

输出一些业务不相关的数据,区别于entity实体,可以理解成普通的数据实例作为传输操作的对象。这里省略getter、setter和构造函数。

Exposer.java

public class Exposer {
    //是否开启秒杀
    private boolean exposed;
    //加密措施
    private String md5;
    //秒杀商品id
    private long seckillId;
    //系统当前时间(毫秒)
    private long now;
    //秒杀的开启时间
    private long start;
    //秒杀的结束时间
    private long end;
}

SeckillExecution.java

public class SeckillExecution {
    private long seckillId;
    //秒杀执行结果的状态
    private int state;
    //状态的明文标识
    private String stateInfo;
    //当秒杀成功时,需要传递秒杀成功的对象回去
    private SuccessKilled successKilled;
}

Enum

将常量封装在枚举类型中,便于统一修改。

public enum SeckillStatEnum {

    SUCCESS(1,"秒杀成功"),
    END(0,"秒杀结束"),
    REPEAT_KILL(-1,"重复秒杀"),
    INNER_ERROR(-2,"系统异常"),
    DATE_REWRITE(-3,"数据篡改");

    private int state;
    private String info;

    SeckillStatEnum(int state, String info) {
        this.state = state;
        this.info = info;
    }

    public int getState() {
        return state;
    }
    
    public String getInfo() {
        return info;
    }
    
    public static SeckillStatEnum stateOf(int index)
    {
        for (SeckillStatEnum state : values())
        {
            if (state.getState()==index)
            {
                return state;
            }
        }
        return null;
    }
}

 

单元测试

IDEA使用shift+ctrl+T快捷键快速生成其方法的测试类。

@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@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 getSeckillList() throws Exception {
        List seckills=seckillService.getSeckillList();
        System.out.println(seckills);
    }

    @Test
    public void getById() throws Exception {

        long seckillId=1000;
        Seckill seckill=seckillService.getById(seckillId);
        System.out.println(seckill);
    }

    @Test//完整逻辑代码测试,注意可重复执行
    public void testSeckillLogic() throws Exception {
        long seckillId=1000;
        Exposer exposer=seckillService.exportSeckillUrl(seckillId);
        if (exposer.isExposed())
        {
            System.out.println(exposer);

            long userPhone=13476191876L;
            String md5=exposer.getMd5();

            try {
                SeckillExecution seckillExecution = seckillService.executeSeckill(seckillId, userPhone, md5);
                System.out.println(seckillExecution);
            }catch (RepeatKillException e)
            {
                e.printStackTrace();
            }catch (SeckillCloseException e1)
            {
                e1.printStackTrace();
            }
        }else {
            //秒杀未开启
            System.out.println(exposer);
        }
    }

    @Test
    public void executeSeckill() throws Exception {
        //将代码整合到testSeckillLogic()中进行完整逻辑测试
    }

}

 

你可能感兴趣的:(Spring)