zoukankan      html  css  js  c++  java
  • Java中实现MongoDB自增主键ID

    https://blog.csdn.net/weixin_42236014/article/details/114200620

    小编典典

    首先使用“ 创建自动递增序列字段”,您应该使用mongoDB shell创建集合,并且集合应为:

    db.counters.insert(

    {

    _id: "userid",

    seq: 0

    })

    因此,您将获得counters包含如下字段的集合_id,seq,现在getNextSequence在Java中创建函数,并且此函数具有userid作为字符串的参数,因此getNextSequence如下所示:

    public static Object getNextSequence(String name) throws Exception{

    MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

    // Now connect to your databases

    DB db = mongoClient.getDB("demo");

    DBCollection collection = db.getCollection("counters");

    BasicDBObject find = new BasicDBObject();

    find.put("_id", name);

    BasicDBObject update = new BasicDBObject();

    update.put("$inc", new BasicDBObject("seq", 1));

    DBObject obj = collection.findAndModify(find, update);

    return obj.get("seq");

    }

    上面的函数返回seq计数并在main方法中使用该函数,例如:

    public static void main(String[] args) throws Exception {

    MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

    // Now connect to your databases

    DB db = mongoClient.getDB("demo");

    DBCollection collection = db.getCollection("counters");

    BasicDBObject document = new BasicDBObject();

    document.put("_id", getNextSequence("userid"));

    document.put("name","Sarah C.");

    collection.insert(document); // insert first doc

    document.put("_id", getNextSequence("userid"));

    document.put("name", "Bob D.");

    collection.insert(document); // insert second doc

    }

    现在,在counters集合包含其中包含三个文件name为Sarah C. and Bob

    D.我们手动插入第一次分别与一个默认文档,并将其增加seq这样的{ "_id" : "userid", "seq" : 2 }

    2020-11-01

     

    https://blog.csdn.net/qq_16313365/article/details/72781469

    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记录自增到了多少,如下代码:

    1. package com.jastar.autokey.entity;
    2.  
    3. import org.springframework.data.annotation.Id;
    4. import org.springframework.data.mongodb.core.mapping.Document;
    5. import org.springframework.data.mongodb.core.mapping.Field;
    6.  
    7. /**
    8. * 模拟序列类
    9. *
    10. * @author Jastar·Wang
    11. * @date 2017年5月27日
    12. */
    13. @Document(collection = "sequence")
    14. public class SeqInfo {
    15.  
    16. @Id
    17. private String id;// 主键
    18.  
    19. @Field
    20. private String collName;// 集合名称
    21.  
    22. @Field
    23. private Long seqId;// 序列值
    24.  
    25. // 省略getter、setter
    26.  
    27. }

    2.2 定义注解AutoIncKey

    我们需要通过这个注解标识主键ID需要自动增长,如下代码:

    1. package com.jastar.autokey.annotation;
    2.  
    3. import java.lang.annotation.ElementType;
    4. import java.lang.annotation.Retention;
    5. import java.lang.annotation.RetentionPolicy;
    6. import java.lang.annotation.Target;
    7.  
    8. /**
    9. * 自定义注解,标识主键字段需要自动增长
    10. * <p>
    11. * ClassName: AutoIncKey
    12. * </p>
    13. * <p>
    14. * Copyright: (c)2017 Jastar·Wang,All rights reserved.
    15. * </p>
    16. *
    17. * @author jastar-wang
    18. * @date 2017年5月27日
    19. */
    20. @Target(ElementType.FIELD)
    21. @Retention(RetentionPolicy.RUNTIME)
    22. public @interface AutoIncKey {
    23.  
    24. }

    2.3 定义业务实体类Student

    1. package com.jastar.autokey.entity;
    2.  
    3. import org.springframework.data.annotation.Id;
    4. import org.springframework.data.mongodb.core.mapping.Document;
    5. import org.springframework.data.mongodb.core.mapping.Field;
    6.  
    7. import com.jastar.autokey.annotation.AutoIncKey;
    8.  
    9. @Document(collection = "student")
    10. public class Student {
    11.  
    12. @AutoIncKey
    13. @Id
    14. private Long id = 0L;// 为什么赋了默认值?文章后说明
    15.  
    16. @Field
    17. private String name;
    18.  
    19. // 省略getter、setter
    20. }

    2.4 定义监听类SaveEventListener

    →2017年7月26日更新:

            注意下面代码中重写的onBeforeConvert方法在1.8版本开始就废弃了,不过官方推荐: Please use onBeforeConvert(BeforeConvertEvent),各位猿友可以研究下这个方法如何使用,我想 BeforeConvertEvent 对象里面应该会有所需要的参数信息,在此我就不再亲测了。

    因为使用的是Spring Data MongoDB,所以可以重写监听事件里面的方法,而进行某些处理,该类需要继承AbstractMongoEventListener类,并且需交由Spring管理,如下代码:

    1. package com.jastar.autokey.listener;
    2.  
    3. import java.lang.reflect.Field;
    4.  
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.data.mongodb.core.FindAndModifyOptions;
    7. import org.springframework.data.mongodb.core.MongoTemplate;
    8. import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
    9. import org.springframework.data.mongodb.core.query.Criteria;
    10. import org.springframework.data.mongodb.core.query.Query;
    11. import org.springframework.data.mongodb.core.query.Update;
    12. import org.springframework.stereotype.Component;
    13. import org.springframework.util.ReflectionUtils;
    14.  
    15. import com.jastar.autokey.annotation.AutoIncKey;
    16. import com.jastar.autokey.entity.SeqInfo;
    17.  
    18. /**
    19. * 保存文档监听类<br>
    20. * 在保存对象时,通过反射方式为其生成ID
    21. * <p>
    22. * ClassName: SaveEventListener
    23. * </p>
    24. * <p>
    25. * Copyright: (c)2017 Jastar·Wang,All rights reserved.
    26. * </p>
    27. *
    28. * @author jastar-wang
    29. * @date 2017年5月27日
    30. */
    31. @Component
    32. public class SaveEventListener extends AbstractMongoEventListener<Object> {
    33.  
    34. @Autowired
    35. private MongoTemplate mongo;
    36.  
    37. @Override
    38. public void onBeforeConvert(final Object source) {
    39. if (source != null) {
    40. ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
    41. public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
    42. ReflectionUtils.makeAccessible(field);
    43. // 如果字段添加了我们自定义的AutoIncKey注解
    44. if (field.isAnnotationPresent(AutoIncKey.class)) {
    45. // 设置自增ID
    46. field.set(source, getNextId(source.getClass().getSimpleName()));
    47. }
    48. }
    49. });
    50. }
    51. }
    52.  
    53. /**
    54. * 获取下一个自增ID
    55. *
    56. * @param collName
    57. * 集合(这里用类名,就唯一性来说最好还是存放长类名)名称
    58. * @return 序列值
    59. */
    60. private Long getNextId(String collName) {
    61. Query query = new Query(Criteria.where("collName").is(collName));
    62. Update update = new Update();
    63. update.inc("seqId", 1);
    64. FindAndModifyOptions options = new FindAndModifyOptions();
    65. options.upsert(true);
    66. options.returnNew(true);
    67. SeqInfo seq = mongo.findAndModify(query, update, options, SeqInfo.class);
    68. return seq.getSeqId();
    69. }
    70. }

    2.5 单元测试

    1. @Test
    2. public void save() {
    3. Student stu = new Student();
    4. stu.setName("张三");
    5. service.save(stu);
    6. // service.update(stu);
    7. System.out.println("已生成ID:" + stu.getId());
    8. }

    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.”,大概意思是说当查询和更新两个操作都存在时,如果查询的字段没有唯一索引的话,该命令可能会在某些情况下更新/插入 文档多次,参考链接:戳我戳我。以上演示的是只存储了集合所对应的实体类的短名称,短名称是会重复的,所以这种方法不妥,还是记录长名称吧。

    好了,文章就到这,就剩我几几了,都下班了,我也要“粽”情端午去咯~

    参考文章:http://www.jianshu.com/p/3418e32ce757

    =========================

    工作机会(内部推荐):发送邮件至gaoyabing@126.com,看到会帮转内部HR。

    邮件标题:X姓名X_X公司X_简历(如:张三_东方财富_简历),否则一律垃圾邮件!

    公司信息:

    1. 1.东方财富|上海徐汇、南京|微信客户端查看职位(可自助提交信息,微信打开);
  • 相关阅读:
    个人作业Week2-代码复审
    个人项目-数独
    个人作业-Week1
    第零次个人作业
    mysql统计某个指标之和大于指定值的记录
    Redis命令行查询缓存值
    Grep命令
    Log4j配置文件中指定日志路径
    Cadence OrCAD17.2禁止和启用start page的设置方法
    Linux内核实现透视---kthread_work
  • 原文地址:https://www.cnblogs.com/Chary/p/14921419.html
Copyright © 2011-2022 走看看