Spring之整合Mybatis底层源码

news2024/11/24 19:50:25

文章目录

    • 一、整体核心思路
      • 1 . 简介
      • 2. 整合思路
    • 二、源码分析
      • 1. 环境准备
      • 2. 源码分析

一、整体核心思路

1 . 简介

有很多框架需要与Spring进行整合,而整合的核心思路就是把其他框架所产生的对象放到Spring容器中,让其成为一个bean。比如Mybatis,Mybatis框架本身是可以单独使用的,而单独使用Mybtis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合SPring就是为了将这些对象放入Spring中成为Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架提供的功能了。

2. 整合思路

  • 安装

导入jar包:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.3</version>
</dependency>
  • 快速上手

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean 来创建 SqlSessionFactory。 要配置这个工厂 Bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>  
    <property name="url" value="jdbc:mysql://localhost/mysql"></property> 
    <property name="username" value="root"></property> 
    <property name="password" value="1234"></property>   
</bean> 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

假设你定义了一个如下的 mapper 接口:

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
} 

那么可以通过 MapperFactoryBean 将接口加入到 Spring 中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。

配置好之后,你就可以像 Spring 中普通的 Bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean 将会负责 SqlSession 的创建和关闭。如果使用了 Spring 的事务功能,那么当事务完成时,Session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常。

要调用 MyBatis 的数据方法,只需一行代码:

public class FooServiceImpl implements FooService {

  private final UserMapper userMapper;

  public FooServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public User doSomeBusinessStuff(String userId) {
    return this.userMapper.getUser(userId);
  }
}

二、源码分析

1. 环境准备

  • 准备个mapper
public interface UserMapper {

	@Select("select 'user'")
	String selectById();
}

  • 准备个UserService
@Component
public class UserService {
	@Autowired
	UserMapper userMapper;

	public void test(){
		System.out.println(userMapper.selectById());
	}
}
  • 测试类代码
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		applicationContext.register(AppConfig.class);
		applicationContext.refresh();
		UserService aService = (UserService) applicationContext.getBean("userService");
		aService.test();
		applicationContext.close();
	}

允许上面代码我们知道肯定会报错,报错的原因是什么,主要是我们的spring容器中是没有userMapper这个bean的,所以依赖注入这里会失败。那我们分析,userMapper这里要注入的到底是什么对象?由Mybatis的工作原理我们知道,userMapper要注入的应该是mybatis为该接口产生的一个代理对象,一旦我们拿到这个Mybatis产生的代理对象,我们就可以真正的做到操作Mybatis框架了,这也是Spring和Mybatis整合的一个关键点,这也是Spring整合其它框架的思路。而创建这个对象的关键就是,FactoryBean。

@Component
public class JackFactoryBean implements FactoryBean {
	@Override
	public Object getObject() throws Exception {
		//使用jdk动态代理的技术
		Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				return null;
			}
		});
		return ProxyInstance;
	}

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

在分析Bean的生命周期源码的时候,我们知道FacotryBean底层的beanName为jackFactoryBean,但它所对应的Bean类型为getObject方法所返回的对象。这里我们以jdk动态代理技术为UserMapper生成了一个代理对象,测试一下上面的测试代码。

现在我们就可以成功注入UserMapper对象到UserService中了

在这里插入图片描述
但我们发现一个问题,如果我们每需要一个Mapper我们都去创建一个FactoryBean,这是很不合理的,所以我做一下改进。

@Component
public class JackFactoryBean implements FactoryBean {
	private Class MapperInterface;
	@Override
	public Object getObject() throws Exception {
		//使用jdk动态代理的技术
		Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{MapperInterface}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				return null;
			}
		});
		return ProxyInstance;
	}

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

但新的问题出来了,由于JackFactoryBean上面加了@Component注解,这是一个单例bean,而我们基于上面的改进代码我们知道,我们是要用这个FactoryBean去创建多个mapper的,所以我们不能这么定义bean,这里我们在测试类中改用以前用过的方式来创建bean。

public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		applicationContext.register(AppConfig.class);

		AbstractBeanDefinition beanDefinition=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(JackFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		applicationContext.registerBeanDefinition("userMapper",beanDefinition);

		AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition1.setBeanClass(JackFactoryBean.class);
		beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
		applicationContext.registerBeanDefinition("orderMapper",beanDefinition1);

		applicationContext.refresh();
		UserService userService = (UserService) applicationContext.getBean("userService");
		userService.test();
		applicationContext.close();
	}

使用上面方法就给JackFactoryBean类型创建了多个bean,注意userMapperorderMapper所对应的类型都是userMapperorderMapper的代理对象。但上面这些代码需要程序员在启动Spring之前自己写,这同样是不可取的。现在我们详细我们的根本目的是往容器中注入BeanDefinition,然后生成我们需要的bean,但仔细回顾我们前面学的知识,还有哪些方式可以注册BeanDefinition,这里我们就想到了前一章学到的BeanDefinitionRegistryPostProcessor接口。

@Component
public class JackBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

		AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(JackFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		registry.registerBeanDefinition("userMapper",beanDefinition);

		AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition1.setBeanClass(JackFactoryBean.class);
		beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
		registry.registerBeanDefinition("orderMapper",beanDefinition1);

	}
} 

在这里插入图片描述
但上面代码的灵活性还是不高,因为我们还是把OrderMapper和UserMapper给写死了,所以我们能不能提供一种包扫描的方式,自动拿到所有的Mapper,所以思路有了下面就开始实现。首先我们自定义一个注解:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JackMapperScan {

	String value();
}

这里我们要知道其实是在实现@MapperScan注解

然后在配置类上加上该注解,现在我们就通过注解拿到我们的路径值了


@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {

}

现在我们采用前面学习过的另一种思路来注入BeanDefinition,即使用ImportBeanDefinitionRegistrar接口,(使用这个类的原因是,我们可以很方便拿到注解信息)

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {

		AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(JackFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		registry.registerBeanDefinition("userMapper",beanDefinition);

		AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition1.setBeanClass(JackFactoryBean.class);
		beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
		registry.registerBeanDefinition("orderMapper",beanDefinition1);

	}
}

然后在配置类上导入该类,我们看过源码知道这个类加@Component是没有效果的,所以我们在配置类上导入该类。

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
@Import(JackImportBeanDefinitionRegistrar.class)
public class AppConfig {

}

现在BeanDefinition是可以注入进容器的。

在这里插入图片描述
我们知道,在Spring解析我们的注解,它会解析该注解和注解底层使用的注解,所以现在我们可以把Import注解加入到我们自定义的注解里面。所以现在我们就可以真正的开始解析扫描路径了。

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
		//然后拿到扫描路径
		String value = (String) annotationAttributes.get("value");
		System.out.println(value);
		AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(JackFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		registry.registerBeanDefinition("userMapper",beanDefinition);

		AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition1.setBeanClass(JackFactoryBean.class);
		beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
		registry.registerBeanDefinition("orderMapper",beanDefinition1);

	}
}

我们就成功拿到了扫描路径
在这里插入图片描述
下面的关键是如何实现扫描逻辑,前面我们说过Spring是提供了一个扫描器的,它在扫描BeanDefinition的时候起了关键作用,即ClassPathBeanDefinitionScanner,下面自定义一个Scanner。

public class JackScanner extends ClassPathBeanDefinitionScanner {
	public JackScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}
}

有了扫描器,现在的问题是我们知道Spring底层的扫描器是不会扫接口的,所以我们要重写isCandidateComponent方法让其支持扫描借口。

public class JackScanner extends ClassPathBeanDefinitionScanner {
	public JackScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}
	//我们是不能使用Spring提供的扫描逻辑的

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		//我们只关心接口
		return beanDefinition.getMetadata().isInterface();
	}
}

另外一点从源码我们知道,源码中会判断includefilters属性,这是Component的一个属性,即加了Component注解会自动包含在includefilters属性中,所以我们也要把自己定义的这些接口全部加入到includefilters属性中。

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
		//然后拿到扫描路径
		String value = (String) annotationAttributes.get("value");
		JackScanner scanner=new JackScanner(registry);
		scanner.addIncludeFilter(new TypeFilter() {
			@Override
			public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
				//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中
				return true;
			}
		});
		scanner.scan(value);
		AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(JackFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		registry.registerBeanDefinition("userMapper",beanDefinition);

		AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition1.setBeanClass(JackFactoryBean.class);
		beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
		registry.registerBeanDefinition("orderMapper",beanDefinition1);

	}
}

我们测试一下,现在能否拿到mapper生成的BeanDefinition,首先完善一下代码:

public class JackScanner extends ClassPathBeanDefinitionScanner {
	public JackScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}
	//我们是不能使用Spring提供的扫描逻辑的


	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
		System.out.println(beanDefinitionHolders);
		return beanDefinitionHolders;
	}

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		//我们只关心接口
		return beanDefinition.getMetadata().isInterface();
	}
}

现在我们通过打破Spring原始扫描的逻辑,确实是拿到了自己需要的mapper的bean

在这里插入图片描述

但现在的问题是,我们需要的不是接口的实际类型,但是我们实际需要的是这些借口的代理对象,所以我们还需要对BeanDefinition做一些处理。

public class JackScanner extends ClassPathBeanDefinitionScanner {
	public JackScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}
	//我们是不能使用Spring提供的扫描逻辑的


	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
		for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
			GenericBeanDefinition beanDefinition= (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
			beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
			beanDefinition.setBeanClassName(JackFactoryBean.class.getName());
			beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		}
		return beanDefinitionHolders;
	}

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		//我们只关心接口
		return beanDefinition.getMetadata().isInterface();
	}
}

再对JackImportBeanDefinitionRegistrar作一些修改:

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());
		//然后拿到扫描路径
		String value = (String) annotationAttributes.get("value");
		JackScanner scanner=new JackScanner(registry);
		scanner.addIncludeFilter(new TypeFilter() {
			@Override
			public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
				//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中
				return true;
			}
		});
		scanner.scan(value);
	}
}

在这里插入图片描述

现在我们的代码就可以成功运行了

现在还有一个问题,就是现在mapper的代理对象是我们自己使用jdk代理生成的,但是我们现在需要的使用Mybatis来生成我们的代理对象。所以在代理这一块我们需要重新写一下。在使用Mybatis时,我们使用的使用的SqlSession对象来获取代理对象的。

public class JackFactoryBean implements FactoryBean {

	private Class MapperInterface;

	private SqlSession sqlSession;

	public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
		sqlSessionFactory.getConfiguration().addMapper(MapperInterface);
		this.sqlSession = sqlSessionFactory.openSession();
	}

	public JackFactoryBean(Class mapperInterface) {
		this.MapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		return sqlSession.getMapper(MapperInterface);
	}

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

在注入SqlSession时,我们使用的是Set方法注入,但是我们需要一个SqlSessionFactory对象在Spring容器中,现在的关键是如何向Spring容器中加入SqlSessionFactory的对象。

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {
	@Bean
	SqlSessionFactory sqlSessionFactory() throws IOException {
		InputStream inputStream= Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
		return  sqlSessionFactory;
	}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
  <environments default="development">
    <environment id="development">
      <!-- 使用jdbc事务管理 -->
      <transactionManager type="JDBC"/>
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&amp;useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>


</configuration>

现在就可以成功的输出sql语句的结果了

在这里插入图片描述
现在Spring和Mybatis已经整合完了,关键点是如何将Mybatis的代理对象注入到Spring容器中。

2. 源码分析

上面我们自动动手整合了Spring和Mybatis,其实spring-mybatis包底层原理几乎差不多,只是过程更复杂,现在我们基于spring-mybatis的版本1.3.2来分析一下源码。

首先我们从@MapperScan注解开始

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  String[] value() default {};
  String[] basePackages() default {};
  Class<?>[] basePackageClasses() default {};
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  Class<? extends Annotation> annotationClass() default Annotation.class;
  Class<?> markerInterface() default Class.class;
  String sqlSessionTemplateRef() default "";
  String sqlSessionFactoryRef() default "";
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
  String lazyInitialization() default "";
  String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;

}

上面就用了@Import(MapperScannerRegistrar.class)注解,这里就对应我们案例中的@Import(JackImportBeanDefinitionRegistrar.class),我们进去看一下这个类。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

上面这个类同样实现ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法。

 AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

上面代码和案例中一样是拿@MapperScan注解的一些信息。

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

上面就是mybatis自定义的扫描器

 scanner.doScan(StringUtils.toStringArray(basePackages));

然后这里就开始使用子定义的扫描器开始扫描

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

上面就是Mybatis重写了spring框架的doscan方法:

Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

上面代码就是调用Spring的doScan方法拿到BeanDefinition

 processBeanDefinitions(beanDefinitions);

上面代码就是去修改BeanDefinition

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

上面代码思路和我们一样同样是遍历并修改BeanDefinition,关键代码就是下面这几行:

definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());

definition.setBeanClass(this.mapperFactoryBean.getClass());


definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

然后this.mapperFactoryBean就是Mybatis写的一个FactoryBean,我们看一下这个类。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  //要生成代理对象的接口的类型
  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    //设置SqlSession
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  .......
}

上面代码和我们一样首先定义了要生成代理对象的接口的类型

 private Class<T> mapperInterface;

然后获取SqlSession对象

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

SqlSessionDaoSupport是MapperFactoryBean的父类,主要就是用来处理SqlSession的,包括设置SqlSessionFactory,前面源码在processBeanDefinitions设置了注入形式为Bytype,所以在注入SqlSession时会自动调用该类的所有set方法,这样SqlSession对象就可以被生成了。

这里有个不同点,我们使用openSession来获取SqlSession对象,但是这里是使用sqlSessionTemplate来创建sqlSession的

这里还是挺重要的,这部分源码需要涉及Mybatis源码分析,这部分在分析Mybatis源码分析。我们现在只需要知道有了sqlSessionTemplate,我们就可以使用sqlSessionTemplate.getMapper()来获取代理对象。前面我们的案例中是直接使用openSession来获取SqlSession对象然后执行selectone等底层函数与数据库进行交互。我们看看sqlSessionTemplate在做什么,因为sqlSessionTemplate在上面代码获得后会被直接返回,所以实际在Mybatis中我们操作的是这个对象来与数据库交互的。

ublic class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;
.....
}

如果我们现在用SqlSessionTemplate对象去进行查询,例如调用selectOne

  @Override
  public <T> T selectOne(String statement) {
    return this.sqlSessionProxy.<T> selectOne(statement);
  }

我们会发现它会使用内部的一个this.sqlSessionProxy的属性去执行selectone

 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

从构造函数中可以发现this.sqlSessionProxy使用jdk代理来SqlSession接口。然后我们进入selectOne方法,可以发现进入了DefaultSqlSession,这就是Mybatis底层sqlSession真正的实现类。Mybatis为上面不直接使用SqlSession而是绕这么大一圈。

我们知道DefaultSqlSession在容器中就这么一个对象,且从其源码看出它是一个线程不安全的类,如果在高并发场景下,多个线程来使用这个DefaultSqlSession那么可能就出现一些难以预料的并非问题。而SqlSessionTemplate却是线程安全的,那么它是如何保证线程安全的?

它的关键点就是使用ThreadLocal,让每个线程都有一份自己的DefaultSqlSession对象。这样就可以保证线程的一个安全性。关键是要理解this.sqlSessionProxy这个对象

 this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new 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);
        }
      }
    }
  }

}

invoke方法中首先调用下面代码获取了一个真正的SqlSession对象

 SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);是Spring事务相关的东西,这里我们先不讲Spring事务相关的内容,我们只需要在这句代码中就操作的ThreadLocal对象,然后ThreadLocal中存的对象是SqlSessionHolder,然后就执行下面这段代码:

 SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
 session = sessionFactory.openSession(executorType);
 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

上面代码就是判断当前线程的ThreadLocal中是否有SqlSessionHolder对象,如果有就直接返回当前线程ThreadLocal中存储的SqlSession对象,如果没有就调用sessionFactory.openSession重新创建一个存入到当前的ThreadLocal中( registerSessionHolder)。

 private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    //判断是否开启了Spring事务
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
        }

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
          }
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
      }
    }
}

if (TransactionSynchronizationManager.isSynchronizationActive())就是判断我们有没有开启Spring事务,如果我们没有开启Spring事务,因为每次在一个方法中执行一条Sql语句(每个Sql都会看成一个独立的整体)都会创建一个新的SqlSession,这就会导致Mysql一级缓存失效,因为一级缓存有效的保证是同一个SqlSession执行多个sql语句。所以要让一级缓存不失效,就开启Spring事务即可,这样在一个方法中的所有sql就会看成一个整体,只需要一个SqlSession处理即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1373859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centenos下载安装

阿里云镜像下载 centos-7-isos-x86_64安装包下载_开源镜像站-阿里云 新建虚拟机 (1) 创建新的虚拟机 可以在主页直接点击创建新的虚拟机也可以在上方&#xff0c;点击文件&#xff0c;新建虚拟机 (2) 选择自定义&#xff08;高级&#xff09; (3) 硬盘兼容性 默认即可。我…

大模型商业化的又一条路,小冰闯出来了

当鲁特格尔哈尔饰演的Roy Batty在《银翼杀手》说出那段影史留名的台词时&#xff0c;人类对“复制人”“仿生人”的未来预想&#xff0c;全部凝聚成一个实体化的智能体&#xff0c;在未来很多年里支配着全世界对人工智能、数字生命的想象力。 但到了今天&#xff0c;当现实场景…

【漏洞复现】大华 DSS 数字监控系统 itcBulletin SQL 注入

漏洞描述 大华 DSS存在SQL注入漏洞,攻击者 pota/services/itcBuletin 路由发送特殊构造的数据包,利用报错注入获取数据库敏感信息。攻击者除了可以利用 SQL注入漏词获取数据库中的信息例如,管理员后台密码、站点的用户人人信息)之外,甚至在高权限的情况可向服务器中写入木…

汽车产线设备CAN总线一键刷写方案

汽车产线设备CAN总线一键刷写方案 一、概述 随着汽车工业的不断发展&#xff0c;CAN总线技术在汽车产线设备中得到了广泛应用。然而&#xff0c;在实际生产过程中&#xff0c;设备的软件升级和配置更改是不可避免的。为了提高生产效率&#xff0c;我们推出了一键刷写CAN总线解…

windows11右键菜单-新建文本文档(记事本、txt文件)不见了的修复方法

windows11右键菜单-新建文本文档&#xff08;记事本、txt文件&#xff09;不见了的修复方法 修改注册表 1、快捷键 WIN R。 2、输入 regedit 点击确定打开“注册表编辑器”。 3、在上边搜索栏直接输入 \HKEY_CLASSES_ROOT.txt 命令进行搜索后会跳转到指定页面 4、双击 …

计算机毕业设计 | SpringBoot图书管理系统(附源码)

1&#xff0c; 概述 1.1 课题背景 开发一个学生成绩管理系统&#xff0c;采用计算机对学生成绩进行处理&#xff0c;进一步提高了办学效益和现代化水平。为广大教师和学生提高工作效率&#xff0c;实现学生成绩信息管理工作流程的系统化、规范化和自动化。现在我国中学的学生…

Python流程控制语句

目录 一、分支结构 &#xff08;一&#xff09;单分支语句 &#xff08;二&#xff09;双分支语句 &#xff08;三&#xff09;多分支语句 &#xff08;四&#xff09;嵌套的分支语句 二、循环结构 循环结构概述 &#xff08;一&#xff09;for循环 &#xff08;二&am…

模板 BIEE(三)如何直接查看表示层列获取的sql语句

举例 想查看如下数据 SELECT 0 s_0, “Financials - AR Overview”.“Facts - AR Turnover”.“Days Sales Outstanding” s_1 FROM “Financials - AR Overview” web查看结果 日志内容如何查看请见《模板 BIEE&#xff08;二&#xff09;》 如下是不勾选高速缓存的结果&am…

如何保护linux服务器远程使用的安全

服务器安全是一个非常敏感的问题&#xff0c;因服务器远程入侵导致数据丢失的安全问题频频出现&#xff0c;一旦服务器入侵就会对个人和企业造成巨大的损失。因此&#xff0c;在日常使用服务器的时候&#xff0c;我们需要采取一些安全措施来保障服务器的安全性。 目前服务器系…

尝试添加服务器中正在运行的docker容器时报错:当前用户没有运行“docker”的权限

尝试添加服务器中正在运行的docker容器时报错&#xff1a;当前用户没有运行“docker”的权限 环境 1&#xff0c;通过vscode ssh到服务器的 2&#xff0c;服务器端有一个contianer&#xff0c;但是无法通过vscode的Dev contianer组件将服务器中正在运行的contianer添加过来 3…

适合游泳的骨传导耳机,推荐四款高质量游泳耳机!

游泳是一项全身性的运动&#xff0c;对于锻炼身体和塑形都很有帮助&#xff0c;但是游泳的时候往往会因为水的阻力而感到动作笨拙&#xff0c;同时也会感到枯燥无味。而一款好的游泳耳机则能够让你在游泳的过程中享受音乐或者其他的音频内容&#xff0c;增加游泳的趣味性&#…

Jenkins基础篇--添加用户和用户权限设置

添加用户 点击系统管理&#xff0c;点击管理用户&#xff0c;然后点击创建用户&#xff08;Create User&#xff09; 用户权限管理 点击系统管理&#xff0c;点击全局安全配置&#xff0c;找到授权策略&#xff0c;选择安全矩阵&#xff0c;配置好用户权限后&#xff0c;点击…

CROS跨域漏洞复现分析

复现环境&#xff1a;bp官方靶场Lab: CORS vulnerability with basic origin reflection | Web Security Academy 复现过程&#xff1a; 首先以普通用户的身份登录系统&#xff1a; 1、靶场环境点击access the lab 和给定账号wiener:peter 2、使用wiener:peter登录系统&…

Taro+vue3 实现选座位 功能 以及座位显示

1.类似选座位那种功能 我的功能座位 不是html元素 而是 座位图片 都是图片 const onConfirm () > {// const area_arr selectedSeat.value.map((item) > {// return item.areaId;// });// const abc isRepeat(area_arr);// if (!abc) {// Taro.showToast({//…

SpringBoot+Vue药品ADR不良反应智能监测系统源码

药品不良反应&#xff08;Adverse Drug Reaction&#xff0c;ADR&#xff09;是指合格药品在正常用法用量下出现的与用药目的无关的有害反应&#xff0c;不包括超说明书用药、药品质量问题等导致的不良后果。 ADR智能监测系统开发环境 ❀技术架构&#xff1a;B/S ❀开发语言&…

Docker部署Homepage个人引导页

个人名片&#xff1a; 对人间的热爱与歌颂&#xff0c;可抵岁月冗长&#x1f31e; 个人主页&#x1f468;&#x1f3fb;‍&#x1f4bb;&#xff1a;念舒_C.ying 个人博客&#x1f30f; &#xff1a;念舒_C.ying Homepage | 主页 1. 安装环境2. Docker部署 原作者&#xff1a;無…

spring boot + mybatis + websocket + js实战

项目技术&#xff1a;spring boot mybatis websocket js 需求背景&#xff1a;当添加一个女孩时&#xff0c;页面的socket收到消息&#xff0c;打印最新的所有女生list&#xff0c;这样可以进一步在react/vue前端框架下&#xff0c;实现当A用户新增了某业务数据后&#xff…

Kafka 除了用作消息队列还能干吗?

Kafka 除了用作消息队列还能干吗&#xff1f; 本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 Kafka 最初是为大规模处理日志而构建的。它可以保留消息直到过期&#xff0c;并让各个消费者按照自己的节奏提取消息。 与其之前的竞品不…

安谋科技“周易”NPU与飞桨完成II级兼容性测试,助力实现多样化AI部署

近日&#xff0c;安谋科技&#xff08;中国&#xff09;有限公司&#xff08;以下简称“安谋科技”&#xff09;“周易”NPU系列IP与飞桨已完成II级兼容性测试&#xff0c;测试结果显示&#xff0c;双方兼容性表现良好&#xff0c;整体运行稳定。这是安谋科技加入“硬件生态共创…

【一周年创作总结】人生是远方的无尽旷野呀

那一眼瞥见的伟大的灵魂&#xff0c;却似模糊的你和我 文章目录 &#x1f4d2;各个阶段的experience&#x1f50e;大一寒假&#x1f50e;大一下学期&#x1f50e;大一暑假&#x1f50e;大二上学期&#xff08;现在&#xff09; &#x1f354;相遇CSDN&#x1f6f8;自媒体&#…