六、mybatis与spring的整合

news2024/9/29 18:48:56

Spring整合Mybaits的步骤

引入依赖

在Spring整合Mybaits的时候需要引入一个中间依赖包mybatis-spring

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.2</version>
</dependency>

添加spring配置

为了整合mybatis,需要在spring的配置文件中引入mybtis的配置,这些配置通过两个类实现,SqlSessionFactoryBean以及MapperFactoryBean;SqlSessionFactoryBean负责加载mybatis-config文件以及注入数据源,MapperFactoryBean负责加载mapper接口

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd
">
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"   destroy-method="close">

        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="username" value="root"></property>
        <property name="password" value="husj0423"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="initialSize" value="1"></property>
        <property name="maxTotal" value="20"></property>
        <property name="maxIdle" value="5"></property>
        <property name="minIdle" value="2"></property>
    </bean>
    <bean id="userService" class="com.handerh.spring.test.aop.db.jdbc.UserServiceImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="configLocation" value="sqlmap/mybatis-config.xml"></property>
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>

添加Mybatis配置文件

<?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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
    <mapper resource="sqlmap/UserMapper.xml"/>
</mappers>
</configuration>

测试

public static void main(String[] args) {

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-mabatis.xml");
    UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");

    User user = userMapper.selectByPrimaryKey(6);
}

Spring整合Mybaits源码分析

在Spring与Myabtis整合的配置文件中,配置了两个重要的beanSqlSessionFactoryBean以及MapperFactoryBean,接下来主要分析这两个类是如何将mybatis整合到Spring中

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

    <property name="configLocation" value="sqlmap/mybatis-config.xml"></property>
    <property name="dataSource" ref="dataSource"></property>
</bean>

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

SqlSessionFactoryBean

在这里插入图片描述

可以看到它实现了两个非常重要的接口:

  • InitializingBean:实现此接口的bean会在初始化时调用afterPropertiesSet方法进行bean的逻辑初始化

  • FactoryBean:一旦某个Bean实现此接口,那么通过getBean获取bean时实际上获取的是此类的getObject返回的实例

SqlSessionFactoryBean的初始化

在实例化SqlSessionFactoryBean会调用afterPropertiesSet方法,在该方法最终会调用buildSqlSessionFactory函数来创建SqlSessionFactory;根据spring配置文件中注入的属性configLocation构造XMLConfigBuilder并且进行解析。Spring不仅可以通过configLocation的方式整合Mybatis的配置,还可以将Mybatis的配置直接整合到Spring配置文件中进行属性注入,并通过targetConfiguration来承载属性,最终使用sqlSessionFactoryBuilder实例根据解析的configuration创建SqlSessionFactory

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
  targetConfiguration = this.configuration;
  if (targetConfiguration.getVariables() == null) {
    targetConfiguration.setVariables(this.configurationProperties);
  } else if (this.configurationProperties != null) {
    targetConfiguration.getVariables().putAll(this.configurationProperties);
  }
} else if (this.configLocation != null) {
  xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
  targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
  targetConfiguration = new Configuration();
  Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}

Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

// 类别名
if (hasLength(this.typeAliasesPackage)) {
  scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
      .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
      .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}

if (!isEmpty(this.typeAliases)) {
  Stream.of(this.typeAliases).forEach(typeAlias -> {
    targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
  });
}

//Mybatis插件,可以修改Mybatis内部的允许规则
if (!isEmpty(this.plugins)) {
  Stream.of(this.plugins).forEach(plugin -> {
    targetConfiguration.addInterceptor(plugin);
    LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
  });
}

//定义类型处理器
if (hasLength(this.typeHandlersPackage)) {
  scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
      .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
      .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
      .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}

if (!isEmpty(this.typeHandlers)) {
  Stream.of(this.typeHandlers).forEach(typeHandler -> {
    targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
    LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
  });
}

if (!isEmpty(this.scriptingLanguageDrivers)) {
  Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
    targetConfiguration.getLanguageRegistry().register(languageDriver);
    LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
  });
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
    .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
  try {
    targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
  } catch (SQLException e) {
    throw new NestedIOException("Failed getting a databaseId", e);
  }
}

Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

if (xmlConfigBuilder != null) {
  try {
    xmlConfigBuilder.parse();
    LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
  } catch (Exception ex) {
    throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
  } finally {
    ErrorContext.instance().reset();
  }
}
// SpringManagedTransactionFactory 事务工厂
targetConfiguration.setEnvironment(new Environment(this.environment,
    this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
    this.dataSource));
    
// mapper文件解析
if (this.mapperLocations != null) {
  if (this.mapperLocations.length == 0) {
    LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
  } else {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }
      try {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
        xmlMapperBuilder.parse();
      } catch (Exception e) {
        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
      } finally {
        ErrorContext.instance().reset();
      }
      LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
    }
  }
} else {
  LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}

return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

获取SqlSessionFactoryBean的实例

当通过getBean方法获取对应SqlSessionFactoryBean实例时,其实获取到的是getObject返回的初始化后的SqlSessionFactory

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    
    return this.sqlSessionFactory;
}

MapperFactoryBean

MapperFactoryBean的继承体系如下:
在这里插入图片描述

可以看到MapperFactoryBean也实现了InitializingBean以及FactoryBean接口,同样的在通过getBean初始化bean实例的前也会调用afterPropertiesSet方法

afterPropertiesSet校验

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
	// Let abstract subclasses check their configuration.
	checkDaoConfig();

	// Let concrete implementations initialize themselves.
	try {
		initDao();
	}
	catch (Exception ex) {
		throw new BeanInitializationException("Initialization of DAO failed", ex);
	}
}

这里的checkDaoConfig()方法主要时做一个验证,校验mapperInterface这个属性是否有值,同时将mapperInterface放入到Mybatis的MapperRegistry中。

getObject获取mapper实例

afterPropertiesSet方法主要是做了一个校验逻辑,真正获取mapper实例的逻辑在getObject方法中

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

看到这里其实就是通过SqlSession来创建Mapper实例了,但是这个SqlSession不是独立使用时的DefaultSqlSession,而是SqlSessionTemplate,这个SqlSessionTemplate是什么时候注入的呢,看上面的类图发现SqlSessionTemplate继承了qlSessionDaoSupport,这个类中有一个属性SqlSessionTemplate,还包含一个set方法setSqlSessionFactory,这个方法完成了SqlSessionTemplate的创建,如下:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;
  
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
}

而我们在Spring配置文件中刚好配置为MapperFactoryBean注入了SqlSessionFactory,通过SqlSessionFactory创建SqlSessionTemplate从而完成mapper实例的创建

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

SqlSessionTemplate获取mapper实例

public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

通过获取Mybatis配置来创建mapper实例,最终调用的是MapperRegistry.getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            //动态代理创建mapper对象,这里的sqlSession是sqlSessionTemplate
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

mapper代理对象的执行

获取到mapper代理对象之后,就可以执行方法调用了。看一下SqlSessionTemplate中的方法:

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

public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
}

public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);
}

public int update(String statement) {
    return this.sqlSessionProxy.update(statement);
}

发现所有的增删改方法都是通过sqlSessionProxy来实现的,这个对象则是在初始化SqlSessionTemplate时生成的一个代理对象:

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());
}

对于JDK动态代理生成的对象方法的调用都说在Invocationhandler中完成的,SqlSessionInterceptor实现了Invocationhandler接口:

private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 1.获取SqlSession
  SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
  try {
    // 2. 执行目标方法
    Object result = method.invoke(sqlSession, args);
    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
      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 {
    //3.关闭sqlSession
    if (sqlSession != null) {
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    }
  }
}

在SqlSessionInterceptor有一个获取SqlSession的操作,来看一下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
  PersistenceExceptionTranslator exceptionTranslator) {
    
    //获取SqlSessionHolder,如果是同一个事物,Spring会保证在改事物中获取到的SqlSession是同一个
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    //从SqlSession中获取SqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
    //通过SqlSessionFactory创建DefaultSqlSession
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    
    //如果开启了事物会将该SqlSession注册到SessionHolder中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
    return session;
}

这段代码的主要逻辑就是创建SqlSession来完成mapper的执行,如果加入了Spring的事物管理,则需要保证在同一个事物中获取到的SqlSession是同一个(事物的提交以及回滚),到这里Mybatis与Spring整合的核心逻辑就已经完成了,另外,Spring为了简化Mapper接口的注册,加入了包扫描,减少在Spring配置文件中的mapper配置。

MapperScannerConfigurer

在Spring配置文件加入如下配置,就不需要为每一个Mapper接口再配置MapperFactoryBean了

<bean  id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="mapper"></property>
</bean>

我们来了解一下MapperScannerConfigurer是如何实现的。先看一下它的继承结构:
在这里插入图片描述

发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,InitializingBean两个接口,不过MapperScannerConfigurer的afterPropertiesSet方法中没有去做任何的逻辑处理,主要逻辑都是在postProcessBeanDefinitionRegistry方法中实现的(实现了BeanDefinitionRegistryPostProcessor接口),这个接口在容器启动的时候(AbstractApplicationContext.refresh())会被调用:

invokeBeanFactoryPostProcessors(beanFactory);
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
	}

看一下MapperScannerConfigurer中postProcessBeanDefinitionRegistry的处理逻辑:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

    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);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    // 对指定路径完成扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

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) {
	    // 扫描basePackage下的java文件
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
		    // 解析该bean是否包含scope注解 默认为single
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			// 生成bean名称 默认首字母小写
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
			    //检测常用注解:Primary Lazy
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			// 检测该bean是否已经注册
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				// 如果当前bean需要被代理 则需要进一步处理
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				// bean的注册
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

processBeanDefinitions构造MapperFactoryBean类型的bean

关键代码就下面两行,生成一个MapperFactoryBean类型的Bean,其中的mapperInterface属性,通过构造函数时将代理的beanClassName传入,这样通过包扫描就可以生成许多的MapperFactoryBean类型的Bean,简化Mapper接口的注册

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
    
      // 构造函数时使用Mapper自身的类,因为MapperFactoryBean中的属性mapperInterface通过构造函数传入,以便进行代理
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 实际上该Bean类型为MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);
    }
} 

总结

spring整合mybatis分为两个方面,一个是加载配置以及mapper实例的初始化,这一块主要通过SqlSessionFactoryBean以及MapperFactoryBean来实现,另一方面是执行流程,通过SqlSessionTemplate以及SqlSessionInterceptor实现

  • SqlSessionFactoryBean:实现了InitiallizeBean,初始化时执行afterProperties方法,根据spring配置文件中的dataSource以及configLocations创建SqlSessionFactory
  • MapperFactoryBean:实现了FactoryBean,在获取bean对象时实际上走的是getObject方法,通过sqlSessionTemplate获取mapper实例
  • SqlSessionTemplate:通过实现SqlSessionDaoSupport接口注入SqlSessionFactory对象时,完成SqlSessionTemplate对象的初始化;它在创建mapper对象时还是通过mybatis原生的配置类来完成的。
  • SqlSessionInterceptor:mapper实例在执行增删改时最终会调用到SqlSession的增删改方法,也就是SqlSessionTemplate的一些方法,这些方法都是通过sqlSessionProxy的一个代理对象来完成的,这个代理对象通过SqlSessionInterceptor创建,在执行具体的方法时会走到SqlSessionInterceptor的invoke方法中,这个方法中才会创建真正的SqlSession负责执行增删改方法,同时将SqlSession与事务关联,如果是同一个事物,Spring会保证在改事物中获取到的SqlSession是同一个。

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

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

相关文章

通过抓包分析gPRC协议

通过抓包分析gPRC协议 前言 gRPC 是一个高性能、开源和通用的 RPC 框架&#xff0c;面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本&#xff0c;分别是&#xff1a;grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C, Node.js, Python, Ruby, Objective-C, PHP 和 …

windows + vscode + rust

1 安装VSCODE略2 安装rust插件1、说明&#xff1a;第4步本人是一个一个点击状态。上图禁用按钮在没装之前是显示“安装”按钮&#xff0c;应该点击“安装”也可以。2、还需要安装C插件&#xff0c;搜索C即可&#xff0c;装微软的3 创建rust工程由于初次使用&#xff0c;不知道目…

实战项目-课程潜在会员用户预测(朴素贝叶斯&神经网络)

目录1、背景介绍2、朴素贝叶斯2.1 模型介绍2.2 模型实现3、人工神经网络1、背景介绍 目标&#xff1a;将根据用户产生的数据对课程潜在的会员用户&#xff08;可能产生购买会员的行为&#xff09;进行预测。 平台的一位注册用户是否购买会员的行为应该是建立在一定背景条件下…

TCP、UDP网络编程面试题

TCP、UDP、Socket、HTTP网络编程面试题 什么是网络编程 网络编程的本质是多台计算机之间的数据交换。数据传递本身没有多大的难度&#xff0c;不就是把一个设备中的数据发送给其他设备&#xff0c;然后接受另外一个设备反馈的数据。现在的网络编程基本上都是基于请求/响应方式…

linux 进程

文章目录1、进程的状态2、进程的组织3、进程的控制3.1、进程的创建fork 函数fork 拷贝和共享fork 原理fork 的写时复制exec 函数族exec 原理3.2、进程的终止exit 函数* 僵死进程* 孤儿进程3.3、进程的阻塞和唤醒3.4、进程的切换4、进程间通信5、进程调度算法进程是可执行程序的…

特斯拉无人驾驶解读

来源于Tesla AI Day Tesla无人驾驶算法的核心任务就是如何理解我们所看到的一切呢?也就是说,不使用高端的设备,比如激光雷达,仅仅使用摄像头就能够将任务做得很好。Tesla使用环绕型的8个摄像头获得输入。 第一步是特征提取模块Backbone,无论什么任务都离不开特征…

chatgpt怎么安装?国内怎么玩chatgpt?

关于chatgpt的传言最近真的是闹得沸沸扬扬&#xff0c;主要是这个chatgpt人工智能的冲击力实在是太大了&#xff0c;它学习了大量的语言知识&#xff0c;具有很强的语言能力&#xff0c;无论是写作&#xff0c;还是诗歌&#xff0c;甚至是代码都是不在话下&#xff0c;美国大学…

【数据库】 如何对数据库进行操作

目录 一&#xff0c;SQL语句基础 1&#xff0c; SQL简介 &#xff08;1&#xff09; SQL语句分类 &#xff08;2&#xff09;SQL语句的书写规范 二&#xff0c;数据库操作 1、查看 &#xff08;1&#xff09;查看所有数据库 &#xff08;2&#xff09;查看有没有指定的数…

ubuntu20下Qt5.14.2+OpenCV(含Contrib)-4.5.0环境搭建

Qt若要能处理图片和视频&#xff0c;就必须安装OpenCV&#xff0c;而OpenCV中很多的高级功能如人脸识别等都包含在Contrib扩展模块中&#xff0c;需要将Contrib与OpenCV一起联合编译&#xff0c;目前所用这两个版本都是4.5.0版。 一、下载OpenCV OpenCV的官方下载地址为http:…

SSM整合SpringSecurity简单使用

一、SpringSecurity 1.1 什么是SpringSecurity Spring Security 的前身是 Acegi Security &#xff0c;是 Spring 项目组中用来提供安全认证服务的框架。(官网地址) Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发…

用网络调试助手测试PLC-Reocrder收听模式的过程

目录 一、测试环境 二、步骤及要点说明 1、PLC-Recorder的通道配置 2、PLC-Recorder启动采集 3、配置网络调试助手 4、启动调试助手的连接&#xff0c;并点击“启动批量发送” 5、停止发送&#xff0c;查看发送和接收的情况 三、小结 一、测试环境 Windows10操作系统&a…

Docker进阶 - 11. Docker Compose 编排服务

注&#xff1a;本文只对一些重要步骤和yml文件进行一些讲解&#xff0c;其他的具体程序没有记录。 目录 1. 原始的微服务工程编排(不使用Compose) 2. 使用Compose编排微服务 2.1 编写 docker-compose.yml 文件 2.2 修改并构建微服务工程镜像 2.3 启动 docker-compose 服务…

数据结构(二):单向链表、双向链表

数据结构&#xff08;二&#xff09;一、什么是链表1.数组的缺点2.链表的优点3.链表的缺点4.链表和数组的区别二、封装单向链表1. append方法&#xff1a;向尾部插入节点2. toString方法&#xff1a;链表元素转字符串3. insert方法&#xff1a;在任意位置插入数据4.get获取某个…

RNN神经网络初探

目录1. 神经网络与未来智能2. 回顾数据维度和神经网络1. 神经网络与未来智能 2. 回顾数据维度和神经网络 循环神经网络&#xff0c;主要用来处理时序的数据&#xff0c;它对每个词的顺序是有要求的。 循环神经网络如何保存记忆功能&#xff1f; 当前样本只有 3 个特征&#x…

git基本概念图示【学习】

基本概念工作区&#xff08;Working Directory&#xff09;就是你在电脑里能看到的目录&#xff0c;比如名字为 gafish.github.com 的文件夹就是一个工作区本地版本库&#xff08;Local Repository&#xff09;工作区有一个隐藏目录 .git&#xff0c;这个不算工作区&#xff0c…

新方案:从错误中学习,点云分割中的自我规范化层次语义表示

前言 LiDAR 语义分割通过直接作用于传感器提供的原始内容来完成细粒度的场景理解而受到关注。最近的解决方案展示了如何使用不同的学习技术来提高模型的性能&#xff0c;而无需更改任何架构或数据集。遵循这一趋势&#xff0c;论文提出了一个从粗到精的设置&#xff0c;该设置从…

查找与排序 练习题

1、下列排序算法中&#xff0c;▁▁B▁▁ 是稳定的。 A.简单选择排序 B.冒泡排序 C.希尔排序 D.快速排序 解析&#xff1a;稳定排序是每次排序得到的结果是唯一的&#xff0c;不稳定排序得到的结果不唯一。 稳定&#xff1a;冒泡排序、归并排序、基数排序 不稳定&#x…

DolphinSchedule基于事件驱动的高性能并发编程

文章目录前言前置知识异步编程基于时间驱动的异步编程模式&#xff08;EAP Event-based Asynchronous Pattern &#xff09;实现EAPDolphinSchedule结合Netty实现Master与Worker之间的高性能处理能力的设计方案设计代码实现总结前言 研究DolphinSchedule的内因在于对调度系统并…

内存访问局部性特征

分享一道360的C语言笔试题。x是一个行列均为1000的二维数组&#xff0c;下面代码运行效率最高的是哪个&#xff1f; 二维数组大家都很熟悉&#xff0c;正常人遍历二维数组都是一行一行来的&#xff0c;为什么很少有人按列去遍历&#xff1f; 这道笔试题其实考察的就是遍历效率…

#车载基础软件——AUTOSAR AP技术形态

车载基础软件——AUTOSAR AP技术形态 我是穿拖鞋的汉子! 今天是2023年2月11日,时间好快,疫情解封已好几个月,生活节奏也在逐渐恢复到三年前的节奏。可能是感觉疫情与自己距离变远了,大家也开始慢慢的不再恐惧! 老规矩分享一段喜欢的文字,避免自己成为高知识低文化的工…