Ye Qingjie, самое четкое решение Mybatis, интегрированное с Spring, во всей сети.

Java

Прежде чем представить принцип интеграции Mybatis в Spring, мы должны сначала представить принцип работы Mybatis.

在这里插入图片描述

Основной принцип работы Mybatis

Кроме того, я собрал сборник 20-летних исследований и вопросов для интервью, в том числе краткий обзор Spring, Concurrency, базы данных, Redis, Distributed, Dubbo, JVM, микросервисов и т. д. Если вам это нужно, нажмите, чтобы получить его самостоятельно:Документация Тенсент

В Mybatis мы можем использовать интерфейс для определения выполняемого sql.Упрощенный код выглядит следующим образом:

Определите интерфейс, @Select указывает, что оператор запроса sql должен быть выполнен.

public interface UserMapper {
  @Select("select * from user where id = #{id}")
  User selectById(Integer id);
}

Ниже приведен код выполнения sql:

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

// 以下使我们需要关注的重点
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
User user = mapper.selectById(id);

Целью Mybatis является:Он позволяет программистам выполнять указанный sql, вызывая метод, и инкапсулирует базовую логику выполнения sql.

Здесь мы сосредоточимся на следующем объекте сопоставления: при вызове метода getMapper класса SqlSession будет создана карта для входящего интерфейса.прокси-объект, и программа фактически использует этот прокси-объект.При вызове метода прокси-объекта Mybatis выведет оператор sql, соответствующий методу, а затем использует JDBC для выполнения оператора sql и, наконец, получит результат.

Проанализируйте проблему, которую необходимо решить

Когда используются Spring и Mybatis, мы должны сосредоточиться на этом прокси-объекте. Потому что цель интеграции состоит в том, чтобы:Поместите прокси-объект Mapper как bean-компонент в контейнер Spring, чтобы прокси-объект можно было использовать как обычный bean-компонент, например, его можно было автоматически внедрить @Autowire.

Например, когда Spring и Mybatis интегрированы, мы можем использовать следующий код для использования прокси-объекта в Mybatis:

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

Свойство userMapper в serService будет автоматически внедрено в качестве прокси-объекта в Mybatis. Если вы отлаживаете на основе интегрированного проекта, вы можете обнаружить, что тип userMapper:org.apache.ibatis.binding.MapperProxy@41a0aa7d. Доказательство действительно является прокси-объектом в Mybatis.

Итак, теперь нам нужно решить проблему: как я могу поместить прокси-объект Mybatis в виде bean-компонента в контейнер Spring?

Чтобы решить эту проблему, нам нужно иметь представление о процессе генерации bean-компонентов Spring.

Процесс генерации бобов в Spring

Во время процесса запуска Spring bean-компонент будет создан примерно с помощью следующих шагов.

  1. Сканировать файлы классов по указанному пути пакета
  2. Создайте соответствующее BeanDefinition в соответствии с информацией о классе.
  3. Здесь программист может использовать некоторый механизм для модификации BeanDefinition.
  4. Создание экземпляра компонента на основе BeanDefinition
  5. Поместите сгенерированный экземпляр компонента в контейнер Spring.

Предположим, есть класс A, допустим, есть следующий код: Класс А:

@Component
public class A {
}

Класс B, нет аннотации @Component

public class B {
}

Выполните следующий код:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

Результат:com.luban.util.A@6acdbdf5Тип объекта bean, соответствующий классу A, по-прежнему остается классом A. Но этот вывод неопределенный, мы можем использовать постпроцессор BeanFactory для изменения BeanDefinition, мы добавляем постпроцессор BeanFactory:

@Component
public class LubanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
        beanDefinition.setBeanClassName(B.class.getName());
    }
}

Это приведет к тому, что BeanDefiniton, соответствующий исходному классу A, будет изменен и изменен на класс B, тогда тип объекта bean, который обычно генерируется впоследствии, будет классом B. На этом этапе вызов следующего кода сообщит об ошибке:

context.getBean(A.class);

Но вызов следующего кода не сообщит об ошибке, хотя в классе B нет аннотации @Component:

context.getBean(B.class);

И результат, возвращаемый следующим кодом:com.luban.util.B@4b1c1ea0

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

Причина этого вопроса - прояснить проблему:В Spring объект bean не имеет прямой связи с классом, имеет прямую связь с BeanDefinition.

Итак, вернемся к проблеме, которую мы хотим решить:Как поместить прокси-объект Mybatis в виде bean-компонента в контейнер Spring?

Весной,Если вы хотите сгенерировать bean-компонент, вам нужно сначала сгенерировать BeanDefinition., точно так же, как если вы хотите создать новый экземпляр объекта, вы должны сначала иметь класс. .

Решать проблему

Продолжая возвращаться к нашей проблеме, теперь мы хотим сгенерировать bean-компонент самостоятельно, поэтому мы должны сначала сгенерировать BeanDefinition, пока существует BeanDefinition, установив его в BeanDefinition.тип объекта bean, а затем добавьте BeanDefinition в Spring, и Spring автоматически сгенерирует для нас объект bean, соответствующий типу согласно BeanDefinition.

Итак, теперь нам предстоит решить две задачи:

  1. Каков тип прокси-объекта Mybatis? Поскольку мы хотим установить BeanDefinition
  2. Как добавить BeanDefinition в контейнер Spring?

Примечание. Постпроцессор BeanFactory, который мы использовали выше, может только изменять BeanDefinition и не может добавлять новое BeanDefinition. Мы должны использовать технику импорта, чтобы добавить BeanDefinition. Позже, если вы используете технологию Import для добавления BeanDefinition, вы можете сначала взглянуть на идеи реализации псевдокода.

Предположение: у нас есть интерфейс UserMapper, а его прокси-объект имеет тип UserMapperProxy. Итак, наша идея такова, псевдокод выглядит следующим образом:

BeanDefinitoin bd = new BeanDefinitoin();
bd.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(bd);

Однако здесь есть серьезная проблема, то есть UserMapperProxy выше — это то, что мы предполагаем, он представляет тип прокси-класса, но прокси-объект в Mybatis реализован с использованием динамической прокси-технологии JDK, то есть прокси прокси-объекта, классы генерируются динамически, и мы просто не можем определить, что такое прокси-класс прокси-объекта.

Итак, вернемся к нашему вопросу:Каков тип прокси-объекта Mybatis?

Ответа могло быть два:

  1. Прокси-класс, соответствующий прокси-объекту
  2. Соответствующий интерфейс прокси-объекта

Тогда ответ 1 эквивалентен нету, потому что прокси-класс генерируется динамически, тогда давайте посмотрим на ответ 2:Интерфейс, соответствующий прокси-объекту

Если мы возьмем ответ 2, то ход наших мыслей таков:

BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);

Однако невозможно установить соответствующий тип BeanDefinition в качестве интерфейса, потому что Spring не имеет возможности обновить экземпляр соответствующего типа в соответствии с этим BeanDefinition, а интерфейс не может напрямую обновить экземпляр.

Итак, теперь возникает проблема, проблема, которую я хочу решить:Каков тип прокси-объекта Mybatis?Оба ответа были отвергнуты нами, поэтому этот вопрос неразрешим, поэтому мы больше не можем думать в этом направлении, а можем только вернуться к исходному вопросу:Как я могу поместить прокси-объект Mybatis в качестве компонента в контейнер Spring?

Подводя итог приведенным выше рассуждениям:Мы хотим установить тип класса BeanDefinition, и тогда Spring автоматически поможет нам сгенерировать соответствующий bean-компонент, но этот способ невозможен.

окончательное решение

Так есть ли у нас другой способ генерировать бобы? а такжеЛогика генерации bean-компонентов не может быть выполнена Spring за нас., мы должны сделать это сами.

FactoryBean

Да, это FactoryBean в Spring. Мы можем использовать FactoryBean для настройки объекта компонента, который мы хотим сгенерировать, например:

@Component
public class LubanFactoryBean implements FactoryBean {
	@Override
	public Object getObject() throws Exception {
		Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if (Object.class.equals(method.getDeclaringClass())) {
					return method.invoke(this, args);
				} else {
					// 执行代理逻辑
					return null;
				}
			}
		});

		return proxyInstance;
	}

	@Override
	public Class<?> getObjectType() {
		return UserMapper.class;
	}
}

Мы определяем LubanFactoryBean, который реализует FactoryBean, а метод getObject используется для настройки логики создания объектов bean.

Выполните следующий код:

public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
		System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
		System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
	}
}

будет печатать: lubanFactoryBean: com.luban.util.LubanFactoryBean1@4d41cee &lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94 lubanFactoryBean-class: class com.sun.proxy.Proxy20

Из результатов видно, что объект bean-компонента с именем «lubanFactoryBean» из контейнера Spring является прокси-объектом, сгенерированным нашим настраиваемым динамическим прокси-сервером jdk.

Итак, мы можем добавить пользовательский объект bean в контейнер Spring через FactoryBean. Определенный выше LubanFactoryBean соответствует UserMapper, что означает, что мы определили LubanFactoryBean, что эквивалентно помещению прокси-объекта, соответствующего UserMapper, в контейнер в качестве bean-компонента.

Но как программисты, мы не можем определять LubanFactoryBean каждый раз, когда мы определяем Mapper, что очень проблематично.Давайте преобразуем LubanFactoryBean, чтобы сделать его более общим, например:

@Component
public class LubanFactoryBean implements FactoryBean {

    // 注意这里
	private Class mapperInterface;
	public LubanFactoryBean(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

				if (Object.class.equals(method.getDeclaringClass())) {
					return method.invoke(this, args);
				} else {
					// 执行代理逻辑
					return null;
				}
			}
		});

		return proxyInstance;
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}
}

После преобразования LubanFactoryBean LubanFactoryBean становится гибким, и различные интерфейсы Mapper могут передаваться через конструкцию при создании LubanFactoryBean.

На самом деле LubanFactoryBean также является Bean.Мы также можем сгенерировать LubanFactoryBean, сгенерировав BeanDefinition, и установить разные значения для параметров конструктора.Например, псевдокод выглядит следующим образом:

BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是LubanFactoryBean
bd.setBeanClassName(LubanFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用LubanFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);

В частности, примечание 2 означает, что когда текущее BeanDefinition генерирует объект bean, он будет создан путем вызова конструктора LubanFactoryBean, и будет передан объект класса UserMapper. Затем при создании LubanFactoryBean прокси-объект, соответствующий интерфейсу UserMapper, будет создан как компонент.

На данный момент мы фактически завершили задачу, которую хотим решить:Поместите прокси-объект в Mybatis как bean-компонент в контейнер Spring.. Просто мы используем простой объект прокси JDK для имитации объекта прокси в MyBatis. Если у нас есть время, мы можем вызвать область метода, предоставляемой в MyBatis, чтобы создать объект прокси. Я не займу время, чтобы представить это здесь.

Import

На данный момент нам еще нужно сделать одно, а именно, как на самом деле определить BeanDefinition и добавить его в Spring.Как упоминалось выше, нам нужно использовать технологию импорта.Например, мы можем добиться этого:

Определите следующий класс:

public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
		AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
		beanDefinition.setBeanClass(LubanFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		// 添加beanDefinition
		registry.registerBeanDefinition("luban"+UserMapper.class.getSimpleName(), beanDefinition);
	}
}

И добавьте аннотацию @Import в AppConfig:

@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {

Таким образом, при запуске Spring будет добавлен новый BeanDefinition. BeanDefinition сгенерирует объект LubanFactoryBean, а когда будет сгенерирован объект LubanFactoryBean, будет передан объект UserMapper.class. эквивалентно автоматическому созданию прокси для объекта интерфейса UserMapper в виде bean-компонента.

Суммировать

Подводя итог нашему анализу, мы должны сделать следующее для интеграции Spring и Mybatis:

  1. Определить LubanFactoryBean
  2. Определите LubanImportBeanDefinitionRegistrar
  3. Добавьте аннотацию @Import(LubanImportBeanDefinitionRegistrar.class) в AppConfig.

оптимизация

Таким образом, требования к интеграции могут быть в основном выполнены.Конечно, есть два момента, которые можно оптимизировать.

Сначала определите отдельную аннотацию @LubanScan следующим образом:

@Retention(RetentionPolicy.RUNTIME)
@Import(LubanImportBeanDefinitionRegistrar.class)
public @interface LubanScan {
}

Таким образом, вы можете использовать @LubanScan непосредственно в AppConfig.

Во-вторых, в LubanImportBeanDefinitionRegistrar мы можем сканировать Mapper.В LubanImportBeanDefinitionRegistrar мы можем получить соответствующую аннотацию @LubanScan через AnnotationMetadata, поэтому мы можем установить значение @LubanScan, чтобы указать путь пакета для сканирования. Затем получите заданный путь к пакету в LubanImportBeanDefinitionRegistrar, затем просканируйте все Mapper по указанному пути, сгенерируйте BeanDefinition и поместите его в контейнер Spring.

Итак, основной принцип интеграции Mybatis в Spring исчерпан, и снова резюмируем:

  1. Определите LubanFactoryBean для создания объекта bean из прокси-объекта Mybatis.
  2. Определите LubanImportBeanDefinitionRegistrar для создания LubanFactoryBean для различных объектов Mapper.
  3. Определите @LubanScan для выполнения логики LubanImportBeanDefinitionRegistrar при запуске Spring и укажите путь к пакету.

Вышеупомянутые три элемента относятся к org.mybatis.spring в:

  1. MapperFactoryBean
  2. MapperScannerRegistrar
  3. @MapperScan

наконец

Небольшой лайк, удачи, обратите внимание, молодость постоянна

在这里插入图片描述