DAO层
1.数据库设计与编码
在sql中增/插
create database,use database;create table;inset into····
2.DAO实体与接口编码
(1)entity编码
编写相应属性,getter、setter、toString
table中列对应–>entity属性。
(2)DAO接口
编写抽象方法,增删改查对应的抽象方法。
参数中要用@Param(“”),因为java没有保存形参的记录queryAll(int offset,int limit)–>queryAll(arg0,arg1)
当有多个参数时,用myBatis提供的注解@Param()。
注意参数和返回类型
3.基于myBatis实现DAO
只写接口, 不写实现类。mybatis来实现接口。
在resources下实现
(1)mybatis-config.xml配置myBatis全局属性
(2)Dao.xml
为DAO接口方法提供sql语句配置。 在mapper下xml中提供SQL.
1 | <mapper namespace="cn.seckill.dao.SeckillDao"> |
xml中<=用标签表示。
Dao中的参数在sql中用#{}表示。
主键冲突时,会报错。用insert ignore into .忽略错误,返回0。
根据id查询SuccessKilled并携带Seckill实体。queryByIdWithSeckill():
若不写连接,写两条sql。先查出SuccessKill,再根据seckillId查出Seckill。
若在一个实体中完成,通过连接的形式inner join,把结果映射到SuccessKilled。
SuccessKilled.java实体中有Seckill seckill属性,如何告诉myBatis把结果映射到SuccessKilled同时映射到seckill属性:seckill表中查找的属性通过列别名的形式(as “”)(as可忽略,要加双引号)赋给seckill属性。
1 | SELECT |
mybatis整合spring
配置spring-dao.xml
配置整合mybatis过程:
(1)配置数据库相关参数properties的属性:context:/
(2)数据库连接池
连接池属性driver、url、username、password:<property name=”” value=”${}”
(3)配置SqlSessionFactory对象
1 | <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> |
(4)配置扫描Dao接口包,动态实现DAO接口,注入到spring容器
1 | <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> |
约定大于配置
DAO层单元测试编码
找到DAO,ctrl+shift+T,生成相应Test。
- 要配置spring和junit整合,使junit自动启动时加载springIOC容器
1 | //junit依赖,junit自动启动时加载springIOC容器 |
注:没有分号(;)。ContextConfiguration括号内有花括号{}。
- 注入DAO实现类依赖
在Test类中
1 |
|
- 编写test方法代码
测试方法内是直接调用对应Dao的相应方法,注意返回类型。若是该方法需要参数,则自定义。
然后将结果System.out.println();
Service层
DAO层:接口设计+SQL编写,代码与SQL分离。
DAO拼接等逻辑在Service层完成。
Service接口设计
业务接口:站在使用者角度设计接口。
三个方面:1.方法定义粒度;2.参数,越简练越好;3.返回类型(return类型一定要友好/或者return异常,我们允许的异常)
Service接口内定义抽象方法。
在一些重要的行为的接口方法,其返回类型不一定是entity。dto。
dto与entity不同。dto(Data Transfer Object)是数据传输对象,在service和web之间传递数据,封装数据对象。dto内定义属性,constructor,getter,setter。
- 执行秒杀操作,可能失败/成功,要抛出允许的异常
Service接口实现
使用注解@Service
创建实现类,implements相应Service,alt+Insert快捷键implements Methods。
将日志的对象写出来
1
private Logger logger= LoggerFactory.getLogger(this.getClass());
将要用到的DAO定义(private),不用初始化,其实现类都在spring的容器中。让spring依赖注入实例。
注入Service依赖@Autowired
编码实现方法。能运用到DAO的相关方法直接用dao调用方法。return。
.getTime()换成毫秒后再进行比较。
- 执行秒杀方法:减库存,增加购买明细。
(1)使用注解@Transactional控制事务方法
(2)执行秒杀方法的实现:将用户传过来的md5与生成的md5对比,若不等,抛出异常。
(3)所有编译期异常,转化为运行期异常。一旦有错,rollback。
对于声明式事务,抛出运行期异常时才会回滚。
(4)在运行期异常catch前显示地抛出允许的异常,对不同的类型做catch,如秒杀关闭、重复秒杀。
- state,stateInfo。
数据字典,用枚举表示常量数据。
Service相关配置(IOC、事务)
配置spring-service.xml
使用Spring托管Service
Service层Spring-IOC依赖注入方式的配置
扫描service包下所有使用注解的类型(如@Service,@Autowired)
1 | <context:component-scan base-package="cn.codingxiaxw.service"/> |
- Spring声明式事务配置
(1)配置事务管理器
1 | <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> |
(2)配置基于注解的声明式事务
1 | //默认使用注解来管理事务行为 |
集成测试Service逻辑
在service类 ctrl+shift+T自动生成测试类。
- 要配置spring和junit整合,使junit自动启动时加载springIOC容器
1
2
3
4
5
6//junit依赖,junit自动启动时加载springIOC容器
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring配置文件
@ContextConfiguration({
"classpath:spring/spring-dao.xml",
"classpath:spring/spring-service.xml"})
加载两个是因为测试Service,要依赖Dao的配置。
日志对象logger
依赖注入service@Autowired
3.编写测试代码
Web层
秒杀API的URL设计
1 | GET/seckill/list |
配置SpringMVC框架
- web.xml:配置DispatcherSerclet。
加载SpringMVC 需要配置的文件
spring-dao.xml,spring-service.xml,spring-web.xml
spring-web.xml: 配置SpringMVC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!--1,开启springmvc注解模式
a.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
b.默认提供一系列的功能:数据绑定,数字和日期的format@NumberFormat,@DateTimeFormat
c:xml,json的默认读写支持-->
<mvc:annotation-driven/>
<!--2.静态资源默认servlet配置-->
<!--
1).加入对静态资源处理:js,gif,png
2).允许使用 "/" 做整体映射
-->
<mvc:default-servlet-handler/>
<!--3:配置JSP 显示ViewResolver-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--4:扫描web相关的controller-->
<context:component-scan base-package="cn.codingxiaxw.web"/>使用SpringMVC实现Restful接口(Controller)
- @Controller
@RequsetMapping(“/seckill”)//代表模块。之后的RequsetMapping中直接写资源/{}/细分。如”/list”。
- Controller类中依赖注入相应Service。
@Autowired
private SeckillService seckillService;
- Controller类中依赖注入相应Service。
针对每个方法:
方法外@RequestMapping(value = “/list”,method = RequestMethod.GET)
若是json格式。@ResponseBody
方法内:调用Service,处理数据。将处理结果封装成json ,return。
(1) 若是jsp格式。方法参数中需要加入Model model。model用来存放所有渲染list.jsp的数据。list.jsp提供页面的模板,model是数据。list.jsp+model=ModelAndView.
return “lisit”;//WEB-INF/jsp/“list”.jsp
(2) 若是json格式,VO用来封装所有的数据结果。
将所有的ajax请求返回类型(为SeckillResult),全部封装成json数据。直接输出json,不需要model。
@RequestMapping中加入 produces = {“application/json;charset=UTF-8”})//contentType
基于bootstrap开发页面结构(jsp)
在webapp下。WEB-INF下放jsp文件。
将通用或重复的部分提取出来,再采用jsp静态包含的方式
1 | //静态包含:会将包含的comon/tag.jap合并到list.jsp中来,作为整个servlet输出..一个servlet |
交互
创建JavaScript文件seckill.js。存放主要交互逻辑js代码。javascript模块化。
在详情页jsp的HTML中引入js文件。
1
<script src="/resources/script/seckill.js" type="text/javascript"></script>
初始化函数,使用EL表达式传入参数
cookie登录交互
手机验证和登录,在cookie中查找手机号。
计时交互
秒杀交互
在seviceImpl,Controller中会private static Logger logger = Logger.getLogger(this.class);
高并发优化
高并发发生在详情页上(做静态化,放到CDN中)、访问系统时间不用优化、计时在js端,浏览器中,对服务器不会有影响。
要优化的有秒杀地址接口、秒杀操作
redis优化“地址暴露接口”
地址暴露接口是根据秒杀单的时间来计算是否秒杀,不方便作为固定的内容放到CSN中作为缓存。要放到服务器端,通过服务器端的逻辑来控制。
在porm中引入redis客户端:jedis的依赖
创建RedisDao。
对serviceImpl中方法优化。
seckillImpl中暴露秒杀地址用redis缓存起来。DAO数据访问对象,放数据库或者其他存储访问的类所在的包。
redis操作逻辑不应该放在service代码中,是数据访问层的逻辑。放在RedisDao中。
Dao中编码构造方法、getSeckill()方法、putSeckill()方法
引入prostuff序列化依赖,采用自定义序列化 。
把对象转换成二进制数组,传递给redis,缓存起来。通过jedis拿Seckill对象,先拿到的是字节数组,再按照schema反序列化成对象。
序列化的本质:通过字节码,通过字节码所对应对象有哪些属性,把字节码的数据传给那些属性,这样就可以序列化好那些对象。
- 写完DAO–>建立junit编码测试。全局测试testSeckill()
在spring-dao.xml中注入RedisDao。
把RedisDao注入到serviceImpl中–>bi编码
java控制事务行为分析:
同一个id执行update减库存:一条update压力测试(约4wQPS)。但是执行Update并不低效。
若都对同一商品id执行减库存:
一个用户执行update减库存—>inset购买明细
这时另一个用户执行update减库存–>会等待行锁block因为当一个事务开启的时候,另一个事务进来的时候发现锁住的是同一行,当之前的事务不去提交或回滚的话,这个行级锁是不会释放的。
当第一个用户insert执行完后,commit/rollback后。等待行锁的才可能获得锁lock。当很多用户都去执行该id的减库存,都会等待—>形成串行化的操作。阻塞操作。
- 瓶颈分析
update减库存–>insert购买明细–>commit/rollback
每一步都会有网络延迟,以及可能出现的GC。
java客户端去执行SQL,等待SQL的结果再去做判断,再去执行SQL。这一长串事务在java客户端执行,但java和数据库通信之间有网络延迟或GC。这些时间会加在事务的执行周期中。而同一行的事务是做串行化的。
- 优化分析:
行级锁rowback是在Commit之后释放—>优化方向减少行级锁持有时间。
优化思路:把客户端逻辑放到MySQL服务端,避免网络延迟和GC影响。
如何放到MySQL服务端:使用存储过程:整个事务在MySQL端完成
秒杀操作-并发优化
简单优化:
insert购买明细–>update减库存–>commit/rollback
insert通过insert ignore,会挡住一部分重复秒杀。update拿到行级锁rowback。update减库存,热点商品竞争,拿到结果后,根据结果rollback/commit。只有一步的网络延迟、GC,减少一倍时间。
深度优化:事务SQL在MySQL端执行(存储过程)
(1)存储过程优化:减少事务行级锁持有时间
(2)不要过度依赖存储过程(在银行用的多)
(3)简单的逻辑可以应用存储过程
(4)QPS:一个秒杀单6000/qps
通过java客户端(seckill项目)调用存储过程:
在service中定义新接口;serviceImpl中实现方法;serviceTest中单元测试
在dao中定义新接口;在Dao.xml中mybatis调用存储过程;
Controller中改用存储过程调用秒杀操作
大型系统部署架构
部署可能用到哪些服务?
(1) CDN :jQuery、Bootstrap一些依赖直接使用公网的CDN;自己开发的js、详情页做静态化处理,推送到CDN。用户在CDN获取到的数据,不需要再访问服务器
(2)WebServer:Nginx+Jetty
一般不直接把java的WebServer直接对外访问;Nginx可能是集群化的,部署在多个服务器上,用来作http服务器,同时会帮后端的Jetty、Tomcat的应用服务器做反向代理(3)Redis:做服务器端的缓存,可以通过redis提供的API来达到热点数据的快速存取的过程
(4)MySQL:借助MySQL的事务来达到秒杀数据的一致性、完整性
- 部署架构;
总结
Dao–>Dao.xml–>test–>Service–>srviceImpl—>test
Controller–>jsp–>js
ajax异步请求.JavaScript发送AJAX请求,URL域名和当前页面一致。
$.post(URL,{},function(result){});Controller接收用户请求,@RequestMapping映射URL,请求参数绑定到方法上。Controller内依赖注入Service,调用service方法处理数据,注意其返回类型,得到数据结果。
VO用来封装所有的数据结果,可以新建dto类(如SeckillResult),为所有的ajax请求返回类型,全部封装成json数据。定义变量及所需的不同构造函数(参数有data,即是sevice处理得到的数据结果)。
result=new SeckillResult<service方法返回类>(构造函数参数) 。泛型。最后return result;
- 在js中通过ajax拿到data。
$.get(),$.post():使用AJAX的HTTP GET或POST请求从服务器加载数据。
回调函数function(result){})是一种以参数形式传递给另一个函数的函数。拿到result之后。通过result[data]可以取得相应数据。