概述
MongoDB 描述等可见其他文章, 本文章主要基于 springboot 项目演示
版本信息:
Java JDK 版本:1.8
SpringBoot 版本:2.4.5.RELEASE
MongoDB 版本:community-4.4
参考地址:
示例项目地址:
项目实战
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<Integer> testList;
}
PostRepository
继承 MongoRepository
/**
* Post Repository
*/
@Repository
public interface PostRepository extends MongoRepository<Post, String> {
}
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<Post> 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<Post> 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<Post> postExample= Example.of(queryCondition);
Optional<Post> 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<Post> queryPost = postRepository.findById(String.valueOf(insertPost.getId()));
Assert.isTrue(!queryPost.isPresent(),"PostRepository delete -> error");
}
}
以上就简单的把使用 Repository 实现的例子演示了下。代码待整个例子实现一起上传~
MongoTemplate
这里使用 Spring Data MongoDB 封装的 MongoDB 官方 Java 驱动 MongoTemplate 对 MongoDB 进行操作。
下偏文章将继续~
备注:个人博客同步至简书。