SpringBoot整合mongodb

一. 概述

参考开源项目https://github.com/xkcoding/spring-boot-demo
此Demo简单集成mongodb,使用官方的 starter 实现增删改查。

二. 安装mongodb

  1. 下载镜像:docker pull mongo
  2. 运行容器:docker run -d -p 27017:27017 --name mongo mongo:latest

三. SpringBoot工程

3.1 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

3.2 application.yml

spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: monge_demo
logging:
  level:
    org.springframework.data.mongodb.core: debug

3.3 启动类

@SpringBootApplication
public class SpringBootDemoMongodbApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoMongodbApplication.class, args);
    }
}

3.4 实体类: Article.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Article {
    /**
     * 文章id
     */
    @Id
    private Long id;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 文章标题
     */
    private String title;

    /**
     * 文章内容
     */
    private String content;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 点赞数量
     */
    private Long thumbUp;

    /**
     * 访客数量
     */
    private Long visits;

}

3.5 持久层: ArticleRepository.java

public interface ArticleRepository extends MongoRepository<Article, Long> {
    /**
     * 根据标题模糊查询
     *
     * @param title 标题
     * @return 满足条件的文章列表
     */
    List<Article> findByTitleLike(String title);
}

注: 可以通过关键字声明方法, 不用写实现
下标源于文章MongoRepository的生成规则

方法关键字 示例 等价于SQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1(参数绑定附加%)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1(参数与预先绑定%)
Containing findByFirstnameContaining … where x.firstname like ?1(参数绑定%)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

3.6 简单的CURD

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ArticleRepositoryTest {
    @Autowired
    private ArticleRepository articleRepo;

    @Autowired
    private MongoTemplate mongoTemplate;

    private static Snowflake snowflake = IdUtil.createSnowflake(1, 1);


    /**
     * 测试新增
     */
    @Test
    public void testSave() {
        Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil.date(), 0L, 0L);
        articleRepo.save(article);
        // 更具ID属性进行新增或更新
        mongoTemplate.save(article);
        log.info("【article】= {}", JSONUtil.toJsonStr(article));
    }

    /**
     * 测试批量新增列表
     */
    @Test
    public void testSaveList() {
        List<Article> articles = Lists.newArrayList();
        for (int i = 0; i < 10; i++) {
            articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), DateUtil.date(), 0L, 0L));
        }
        articleRepo.saveAll(articles);

        log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream().map(Article::getId).collect(Collectors.toList())));
    }

    /**
     * 测试更新
     */
    @Test
    public void testUpdate() {
        articleRepo.findById(1L).ifPresent(article -> {
            article.setTitle(article.getTitle() + "更新之后的标题");
            article.setUpdateTime(DateUtil.date());
            articleRepo.save(article);
            log.info("【article】= {}", JSONUtil.toJsonStr(article));
        });
    }

    /**
     * 测试删除
     */
    @Test
    public void testDelete() {
        // 根据主键删除
        articleRepo.deleteById(1L);

        // 全部删除
        articleRepo.deleteAll();
    }

    /**
     * 测试分页排序查询
     */
    @Test
    public void testQuery() {
        Sort sort = Sort.by("thumbUp", "updateTime").descending();
        PageRequest pageRequest = PageRequest.of(0, 5, sort);
        Page<Article> all = articleRepo.findAll(pageRequest);
        log.info("【总页数】= {}", all.getTotalPages());
        log.info("【总条数】= {}", all.getTotalElements());
        log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent().stream().map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp() + "更新时间:" + article.getUpdateTime()).collect(Collectors.toList())));
    }
}

3.7 更新

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ArticleRepositoryTest {
    @Autowired
    private ArticleRepository articleRepo;

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 测试点赞数、访客数,使用save方式更新点赞、访客
     */
    @Test
    public void testThumbUp() {
        articleRepo.findById(1L).ifPresent(article -> {
            article.setThumbUp(article.getThumbUp() + 1);
            article.setVisits(article.getVisits() + 1);
            articleRepo.save(article);
            log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits());
        });
    }

    /**
     * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客
     */
    @Test
    public void testThumbUp2() {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(1L));
        Update update = new Update();
        update.inc("thumbUp", 1L);
        update.inc("visits", 1L);
        mongoTemplate.updateFirst(query, update, "article");

        articleRepo.findById(1L).ifPresent(article -> log.info("【标题】= {}【点赞数】= {}【访客数】= {}", article.getTitle(), article.getThumbUp(), article.getVisits()));
    }
}

3.8 高级查询

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ArticleRepositoryTest {
    @Autowired
    private ArticleRepository articleRepo;

    @Autowired
    private MongoTemplate mongoTemplate;
    /**
     * 查询,条件匹配/排序/分页, 基于继承MongoRepository实现
     */
    @Test
    public void testQuery1() {
        /**
         * 匹配条件构造
         */
        Article article = Article.builder()
            .title("ayyg6qetc2jigduentiz")
            .content("tx1549k4dbu05ou83tx8te0gx1")
            .build();
        // 指定字段匹配类型
        ExampleMatcher withMatcher = ExampleMatcher.matching()
            // 忽略大小写
            .withIgnoreCase()
            // 指定"title"为精确匹配
            .withMatcher("title", ExampleMatcher.GenericPropertyMatcher::exact)
            // 指定"content"为模糊匹配
            .withMatcher("content", ExampleMatcher.GenericPropertyMatcher::contains);
        Example<Article> example = Example.of(article,withMatcher);

        /**
         * 排序规则
         */
        Sort sort = Sort.by("updateTime").descending();

        /**
         * 分页
         */
        PageRequest pageRequest = PageRequest.of(0, 5, sort);

        /**
         * 分页查询
         */
        Page<Article> articleRepoAll = articleRepo.findAll(example, pageRequest);

        /**
         * 打印
         */
        log.info(JSONUtil.toJsonStr(articleRepoAll.getContent()));
    }

    /**
     * 查询,条件匹配/排序/分页, 基于MongoTemplate实现
     */
    @Test
    public void testQuery2() {
        /**
         * 查询条件
         */
        Criteria criteria = Criteria
            // 精确匹配
            .where("title").is("ayyg6qetc2jigduentiz")
            // 模糊匹配, 用正则: .*[xxx].*
            .and("content").regex(".*tx1549k4dbu05ou83tx8te0gx1.*")
            // 匹配明细里的字段
            .and("ids").elemMatch(Criteria.where("id").is(1))
            // 匹配多个并行或
            .andOperator(
                        new Criteria().orOperator(
                                Criteria.where("visits").exists(false),
                                Criteria.where("visits").is(1)
                        ),
                        new Criteria().orOperator(
                                Criteria.where("thumbUp").exists(false),
                                Criteria.where("thumbUp").is(1)
                        )

                );
            ;

        /**
         * 排序规则
         */
        Sort sort = Sort.by("updateTime").descending();


        /**
         * 分页
         */
        PageRequest pageRequest = PageRequest.of(1, 5, sort);

        Query query = Query.query(criteria).with(sort).with(pageRequest);

        List<Article> articles = mongoTemplate.find(query, Article.class);
        PageImpl<Article> page = (PageImpl<Article>) PageableExecutionUtils.getPage(articles, pageRequest, () -> mongoTemplate.count(Query.query(criteria),Article.class));
        
        // 打印
        Optional.of(page.getContent()).ifPresent(articles1 -> {
            articles1.forEach(article -> {
                log.info("打印数据:{}",JSONUtil.toJsonStr(article));
            });
        });
    }
}

3.9 MongoTemplate 实现 联表/分页/排序查询

    public IPage<Article> pageInfo(){

        /**
         * 联表查询
         * 参数1: 从表表名
         * 参数2: 主表关联字段
         * 参数3: 从表关联字段
         * 参数4: 查出从表数据集合的别名 例如主表数据{"name":"wpr","age":18} , 关联从表后结果{"name":"wpr","age":18,"userInfo":[]}, 从表没数据则为[]
         */
        LookupOperation lookup = Aggregation.lookup("user", "userId", "userId", "userInfo");

        // 子集合不能为空
        Criteria criteria = Criteria.where("userInfo").not().size(0);
        // 子集合条件
        criteria.and("userInfo.six").is(1);
        // 主表条件
        criteria.and("title").is("hello_world");
        // 条件类型转换
        MatchOperation matchOperation = Aggregation.match(criteria);

        /**
         * 查询总数
         */
        CountOperation countOperation = Aggregation.count().as("total");
        // project: 表示结果只查询字段:total
        ProjectionOperation project = Aggregation.project("total");
        // 条件一定要排好先后顺序
        Aggregation aggregation = Aggregation.newAggregation(lookup, matchOperation, countOperation, project);
        AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation, "article", Map.class);
        List<Map> aggregateMappedResults = aggregate.getMappedResults();
        // 总数
        Integer total = CollectionUtils.isEmpty(aggregateMappedResults) ? 0 : (int)aggregateMappedResults.get(0).get("total");

        if(Objects.equals(total,0)){
            return new Page<>();
        }

        /**
         * 分页查询
         */
        // 排序条件
        SortOperation sortOperation = Aggregation.sort(Sort.by("updateTime").descending());
        // 过滤前n条数据
        SkipOperation skipOperation = Aggregation.skip(0L);
        // 查询n条数据
        LimitOperation limitOperation = Aggregation.limit(10);
        Aggregation pageAggregation = Aggregation.newAggregation(lookup, matchOperation, sortOperation,skipOperation,limitOperation);
        AggregationResults<Article> result = mongoTemplate.aggregate(pageAggregation,"article", Article.class);
        List<Article> articles = result.getMappedResults();
        Page<Article> page = new Page<>();
        page.setTotal(total);
        page.setRecords(articles);
        return page;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容