Традиционный метод разработки Mybaits заключается в глобальной настройке инфраструктуры через mybatis-config.xml, например, кэше первого уровня, генераторе первичных ключей и т. д.
После выпуска SpringBoot, представив mybatis-spring-boot-starterПакет зависимостей,Может значительно снизить рабочую нагрузку,Достичь быстрого внедрения,Вы можете обратиться к предыдущемуиз Случай статьи:SpringBoot интегрирует Mybatis;Ниже мы объединяемSpringBootанализироватьMybatisиз Процесс инициализациии Процесс исполнения。
Сначала предварительные знания,Потому что автоматическая сборка SpringBoot упрощает многие конфигурации Mybatis.,Мы должны ввести процесс автоматического импорта Springbootizbean.
Когда запустится Springboot, он прочитает файл META-INF\spring.factories.
Загрузите строку key=org.springframework.boot.autoconfigure.EnableAutoConfigurationiz в качестве имени класса.
(Запуск будет сотрудничать с содержимым в META-INF\spring-autoconfigure-metadata.properties, чтобы отфильтровать сцены, которые не соответствуют предыдущей сцене)
Вы можете обратиться к статье:«Исходный код Spring: процесс загрузки компонента»
Разъясним несколько принципов:
Согласно конвенции,Мы Анализ исходного кода, также следует два маршрута: определение регистрации фреймворка и время выполнения фреймворка.
<!-- Интеграция зависимостей, связанных с mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
Сначала мы добавили зависимость mybatis-spring-boot-starter. Когда мы щелкнем по ней, мы увидим, что mybatis-spring-boot-starter также использует другие сторонние jar-пакеты:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
Обратите внимание, что существует также зависимость mybatis-spring-boot-autoconfigure.
// ...Другие примечания
@SpringBootApplication
@MapperScan("com.bryant.mapper") // Mapper сканирует путь к пакету
public class UserServer {
public static void main(String[] args) {
SpringApplication.run(UserServer.class, args);
}
}
Видим @SpringBootApplication, а это значит, что будет запущен следующий процесс автоматической сборки
Мы вернулись к пакету зависимостей mybatis-spring-boot-autoconfigure и обнаружили, что файл Spring.factories действительно существует.
/mybatis-spring-boot-autoconfigure/2.1.2/mybatis-spring-boot-autoconfigure-2.1.2.jar!/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
Мы добавили такую аннотацию @MapperScan в класс запуска
@MapperScan("com.bryant.mapper") // Mapper сканирует путь к пакету
Благодаря использованию @MapperScan будет инициировано введение других ресурсов.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
@Import инициирует автоматическое введение класса MapperScannerRegistrar.
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// MapperFactoryBean установлен.
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
// ... опуская многие детали
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
}
Анализ кода:
Mybatis имеет множество этапов автоматической настройки и множество компонентов. Фактически, это можно сделать с помощью блок-схемы загрузки классов:
Прочитайте и проанализируйте конфигурацию, начиная с mybatis, например:
#Укажите Mapper из файла конфигурации из пути к папке Damapper из всех xml-файл.
mybatis.mapper-locations=classpath:mapper/*.xml
DataSourceProperties используется для чтения и анализа конфигурации, начиная с Spring.data, например:
#Конфигурация источника данных
spring.datasource.url = jdbc:mysql://localhost:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.name=xxx
Здесь то же самое, что и 1,5. изMapperScannerRegistrarиззагрузка интерфейса мапперадавзаимоисключающие отношения,Так что это не происходит одновременно.
Как следует из названия, если связанный с MapperScan bean-компонент не определен, то выполняется класс конфигурации.
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}
Реализовано 2 интерфейса: BeanFactoryAware, ImportBeanDefinitionRegistrar.
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
Поскольку класс MapperScannerConfigurer реализует интерфейс BeanDefinitionRegistryPostProcessor, он будет загружен до создания компонента и вызова его метода postProcessBeanDefinitionRegistry.
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// (1) Установите значения атрибутов
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
// (2) здесь установлен MapperFactoryBeanClassдаMapperFactoryBean
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
// (3) Сканирование интерфейса картографа на основе значений атрибутов.
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
// (1) Установите значения атрибутов
private void processPropertyPlaceHolders() {
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
// Получить определение Bean через BeanFactory
BeanDefinition mapperScannerBean =
((ConfigurableApplicationContext) applicationContext).getBeanFactory()
.getBeanDefinition(beanName);
// Выньте значение атрибута «basePackage» и присвойте его.
PropertyValues values = mapperScannerBean.getPropertyValues();
this.basePackage = updatePropertyValue("basePackage", values);
}
this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
}
Ссылаясь на внедрение конфигурации интерфейса преобразователя в [1], MapperScannerRegistrar наконец добавляет BeanDefinition с именем MapperScannerRegistrar в BeanFactory и его значение basePackage = com.bryant.mapper.
Тогда существование【3】изMapperScannerConfigurer передаст beanName=MapperScannerRegistrar,Извлеките файл MapperScannerRegistrarизBeanDefinition из BeanFactory.,а затем извлечьbasePackageизценить = com.bryant.mapper。
Хорошо, теперь мы поговорим о том, как сканировать соответствующий файл пути к пакету.
Наследуя родительский класс ClassPathBeanDefinitionScanner, doScan выполняет внутренний обратный вызов метода родительского класса, а затем выполняет обработку определения компонента.
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// (1) Вызовите метод родительского класса ClassPathBeanDefinitionScanner#doScan.
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// (2) Обработка коллекции определений bean-компонентов
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
//(1)org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//Вот путь к классу сканирования и класс аннотации измэппера.
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//Получить имя компонента
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//Это определяет, содержит ли beanFactory определение beanNameizbeaniz. Если нет, он войдет в ветку. В этой ветке нет ничего особенного. Просто добавьте определение beaniz в beanFactory.
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
definition.setBeanClass(this.mapperFactoryBeanClass);
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
После описанных выше шагов написанный нами интерфейс Mapper будет просканирован платформой и готов к определению компонента. Итак, какой объект генерирует этот MapperBean?
Давайте рассмотрим пункт 3 из [3] выше MapperScannerConfigurer;
В процессе сканера ClassPathMapperScanner одним из шагов является setMapperFactoryBeanClass, который присваивает фабричный класс MapperFactoryBean, что означает, что все последующие динамические прокси-объекты Mapper создаются на основе MapperFactoryBean#GetObject.
MapperFactoryBean наследует SqlSessionDaoSupport, который инкапсулирует SqlSessionTemplate.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
public class MapperProxyFactory<T> {
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
Анализ кода:
cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// ...здесь да против Интерфейса иззакрытый метод(defaultметод、статический метод и т. д.)
} else {
// Путь по умолчанию — издаPlainMethodInvoker.
return new PlainMethodInvoker(
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
Конструктор
public class MapperMethod {
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
Выше показано, как Mybatis инкапсулирует код SqlSession.,это действительно сложно,Объект из цепочки вызовов очень длинный,但нас用一张图来Подвести итоги предыдущего дня:
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
//...Опустить настройки атрибутов
return factory.getObject();
}
Если SqlSessionFactory отсутствует, этот метод вызывается по умолчанию.
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
Логика агента SqlSessionInterceptor относительно сложна. Ниже мы используем рисунок для представления его процесса:
Мы только что узнали из исходного кода, что исходный код getSqlSession для получения операции сеанса, потокобезопасность SqlSession достигается за счет менеджера синхронизации транзакций TransactionSynchronizationManager.
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
/**
* Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of
* current transaction. If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the
* transaction if Spring TX is active and <code>SpringManagedTransactionFactory</code> is configured as a transaction
* manager.
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
SqlSessionHolder holder = (SqlSessionHolder)
TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
return value;
}
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
Анализ кода:
После описанного выше процесса sqlSessionTemplate был построен, и теперь пришло время вступить в игру sqlSessionTemplate!
Однако, будучи простой и эффективной платформой ORM, Mybatis, конечно же, не будет напрямую предоставлять пользователям возможность вызова sqlSessionTemplate. Вместо этого он будет инкапсулировать его слой за слоем, что сделает его более соответствующим привычкам развития бизнеса.
Когда мы хотим запросить SQL, как нам это сделать? Выполните ли вы следующие действия:
Выше мы уже знаем, что когда вызывается Mapper, внедренный @Autowired, он в конечном итоге запускает выполнение динамического прокси-класса. После приведенного выше анализа метод каждого объекта Mapper в конечном итоге будет преобразован в MapperMethod, и существует метод; самый уровень цепочки инкапсуляции сложных объектов, как показано ниже:
Упомянутый выше MapperMethod — это место, где выполняется окончательный SQL. Основной метод — это метод выполнения:
org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
Анализ кода:
Думаете, на этом анализ заканчивается? Нет, там еще и выполнение sqlSession!
DefaultSqlSession реализует методы, определенные интерфейсом SqlSession, и предоставляет перегрузки для каждой операции с базой данных, как показано на рисунке ниже:
Ну а нижний слой оказывается называется обновлением. .
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Ну, нижний уровень изначально также назывался обновлением. .
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Думаете, на этом анализ заканчивается? Нет, есть еще и казнь Палача!
BaseExecutor — это класс шаблона, который содержит основные операции с базой данных.
org.apache.ibatis.executor.BaseExecutor#update
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
SimpleExecutor является исполнителем по умолчанию. Мы в основном фокусируемся на нем, продолжаем уделять внимание методу doUpdate и продолжаем отлаживать содержимое SimpleExecutor.
org.apache.ibatis.executor.SimpleExecutor#doUpdate
/**
* Выполнить операцию обновления метода реализации.
*
* @param ms Объект оператора сопоставления, содержащий информацию о выполнении SQL.
* @param parameter Параметры, передаваемые в SQLиз
* @return Количество строк, на которые влияет операция обновления.
* @throws SQLException Если во время выполнения возникает исключение SQL
*/
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
// Получите объект конфигурации, используемый для создания StatementHandler.
Configuration configuration = ms.getConfiguration();
// Создайте StatementHandler, отвечающий за выполнение операторов SQL.
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// Подготовьте оператор SQL и верните объект Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// Выполняет операцию обновления и возвращает количество затронутых строк.
return handler.update(stmt);
} finally {
// Закрыть ресурс заявления
closeStatement(stmt);
}
}
Анализ кода:
Мы обнаружили, что окончательное выполнение передается классу реализации StatementHandler.
BaseExecutor#query: предоставляет базовые функции управления кэшем и транзакциями.
org.apache.ibatis.executor.BaseExecutor#query
/**
* Запросить данные и вернуть список результатов
*
* @param ms Объект оператора сопоставления, включая шаблон SQL и другую информацию о конфигурации.
* @param parameter Параметры, передаваемые в оператор SQL
* @param rowBounds Используется для пейджингового запроса из диапазона строк.
* @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
* @return Возвращает список, содержащий результаты запроса.
* @throws SQLException Если во время запроса возникает исключение SQL
*
* Этот метод является одним из основных методов выполнения запросов. Он отвечает за выполнение SQL-запросов на основе предоставленных параметров и информации о конфигурации.
* И передать результат обработчику результатов для обработки или инкапсулировать его в список и вернуть.
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// Подготовьте оператор SQL и сгенерируйте кэш-ключ CacheKey
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// Выполнить запрос и вернуть результаты
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* Запросить базу данных и вернуть список результатов
* Этот метод выполняет основную часть запроса и отвечает за обработку кэша, запроса к базе данных и обработку результатов.
*
* @param ms Объект оператора сопоставления, содержащий информацию о конфигурации запроса из SQLи.
* @param parameter Параметры запроса могут быть любыми
* @param rowBounds Объект диапазона строк, используемый для пейджинговых запросов.
* @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
* @param key Ключ кэша, используемый для идентификации запроса кэша
* @param boundSql Подготовьте объект оператора изSQL
* @return Список результатов запроса, тип элемента – E.
* @throws SQLException Если во время запроса возникает ошибка доступа к базе данных
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// существующий стек запроса пуст и оператор сопоставления требует обновления кэша, очистите локальный кэш
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// Увеличьте глубину стека запросов, чтобы рекурсивные запросы не вызывали проблем.
queryStack++;
// Попробуйте получить результат из локального кэша. Если resultHandler равен нулю и сохраните существующий результат, используйте результат кэша.
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// Если результаты получены из кэша, обработать выходные параметры локального кэша.
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// Если результатов в кэше нет, запросите данные из базы данных
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// Уменьшите глубину стека запросов и убедитесь, что ресурсы освобождаются правильно.
queryStack--;
}
// Обработка отложенной загрузки и локального кэша, когда стек запросов пуст
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// Если уровень операторов включен, данные уровня сеанса должны быть очищены.
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
/**
* Запросить данные из базы данных и сохранить результаты в существующем кэше.
* Если тип объявлен CALLABLE, выходные параметры также сохраняются в существующем кэше.
*
* @param ms Объект оператора сопоставления, содержащий информацию и конфигурацию SQL.
* @param parameter параметры запроса
* @param rowBounds Диапазон строк, используемый для пейджинга
* @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
* @param key Ключ кэша, используемый для хранения и получения данных в кэше.
* @param boundSql Скомпилированный оператор SQL и сопоставление параметров
* @return Список результатов запроса
* @throws SQLException Если во время запроса возникает исключение SQL
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// Сначала поместите заполнитель выполнения в локальный кэш, чтобы указать, что запрос выполняется.
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// Выполнить запрос
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// После завершения запроса удалите заполнитель выполнения из локального кэша.
localCache.removeObject(key);
}
// Поместите результаты запроса в локальный кэш.
localCache.putObject(key, list);
// Если тип объявлен CALLABLE, выходные параметры помещаются в локальный кэш.
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
/**
* Выполнить запросметод
*
* @param ms Объект оператора сопоставления, содержащий оператор SQL и информацию о конфигурации.
* @param parameter Параметры, передаваемые в оператор SQL
* @param rowBounds Используется для постраничного запроса параметров, определяющих начальную и конечную запись.
* @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
* @param boundSql Скомпилированный объект оператора изSQL содержит окончательное сопоставление параметров изSQL.
* @return Вернуться к списку результатов запроса,Тип элемента в списке определяется общим E
* @throws SQLException Если во время запроса возникает исключение SQL
*
* Этот метод отвечает за выполнение SQL-запроса в операторе сопоставления и обработку результатов запроса через StatementHandler.
* Он инкапсулирует процесс запроса,включая созданиеStatementHandler、Подготовьте оператор SQL、Выполнить запроси Закрыть ресурс
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// Получите объект конфигурации. Конфигурация содержит глобальную информацию о конфигурации.
Configuration configuration = ms.getConfiguration();
// Создайте StatementHandler для обработки выполнения инструкций SQL и обработки набора результатов.
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// Подготовьте операторы SQL, включая создание операторов, настройку параметров и т. д.
stmt = prepareStatement(handler, ms.getStatementLog());
// Выполнить запрос и вернуть результаты
return handler.query(stmt, resultHandler);
} finally {
// Закрытие заявления и выпуск ресурсов
closeStatement(stmt);
}
}
Приведенный выше код очень длинный, но в конце концов мы обнаружили, что обработка окончательного SQL-кода также остается на усмотрение оператора.
Мы можем использовать блок-схему для анализа
CacheKey в приведенном выше коде на самом деле является кешем первого уровня, используемым по умолчанию при открытии объекта SqlSession. Его жизненный цикл такой же, как и у SqlSession.
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
Объект CacheKey состоит из следующих частей: идентификатор, смещение и ограничение, sql, предоставленные пользователем параметры и идентификатор среды.
Кэш первого уровня в mybatis хранится в hashMap. Причина, по которой потокобезопасные коллекции не используются, заключается главным образом в том, что SqlSession небезопасен для потоков, поэтому в этом нет необходимости.
иSqlSessionПравила безопасности потоковдапередается каждому триггерному поведениюизclearCache()метод,когдакаждое исполнениеsqlПосле завершения,Весь контент будет удален,Таким образом, вы сможете избежать проблем с потокобезопасностью, вызванных кэшем!
тип | Функции | кэш |
---|---|---|
SimpleExecutor | Сосредоточьтесь на реализации 4 основных методов | Кэш 1 уровня |
ReuseExecutor | Уменьшите накладные расходы на предварительную компиляцию SQL, создайте и уничтожьте накладные расходы на объекты Statement и улучшите производительность. | Кэш 1 уровня |
BatchExecutor | Метод оптимизации пакетной обработки отправляет несколько SQL-запросов в базу данных для выполнения, что снижает нагрузку на сеть и повышает производительность. | Кэш 1 уровня |
CachingExecutor | Увеличение Кэш Уровень операторов L2 из служебных данных,Уменьшите количество выполнений базы данных,Улучшите производительность | Кэш 1 уровня Кэш L2 |
Далее мы сосредоточимся на CachingExecutor.
CachingExecutor инкапсулирует объект Executor для выполнения операций с базой данных и управляет объектом L2изTransactionalCacheManager.
Кэш L2: Уровень приложения изкэш, состоящий из TransactionalCacheManager&TransactionalCacheДва компонента достижения:
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//【1】Запрос пространства имен, соответствующего существуиз Кэш L2
Cache cache = ms.getCache();
if (cache != null) {
//【2】
flushCacheIfRequired(ms);
//【3】
if (ms.isUseCache() && resultHandler == null) {
//【4】
ensureNoOutParams(ms, boundSql);
//【5】
List<E> list = (List<E>) tcm.getObject(cache, key);
//【6】
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//【7】
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
Анализ кода:
Сборка MyBatisиз Кэш L2реализована по шаблону декоратораиз
Цепочка украшений: SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache
SynchronizedCache: синхронизированный кеш,Реализация относительно проста,Используйте Synchronized напрямую для изменения метода.
LoggingCache: функция ведения журнала, класс оформления, используемый для записи частоты попаданий. Если включен режим DEBUG, будет выведен журнал частоты попаданий.
SerializedCache: функция сериализации, сериализует значение и сохраняет его в кэше. Эта функция используется для возврата копии экземпляра изCopy для сохранения потокобезопасности.
LruCache: принимает реализацию алгоритма Lru из Cache, удаляя наименее использованный ключ/значение.
PerpetualCache: как самый основнойизкэшдобрый,Первый этаж Реализация относительно проста,Используйте HashMap напрямую
Всякий раз, когда выполняется insert、update、delete,flushCache=true Когда, Кэш L2 будет очищен.
Кэш L2 должна быть отправлена существующая транзакция сеанса, прежде чем она вступит в силу! После того, как существование найдет результат, он вызовет SqlSession из commit метод отправки.
public class TransactionalCache implements Cache {
private final Cache delegate;
private boolean clearOnCommit;
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
public void commit() {
// 【1】Перед отправкой транзакции очистите существующие. Грязные данные L2
if (clearOnCommit) {
delegate.clear();
}
//【2】Сохраняем коллекцию записейToAddOnCommit в Кэш L2
flushPendingEntries();
// 【3】clearOnCommit сбрасывается на false
reset();
}
private void flushPendingEntries() {
// 【3】Пройдите по коллекции записей ToAddOnCommit и добавьте записи изкэш в Кэш. Внутри L2
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
// 【4】Пройдите по коллекции записейMissedInCache, добавьте в Кэш коллекцию записейToAddOnCommit, не содержащую элементов изкэша. Л2
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
}
Чтобы повысить производительность запросов, MyBatis Кэш предоставлен Архитектура L2, разделенная на один уровень кэши Кэш L2。
Самая большая разница между этими двумя уровнями заключается в следующем:
SimpleStatementHandler#update
/**
* Выполнить операцию обновления
*
* @param statement Оператор SQL из исполняемого объекта
* @return к Количество строк, на которые влияет операция обновления.
* @throws SQLException если Выполнить операцию Произошла ошибка при обновлении
*/
@Override
public int update(Statement statement) throws SQLException {
// Получить оператор SQL
String sql = boundSql.getSql();
// Получать Параметры, передаваемые в оператор SQL
Object parameterObject = boundSql.getParameterObject();
// Получить генератор ключей
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// Используется для записи количества затронутых строк.
int rows;
// Если генератор ключей даJdbc3KeyGeneratorтип
if (keyGenerator instanceof Jdbc3KeyGenerator) {
// Выполнить SQL и вернуть сгенерированные ключи
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// Получить количество затронутых строк
rows = statement.getUpdateCount();
// существуют После выполнения постобработки SQL сгенерируйте ключ из
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
// Если генератор ключейдаSelectKeyGeneratorтип
// Выполнить SQL, но не возвращать сгенерированные ключи
statement.execute(sql);
// Получить количество затронутых строк
rows = statement.getUpdateCount();
// существуют После выполнения постобработки SQL сгенерируйте ключ из
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
// Если генератор ключей не поддерживает два вышеуказанных типа
// Простое выполнение SQL
statement.execute(sql);
// Получить количество затронутых строк
rows = statement.getUpdateCount();
}
// Возвращает количество затронутых строк
return rows;
}
Jdbc3KeyGenerator в качестве примера
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 【1】
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 【2】
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 【3】
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
Мы можем использовать блок-схему для анализа
/**
* Выполнить запрос并иметь дело срезультатнабор
*
* @param statement Оператор SQL из инкапсулированного объекта, используемый для выполнения операторов SQL.
* @param resultHandler Объект обработки набора результатов, используемый для обработки результатов запроса.
* @return Возвращает список, содержащий результаты запроса.
* @throws SQLException Если при выполнении оператора SQL возникает ошибка
*
*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// Из объекта BoundSql оператор SQL
String sql = boundSql.getSql();
// Выполнить оператор SQL
statement.execute(sql);
// Обработать и вернуть набор результатов
return resultSetHandler.handleResultSets(statement);
}
Приведенный выше даSpringBoot интегрирует код фреймворка Mybatis. Анализ исходного кода Мы исходим из двух аспектов:
Подвести итог: получил следующие знания: