предисловие
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.
После импорта проекта с помощью 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 с аутентификацией пользователя форма uri
mongodb://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в базе данных и могут быть запрошены.
мы также можемMongoDBСервер нашел эту запись:
Еще из записи_ 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Как насчет значения поля для запроса совпадающей записи?Давайте ясно увидим это в фактическом анализе ниже.
До сих пор мы пробовали добавлять, изменять и проверять данные, и удаление на самом деле очень просто, просто вызовитеpostRepositoryизdeleteМетода достаточно, а теперь самое главное изучитьPostRepositoryтолько по наследствуMongoRepositoryКак реализовать добавление, удаление и изменение данных?
Фактический анализ
Выполнение нижнего слоя postRepository
После реализации основных операций с данными, давайте посмотрим, как все это делается?testUpdateсерединаpostRepository#saveОтлаживайте точки останова и наблюдайте за путем выполнения программы.saveВнутри метода выполняется кодJdkDynamicAopProxyПод типом цепочка вызовов кода показана на следующем рисунке
Очевидно, что здесь используетсяSpringизJDKдинамический прокси, в то время какinvokeметод внутри этогоproxyОбъект очень заметен, фактический метод вызывается при выполнении метода.proxyизsaveметод, и этоproxyявляетсяorg.springframework.data.mongodb.repository.support.SimpleMongoRepository@8deb645
, ДаSimpleMongoRepositoryЭкземпляр класса. Тогда окончательный вызов упадет наSimpleMongoRepository# saveметод, мы снова делаем точку останова в этом методе и продолжаем выполнение.
Отсюда видно, что внутри метода сохранения есть две операции: если пришедшая сущность является новой записью, выполнитьinsert, иначе выполнитьsaveОперация обновления, судя по всему, сейчас будет выполняться последняя.
И завершить операцию с двумя объектамиentityInformationа такжеmongoOperationsЕсть тесная связь, что они делают, и когда они инициализируются.
Сначала давайте посмотрим на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Цепочка вызовов объекта.Вся цепочка находится следующим образом, от запуска тестового примера до длинной цепочки выполнения здесь обозначены только те классы и методы, на которые нужно обратить внимание.
Из исходного кода слой за слоем можно проследить до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построенный в классе.
Во второй половине цепочки вызовов давайте посмотрим на источник всего происходящего, найдемorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanметод, созданный внутриBeanпримерdoCreateBeanПараметры вызоваpostRepositoryа такжеMongoRepositoryFactoryBeanэкземпляр, то есть созданиеpostRepositoryзавершено во время экземпляра.
при созданииpostRepositoryСоответствующий объект сущности на самом делеMongoRepositoryFactoryBeanэта фабрикаBean
когда нужно использоватьpostRepositoryКогда объект используется, это фактически метод использования фабричного объекта.MongoRepositoryFactoryBean#getObjectвернутьSimpleMongoRepositoryобъект, см., когда классAbstractBeanFactoryизdoGetBeanметод, когда параметрnameзаpostRepositoryЦепочка вызовов временного кода.
Хорошо, здесь мы в основном закончилиpostRepositoryкак это делаетсяMongoDBДругая проблема с операциями с базами данных заключается в том, что определены только методы интерфейса.findByTitle, как добитьсяtitleпоиск поля.
Найдите реализацию findByTitle
точка останова на выполнениеfindByTitleметод, отладьте его, как и раньшеJdkDynamicAopProxyclass, и при получении цепочки вызовов
, класс перехватчика в перехватчике, принадлежащем этому прокси-объектуorg.springframework.data.repository.core.support.RepositoryFactorySupport.QueryExecutorMethodInterceptorПривлек мое внимание.С точки зрения названия, это перехватчик, который специально обрабатывает методы запроса.Я пытался перехватить в этомinvokeМетод ставит точку останова и выполняетсяfindByTitleКогда программа выполняется здесь.
Затем оцените, является ли метод методом запроса в методе-перехватчике, и если да, то он будет вызываться с параметрами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Атрибут, этот объект является отношением, которое строит условный запрос.
а также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Собственно боевой разбор закончен.Если внимательно посмотреть на внутренний исходный код, то хотя уровень структуры ясен, часто в исходном коде легко заблудиться из-за сложных взаимосвязей вызовов между модулями.Терпение и Четкая цель имеет решающее значение. Это также и на этот раз. Урожай анализа исходного кода, я надеюсь, что эта статья может принести больше урожая, увидимся в следующей статье.😁😁😁