предисловие
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
метод, отладьте его, как и раньшеJdkDynamicAopProxy
class, и при получении цепочки вызовов
, класс перехватчика в перехватчике, принадлежащем этому прокси-объекту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
это то, что мы упоминали ранееMongoDBTemplate
instance, поэтому при выполнении пользовательского метода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
Собственно боевой разбор закончен.Если внимательно посмотреть на внутренний исходный код, то хотя уровень структуры ясен, часто в исходном коде легко заблудиться из-за сложных взаимосвязей вызовов между модулями.Терпение и Четкая цель имеет решающее значение. Это также и на этот раз. Урожай анализа исходного кода, я надеюсь, что эта статья может принести больше урожая, увидимся в следующей статье.😁😁😁