数据库

SpringBoot整合mongodb

一. 概述


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

二. 安装mongodb


三. 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

@Document(collection = "UserInfo")
@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的生成规则

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;
    }