Боевой анализ SpringBoot — работа MongoDB

задняя часть база данных сервер MongoDB

предисловие

MongoDBКак база данных на основе распределенного хранилища файлов, она широко используется в сфере микросервисов.В этой статье вы узнаетеSpring BootКак выполняется программаMongoDBАнализ исходного кода операций и базовых реализаций, чтобы помочь нам лучше понять операции программы Spring.MongoDBПоведение базы данных Следующие два пункта являются результатом анализа исходного кода, давайте посмотрим, как они были обнаружены.

  • MongoDBTemplate, используемый на нижнем уровне среды Spring для работы с данными MongoDB, и фактическое использование динамического прокси-сервера JDK иAOPМетод перехватчика называется послойным.
  • Пользовательский метод запроса в вашем собственном объекте DAO должен соответствоватьspring-boot-data-mongodbПравила именования методов фреймворка позволяют добиться эффекта полностью автоматической обработки.

текст

В этой статье используетсяMongoDBВерсия сервера 4.0.0

MongoDBПо установке сервера можете обратиться к другому моему блогу:Серия построения серверной архитектуры MongoDB

Скачать пример проекта

первый вSPRING INITIALIZRЗагрузите образец проекта с веб-сайта, версия Spring Boot — 1.5.17, и он зависит только от одной MongoDB.

image-20181020201332247

После импорта проекта с помощью IDE и открытия файла POM вы можете видеть, что MongoDB зависит от соответствующих координат Maven, а соответствующая сторонняя библиотекаspring-boot-starter-data-mongodb

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

Тогда мы будемSpring BootИспользование проектаMongoDBна главнойPOMМожно импортировать координаты этой библиотеки в файл.

а такжеspring-boot-starter-data-mongodbдаspring-dataподпроект, его роль заключается в том, чтобы нацелитьMongoDBДоступ обеспечивает богатые операции и упрощения.

Настройте соединение с MongoDB.

работатьMongoDBбазу данных, сначала позвольте программе подключиться кMongoDBсервер, потому чтоSpring BootМощные упрощенные функции конфигурации, хотите подключитьсяMongoDBсервер, нам просто нужно положить файл в папку ресурсовapplication.propertiesПросто добавьте новую строку конфигурации в файл.

spring.data.mongodb.uri=mongodb://localhost:27017/test

При подключении к серверу MongoDB с аутентификацией пользователя форма urimongodb://name:password@ip:port/dbName

Написать код

После настройки давайте сначала создадим объектPost, содержит свойства:id,title,content,createTime

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

    public Post() {
    }

    public Post(Long id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createTime = new Date();
    }
    // 省略 setter,getter 方法
}

Аннотация здесь@IdУказывает, что атрибут сущности соответствует первичному ключу записи базы данных.

Затем предоставьте объект хранилища для доступа к данным в PostPostRepository, доставшийся от официальногоMongoRepositoryинтерфейс

public interface PostRepository extends MongoRepository<Post,Long> {
    void findByTitle(String title);
}

Прямо здесьPostСущностьCRUDКод операции выполнен. Что !!! Мы закончили без написания кода? Давайте напишем тестовый пример, чтобы увидеть это сейчас.

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

	@Test
	public void testInsert() {
		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
	}
}

Запустите тестовый пример, результаты следующие:PostДанные успешно сохранены вMongoDBв базе данных и могут быть запрошены.

image-20181020205843907

мы также можемMongoDBСервер нашел эту запись:

image-20181020210805972

Еще из записи_ classЗначение поля фактически определяетсяMongoRepositoryДля нас автоматически установлено указание типа объекта, соответствующего этой записи, но когда работает нижний слой, мы с нетерпением ожидаем раскрытия ответа в нашем последующем анализе.

После добавления снова попробуем операцию обновления, которая здесь тоже используется.saveметод, кроме того, мы также используем метод интерфейса, написанный намиfindByTitleприйти в соответствии сtitleПоле для поиска объекта Post.

@Test
public void testUpdate() {
    Post post = new Post();
    post.setId(1L);
    post.setTitle("sayHi");
    post.setContent("hi,springboot");
    post.setCreateTime(new Date());
    postRepository.save(post); // 更新 post 对象
    Post updatedPost = postRepository.findByTitle("sayHi"); // 根据 title 查询
    Assert.assertEquals(updatedPost.getId(),post.getId());
    Assert.assertEquals(updatedPost.getTitle(),post.getTitle());
    Assert.assertEquals(updatedPost.getContent(),"hi,springboot");
}

Запустив этот тест-кейс, результат тоже пройден.Но также есть вопрос: как можно реализовать программу по предоставленной нами методике, не написав, как сделать то, что мы хотим: поtitleКак насчет значения поля для запроса совпадающей записи?Давайте ясно увидим это в фактическом анализе ниже.

image-20181020212654792

До сих пор мы пробовали добавлять, изменять и проверять данные, и удаление на самом деле очень просто, просто вызовитеpostRepositoryизdeleteМетода достаточно, а теперь самое главное изучитьPostRepositoryтолько по наследствуMongoRepositoryКак реализовать добавление, удаление и изменение данных?

Фактический анализ

Выполнение нижнего слоя postRepository

После реализации основных операций с данными, давайте посмотрим, как все это делается?testUpdateсерединаpostRepository#saveОтлаживайте точки останова и наблюдайте за путем выполнения программы.saveВнутри метода выполняется кодJdkDynamicAopProxyПод типом цепочка вызовов кода показана на следующем рисунке

image-20181020215752763

Очевидно, что здесь используетсяSpringизJDKдинамический прокси, в то время какinvokeметод внутри этогоproxyОбъект очень заметен, фактический метод вызывается при выполнении метода.proxyизsaveметод, и этоproxyявляетсяorg.springframework.data.mongodb.repository.support.SimpleMongoRepository@8deb645

, ДаSimpleMongoRepositoryЭкземпляр класса. Тогда окончательный вызов упадет наSimpleMongoRepository# saveметод, мы снова делаем точку останова в этом методе и продолжаем выполнение.

image-20181020220728719

Отсюда видно, что внутри метода сохранения есть две операции: если пришедшая сущность является новой записью, выполнитьinsert, иначе выполнитьsaveОперация обновления, судя по всему, сейчас будет выполняться последняя.

И завершить операцию с двумя объектамиentityInformationа такжеmongoOperationsЕсть тесная связь, что они делают, и когда они инициализируются.

image-20181020221604857

Сначала давайте посмотрим наmongoOperationsэтот объект, используяIDEAСредства отладки могут видетьmongoOperationsНа самом деле этоMongoTemplateпредмет, подобныйJDBCTemplate,противMongoDBДобавление, удаление и изменение данных,Springтакже использовать похожие имена иAPI.so реальная операцияMongoDBНижний слой базы данных этоMongoTemplateобъект.

Что касаетсяentityInformationкласс, к которому принадлежит объектMappingMongoEntityInformation, хранитсяMongoИнформация об объекте данных, например имя коллекции, тип первичного ключа, некоторые метаданные сопоставленного объекта и т. д.

Давайте посмотрим на время их инициализации, вSimpleMongoRepositoryклассы, вы можете найти их все инициализированными в конструкторе

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

Таким же образом вSimpleMongoRepositoryСделайте точку останова в конструкторе, чтобы снова включить наблюдение за инициализациейSimpleMongoRepositoryЦепочка вызовов объекта.Вся цепочка находится следующим образом, от запуска тестового примера до длинной цепочки выполнения здесь обозначены только те классы и методы, на которые нужно обратить внимание.

image-20181020224041366

Из исходного кода слой за слоем можно проследить доSimpleMongoRepositoryСоздание и инициализация класса выполняется фабричным классом.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); // 获取初始化后的SimpleMongoRepository对象.

		// Create proxy
		ProxyFactory result = new ProxyFactory();
		result.setTarget(target);
		result.setInterfaces(new Class[] { repositoryInterface, Repository.class }); 
    	// 对 repositoryInterface接口类进行 AOP 代理

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

		return (T) result.getProxy(classLoader);
}

На картинке нижеMongoRepositoryFactoryдиаграмма классов, аMongoRepositoryFactoryопять такиMongoRepositoryFactoryBeanпостроенный в классе.

image-20181020224811986

Во второй половине цепочки вызовов давайте посмотрим на источник всего происходящего, найдемorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanметод, созданный внутриBeanпримерdoCreateBeanПараметры вызоваpostRepositoryа такжеMongoRepositoryFactoryBeanэкземпляр, то есть созданиеpostRepositoryзавершено во время экземпляра.

image-20181020230418830

при созданииpostRepositoryСоответствующий объект сущности на самом делеMongoRepositoryFactoryBeanэта фабрикаBean

image-20181020232644057

когда нужно использоватьpostRepositoryКогда объект используется, это фактически метод использования фабричного объекта.MongoRepositoryFactoryBean#getObjectвернутьSimpleMongoRepositoryобъект, см., когда классAbstractBeanFactoryизdoGetBeanметод, когда параметрnameзаpostRepositoryЦепочка вызовов временного кода.

image-20181021000306776

Хорошо, здесь мы в основном закончилиpostRepositoryкак это делаетсяMongoDBДругая проблема с операциями с базами данных заключается в том, что определены только методы интерфейса.findByTitle, как добитьсяtitleпоиск поля.

Найдите реализацию findByTitle

точка останова на выполнениеfindByTitleметод, отладьте его, как и раньшеJdkDynamicAopProxyclass, и при получении цепочки вызовов

, класс перехватчика в перехватчике, принадлежащем этому прокси-объектуorg.springframework.data.repository.core.support.RepositoryFactorySupport.QueryExecutorMethodInterceptorПривлек мое внимание.С точки зрения названия, это перехватчик, который специально обрабатывает методы запроса.Я пытался перехватить в этомinvokeМетод ставит точку останова и выполняетсяfindByTitleКогда программа выполняется здесь.

image-20181021085124017

Затем оцените, является ли метод методом запроса в методе-перехватчике, и если да, то он будет вызываться с параметрамиPartTreeMongoQueryобъект унаследованAbstractMongoQuery#executeметод.

// 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();
    // 构建查询执行对象
    MongoQueryExecution execution = getExecution(query, accessor,new ResultProcessingConverter(processor, operations, instantiators));
    
    return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
}

а такжеMongoQueryExecution#executeВ методе фактическое выполнение вызывается слой за слоем и следующий код:

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

здесьoperationsэто то, что мы упоминали ранееMongoDBTemplateinstance, поэтому при выполнении пользовательского методаfindByTitileПри запросе базовый вызов по-прежнемуMongoDBTemplate#findOne.

И вот вопрос: строитьQueryЗначение параметра можно получить, когда объектsayHi, как получить соответствующее поле запроса какtitleкак насчет?

в методеcreateQueryэто метод шаблона, который фактически выполняется в классе PartTreeMongoQuery.

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

здание здесьMongoQueryCreatorкогда естьtreeАтрибут, этот объект является отношением, которое строит условный запрос.

image-20181021092719052

а такжеtreeОбъект инициализируется вPartTreeMongoQueryделается в конструкторе этого класса по имени метода,PartTreeКак он устроен.

//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));
        // 构造查询字段的关键
        this.predicate = new Predicate(source.substring(matcher.group().length()), domainClass);
    }
}

Как видно из приведенного выше кода, имя метода сопоставляется обычным образом, гдеPREFIX_TEMPLATEозначает^(find|read|get|query|stream|count|exists|delete|remove)((\p{Lu}.*?))??By, если совпадение найдено, извлекается слово, следующее сразу за By, и атрибут соответствующего класса внутренне сопоставляется по имени.После завершения построения он будет помещен вArrayListОн хранится в коллекции и используется при ожидании последующих запросов.

Таким образом, также видно, что наш пользовательский методfindByTitleОн соответствует стандартным требованиям фреймворка по умолчанию, поэтому его можно автоматически извлечь вPostизtitleполя как поля запроса. В противном случае используйте что-то вродеqueryBy,getByи т. д. тоже можно добиться того же эффекта, который здесь отраженSpring FrameworkСоглашение Из-за идеи конфигурации, если мы произвольно определяем имя метода, фреймворк не может напрямую идентифицировать поле запроса.

Хорошо, давайте еще раз подведем итоги анализа исходного кода:

  • определениеpostRepositoryвыполнитьMongoRepositoryинтерфейс, MongoDBTemplate, используемый в нижней части операции данных MongoDB, и фактическое использование через динамический прокси JDK иAOPМетод перехватчика называется послойным.
  • существуетpostRepositoryПользовательский метод запроса должен соответствоватьspring-boot-data-mongodbПравила именования методов фреймворка позволяют добиться эффекта полностью автоматической обработки.

Эпилог

здесь, нашSpring Bootа такжеMongoDBСобственно боевой разбор закончен.Если внимательно посмотреть на внутренний исходный код, то хотя уровень структуры ясен, часто в исходном коде легко заблудиться из-за сложных взаимосвязей вызовов между модулями.Терпение и Четкая цель имеет решающее значение. Это также и на этот раз. Урожай анализа исходного кода, я надеюсь, что эта статья может принести больше урожая, увидимся в следующей статье.😁😁😁

Ссылаться на