Springboot MongoDB CRUD(一)

概述

MongoDB 描述等可见其他文章, 本文章主要基于 springboot 项目演示

  • MongoDB 安装[Mac brew & Docker 方式]
  • MongoDB 基础概述
  • MongoDB Shell CRUD

版本信息:

  • Java JDK 版本:1.8

  • SpringBoot 版本:2.4.5.RELEASE

  • MongoDB 版本:community-4.4

参考地址:

  • MongoDB 官方网址
  • Spring Data MongoDB 官方文档 3.0.9.RELEASE

示例项目地址:

  • SpringBoot 集成 MongoDB 示例项目 Github 地址

项目实战

SpringBoot 操作主要有两种方式,一种基于 JPA(Repositories)、一种基于MongoDB 官方 Java 驱动 MongoTemplate,直接用代码说话吧。

项目构建等忽略, gradle 构建,配置文件:

plugins {
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.xjxxxc.mongodb.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenLocal()
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    implementation 'com.google.guava:guava:30.1.1-jre'
    implementation 'org.apache.commons:commons-lang3'
    implementation 'com.alibaba:fastjson:1.2.76'

    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'

    testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation 'org.projectlombok:lombok'
}

test {
    useJUnitPlatform()
}

包说明:

├── com
│   └── xjxxxc
│       └── mongodb
│           └── demo
│               ├── MongodbDemoApplication.java # 启动类
│               ├── config # 配置类,如数据源
│               ├── listener # 监听器,如 MongoEvent
│               ├── model # 模型
│               │   └── entity # 实体,JPA 使用
│               ├── repository # JPA repository
│               ├── service # *服务层,提供 MongoDB 特性。
│               └── template # MongoDB 模板类使用 DEMO

项目基本配置

application.properties

server.port=8866
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=test
spring.data.mongodb.username=test
spring.data.mongodb.password=123456

根据情况修改即可,为了演示方便直接写在 application.properties

MongoDBDataSourceConfig

继承 AbstractMongoClientConfiguration 类,重写 mongoClient() 、getDatabaseName() 、mappingMongoConverter() 方法,并且去除 _class 字段。

@Configuration
@EnableMongoRepositories(basePackages = {"com.xjxxxc.mongodb.demo.*"})
public class MongoDBDataSourceConfig extends AbstractMongoClientConfiguration {
    @Value(value = "${spring.data.mongodb.host:127.0.0.1}")
    private String host;

    @Value(value = "${spring.data.mongodb.port:27017}")
    private Integer port;

    @Value(value = "${spring.data.mongodb.database:test}")
    private String database;

    @Value(value = "${spring.data.mongodb.username:test}")
    private String username;

    @Value(value = "${spring.data.mongodb.password:123456}")
    private String password;

    @Override
    public MongoClient mongoClient() {
        MongoClient mongoClient = MongoClients.create(
                MongoClientSettings.builder()
                        // 集群设置
                        .applyToClusterSettings(builder ->
                                builder.hosts(Arrays.asList(new ServerAddress(host, port))))
                        // 凭据
                        .credential(
                                MongoCredential
                                        .createCredential(username, database, password.toCharArray()))
                        .build());
        return mongoClient;
    }


    /**
     * Return the name of the database to connect to.
     *
     * @return must not be {@literal null}.
     */
    @Override
    protected String getDatabaseName() {
        return database;
    }

    /**
     * 去除 _class
     * @param databaseFactory
     * @param customConversions
     * @param mappingContext
     * @return MappingMongoConverter
     */
    @Override
    public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory databaseFactory, MongoCustomConversions customConversions, MongoMappingContext mappingContext) {

        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
        converter.setCustomConversions(customConversions());
        converter.setCodecRegistryProvider(mongoDbFactory());
        // Don't save _class to mongo
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));

        return converter;
    }


}

Repositories

关于使用简单的 Repositories 方式来操作 MongoDB 这种用法只能实现较简单的操作[可使用Example封装复杂的查询],使用简单但是灵活性比较差。

@CustomerId [自定义 ID 生成策略]

MongoDB 默认会为 _id 生成 ObjectId 对象,但业务使用时可能有自己的生成策略[需配合监听器实现]。

/**
 * 自定义 ID
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomerId {
}

SaveEventListener

继承 AbstractMongoEventListener 重写相应的方法,具体可见文档。
备注:onBeforeConvert、onBeforeSave 均可实现功能,具体可见生命周期。


/**
 * MongoDB 监听器
 */
@Slf4j
@Component
public class SaveEventListener extends AbstractMongoEventListener {

    /**
     * 在将Document插入或保存在数据库中之前,在 insert,insertList和save操作中调用。
     * @param event
     */
    @Override
    public void onBeforeSave(BeforeSaveEvent event) {
        super.onBeforeSave(event);
        Object source = event.getSource();
        ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                ReflectionUtils.makeAccessible(field);
                if (!field.isAnnotationPresent(CustomerId.class)) {
                    return;
                }
                // 如果字段添加了我们自定义的 @CustomerId 注解
                Object object = field.get(source);
                // 需根据是否有值进行主键生成,save 操作时存在 update 与 insert ,会导致更新时变成 insert 。
                if(Objects.nonNull(object) && !StringUtils.isNotEmpty(object.toString())){
                    try {
                        // 剔除 Id >= OL 的情况 不注入
                        Long tmp = Long.parseLong(object.toString());
                        if(tmp.longValue() > 0L){
                            return;
                        }
                    }catch (Exception e){
                        // 非法 ID
                        log.info("自动注入 ID 有异常,{}", e.getMessage());
                    }
                }
                // 具体业务使用其他方式
                field.set(source, RandomUtils.nextLong());
            }
        });

        if(log.isDebugEnabled()){
            log.debug("onBeforeSave -> {}", event);
        }
    }

    /**
     * 在 object 被MongoConverter转换为Document之前,在MongoTemplate insert,insertList和save操作中调用。
     * @param event
     */
    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                ReflectionUtils.makeAccessible(field);
                if (!field.isAnnotationPresent(CustomerId.class)) {
                    return;
                }
                // 如果字段添加了我们自定义的 @CustomerId 注解
                Object object = field.get(source);
                // 需根据是否有值进行主键生成,save 操作时存在 update 与 insert ,会导致更新时变成 insert 。
                if(Objects.nonNull(object) && !StringUtils.isNotEmpty(object.toString())){
                    try {
                        // 剔除 Id >= OL 的情况 不注入
                        Long tmp = Long.parseLong(object.toString());
                        if(tmp.longValue() > 0L){
                            return;
                        }
                    }catch (Exception e){
                        // 非法 ID
                        log.info("自动注入 ID 有异常,{}", e.getMessage());
                    }
                }
                // 具体业务使用其他方式
                field.set(source, RandomUtils.nextLong());
            }
        });
        if(log.isDebugEnabled()){
            log.debug("onBeforeConvert -> {}", event);
        }
    }
}

BaseEntity

实际项目中,都会存在部分通用字段,需抽出父类,故示例也尽量结合项目。

/**
 * 基础 Entity ,抽出公用字段
 */
@Getter
@Setter
@SuperBuilder
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity implements Serializable {

    /**
     * 使用自定义 ID
     */
    @Id
    @CustomerId
    private Long id;
    
    @Field("saas_id")
    @Indexed
    protected Long saasId;

    @Field("del_status")
    protected Integer delStatus;

    @CreatedBy
    @Field("create_user")
    protected String createUser;

    @LastModifiedBy
    @Field("update_user")
    protected String updateUser;

    @CreatedDate
    @Field("create_time")
    protected Date createTime;

    @LastModifiedDate
    @Field("update_time")
    protected Date updateTime;

    /**
     * 创建基础字段默认值
     */
    public void initBaseProperties() {
        if (this.delStatus == null) {
            this.delStatus = 1;
        }
        if (createUser == null) {
            this.createUser = "SYSTEM";
        }
        if (updateUser == null) {
            this.updateUser = "SYSTEM";
        }
        if (createTime == null) {
            this.createTime = new Date(System.currentTimeMillis());
        }
        if (updateTime == null) {
            this.updateTime = new Date(System.currentTimeMillis());
        }
    }
}

Post

实现业务对象 Post, 继承 BaseEntity

/**
 * Post entity
 */
@Data
@SuperBuilder
@Document(collection = "post")
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper=false)
public class Post extends BaseEntity {
    private String title;

    private String content;

    private String remark;

    private List testList;

}

PostRepository

继承 MongoRepository

/**
 * Post Repository
 */
@Repository
public interface PostRepository extends MongoRepository {
}

PostRepositoryTest

测试 PostRepository 单元测试。

/**
 * PostRepository 测试
 */
@SpringBootTest
class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    /**
     * 测试 Post 实例
     */
    Post post = Post.builder()
            .content("conten of " + Math.random() * 10000)
            .title("conten of " + Math.random() * 10000)
            .remark("")
            .build();

    /**
     * 测试 Repository insert
     */
    void insert(){
        Post insertPost = postRepository.insert(post);
        Assert.notNull(insertPost,"PostRepository insert -> error");
    }


    /**
     * 测试 Repository save
     */
    void save(){
        // save 方法会根据主键来确定是 insert or update
        Post insertPost = postRepository.save(post);
        Assert.notNull(insertPost,"PostRepository save -> insert error");

        insertPost.setRemark("update");
        Post updatePost = postRepository.save(insertPost);
        Assert.isTrue("update".equals(updatePost.getRemark()),"PostRepository save -> update error");

    }

    /**
     * 测试 Repository findAll
     */
    void findAll(){
        List postList = postRepository.findAll();

        Assert.notEmpty(postList,"PostRepository findAll -> error");
    }

    /**
     * 测试 Repository findAll
     */
    void findById(){
        Post insertPost = postRepository.save(post);
        Assert.notNull(insertPost,"PostRepository findAll -> insert error");

        Optional queryPost = postRepository.findById(String.valueOf(insertPost.getId()));
        Assert.isTrue(queryPost.isPresent(),"PostRepository findById -> error");
    }

    /**
     * 测试 Repository findAll
     */
    void findOne(){
        post.setRemark("findOne");
        Post insertPost = postRepository.save(post);
        Assert.notNull(insertPost,"PostRepository findAll -> insert error");

        Post queryCondition = Post.builder().remark("findOne").build();
        Example postExample= Example.of(queryCondition);
        Optional queryPost = postRepository.findOne(postExample);
        Assert.isTrue(queryPost.isPresent(),"PostRepository findOne -> error");
    }

    /**
     * 测试 Repository delete
     */
    void delete(){
        // 先插入数据
        post.setRemark("delete");
        Post insertPost = postRepository.save(post);

        postRepository.delete(insertPost);

        Optional queryPost = postRepository.findById(String.valueOf(insertPost.getId()));
        Assert.isTrue(!queryPost.isPresent(),"PostRepository delete -> error");
    }

}

以上就简单的把使用 Repository 实现的例子演示了下。代码待整个例子实现一起上传~

MongoTemplate

这里使用 Spring Data MongoDB 封装的 MongoDB 官方 Java 驱动 MongoTemplate 对 MongoDB 进行操作。

下偏文章将继续~

备注:个人博客同步至。

你可能感兴趣的:(Springboot MongoDB CRUD(一))