CXF之八 RESTFul服务
1.了解MongoDB的ObjectId
MongoDB的文档固定是使用“_id”作为主键的,它可以是任何类型的,默认是个ObjectId对象(在Java中则表现为字符串),那么为什么MongoDB没有采用其他比较常规的做法(比如MySql的自增主键),而是采用了ObjectId的形式来实现?别着急,咱们看看ObjectId的生成方式便可知悉。
ObjectId使用12字节的存储空间,每个字节两位十六进制数字,是一个24位的字符串。由于看起来很长,不少人会觉得难以处理,其实不然。ObjectId是由客户端生成的,按照如下方式生成:
前4位是一个从标准纪元开始的时间戳,是一个int类别,只不过从十进制转换为了十六进制。这意味着这4个字节隐含了文档的创建时间,将会带来一些有用的属性。并且时间戳处于字符的最前面,同时意味着ObjectId大致会按照插入顺序进行排序,这对于某些方面起到很大作用,如作为索引提高搜索效率等等。使用时间戳还有一个好处是,某些客户端驱动可以通过ObjectId解析出该记录是何时插入的,这也解答了我们平时快速连续创 建多个Objectid时,会发现前几位数字很少发现变化的现实,因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个时间戳的真实值并 不重要,只要其总不停增加就好。
接下来的3个字节,是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectid中间的字符串都是一模一样的原因。
上面的机器字节是为了确保在不同机器产生的ObjectId不冲突,而PID就是为了在同一台机器不同的mongodb进程产生了ObjectId不冲突。
前面的9个字节是保证了一秒内不同机器不同进程生成ObjectId不冲突,最后的3个字节是一个自动增加的计数器,用来确保在同一秒内产生的ObjectId也不会冲突,允许256的3次方等于16777216条记录的唯一性。
因此,MongoDB不使用自增主键,而是使用ObjectId。在分布式环境中,多个机器同步一个自增ID不但费时且费力,MongoDB从一开始就是设计用来做分布式数据库的,处理多个节点是一个核心要求,而ObjectId在分片环境中要容易生成的多。
2.手动实现自增ID
ObjectId确实是有很大的好处,但有时候由于某些不可抗力的因素或需求,我们仍需要实现一个自增的数值ID,笔者查阅了网上的资料,大多都是一个套路:使用一个单独的集合A来记录每个集合中的ID最大值,然后每次向集合B中插入文档时,去查找集合A中集合B所对应的ID最大值,取出来并+1,然后更新集合A、根据这个ID再插入文档。下面笔者通过网上一种自认为好点的方式来实现,因为笔者用的是Spring Data MongoDB……
2.1 定义序列实体类SeqInfo
我们需要用这个集合来存储每个集合的ID记录自增到了多少,如下代码:
package com.jastar.autokey.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* 模拟序列类
*
* @author Jastar·Wang
* @date 2017年5月27日
*/
@Document(collection = "sequence")
public class SeqInfo {
@Id
private String id;// 主键
@Field
private String collName;// 集合名称
@Field
private Long seqId;// 序列值
// 省略getter、setter
}
2.2 定义注解AutoIncKey
我们需要通过这个注解标识主键ID需要自动增长,如下代码:
package com.jastar.autokey.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,标识主键字段需要自动增长
* <p>
* ClassName: AutoIncKey
* </p>
* <p>
* Copyright: (c)2017 Jastar·Wang,All rights reserved.
* </p>
*
* @author jastar-wang
* @date 2017年5月27日
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIncKey {
}
2.3 定义业务实体类Student
package com.jastar.autokey.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import com.jastar.autokey.annotation.AutoIncKey;
@Document(collection = "student")
public class Student {
@AutoIncKey
@Id
private Long id = 0L;// 为什么赋了默认值?文章后说明
@Field
private String name;
// 省略getter、setter
}
2.4 定义监听类SaveEventListener
→2017年7月26日更新:
注意下面代码中重写的onBeforeConvert方法在1.8版本开始就废弃了,不过官方推荐: Please use onBeforeConvert(BeforeConvertEvent),各位猿友可以研究下这个方法如何使用,我想 BeforeConvertEvent 对象里面应该会有所需要的参数信息,在此我就不再亲测了。
因为使用的是Spring Data MongoDB,所以可以重写监听事件里面的方法,而进行某些处理,该类需要继承AbstractMongoEventListener类,并且需交由Spring管理,如下代码:
package com.jastar.autokey.listener;
import java.lang.reflect.Field;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import com.jastar.autokey.annotation.AutoIncKey;
import com.jastar.autokey.entity.SeqInfo;
/**
* 保存文档监听类<br>
* 在保存对象时,通过反射方式为其生成ID
* <p>
* ClassName: SaveEventListener
* </p>
* <p>
* Copyright: (c)2017 Jastar·Wang,All rights reserved.
* </p>
*
* @author jastar-wang
* @date 2017年5月27日
*/
@Component
public class SaveEventListener extends AbstractMongoEventListener<Object> {
@Autowired
private MongoTemplate mongo;
@Override
public void onBeforeConvert(BeforeConvertEvent<Object> event) {
final Object source = event.getSource();
if (source != null) {
ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
/** Perform an operation using the given field.
* @param field the field to operate on */
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
// 如果字段添加了我们自定义的AutoIncKey注解
if (field.isAnnotationPresent(AutoIncKey.class)
//判断注解的字段是否为number类型且值是否等于0.如果大于0说明有ID不需要生成ID
&& field.get(source) instanceof Number
&& field.getLong(source) == 0) {
// 设置自增ID
field.set(source, getNextId(source.getClass().getSimpleName()));
logger.debug("increase key, source = {} , nextId = {}", source, field.get(source));
}
}
});
}
}
/**
* 获取下一个自增ID
*
* @param collName
* 集合(这里用类名,就唯一性来说最好还是存放长类名)名称
* @return 序列值
*/
private Long getNextId(String collName) {
Query query = new Query(Criteria.where("collName").is(collName));
Update update = new Update();
update.inc("seqId", 1);
FindAndModifyOptions options = new FindAndModifyOptions();
options.upsert(true);
options.returnNew(true);
SeqInfo seq = mongo.findAndModify(query, update, options, SeqInfo.class);
return seq.getSeqId();
}
}
2.5 单元测试
@Test
public void save() {
Student stu = new Student();
stu.setName("张三");
service.save(stu);
// service.update(stu);
System.out.println("已生成ID:" + stu.getId());
}
2.6 总结
经过测试,以上流程没有问题,会得到期望的结果,但是有以下几点需要注意:
(1)为什么我在Student类中为主键赋了一个默认值0L?
答:我在此自增方式原作者文章中发现这么一句,“注意自增ID的类型不要定义成Long这种包装类,mongotemplate的源码里面对主键ID的类型有限制”。测试后发现,如果ID定义为原生类型确实是没有问题的。当ID定义为包装类的情况下,如果在onBeforeConvert方法之前没有给ID设置值,是会报错的,我猜测可能是因为内部转换类型时如果ID是空值而无法转换引起的,因此,我赋了一个默认值,这样就不会报错了,包装类也可以使用(不过这样好像跟原生类型就没什么区别了,没什么意义)。
(2)这个监听器会不会影响修改操作?
答:测试发现,不会影响,水平有限,本人也不知作何解释,不要打我……
(3)这种方式会有并发问题吗?
答:不会的!根据官方文档说明,findAndModify一个原子性操作,不过有这么一句“When the findAndModify command includes the upsert: true option and the query field(s) is not uniquely indexed, the command could insert a document multiple times in certain circumstances.”,大概意思是说当查询和更新两个操作都存在时,如果查询的字段没有唯一索引的话,该命令可能会在某些情况下更新/插入 文档多次,参考链接:戳我戳我。以上演示的是只存储了集合所对应的实体类的短名称,短名称是会重复的,所以这种方法不妥,还是记录长名称吧。
CXF之八 RESTFul服务的更多相关文章
- 使用CXF开发RESTFul服务
相信大家在阅读CXF官方文档(http://cxf.apache.org/docs/index.html)时,总是一知半解.这里向大家推荐一本PacktPub.Apache.CXF.Web.Servi ...
- CXF+Spring+JAXB+Json构建Restful服务
话不多说,先看详细的样例: 文件文件夹结构: web.xml <?xml version="1.0" encoding="UTF-8"? > < ...
- 用cxf开发restful风格的WebService
我们都知道cxf还可以开发restful风格的webService,下面是利用maven+spring4+cxf搭建webService服务端和客户端Demo 1.pom.xml <projec ...
- CXF发布restful WebService的入门例子(服务器端)
研究了两天CXF对restful的支持. 现在,想实现一个以 http://localhost:9999/roomservice 为入口, http://localhost:9999/roomse ...
- WebService--使用 CXF 开发 REST 服务
现在您已经学会了如何使用 CXF 开发基于 SOAP 的 Web 服务,也领略了 Spring + CXF 这个强大的组合,如果您错过了这精彩的一幕,请回头看看这篇吧: Web Service 那点事 ...
- 采用CXF+spring+restful创建一个web接口项目
这篇文章是http://blog.csdn.net/zxnlmj/article/details/28880303下面,加入的基础上的restful特征 1.参加restful必jar包裹 jsr31 ...
- CXF 开发 REST 服务
今天我们将视角集中在 REST 上,它是继 SOAP 以后,另一种广泛使用的 Web 服务.与 SOAP 不同,REST 并没有 WSDL 的概念,也没有叫做"信封"的东西,因为 ...
- 开发基于CXF的 RESTful WebService web 项目 webservice发布
配置步骤 开发基于CXF的 RESTful WebService 1.创建Web项目并导入CXF的jar 2.在Web.xml中配置 CXFServlet <servlet> <se ...
- REST,Web 服务,REST-ful 服务
介绍 REpresentational State Transfer (REST) 是一种架构原则,其中将 web 服务视为资源,可以由其 URL 唯一标识.RESTful Web 服务的关键特点是明 ...
随机推荐
- SPOJ 3693 Maximum Sum(水题,记录区间第一大和第二大数)
#include <iostream> #include <stdio.h> #include <algorithm> #define lson rt<< ...
- C#接口的经典案例
C#接口(interface)实例子(简单而经典)2008/12/04 10:04using System; using System.Collections.Generic; using Syste ...
- 李洪强iOS开之【零基础学习iOS开发】【02-C语言】04-常量、变量
在我们使用计算机的过程中,会接触到各种各样的数据,有文档数据.图片数据.视频数据,还有聊QQ时产生的文字数据.用迅雷下载的文件数据等.这讲我们就来介绍C语言中数据的处理. 一.数据的存储 1.数据类型 ...
- 进程内核栈、用户栈及 Linux 进程栈和线程栈的区别
Linux 进程栈和线程栈的区别 http://www.cnblogs.com/luosongchao/p/3680312.html 总结:线程栈的空间开辟在所属进程的堆区,线程与其所属的进程共享进程 ...
- iOS 越狱机免证书调试
目前在XCode上开发的iOS程序只能在模拟器Simulator中运行,如果要放到真机上测试,需要苹果官方认证的开发者账号,购买开发者证书iDP,99美金一年啊! 作为刚开始学习iOS编程的菜鸟,这么 ...
- IP地址字符串与BigInteger的转换
/** * Copyright (c) 2010, 新浪网支付中心 * All rights reserved. * * Java IP地址字符串与BigInteger的转换, * ...
- 腾讯大讲堂ppt全集
腾讯大讲堂ppt全集 腾讯大讲堂ppt全集资料下载 腾讯大讲堂ppt1-62资料下载 最新最全的腾讯大讲堂ppt全集 腾讯大讲堂ppt全集资料下载 腾讯大讲堂ppt1-62资料下载地址 http:// ...
- android下activity中多个listview只允许主界面滚动
之前发现了自己的APP在处理两个listview时产生的一个bug.当两个listview中的item数量多出手机屏幕时,listview不能显示完全.一开始觉得只要加一个scrollview就可以了 ...
- POJ 1808 Quadratic Residues(平方剩余相关)
题目链接:http://poj.org/problem?id=1808 题意:如下.对于素数p,若存在x使得x^2%p=a,则其值为1.否则为-1.现在给出a.p,计算其值. 思路: 若a为正数则利用 ...
- java开发之关键字
abstract //抽象方法,抽象类的修饰符assert //断言条件是否满足boolean //布尔数据类型break //跳出循环或者label代码段byte //8-bit 有符号数据类型ca ...