本文转载自:http://www.54chen.com/rose.html
rose项目源代码地址:http://code.google.com/p/paoding-rose/
人人网、糯米网释出的、开源的高效Java web开发框架。在小米米聊服务端再次被验证和使用。一个从零开始的创业公司,在大家技术背景不一的情况下,rose很简单快速地传达到了大家中间。本手册致力于让php开发人员也能快速使用上java开发高性能服务。
如果在阅读过程中有任何疑问,欢迎来信咨询:[email protected] (或者在 http://www.54chen.com 留言),将会第一时间得到答复。
本文档同时提供了pdf和mobi文档下载: https://github.com/XiaoMi/rose/tree/master/ebook
小米科技 bmw 团队荣誉出品。(BasicMiliaoWare)
rose手册第三章:框架功能参考
rose手册第四章:安全
rose手册第五章:FAQ 常见问题
rose手册第六章:附录
人人网、糯米网释出的、开源的高效Java web开发框架。在小米米聊服务端再次被验证和使用。一个从零开始的创业公司,在大家技术背景不一的情况下,rose很简单快速地传达到了大家中间。本手册致力于让php开发人员也能快速使用上java开发高性能服务。
如果你是一个创业公司在选择php还是java,同时如果你的团队有一个人写过一年java其他人都没写过。如果你想选择一个更加大型的系统框架,请使用rose,它收集了来自人人网、糯米网、小米科技的众多工程师的经验,你可以免费拥有这些。
*下面开始来进入rose框架下的开发。只需要有一个感性的认识即可。下一章里会专门详细的手把手教你搭建hello world项目。
也许还需要一个nexus,用来搭建自己的maven仓库(这些都是门槛啊,知道为什么java用的人在全球多,而在中国php的人似乎更多,因为我们的基础设施太落后了)。nexus的作用是配合maven工作。(54chen正在向sonatype申请将rose项目push到sonatype的官方库中,成功后这将省略掉这一步)
然后需要在你的项目的pom文件中添加:
com.54chen
paoding-rose
1.0
com.54chen
paoding-rose-jade
1.1
com.54chen
paoding-rose-scanning
1.0
@Path("/")
public class TestController {
@Get("hello")
public String test(){
return "@a";
}
}
http://localhost/hello
将会返回:a。就是这么简单。
打开2.3建立好的项目,打开pom.xml,添加下面的段落到project中:
com.54chen
paoding-rose-scanning
1.0
com.54chen
paoding-rose
1.0
com.54chen
paoding-rose-portal
1.0
com.54chen
paoding-rose-jade
1.1
上述是rose环境最基础的依赖包。再添加一点常见的编译设置:
src/main/resources
**/*.*
org.apache.maven.plugins
maven-war-plugin
2.0.2
WEB-INF
true
src/main/resources
**/*.xml
**/*.properties
WEB-INF
org.apache.maven.plugins
maven-compiler-plugin
1.6
1.6
true
true
UTF-8
${project.basedir}/src/main/java
org.apache.maven.plugins
maven-surefire-plugin
true
上述是编译设置,也是放在project段落里。
在src/main/webapp/WEB-INF文件夹下建立web.xml:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
rose-example
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
log4jConfigLocation
/WEB-INF/log4j.xml
org.springframework.web.util.Log4jConfigListener
roseFilter
net.paoding.rose.RoseFilter
roseFilter
/*
REQUEST
FORWARD
INCLUDE
src/main/resources/applicationContext.xml是spring环境的油门,所有包的扫描和启动都在这里定义:
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-lazy-init="true">
/>
base-package="com.chen">
/**
* @author 54chen(陈臻) [[email protected] [email protected]]
* @since 2012-4-10 上午11:14:46
*/
package com.chen.controllers;
import net.paoding.rose.web.annotation.Path;
import net.paoding.rose.web.annotation.rest.Get;
@Path("")
public class HelloController {
@Get("")
public String index() {
return "@hello world";
}
}
/**
* @author 54chen(陈臻) [[email protected] [email protected]]
* @since 2012-4-10 上午11:14:46
*/
package com.chen.controllers;
import net.paoding.rose.web.annotation.Path;
import net.paoding.rose.web.annotation.rest.Get;
@Path("/hello/")
public class HelloController {
@Get("world")
public String index() {
return "@hello world";
}
}
EOF
先看看怎样把url和某个方法对应起来。为了方便说明,现在我们来一起完成一个极简版的贴吧。
贴吧中当然会有很多“主帖”(topic),“主帖”下会有很多“跟帖”(comment)。
一般,贴吧中最基本的,会有下面这几个功能需要我们完成:
然后让我们来规划一个REST风格的 web API :(“GET”和“POST”是指HTTP1.1中的请求方法)
可以发现一个共同点,所有API中,URI部分的第一级都是“/myforum”(但这并不是规定,仅仅为了演示)。
首先新建一个类,这个类的类名必须以“Controller”结尾:
@Path("myforum")
public class ForumController {
}
注意标注在类(class)上的注解“@Path("myforum")”,这意味着,这个类中定义的所有API的URI,都必须以“myforum”开头,比如“/myforum/xxx”和“/myforum/yyy”等(但“myforum”不一定是整个URI的第一级,比如“/aaa/myforum/bbb”)。
接着,实现第一个API——“GET http://github.com/myforum/topic”:
@Path("myforum")
public class ForumController {
@Get("topic")
public String getTopics() {
//显示主帖列表
return "topiclist";
}
}
因为是“GET”方法,所以在该方法上标注“@Get("")”,URI“/myforum/topic”中的“myforum”已经在“@Path("myforum")”中定义过了,所以只剩下“topic”,于是写“@Get("topic")”。
再看第二个API——“GET http://github.com/myforum/topic/123”。
跟前一个的唯一区别是,后面多了个“/123”,表示主帖id,而这个id当然不是固定的,只有用户点击链接发来请求时才能知道,肿么办?
没关系,rose支持正则表达式!可以这么写:
@Get("topic/{topicId:[0-9]+}")
public String showTopic(@Param("topicId") int topicId) {
//显示单个主帖和它的跟贴
return "topic";
}
与前一个API相比,多了段“/{topicId:[0-9]+}”。正则表达式被大括号"{}"包围,格式为“{ paramName : regularExpression }”,只有请求的URI能被正则表达式匹配时,才会执行这个方法,而被匹配的值将被保存在名为“topicId”的参数中。
同理,实现第三个API,稍微复杂一点:
@Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
public String showComment(@Param("topicId") int topicId, @Param("commentId") int commentId) {
//显示单个跟贴
return "comment";
}
最后两个API使用POST方法,其他与前面相同:
@Post("topic")
public String createTopic(){
//创建一个主帖
return "topic";
}
@Post("topic/{topicId:[0-9]+}/comment")
public String createComment(@Param("topicId") int topicId){
//创建一个跟贴
return "comment";
}
完整的代码如下(省略了import语句):
@Path("myforum")
public class ForumController {
@Get("topic")
public String getTopics() {
//显示主帖列表
return "topiclist";
}
@Get("topic/{topicId:[0-9]+}")
public String showTopic(@Param("topicId") int topicId) {
//显示单个主帖和它的跟贴
return "topic";
}
@Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
public String showComment(@Param("topicId") int topicId, @Param("commentId") int commentId) {
//显示单个跟贴
return "comment";
}
@Post("topic")
public String createTopic(){
//创建一个主帖
return "topic";
}
@Post("topic/{topicId:[0-9]+}/comment")
public String createComment(@Param("topicId") int topicId){
//创建一个跟贴
return "comment";
}
}
至此,一个贴吧功能的Controller就编写完成了。
除了上面例子中的做法(@Path(""),@Get("")和@Post("")),还可以通过包路径来规划URI。
比如前面例子中的Controller,在API不变的前提下,还可以这么做:
1.在controllers路径下新建一个叫做“myforum”的文件夹。
2.将ForumController从“xxx.controllers”移动到“xxx.controllers.myforum”
并改成下面这样:
@Path("")
public class ForumController {
@Get("topic")
public String getTopics() {
//显示主帖列表
return "topiclist";
}
... ...
}
只是将“@Path("myforum")”改成了“@Path("")”。这样做的好处是可以让项目中的代码组织清晰。
web开发中最常规的做法是,运行Servlet中的方法,最后将渲染好的页面内容返回。下面说说rose是怎么做的。
上面的贴吧例子中,每个方法的返回值都是一个普通字符串,比如“comment”,意思是,找到web项目中“webapp/views”路径下名叫“comment”的视图文件,比如“comment.jsp”,用这个视图文件来渲染网页结果并返回。
comment.jsp的代码如下:
...
昵称:${name}
回复内容:${commentContent}
...
页面中有两个变量——name和commentContent,变量的值是在java代码中设置的,如下:
@Get("topic/{topicId:[0-9]+}/comment/{commentId:[0-9]+}")
public String showComment(Model model, @Param("topicId") int topicId, @Param("commentId") int commentId) {
//显示单个跟贴
model.add("name", "郭德纲");
model.add("commentContent", "今天来人不少,我很欣慰啊!");
return "comment";
}
总结一句话,通过rose提供类net.paoding.rose.web.var.Model来设置变量名和变量值,然后在视图文件中用“${paramName}”的方式得到变量值。
变量的值可以是String,boolean,数字,数组,对象(JavaBean)。
如果是对象,使用方法如下:
javaBean:
public class Bean{
private String beanValue;
public String getBeanValue(){...}
public String setBeanValue(String beanValue){...}
}
==================
controller中的方法:
@Get("test")
public String test(Model model) {
Bean bean = new Bean();
bean.setBeanValue("this_is_a_bean");
model.add("mybean", bean);
return "test";
}
==================
test.jsp文件:
...
bean里的值:${mybean.beanValue}
...
==================
输出为:
bean里的值:this_is_a_bean
如果是个数组,可以结合JSTL对数组循环访问:
controller中的方法:
@Get("test")
public String test(Model model) {
String[] array = {"111","222","333"};
model.add("array", array);
return "test";
}
==================
test.jsp文件:
...
打印:${item}
...
==================
输出为:
111
222
333
rose中,controller方法的返回值有下面几种规则:
1.返回普通字符串,如上所述,最常用的做法,渲染视图文件并返回。
2.以“@”开头的字符串,比如“return "@HelloWorld";”,会将“@”后面的字符串“HelloWorld”作为结果返回;
3.以“@json:”开头的字符串,比如:
@Get("json")
public String returnJson(){
JSONObject jo = new JSONObject();
return "@json:"+jo.toString();
}
将会返回一个字符串(jo.toString()),并自动将“HttpServletResponse”中的“contentType”设置为“application/json”。
4.【不推荐使用】以“r:”开头的字符串,比如“return "r:/aaa";”,等效于调用“javax.servlet.http.HttpServletResponse.sendRedirect("/aaa")”,将执行301跳转。
5.【不推荐使用】以“a:”开头的字符串,比如“return "a:/bbb";”,将会携带参数再次匹配roseTree,找到controller中某个方法并执行,相当于“javax.servlet.RequestDispatcher.forward(request, response)”。
Rose 是一个基于Servlet规范、Spring“规范”的WEB开发框架。
Rose 框架通过在web.xml配置过滤器拦截并处理匹配的web请求,如果一个请求应该由在Rose框架下的类来处理, 该请求将在Rose调用中完成对客户端响应. 如果一个请求在Rose中没有找到合适的类来为他服务,Rose将把该请求移交给web容器的其他组件来处理。
Rose使用过滤器而非Servlet来接收web请求,这有它的合理性以及好处。
Servlet规范以“边走边看”的方式来处理请求, 当服务器接收到一个web请求时,并没有要求在web.xml必须有相应的Servlet组件时才能处理,web请求被一系列Filter过滤时, Filter可以拿到相应的Request和Response对象 ,当Filter认为自己已经能够完成整个处理,它将不再调用chain.doNext()来使链中下个组件(Filter、Servlet、JSP)进行处理。
使用过滤器的好处是,Rose可以很好地和其他web框架兼容。这在改造遗留系统、对各种uri的支持具有天然优越性。正是使用过滤器,Rose不再要求请求地址具有特殊的后缀。
为了更好地理解,可以把Rose看成这样一种特殊的Servlet:它能够优先处理认定的事情,如无法处理再交给其它Filter、Servlet或JSP来处理。这个刚好是普通Servlet无法做到的 : 如果一个请求以后缀名配置给他处理时候 ,一旦该Servlet处理不了,Servlet规范没有提供机制使得可以由配置在web.xml的其他正常组件处理 (除404,500等错误处理组件之外)。
一个web.xml中可能具有不只一个的Filter,Filter的先后顺序对系统具有重要影响,特别的,Rose自己的过滤器的配置顺序更是需要讲究 。如果一个请求在被Rose处理前,还应该被其它一些过滤器过滤,请把这些过滤器的mapping配置在Rose过滤器之前。
像前面提到过的,RoseFilter的配置,建议按以下配置即可:
roseFilter
net.paoding.rose.RoseFilter
roseFilter
/*
REQUEST
FORWARD
INCLUDE
大多数请况下,filter-mapping 应配置在所有Filter Mapping的最后。 不能将 FORWARD、INCLUDE 的 dispatcher 去掉,否则forward、 include的请求Rose框架将拦截不到。
Rose框架内部采用"匹配->执行"两阶段逻辑。Rose内部结构具有一个匹配树, 这个数据结构可以快速判断一个请求是否应该由Rose处理并进行, 没有找到匹配的请求交给过滤器的下一个组件处理。匹配成功的请求将进入”执行“阶段。 执行阶段需要经过6个步骤处理:“参数解析 -〉 验证器 -〉 拦截器 -〉 控制器 -〉 视图渲染 -〉渲染后"的处理链。
匹配树: 匹配树是一个多叉树,下面是一个例子:
ROOT
GET="HomeController#index" package="com.xiaonei.xxx.controllers"
/about
GET="HomeController#about" package="com.xiaonei.xxx.controllers"
/book
GET="BookController#list" package="com.xiaonei.xxx.controllers.sub"
POST="BookController#add" package="com.xiaonei.xxx.controllers.sub"
/book/
/book/{id}
GET="BookController#show" package="com.xiaonei.xxx.controllers.sub"
/help
GET="HomeController#help" package="com.xiaonei.xxx.controllers"
ROOT代表这是一个根地址,也就是 http://localhost/ 代表的地址;
ROOT的下级有个GET结点,代表对该地址支持GET访问,不支持POST等其它访问,如果进行POST访问将以405错误回应。
/book代表这是一个/book地址,也就是 http://localhost/book 代表的地址;
/book下级有GET、POST两个结点,说明它支持GET和POST方法,根据HTTP语义,GET代表浏览,POST代表追加(向一个集合中追加一个条目)。
/book下还有/book/地址,这个地址有点特别,它以'/'结尾,但实际它不会被任何地址访问到,rose对http://localhost/book/的处理会将它等价于 http://localhost/book 。
这个特别的地址的存在完全是匹配树结构所需导致的,但不对实际匹配有任何坏的影响,所以也没有任何GET、POST等子结点。
/book/{id}代表是一个/book/123456、/book/654321这样的地址,当然这可以支持正则表达式的。
大部分情况下,匹配树的结构和实际的URI结构会一致,也因此匹配树的深度并不固定,每一个中间结点或叶子节点都有可能代表一个最终的URI地址,可以处理GET、POST等请求。对于那些匹配树存在的地址,但没有GET、POST、DELETE等子结点的,一旦用户请求了该地址,rose将直接把该请求转交给web容器处理,如果容器也不能处理它,最终用户将得到404响应。
匹配过程: Rose以请求的地址作为处理输入(不包含Query串,即问号后的字符串)。如果匹配树中存在对应的地址,且含有对应请求方法(GET、POST、PUT、DELETE)的,则表示匹配成功;如果含有其他方法的,但没有当前方法的(比如只支持GET,但当前是POST的),则也表示匹配成功,但最后会以405响应出去;如果所给的地址没有任何支持的方法或者没有找到匹配地址的,则表示匹配失败。1.0.1不支持回朔算法,1.0.2将支持部分回朔算法(待发布时再做详细介绍)。
参数解析: 在调用验证器、拦截器 控制器之前,Rose完成2个解析:解析匹配树上动态的参数出实际值,解析控制器方法中参数实际的值。参数可能会解析失败(例如转化异常等等 ),此时该参数以默认值进行代替,同时Rose解析失败和异常记录起来放到专门的类中,继续下一个过程而不打断执行。
public class AccessTrackInterceptor extends ControllerInterceptorAdapter {
public AccessTrackInterceptor() {
setPriority(29600);
}
@Override
public Class extends Annotation> getRequiredAnnotationClass() {
return PriCheckRequired.class; // 这是一个注解,只有标过的controller才会接受这个拦截器的洗礼。
}
@Override
public Object before(Invocation inv) throws Exception {
// TODO ....
return super.before(inv);
}
@Override
public void afterCompletion(final Invocation inv, Throwable ex) throws Exception {
// TODO ....
}
}
需要注意几点:
[文中所提代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example]
/**
* @author [email protected]
* 2010-12-1
*/
package com.chen.controllers;
import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.Invocation;
public class ErrorHandler implements ControllerErrorHandler {
public Object onError(Invocation inv, Throwable ex) throws Throwable {
// TODO logger.error("handle err:", ex);
return "@error";
}
}
这是这么简单,不用怀疑!
@Path("")
public class HelloController {
@Get("")
public String index2() throws Exception {
return "@hello world";
}
}
/**
* @author [email protected]
* 2010-12-1
*/
package com.chen.controllers;
import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.Invocation;
public class ErrorHandler implements ControllerErrorHandler {
public Object onError(Invocation inv, Throwable ex) throws Throwable {
// TODO logger.error("handle err:", ex);
if (ex instanceof RuntimeException) {
return "@runtime";
}
return "@error";
}
}
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
ChenBeanResolver.java放在controllers目录下:
public class ChenBeanResolver implements ParamResolver {
@Override
public Object resolve(Invocation inv, ParamMetaData metaData) throws Exception {
for (String paramName : metaData.getParamNames()) {
if (paramName != null) {
Chen chen = new Chen();
String value1 = inv.getParameter("chen1");
String value2 = inv.getParameter("chen2");
chen.setChen1(value1);
chen.setChen2(value2);
return chen;
}
}
return null;
}
@Override
public boolean supports(ParamMetaData metaData) {
return Chen.class == metaData.getParamType();
}
}
上述代码的意思:
配合上述resolver的action代码为:
@Get("/param")
public String param(Chen chen) throws Exception {
return "@hello world:" + chen.getChen1() + ":" + chen.getChen2();
}
除了上述的自定义resolver外,rose还内置了丰富的resolver,都是大家的经验总结,使用起来会非常方便,它们是:
@Post
public String post(User user) {
return "@" + user.getId() + "; level.id=" + user.getLevel().getId();
}
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
public class NotBlankParamValidator implements ParamValidator {
@Override
public boolean supports(ParamMetaData metaData) {
return metaData.getAnnotation(NotBlank.class) != null;
}
@Override
public Object validate(ParamMetaData metaData, Invocation inv, Object target, Errors errors) {
String paramName = metaData.getParamName();
String value = inv.getParameter(paramName);
if (StringUtils.isBlank(value)) {
return "@参数不能为空";
}
return null;
}
}
解读:
@Get("/notBlank")
public String notBlank(@NotBlank @Param("messages") String messages) throws Exception {
return "@hello world";
}
解读:
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
@Get("/flash1")
public String flashStep1(Flash flash) {
flash.add("msg", "修改成功!");
return "r:/flash2";
}
@Get("/flash2")
public String flashStep2(Invocation inv, Flash flash) {
inv.addModel("info", flash.get("msg"));
return "flash";
}
<%@ page contentType="text/html;charset=UTF-8"%>
提示信息:${info}
flash功能利用了浏览器的cookies功能,如果用户的环境不能使用cookies将不会有任何效果。
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
*字面意思,做门户用的。 *简单来说,把一个网页分成了N个区域,每个区域由不同的action去执行,多线程并行提高cpu使用率。
*要使用portal,必须先在web.xml里声明所使用的线程池大小:
portalExecutorCorePoolSize
1024
*然后看示例代码:
@Get("/3.7")
public String portal(Portal portal) {
portal.addWindow("p1", "/wp1");
portal.addWindow("p2", "/wp2");
return "portal";
}
@Get("/wp1")
public String portal1() {
return "@this is p1";
}
@Get("/wp2")
public String portal2() {
return "@this is p2";
}
*然后在第一个action中的portal.jsp中写到:
<%@ page contentType="text/html;charset=UTF-8"%>
portal演示信息:
${p1}
${p2}
*当我们部属好了之后,访问http://127.0.0.1/3.7 *将从浏览器中得到: *portal演示信息: *this is p1 *this is p2
*更加充分地使用多核cpu。 *更加方便多人协作时对项目进行模块划分,搞的时候,按照url一分,一个url一个模块,所有的页面都可以切成小的豆腐块,所以,你懂的。
http://www.54chen.com/architecture/rose-open-source-portal-framework.html
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供 。
HelloController.java
@Get("/3.8")
public String pipe(Pipe pipe) {
pipe.addWindow("p1", "/wp1");
pipe.addWindow("p2", "/wp2");
return "pipe";
}
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="http://paoding.net/rose/pipe" prefix="rosepipe"%>
http-equiv="Content-Type" content="text/html; charset=UTF-8">
portal/pipe演示信息
portal/pipe演示信息:
id="p1">
id="p2">
${p1}
${p2}
久经考验
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
@Post("/doUpload")
public String doUpload(@Param("file") MultipartFile file) {
return "@ upload ok!" + file.getOriginalFilename();
}
// 不声明@Param
// files可以是一个数组或者List
public String upload(MultipartFile[] files) {
return "@ok-" + Arrays.toString(files);
}
同时也可以使用@Param传递不同的name。
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
本章开始进入对DB层的支持,同进也是日常开发用得最多的章节。
pom.xml
com.54chen
paoding-rose-jade
1.1
commons-dbcp
commons-dbcp
1.2.2
mysql
mysql-connector-java
5.1.10
在war项目的applicationContext.xml中增加数据源定义:
id="jade.dataSource.com.chen.dao" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
name="driverClassName" value="com.mysql.jdbc.Driver">
name="url"
value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8">
name="username" value="test">
name="password" value="test">
name="timeBetweenEvictionRunsMillis" value="3600000">
name="minEvictableIdleTimeMillis" value="3600000">
create table test (int id, varchar(200) msg);
insert into test values(111,'adfafafasdf');
@DAO
public interface TestDAO {
@SQL("select id,msg from test limit 1")
public Test getTest();
}
Test是一个class,里面有标准的getter和setter。
然后从一个类去调用它:
@Service
public class TestService {
@Autowired
private TestDAO testDAO;
public Test getTest() {
return testDAO.getTest();
}
}
你可以将此节发布到resin或者tomcat后访问:http://127.0.0.1/3.10 查看结果。
文中所提及代码均在 https://github.com/XiaoMi/rose/tree/master/rose-example 提供。
@SQL("insert into test (id,msg) values (:t.id,:t.msg)")
public void insertTest(@SQLParam("t") Test test);
上列中Test对象通过t传递到sql执行中去,并且可以分别使用其中的属性。这感觉是不是很自然?
当然,如果是一个int、long、String等自在不言中。
当是list时,会有自动的batch操作,将sql拆为多条sql执行。这个小技巧会在后面的章节里讲。平时很少用到。
@ReturnGeneratedKeys
@SQL("insert into test (id,msg) values (:t.id,:t.msg)")
public int insertTest(@SQLParam("t") Test test);
jade支持一些常规的表达式。
语法一:常见的变量赋值
语法二:字符串连接
@SQL("SELECT user_id, device_token FROM test_##(:partition) LIMIT :limit")
public List<Test> getTests(@SQLParam("partition") int partition, @SQLParam("limit") int limit);
@SQL("SELECT user_id, device_token FROM test_##(:partition) #if(:user>0){ where user_id=:user } LIMIT :limit")
public List<Test> getTestsIf(@SQLParam("partition") int partition, @SQLParam("limit") int limit, @SQLParam("user") int user);
@SQL("SELECT user_id, device_token FROM test_##(:partition) where user_id in(:ids)")
public List<Test> getTestsByIds(@SQLParam("partition") int partition, @SQLParam("ids") List<Integer> ids);
欢迎顺利进入本章,如果您的企业需要这一节的内容,那么说明用户量很有前途,如果使用了本节的内容,不防向[email protected]发信一封以表谢意,我们会很高兴收到各种反馈。
以下是个人从业经验中的分表规则:
按照 id%10 分为十份
以上分表规则特别在mysql中使用机会比较多,各有优势,没有对错,只有最好与最不好用。
要使用分表,需要添加新的依赖,由bmw提供的bmwutils。
com.54chen
bmwutils
0.0.2
在开写代码之前,需要告诉DAO是哪个表需要分表,按照什么规则分,分多少份。
id="jade.routerInterpreter" class="com.xiaomi.common.service.dal.routing.RewriteSQLInterpreter">
name="routingConfigurator" ref="jade.routingConfigurator" />
id="jade.routingConfigurator" class="com.xiaomi.common.service.dal.routing.RoutingConfigurator">
name="partitions">
hash:test:id:test_{0}:100
@SQL("SELECT user_id, device_token FROM test where user_id =:id")
public List<Test> getTestsById(@ShardBy @SQLParam("id") int id);
与不分表的dao相比,只多了一个shardBy,标识按照这个参数值分表。
如果你使用maven,在pom中添加如果定义之后,你打出来的jar包就会被rose扫描到并且引入到上下文环境中:
org.apache.maven.plugins
maven-jar-plugin
*
如果你的sql在执行update insert delete,并且dao的第一个参数是list类的多个值,那这条sql会被拆成多条sql依次执行,执行的结果会以各条sql的返回组成的数组返回。
我们一般会把项目规定为:controller/service/biz/dao层,不能跨层调用,只在service层允许同时调用子层多个方法。
相当多的初次使用DAO的朋友,会将XXXDAO写作XXXDao,注意大小写,只有DAO全大写时的class,才会被jade认识并扫描入环境中。
附录里会有qieqie同学亲笔的rose编年史和发展规划。