preface

As a database based on distributed file storage, MongoDB is widely used in the field of microservices. This article will learn how Spring Boot programs perform MongoDB operations and source code analysis of the underlying implementation to better understand the behavior of Spring programs operating MongoDB databases. Here are two takeaways from source code analysis, and let’s take a look at how we found them.

  • The Spring framework uses the MongoDBTemplate underlying to manipulate MongoDB data, in practice using JDK dynamic proxies andAOPThe interceptor mode is invoked layer by layer.
  • Custom query methods in your own DAO objects are required to conformspring-boot-data-mongodbFramework method naming rules, to achieve fully automatic processing effect.

The body of the

The MongoDB server version used in this article is 4.0.0

The MongoDB server installation can be seen in my other blog post, the Back-end Architecture Series MonogDB

Download the sample project

Start by downloading the sample project from the SPRING INITIALIZR website, SPRING Boot version 1.5.17, and relying on only one MongoDB.

Use IDE to import project and open POM file, you can see MongoDB dependency corresponding Maven coordinates and corresponding third-party library is spring-boot-starter-data-mongodb

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

We can then import the coordinates of the library into the main POM file when we use MongoDB in Spring Boot projects.

Spring-boot-starter-data-mongodb is a subproject of spring-data, which provides rich operations and simplification for mongodb access.

Configuring the MongoDB Connection

To operate the MongoDB database, you must first connect the application to the MongoDB server. Due to Spring Boot’s powerful simplified configuration features, you want to connect to the MongoDB server. Simply add a new line of configuration to the application.properties file in the resources folder.

spring.data.mongodb.uri=mongodb://localhost:27017/test
Copy the code

If a user-authenticated MongoDB server is connected, the URI format is MongoDB ://name:password@ip:port/dbName

Write the code

Configuration, after the next Post, we first create a entity attributes: id, title, content, createTime

public class Post {
    @Id
    private Long id;
    private String title;
    private String content;
    private Date createTime;

    public Post(a) {}public Post(Long id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createTime = new Date();
    }
    // omit setter,getter methods
}
Copy the code

The annotation @ID here indicates that the entity attribute corresponds to the primary key of the database record.

We then provide PostRepository, a storage object that accesses the Post data, inheriting the MongoRepository interface provided by the government

public interface PostRepository extends MongoRepository<Post.Long> {
    void findByTitle(String title);
}
Copy the code

This completes the CRUD code for the Post entity. What !!! We haven’t even written any code yet? Let’s write a test case right now.

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootMongodbApplicationTests {
	@Autowired
	private PostRepository postRepository;

	@Test
	public void testInsert(a) {
		Post post = new Post(1L."sayhi"."hi,mongodb");
		postRepository.insert(post);
		List<Post> all = postRepository.findAll();
		System.out.println(all); 
        // [Post{id=1, title='sayhi', content='hi,mongodb',
        //createTime=Sat Oct 20 20:55:15 CST 2018}]
		Assert.assertEquals(all.size(),1); // true}}Copy the code

Run the test case and the results are as follows: The Post data is successfully stored in the MongoDB database and can be queried.

We can also find this record in the MongoDB server:

We can see that there is an extra _ class field value in the record, which is actually set by MongoRepository automatically to represent the entity type corresponding to this record, but when is the underlying operation, we look forward to the answer in our subsequent analysis.

After the addition, we try to update the operation, here we also use the inherited save method, in addition to the interface method we wrote findByTitle to query the Post entity according to the title field.

@Test
public void testUpdate(a) {
    Post post = new Post();
    post.setId(1L);
    post.setTitle("sayHi");
    post.setContent("hi,springboot");
    post.setCreateTime(new Date());
    postRepository.save(post); // Update the POST object
    Post updatedPost = postRepository.findByTitle("sayHi"); // Query by title
    Assert.assertEquals(updatedPost.getId(),post.getId());
    Assert.assertEquals(updatedPost.getTitle(),post.getTitle());
    Assert.assertEquals(updatedPost.getContent(),"hi,springboot");
}
Copy the code

Running the test case also passed. But there is also a question: the method provided by themselves, without writing how to achieve, how can the program according to what we want: according to the value of the title field to query to match the record? This is also in the following actual combat analysis to see a clear.

We have already tried adding, modifying, and querying data. Deleting data is also very simple, just call the delete method of postRepository. Now we will mainly explore how to add, delete, modify, and query data through only MongoRepository inheritance of postRepository.

Practical analysis

The execution bottom layer of postRepository

Now that we’ve implemented the basic data manipulation, how does it all work? First we debug the breakpoint on postRepository#save in the test case testUpdate and observe the execution path of the program. Inside the single-step save method, the code is executed under type JdkDynamicAopProxy. At this point, the code invocation chain is shown in the figure below

This is obviously the JDK dynamic proxy that uses Spring, and the proxy object inside the Invoke method is very noticeable, The save method of the proxy that is actually called when the method is executed, and this proxy is org.springframework.data.mongodb.repository.support.SimpleMongoRepository@8deb645

Is an instance of the SimpleMongoRepository class. So the final call will go to the SimpleMongoRepository# save method, where we’ll do the breakpoint again and continue running.

From here, you can see that there are two operations inside the save method: insert if the incoming entity is a new record, and save updates otherwise. Clearly the latter is now required.

And to complete the operation is closely related to the two objects entityInformation and mongoOperations, what they do and when they are initialized.

MongoOperations is actually a MongoTemplate object using IDEA debugging tool. Similar to JDBCTemplate, mongoOperations can add, delete, change and check MongoDB data. Spring uses a similar naming scheme and API. So the MongoTemplate object is really the base of the MongoDB database.

As for MappingMongoEntityInformation entityInformation objects belonging to the class, stores the Mongo data entity information, such as the collection name, primary key type, some mapping of entity metadata, etc.

In the SimpleMongoRepository class, you can find that they are both initialized in the constructor

public SimpleMongoRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
	Assert.notNull(metadata, "MongoEntityInformation must not be null!");
	Assert.notNull(mongoOperations, "MongoOperations must not be null!");
    
	this.entityInformation = metadata;
	this.mongoOperations = mongoOperations;
}
Copy the code

In the same way, breakpoints are made in the SimpleMongoRepository constructor to re-observe the call chain when initializing the SimpleMongoRepository object. It turns out that the entire link is as follows, from running the test case to the long execution link here, where only the classes and methods we need to focus on are identified.

The SimpleMongoRepository class creation and initialization is done by MongoRepositoryFactory,

public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) { RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); Class<? > customImplementationClass =null == customImplementation ? null : customImplementation.getClass();
		RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);

		validate(information, customImplementation);

		Object target = getTargetRepository(information); // Get the SimpleMongoRepository object after initialization.

		// Create proxy
		ProxyFactory result = new ProxyFactory();
		result.setTarget(target);
		result.setInterfaces(new Class[] { repositoryInterface, Repository.class }); 
    	// AOP proxies the repositoryInterface class

		result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
		result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
    

		return (T) result.getProxy(classLoader);
}
Copy the code

Below is the class diagram MongoRepositoryFactory, and MongoRepositoryFactory in MongoRepositoryFactoryBean class structure.

At the bottom of the call chain, let’s look at where everything is coming from, Find the org. Springframework. Beans. Factory. Support. AbstractAutowireCapableBeanFactory# createBean method, internal create instances of the Bean For postRepository doCreateBean call parameters and MongoRepositoryFactoryBean instance, namely to create instances of postRepository when completed.

And create postRepository corresponding entity object for actual MongoRepositoryFactoryBean this factory Bean

When you need to use postRepository objects, the actual object is to use the factory method MongoRepositoryFactoryBean# getObject returns SimpleMongoRepository object, see AbstractBeanFactory class doGetBean method, code call chain when name is postRepository.

The postRepository interface findByTitle is used to query the MongoDB database, and the postRepository interface findByTitle is used to query the database.

FindByTitle search implementation

Breakpoint to where the findByTitle method is executed and debug in as before in the JdkDynamicAopProxy class while fetching the call chain

The interceptor, the proxy objects have a interceptor class in org. Springframework. Data. The repository. The core. Support. RepositoryFactorySupport. QueryExecutorMethodInter Ceptor caught my eye. The name is an interceptor that deals specifically with query methods. I tried to do a breakpoint on the intercepted Invoke method, and sure enough findByTitle was executed there.

It then determines whether the method is a query method in the interceptor method, if so it calls the AbstractMongoQuery#execute method inherited from the PartTreeMongoQuery object with arguments.

// AbstractMongoQuery
public Object execute(Object[] parameters) {
    MongoParameterAccessor accessor = new MongoParametersParameterAccessor(method, parameters);
    Query: {"title" : "sayHi"}, Fields: null, Sort: null
    Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), accessor));

    applyQueryMetaAttributesWhenPresent(query);
    ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
    String collection = method.getEntityInformation().getCollectionName();
    // Build the query execution object
    MongoQueryExecution execution = getExecution(query, accessor,new ResultProcessingConverter(processor, operations, instantiators));
    
    return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
}
Copy the code

The mongoQueryExecute #execute method goes through layers of calls to actually execute and the following code:

// AbstractMongoQuery#execute =>
	// MongoQueryExecution.ResultProcessingExecution#execute =>
		// MongoQueryExecution.SingleEntityExecution#execute
@Override
public Object execute(Query query, Class
        type, String collection) {
	return operations.findOne(query, type, collection);
}
Copy the code

The Operations here is the MongoDBTemplate instance we mentioned earlier. So when executing the findByTitile custom query, the underlying call is still MongoDBTemplate#findOne.

Query (sayHi, title); Query (sayHi, title, title);

The createQuery method is a template method that actually executes on the PartTreeMongoQuery class.

@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
	MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
	Query query = creator.createQuery();
	/ /...
	return query
}
Copy the code

Here we have a tree property when we build MongoQueryCreator, which is the relationship to build conditional queries.

The tree object is initialized in the PartTreeMongoQuery constructor, and PartTree is constructed based on the method name.

//PartTree.java
public PartTree(String source, Class
        domainClass) {

    Assert.notNull(source, "Source must not be null");
    Assert.notNull(domainClass, "Domain class must not be null");

    Matcher matcher = PREFIX_TEMPLATE.matcher(source);
    if(! matcher.find()) {this.subject = new Subject(null);
        this.predicate = new Predicate(source, domainClass);
    } else {
        this.subject = new Subject(matcher.group(0));
        // The key to construct the query field
        this.predicate = newPredicate(source.substring(matcher.group().length()), domainClass); }}Copy the code

Code can be seen from the above that match the method name, in a regular way in which PREFIX_TEMPLATE said the ^ (find | read | get | query | stream | count | exists | delete | remove) ((\ p {Lu}. *?) )?? By, if a match is found, the word immediately following By will be extracted, and the attributes of the corresponding class will be matched internally according to the name. After the construction is found, it will be stored in an ArrayList collection, which will be used in the subsequent query.

Therefore, we can also see that our custom method findByTitle conforms to the default regex requirements of the framework, so it can automatically extract the title field of Post as the query field. You can also use queryBy,getBy, etc. The Spring Framework convention is that if you define method names arbitrarily, the Framework will not be able to identify query fields directly.

Ok, here, we once again summarize the source code analysis results:

  • definepostRepositoryimplementationMongoRepositoryInterface to manipulate MongoDB data using the MongoDBTemplate underlying, which is used via JDK dynamic proxies andAOPThe interceptor mode is invoked layer by layer.
  • inpostRepositoryThe custom query method is to conform tospring-boot-data-mongodbFramework method naming rules, to achieve fully automatic processing effect.

conclusion

Here, our Spring Boot and MongoDB actual analysis is over, look at the internal source code, although the structure is clear, but because of the complex call relationship between modules, it is often easy to get lost in the source code, at this time patience and clear goal is crucial. This is also the harvest of this source code analysis, I hope this article can have more harvest, we will see you in the next articles. 😁😁😁

reference

  • Blog.didispace.com/springbootm…