搜档网
当前位置:搜档网 › 基本框架梳理

基本框架梳理

基本框架梳理

缓存 (4)

MemCached简介 (4)

memcached的特征 (4)

协议简单 (5)

基于libevent的事件处理 (5)

内置内存存储方式 (5)

memcached不互相通信的分布式 (5)

Memcache系统设计 (6)

方法缓存(MethodCache) (6)

页面缓存(PageCache) (7)

Hibernate-memcached 二级缓存 (9)

对象缓存 (9)

查询缓存 (10)

缓存策略 (11)

总结: (11)

统一日志服务 (12)

DAO (13)

BaseDao (13)

QuickStart (18)

最佳实践 (19)

DynamicFinder使用指南 (19)

API示例 (19)

规则说明 (19)

代码规范 (20)

使用AcceptHashMap在controller中组装参数Map (21)

MAP传递参数改进 (21)

Ibatis & JDBC (22)

事务相关 (23)

2.事务注解的设置: (24)

OpenSessionInClient (24)

定时任务 (25)

任务跟踪: (25)

任务执行期唯一性保证: (26)

Spring 3 MVC (27)

简介 (27)

对restful支持 (28)

返回视图(View) (28)

JsonView (28)

XmlView (29)

XlsView (30)

服务端controller请求参数验证 (31)

请求参数解析器(HttpParameterParser) (33)

Spring MVC VS Struts2 MVC (33)

邮件 (35)

Pagination(分页) (37)

自定义Tag (38)

图形报表(FusionCharts) (41)

JS框架 (42)

area_mootools(区域级联下来菜单) (42)

Calendar(日期控件) (43)

Js验证框架(formcheck.js) (43)

JS动态级联下拉菜单(dynamic_menu.js) (43)

JS分页(paging.js) (43)

弹出对话框(pop.js) (43)

身份验证 (44)

优势 (44)

名词解释 (45)

处理方式 (45)

Sha-1摘要保证安全性 (45)

会话cookie避免身份掉包 (45)

使用缓存提高性能: (45)

检验方式(Verifier): (46)

具体实现: (46)

时序图实例 (49)

登录时序图 (49)

验证是否登录时序图: (50)

是否是访问者时序图: (51)

商户管理员授权认证 (52)

名词解释 (52)

具体实现: (53)

后期扩展: (53)

RESTFUL设计 (54)

RESTful Web 服务简介: (54)

典型应用举例: (54)

REST特点: (55)

REST设计原则: (55)

SPRING 3 MVC对RESTFUL支持: (55)

js mootools 对RESTFUL 支持: (56)

Httpclient客户端对restful支持: (56)

工具类: (57)

安全相关工具类: (57)

文件上传: (57)

http client (57)

日期处理工具 (57)

图像处理工具: (57)

压缩工具 (58)

其它: (58)

Maven (58)

其它建议和意见 (59)

SCM (59)

数据库 (60)

负载均衡: (60)

缓存

MemCached简介

许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。

这时就该memcached大显身手了(例如sns类网站、blog类网站、bbs类网站。著名的facebook据说有3000+memcached服务器)。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

memcached的特征

memcached作为高速运行的分布式缓存服务器,具有以下的特点。

协议简单

基于libevent的事件处理

内置内存存储方式

memcached不互相通信的分布式

协议简单

memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据、取得数据。下

基于libevent的事件处理

libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。

内置内存存储方式

为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。

memcached不互相通信的分布式

memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现。目前我已经在192.168.30.20和192.168.30.28同时部署测试分布式缓存通过

详细介绍见附件:

Memcache系统设计

类图(打开超链接放大显示)

方法缓存(MethodCache)

简介:对指定方法的返回结果进行缓存。

应用场景:对于那种经常被调用,且运行过程比较消耗资源的方法,可配置方法缓存。

特别说明:返回数据不不与支持open session in view式的数据级联操作。所以数据的所有加载必须放在加入缓存前加载。

示例代码:在需要配置方法缓存的方法上加@MethodCache注解,例:

注意事项:

1.对于要配置缓存的方法,其返回值必须是实现了Serializable接口的。

及该类的bean id要以Manager结尾,或者在该配置文件中加入需要配置的名称。

页面缓存(PageCache)

应用场景:对于网站经常被访问到的,或者页面中需要加载较多信息的页面,可配置页面缓存。

简介:网站的页面缓存是通过过滤器实现,过滤器中的可配置参数:目前系统实现了基于spring controller的响应缓存。后期可考虑基于html,jsp,jpg等响应的缓存实现

pageCache注解可选参数及其默认值:

Web.xml过滤器配置

Hibernate-memcached 二级缓存

Hibernate中提供了两级Cache:

第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;

第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。二级缓存可以使用不同的缓存类库

现系统中已用memcached代替了hibernate 的默认缓存provider ehcache。ehcache和memcached的结构是完全不相同的。一个ehcache缓存系统可以同时定义多个cache,每个cache使用key-value方式存储数据,而memcahced只有key-value,它是一个大的哈希表。所以Hibernate -memcached缓存策略只能定义一种缓存存储-过期策略,不像ehcache可定义多个缓存存储-过期策略。

对象缓存

对于一条记录,也就是一个PO来说,是根据ID来找的,缓存的key就是ID,value 是POJO。无论list,load还是iterate,只要读出一个对象,都会填充缓存。但是list不会使用缓存,而iterate会先取数据库select id出来,然后一个id一个id的load,如果在缓存里

特别说明的是如果对象的级联对象需要缓存的话。级联对象同样需要设置缓存策略

如果你使用的二级缓存实现是ehcache的话,需要配置ehcache.xml ,若不配置,hibernate 会自己使用默认配置。

当某个ID通过hibernate修改时,hibernate会移除缓存。

这样大家可能会想,同样的查询条件,第一次先list,第二次再iterate,就可以使用到缓存了。实际上这是很难的,因为你无法判断什么时候是第一次,而且每次查询的条件通常是不一样的,假如数据库里面有100条记录,id从1到100,第一次list的时候出了前50

个id,第二次iterate的时候却查询到30至70号id,那么30-50是从缓存里面取的,51到70是从数据库取的,共发送1+20条sql。所以我一直认为iterate没有什么用,总是会有1+N 的问题。

(题外话:有说法说大型查询用list会把整个结果集装入内存,很慢,而iterate只select id比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页20条的话,iterate共需要执行21条语句,list虽然选择若干字段,比iterate第一条select id 语句慢一些,但只有一条语句,不装入整个结果集hibernate还会根据数据库方言做优化,比如使用mysql的limit,整体看来应该还是list快。)

如果想要对list或者iterate查询的结果缓存,就要用到查询缓存了.

查询缓存

比如hql:from Cat c where https://www.sodocs.net/doc/8d17046019.html, like ?

生成大致如下的sql:select * from cat c where https://www.sodocs.net/doc/8d17046019.html, like ?

参数是"tiger%",那么查询缓存的key*大约*是这样的字符串:select * from cat c where https://www.sodocs.net/doc/8d17046019.html, like ? , parameter:tiger%

这样,保证了同样的查询、同样的参数等条件下具有一样的key。

现在说说缓存的value,如果是list方式的话,value在这里并不是整个结果集,而是查询出来的这一串ID。也就是说,不管是list方法还是iterate方法,第一次查询的时候,它们的查询方式很它们平时的方式是不一样的,list执行一条sql,iterate执行1+N条,多出来的行为是它们填充了缓存。但是到同样条件第二次查询的时候,就都和iterate的行为一样了,根据缓存的key去缓存里面查到了value,value是一串id,然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。可以看出来,查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候,都是既填充查询缓存又填充class缓存的。

这里还有一个很容易被忽视的重要问题,即打开查询缓存以后,即使是list方法也可能遇到1+N的问题!相同条件第一次list的时候,因为查询缓存中找不到,不管class缓存是否存在数据,总是发送一条sql语句到数据库获取全部数据,然后填充查询缓存和class缓存。但是第二次执行的时候,问题就来了,如果你的class缓存的超时时间比较短,现在class 缓存都超时了,但是查询缓存还在,那么list方法在获取id串以后,将会一个一个去数据库load!因此,class缓存的超时时间一定不能短于查询缓存设置的超时时间!如果还设置了发呆时间的话,保证class缓存的发呆时间也大于查询的缓存的生存时间。这里还有其他情况,比如class缓存被程序强制evict了,这种情况就请自己注意了。

另外,如果hql查询包含select字句,那么查询缓存里面的value就是整个结果集了。

当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢?hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面org.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可

能会比较低。

缓存策略

1: 事务型(transactional)策略:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。

2: 读写型(read-write)策略:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。

3: 非严格读写型(nonstrict-read-write)策略:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。

4) 只读型策略(read-only):对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。

事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。

读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。不严格读写缓存不锁定缓存中的数据。

总结:

Memcached是“分布式”的内存对象缓存系统,那么就是说,那些不须要“分布”的,不须要共享的,或者干脆规模小到只有一台服务器的使用,memcached不会带来任何优点,相反还会拖慢系统效率,因为网络连接同样须要资源,即使是LINUX本地连接也一样。须要留心的是,memcached运用内存管理数据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以memcached不能用来持久保存数据。

不经常更新的数据考虑使用缓存。对于频繁更新的数据不需要提供缓存。需要经常访问的数据考虑缓存,否则不需要考虑;。命中率=命中数/(命中数+没有命中数),缓存命中率是判断缓存效果好坏的重要因素之一

方法缓存、页面缓存是一种只读型策略。不像hibernate-cached有缓存同步策略防止脏读。所以请配置好过期策略。如果有必须可后期加入该设计

不同的系统(应用)可共用缓存服务器,但一定要一定不用系统的key前缀、或key的hash算法。否则不同系统的key很可能冲突。这是一定要避免的

统一日志服务

目前各个系统之间的日志是分别分散记录在各个应用服务器上,而且日志的记录、配置方式也不统一。后续对日志的跟踪、查询、统一管理很是麻烦。

基于log4j开发了一套日志服务器。原理客户端采用socke与服务端通讯记录日志。

我在新UAT环境192.168.200.6部署、测试通过

项目地址:

https://192.168.30.21:18080/svn/logserver/trunk

log4j日志配置:

采用xml的方式配置服务端日志(也提供了properties方式的日志配置)

服务端配置参考:log4jServer.xml

客服端配置参考:log4jClient.xml

部署:

采用mavn插件appassembler-maven-plugin 创建运行文件

具体命令:package appassembler:assemble

该命令将创建了一个基于linux和windows的运行文件目录在target/{appname}/bin/

在linux或windows环境直接运行该文件既可

其它:

1、各个应用服务器最好还是保留日志副本。而不完全依赖日志服务器,保证日志

服务器出现故障时日志依然能生成。

2、文件服务器的日志可通过email的方式定时发送日志文件到指定邮箱中。

DAO

data access object ,数据访问对象。它位于数据存储层(数据库)与业务逻辑层之间,有两个作用:隔离数据访问差异(eg :对于mysql 或oracle 数据库,应用dao 模式可以做到使用相同的接口来访问数据);划分应用程序的层次。

典型的dao 模式类图:

Service

业务逻辑实现类调用统一的dao 接口完成数据存取。

显而易见,随领域对象的增多,dao 也会相应增加,即便很多dao 仅仅是完成简单的更新、查找等操作,也需要开发dao 代码。

hibernate

hibernate 实现了隔离数据访问差异的功能。有一种主张说使用hibernate 后,完全可以消除dao layer ,其实就是因为hibernate 本身就隔离了数据访问差异。但另外一个问题,应用程序层次的问题,假如使用hibernate 而消除dao ,可以预见的情况是业务逻辑类里到处充斥着hql 语句和hibernate api ,这显然是一种很严重的bad smell 。

权衡

需要权衡一下应用程序分层与开发简洁的问题了。

BaseDao

BaseDao 提供dao 应该具有的绝大部分数据访问方法,它的一个实现类BaseDaoHibenrateImpl 是利用hibernate 来完成上述功能。其中增加、删除、更新等逻辑很简单,查询逻辑较为复杂,比如根据参数动态查询。在此工具组件中,提供了一系列以 Finder 为基接口的查询器,使用Finder 接口的意义是消除hql 语句和hibernate api ,这个接口会接收一些通用的参数(eg :java.util.Map ),转换为hibernate 需要的查询语句和参数等,目前已经实现的查询器已经能够满足绝大部分查询的需求,如果有特别复杂的查询需求,也可以继承已知的Finder 类来实现。

以下是完整类图一般的应用中只需要了解BaseDao 接口方法和Finder 子类分别是在什么情况使用的就行了。

Finder子类说明:(需要了解哪些情况用哪个类)

QueryStringFinder:抽象类,根据查询语句来进行查找的查询器SimpleQueryStringFinder:QueryStringFinder的简单实现,通过查询语句来查找。不带任何参数的传递。,

ParametersFinder:抽象类,通过动态的参数生成查询语句来查找的查找器SimpleParametersFinder:ParametersFinder的简单实现,通过传入查询语句和参数来查找PropertiesFinder:ParametersFinder的实现,通过传入class类型和参数来构造查询语句进行查找;内部利用java反射机制构造最终的hql语句

PlaceholderFinder:ParametersFinder的实现;通过占位符来构造语句进行查询。目前该查询器未实现分页。

DynamicFinder:ParametersFinder的实现;功能强大的动态查询器。通过传入动态HQL语句和参数来实现具体查询条件

FinderHibernateCallback为工具内部调用的回调类,客户程序不会使用,如果为了了解细节,可以看一下。

QuickStart

配置泛型DAO有两种方式,一种通过spring配置完成;一种是通过代码继承BaseDaoHibernateImpl来实现

最佳实践

1.如果是根据一个对象的基本属性作为条件做查询推荐使用PropertiesFinder;属性查询器

不光可传递领域对象的基本属性而且可传递关联对象。

2.如果查询条件明确有具体查询参数请使用SimpleParametersFinder(支持分页)或

PlaceholderFinder(不支持分页)

3.如果查询条件带有动态的查询的方式(不确定具体的参数)请使用

4.如果查询逻辑复杂,需要根据情况实现一个Finder DynamicFinder

5.多种方式可以达到目的,选择一个在service中不会出现hql语句的方式。DynamicFinder

不要滥用在有明确具体查询参数情况下不推荐使用DynamicFinder

6.更具体的使用请参考代码

DynamicFind er使用指南

如果页面做查询时不确定需要传递哪些查询条件是。就涉及到动态查询。传统的做法是根据传递的具体参数拼接SQL语句。需要写大量的if else语句,还有可能不的查询条件组合;查询逻辑完全不一样。那么DynamicFinder就派上用场了

DynamicFinder是一个功能强大的动态查询器。通过传入动态HQL语句和参数来实现具体查询条件。完全消除了烦琐的代码编写。

API示例

规则说明

使用{}包围动态条件,动态条件中如果包括匹配正则表达式:\w+ 的字符串,检查传入的Map参数中是否有该字符串的键,如果动态条件中所有匹配的字符串都在Map中找到,则将该条件生效,否则失效;动态条件中如果包括匹配正则表达式\?\w+ 的字符串,判断逻辑一致,同时将Map中对应键的值替换该字符串。

public void testParse() {

String dynamicString="select * from Order o where 1=1 " +

"{and o.orderDate>=?beginOrderDate} " +

"{and o.orderDate<=:endOrderDate} " +

"{and :minListPrice<=o.listPrice>} " +

"{and :maxListPrice>=o.listPrice} "+

"{order by ?orderby}";

Map parameters=new HashMap();

parameters.put("beginOrderDate", "2009-01-01");

parameters.put("endOrderDate", "xxx");

parameters.put("maxListPrice", "xxx");

parameters.put("dir", "id");

parameters.put("orderby", "id desc");

String actual=DynamicQueryUtils.parse(dynamicString, parameters);

String expected="select * from Order o where 1=1 " +

"and o.orderDate>=2009-01-01 " +

"and o.orderDate<=:endOrderDate " +

"and :maxListPrice>=o.listPrice "+

"order by id desc";

assertEquals(expected, actual);

}

代码规范

动态条件字符串以常量形式定义在被使用的类中,常量名以被直接调用的方法的名称改大写命名,骆驼改下划线连接,最后加QUERY_STRING结尾,如果一个方法使用多个这样的动态条件字符串,则对应常量加索引区分。

private static final String FIND_ORDER_QUERY_STRING=….;

public List findOrder(Map parameters){

return baseDao.find(new DynamicFinder(FIND_ORDER_QUERY_STRING, parameters));

}

private static final String FIND_CUSTOMER_QUERY_STRING0=…;

private static final String FIND_CUSTOMER_QUERY_STRING1=…;

public List findCustomer(Map parameters,boolean flag){

String queryString= flag? FIND_CUSTOMER_QUERY_STRING0: FIND_CUSTOMER_QUERY_STRING1;

return baseDao.find(new DynamicFinder(queryString, parameters));

}

相关主题