文章 | 状态 | 时间 | 描述 |
---|---|---|---|
(一)Mybatis 基本使用 | 已复习 | 2022-12-14 | 对Mybtais的基本使用,能够开发 |
(二)Mybatis-config.xml的初始化 | 已复习 | 2023-02-10 | 对我们编写的mybatis配置文件的解析 |
(三)SqlSessionFactory的初始化 | 已复习 | 2023-02-11 | SqlSession会话工厂的初始化 |
(四)Mapper文件的解析 | 已复习 | 2023-02-12 | 主要对我们编写的Mapper.xml进行解析 |
(五)SqlSession的创建 | 已复习 | 2023-02-13 | 主要介绍构建DefaultSqlSessionFactory |
(六)Mapper的接口代理 | 已复习 | 2023-02-14 | 如何通过动态代理来执行我们编写的方法 |
(七)MapperMethod的INSERT分析 | 已复习 | 2023-02-15 | 通过代理对象来执行Insert语句,返回结果 |
(八)MapperMethod的Select分析 | 已复习 | 2023-02-16 | 通过代理对象来执行Select语句,返回结果 |
(九)Mybatis的PreparedStatement | 已复习 | 2023-02-17 | 预处理语句的常见,以及与数据库打交道 |
(十)Mybatis的结果隐射 | 已复习 | 2023-02-18 | 数据库结果与实体类对象的转换 |
(十一)Mybatis的一级缓存与二级缓存 | 已复习 | 2023-02-24 | Mybatis中一级缓存与二级缓存 |
(十二)Mybatis的插件开发及原理分析 | 已复习 | 2023-02-25 | Mybatis中的插件运行机制与开发 |
(十三)Mybatis中的四大组件梳理 | 计划中 | – | Mybatis中的四大组件的梳理 |
(十四)Mybatis中的设计模式梳理 | 计划中 | – | Mybatis中设计模式的整理 |
(十五)Spring-Mybatis整理 | 计划中 | – | Spring与Mybatis整合 |
(十六)Mybatis疑惑总结 | 计划中 | – | 我遇到的疑惑与问题 |
详细参考:Spring整合MyBatis——超详细_spring mybatis整合
依赖导入
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.22version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.3version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.3version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.3version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.2version>
dependency>
dependencies>
实体类
package com.shu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author SHU
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String email;
private Integer age;
private Integer sex;
private String schoolname;
}
编写Mapper文件
package com.shu.mapper;
import com.shu.pojo.User;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @description:
* @author: shu
* @createDate: 2023/3/18 19:46
* @version: 1.0
*/
@Repository
public interface UserMapper {
/**
* 查询所有用户
* @return
*/
List<User> getAllUser();
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shu.mapper.UserMapper">
<select id="getAllUser" resultType="com.shu.pojo.User">
select * from user
select>
mapper>
编写配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true">property>
<property name="username" value="root">property>
<property name="password" value="123456">property>
bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.shu.mapper"/>
bean>
beans>
测试类
package com.test;
import com.shu.mapper.UserMapper;
import com.shu.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* @description:
* @author: shu
* @createDate: 2023/3/18 20:49
* @version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class SpringMyBatisTest {
@Autowired
UserMapper mapper;
@Test
public void testFindUserList(){
List<User> userList = mapper.getAllUser();
for (User user : userList) {
System.out.println(user);
}
}
}
上面我们把一个测试环境搭建完毕,方便我们自己更好的理解源码,根据我们上面的重点,我们可以看到一个关键类:SqlSessionFactoryBean,下面我们来看看
Spring中的概念可以帮助我们理解,但是这里不会详细讲解Sring的分析,后面在详细介绍
Spring容器在启动时,首先会对Bean的配置信息进行解析,把Bean的配置信息转换为BeanDefinition对象,BeanDefinition是一个接口,通过不同的实现类来描述不同方式配置的Bean信息。
BeanDefinition用于描述Spring Bean的配置信息,Spring配置Bean的方式通常有3种
BeanDefinitionRegistry是BeanDefinition容器,所有的Bean配置解析后生成的BeanDefinition对象都会注册到BeanDefinitionRegistry对象中,Spring提供了扩展机制,允许用户在Spring框架启动时,动态地往BeanDefinitionRegistry容器中注册BeanDefinition对象。
BeanFactory是Spring的Bean工厂,负责Bean的创建及属性注入,它同时是一个Bean容器,Spring框架启动后,会根据BeanDefinition对象创建Bean实例,所有的单例Bean都会注册到BeanFactory容器中。
BeanFactoryPostProcessor是Spring提供的扩展机制,用于在所有的Bean配置信息解析完成后修改Bean工厂信息。
ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于Spring解析Bean的配置阶段,当解析@Configuration注解时,可以通过ImportBeanDefinitionRegistrar接口的实现类向BeanDefinitionRegistry容器中添加额外的BeanDefinition对象。
Bean的后置处理器,在Bean初始化方法(init-method属性指定的方法或afterPropertiesSet()方法)调用前后,会执行BeanPostProcessor中定义的拦截逻辑。
ClassPathBeanDefinitionScanner是BeanDefinition扫描器,能够对指定包下的Class进行扫描,将Class信息转换为BeanDefinition对象注册到BeanDefinitionRegistry容器中。
FactoryBean是Spring中的工厂Bean,通常用于处理Spring中配置较为复杂或者由动态代理生成的Bean实例。
SqlSessionFactoryBean是一个FactoryBean,通过名称sqlSessionFactory从Spring容器中获取Bean时,获取到的实际上是SqlSessionFactoryBean对象的getObject()方法返回的对象。
首先看看我们的配置文件,我们配置的SessionFactory的Bean,我们再来看看这个接口信息
<!-- 配置SessionFactory的Bean -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 指定MyBatis配置文件的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 指定MyBatis的Mapper文件的位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
SqlSessionFactoryBean
// 解析mybatisConfig.xml文件和mapper.xml,设置数据源和所使用的事务管理机制,将这些封装到Configuration对象
// 使用Configuration对象作为构造参数,创建SqlSessionFactory对象,其中SqlSessionFactory为单例bean,最后将SqlSessionFactory单例对象注册到spring容器。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
// mybatis配置mybatisConfig.xml的资源文件
private Resource configLocation;
//解析完mybatisConfig.xml后生成Configuration对象
private Configuration configuration;
// mapper.xml的资源文件
private Resource[] mapperLocations;
// 数据源
private DataSource dataSource;
// 事务管理,mybatis接入spring的一个重要原因也是可以直接使用spring提供的事务管理
private TransactionFactory transactionFactory;
private Properties configurationProperties;
// mybatis的SqlSessionFactoryBuidler和SqlSessionFactory
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
// 实现FactoryBean的getObject方法
@Override
public SqlSessionFactory getObject() throws Exception {
//...
}
// 实现InitializingBean的
@Override
public void afterPropertiesSet() throws Exception {
//...
}
// 为单例
public boolean isSingleton() {
return true;
}
}
我们可以 看到SqlSessionFactory的接口设计如下:实现了spring提供的FactoryBean,InitializingBean和ApplicationListener这三个接口,在内部封装了mybatis的相关组件作为内部属性,如mybatisConfig.xml配置资源文件引用,mapper.xml配置资源文件引用,以及SqlSessionFactoryBuilder构造器和SqlSessionFactory引用。
bean实现的接口,这些bean需要在BeanFactory设置其所有属性后做出反应:例如,执行自定义初始化,或者仅仅检查是否已设置了所有强制属性。
public interface InitializingBean {
/**
* Invoked by the containing {@code BeanFactory} after it has set all bean properties
* and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
* This method allows the bean instance to perform validation of its overall
* configuration and final initialization when all bean properties have been set.
* @throws Exception in the event of misconfiguration (such as failure to set an
* essential property) or if initialization fails for any other reason
*/
void afterPropertiesSet() throws Exception;
}
我们来看看SqlSessionFactoryBean组件的初始化代码,其实就是SqlSessionFactory的初始化构建,当然我们也可以自定义一个类,实现这个接口来完成一些组件类的初始化
SqlSessionFactoryBean
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 创建sqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 配置类
Configuration configuration;
// 解析mybatis-Config.xml文件,
// 将相关配置信息保存到configuration
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
//资源文件不为空
} else if (this.configLocation != null) {
//根据configLocation创建xmlConfigBuilder,XMLConfigBuilder构造器中会创建Configuration对象
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
//将XMLConfigBuilder构造器中创建的Configuration对象直接赋值给configuration属性
configuration = xmlConfigBuilder.getConfiguration();
}
//略....
if (xmlConfigBuilder != null) {
try {
//解析mybatis-Config.xml文件,并将相关配置信息保存到configuration
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
}
}
if (this.transactionFactory == null) {
//事务默认采用SpringManagedTransaction,这一块非常重要,我将在后买你单独写一篇文章讲解Mybatis和Spring事务的关系
this.transactionFactory = new SpringManagedTransactionFactory();
}
// 为sqlSessionFactory绑定事务管理器和数据源
// 这样sqlSessionFactory在创建sqlSession的时候可以通过该事务管理器获取jdbc连接,从而执行SQL
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 解析mapper.xml
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 解析mapper.xml文件,并注册到configuration对象的mapperRegistry
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
// 将Configuration对象实例作为参数,
// 调用sqlSessionFactoryBuilder创建sqlSessionFactory对象实例
return this.sqlSessionFactoryBuilder.build(configuration);
}
总结
buildSqlSessionFactory的核心逻辑:解析mybatis配置文件mybatisConfig.xml和mapper配置文件mapper.xml并封装到Configuration对象中,最后调用mybatis的sqlSessionFactoryBuilder来创建SqlSessionFactory对象。这一点相当于前面介绍的原生的mybatis的初始化过程。另外,当配置中未指定事务时,mybatis-spring默认采用SpringManagedTransaction,这一点非常重要,请大家先在心里做好准备。此时SqlSessionFactory已经创建好了,并且赋值到了SqlSessionFactoryBean的sqlSessionFactory属性中。
由BeanFactory中使用的对象实现的接口,这些对象本身就是单个对象的工厂,如果一个bean实现了这个接口,那么它将被用作要公开的对象的工厂,而不是直接用作将自己公开的bean实例。
spring的IOC容器在启动,创建好bean对象实例后,会检查这个bean对象是否实现了FactoryBean接口,如果是,则调用该bean对象的getObject方法,在getObject方法中实现创建并返回实际需要的bean对象实例,然后将该实际需要的bean对象实例注册到spring容器;如果不是则直接将该bean对象实例注册到spring容器。
我们来看看getObject()方法
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
总结
SqlSessionFactoryBean的getObject方法实现如下:由于spring在创建SqlSessionFactoryBean自身的bean对象时,已经调用了InitializingBean的afterPropertiesSet方法创建了sqlSessionFactory对象,故可以直接返回sqlSessionFactory对象给spring的IOC容器,从而完成sqlSessionFactory的bean对象的注册,之后可以直接在应用代码注入或者spring在创建其他bean对象时,依赖注入sqlSessionFactory对象。