前言:
今天咱们对“springboot mybatis crud”可能比较看重,大家都需要了解一些“springboot mybatis crud”的相关知识。那么小编在网上网罗了一些对于“springboot mybatis crud””的相关内容,希望看官们能喜欢,同学们一起来学习一下吧!内容简介:springboot整合mybatis实践和源码解析,共两部分
相关链接:P2-1使用easyCode自动生成代码
本文代码请点赞私信我(回复“代码P2”)获取链接:
背景:win10+64位+idea
一、实践
本文代码是在P2-1使用easyCode自动生成代码 基础上进行的编写。
1-添加依赖
1.1-其中 mysql-connector-java 是MySQL的JDBC驱动包,若没有这个包会出现以下错误
1.2-MyBatis-Spring-Boot-Starter作用是链接Spring Boot和MyBatis,。
该依赖将会提供如下作用
(1)自动检测现有的DataSource
(2)将创建并注册SqlSessionFactory的实例,该实例使用SqlSessionFactoryBean将该DataSource作为输入进行传递
(3)将创建并注册从SqlSessionFactory中获取的SqlSessionTemplate的实例。
(4)自动扫描您的mappers,将它们链接到SqlSessionTemplate并将其注册到Spring上下文,以便将它们注入到您的bean中。
使用了该Starter之后,只需要定义一个DataSource即可(application.properties或application.yml中可配置),它会自动创建使用该DataSource的SqlSessionFactoryBean以及SqlSessionTemplate。会自动扫描你的Mappers,连接到SqlSessionTemplate,并注册到Spring上下文中。
2-添加mybatis配置文件
3-在yml文件中配置mybatis信息和数据库信息。
4-测试
使用postman进行测试
查看控制台,操作成功
二、理论
(一)mybatis执行原理
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。其执行流程如下图所示。
mybatis通过SqlSessionFactoryBuilder类去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory,而SqlSession对象是通过SqlSessionFactory的open方法获取到的,这里我们说下这三个对象的生命周期,对于SqlSessionFactoryBuilder来说,他是为了创建SqlSessionFactory而存在的,SqlSessionFactory创建完成后就可以丢弃不要了,而SqlSessionFactory是创建SqlSession的基础,只要程序在运行,则SqlSessionFactory必须存在,所以SqlSessionFactory可以做成单例存在程序运行期间,而SqlSession由于是线程不安全的,所以存在方法体内或者一次请求的对象更合适。
1-读取mybatis的配置文件,build SqlSessionFactory
首先,SqlSessionFactoryBuilder去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory。源码如下:
2-获取SqlSession对象
当我们获取到SqlSessionFactory之后,就可以通过SqlSessionFactory去获取SqlSession对象。源码如下:
通过以上步骤,咱们已经得到SqlSession对象了,就可以调用SqlSession中的select..., insert..., update..., delete...方法进行CRUD操作了。
3-MapperProxy动态代理
但是我们会发现整个操作过程中存在着重复的动作(创建sqlsession、调用sqlsession方法和关闭sqlsession)。由此接下来将介绍mybatis的另一个重要概念,代理模式。在mybatis中,通过MapperProxy动态代理咱们的dao
一步步跟进来
Proxy.newProxyInstance()方法动态代理我们写的dao接口
通过以上的动态代理,咱们就可以方便地使用dao接口啦,例如
UserDao userMapper = sqlSession.getMapper(UserDao.class);
User user=userMapper .selectOne(user);
我们在调用Mapper进行sql查询的时候,实际上,获取的的类型是经过后置处理的MapperFactoryBean类型,他又会在IOC进行getBean的时候,调用getObject()获取实际的类型,最终实际上是MapperProxy类型,
4-SQL的完整执行流程
接下来以具体的查询数据库的操作为例讲解下SQL的完整执行流程,此处以selectList函数为例
在DefaultSqlSession.selectList中的各种CURD操作都是通过Executor进行的,这里executor的类型是CachingExecutor,接着跳转到其中的query方法中。
接着就进入到了BaseExecutor的query方法中
若是第一次SQL查询操作,会调用queryFromDatabase方法来执行查询。
进入到SimplyExecutor中进行doQuery操作。
上图中的handler.<E>query(stmt, resultHandler);就是调用的PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement)的query方法
到此, 一次sql的执行流程就完了。
参考资料
官方文档(最重要)
(二)springboot整合mybatis原理
上文介绍了mybatis的执行原理,接下来介绍下springboot整合mybatis原理,springboot在整合mybatis时做了一些改变,SqlSessionFactoryBean代替了SqlSessionFactoryBuilder的用,SqlSessionTemplate代替了Sqlsession,看下面的代码我们通过注解的方式自定义了SqlSessionFactory和SqlSessionTemplate,接下来我们看看spring是如何整合mybatis的。
1-依赖引入
查看mybatis-spring-boot-starter的pom文件可以看到,他引入了mybatis-spring-boot-autoconfigure依赖
从mybatis-spring-boot-autoconfigure的jar包中可以看到,在项目进行自动配置时会引入MybatisAutoConfiguration这个类
2-MybatisAutoConfiguration类
在这个配置类中,SpringBoot为我们自动注册了相应的组件:
SqlSessionFactoryBean:用于构建MyBatis的SqlSessionFactory
SqlSessionTemplate:MyBatis的代理类,将SqlSession与Spring的事务进行了整合
ClassPathMapperScanner:提供MyBatis的Mapper的自动扫描
这里有一个点,就是 @EnableConfigurationProperties(MybatisProperties.class);点开MybatisProperties.class文件会发现,这里面声明的就是,在application.properties配置文件中,mybatis提供的配置信息:比如mybatis.mapper-locations=classpath:mapping/*Mapper.xml;
(0)首先看下他的构造函数
MybatisProperties是mybatis相关的属性,在application.yml或者application.properties文件中配置
databaseIdProvider元素主要是支持不同厂商的数据库。举个例子,比如我现在用的是MySQL数据库,而客户只打算用Oracle,这时就可以通过databaseIdProvider进行支持。
ConfigurationCustomizer 主要是提供一个接口可以获取 mybatis 的 configuration 或者更改 configuration 中的属性,比如向configuration 中注册插件。比如出名的分页插件 PageHelpler 就是通过实现这个接口来获取configuration并且注册了拦截器。来拦截sql处理分页逻辑。
(1)-SqlSessionFactory
sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类(mybatis最主要的类,保存着与mybatis相关的东西)。他通过SqlSessionFactoryBean构建,SqlSessionFactoryBean是一个工厂Bean,其作用就是加载用户自定义的配置,然后使用MyBatis的API创建一个SqlSessionFactory。
@ConditionalOnMissingBean注解,即没有这个bean的时候才生效,例如我们自己注册了SqlSessionFactory,因此MybatisAutoConfiguration的就不再实例化了。而SqlSessionTemplate是SqlSession的实现类,可以推测spring使用这个代替mybatis的DefaultSqlSession。
具体实现逻辑见buildSqlSessionFactory方法,
该方法最后通过SqlSessionFactoryBuilder类的build方法生成SqlSessionFactory对象
(2)-SqlSessionTemplate
它的作用就是将SqlSession与当前的事务所绑定,而且是线程安全的,一个SqlSessionTemplate可以被多个dao所共享。SqlSessionTemplate基于动态代理模式,内部委托了一个SqlSession对象,并且在其基础上进行了增强。
创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法(代理对象的InvocationHandler就是SqlSessionInterceptor)
我们看下SqlSessionInterceptor,核心代码就在 SqlSessionInterceptor的invoke方法当中。
然后看下getSqlSession的源码
其中openSession方法就是调用的Java API,它是调用了DefaultSqlSessionFactory类中的openSession方法,前文中已经介绍,此处就不再赘述
3-AutoConfiguredMapperScannerRegistrar
通过ClassPathMapperScanner扫描mapper文件
@Import引入了AutoConfiguredMapperScannerRegistrar类,该注解会触发AutoConfiguredMapperScannerRegistrar实例化
我们来看下AutoConfiguredMapperScannerRegistrar做了什么,首先它继承了ImportBeanDefinitionRegistrar, 所以,spring在refresh的时候,会执行这个类的 registerBeanDefinitions()方法,将 MapperScannerConfigurer存到了beanDefinitionMap中
ClassPathMapperScanner会将所有的Mapper扫描进来,并且将每个Mapper包装成一个类型为MapperFactoryBean的BeanDefinition,注册到IoC容器中。
processBeanDefinitions方法其主要逻辑为
设置 bean 组件初始化时调用的构造函数类型 ·
设置bean 定义的实际类型(在 IOC 容器进行 bean 初始化时,将初始化此类型)
给 bean 组件的属性进行赋值
设置 bean 组件的自动装配类型。
以上就是对mybatis自动装配的介绍。
总结一下:
在SpringBoot项目启动之后,在解析启动类上面的注解时,向Spring容器中导入一个mybatis自定义的BeanDefinitionRegistryPostProcessor的实现类进行扫描注定包下面的接口,在Spring调用bean工厂的后期处理器时,会调用这个mybatis自定义的BeanDefinitionRegistryPostProcessor的实现类,进行扫描指定包然后返回扫描到的BeanDefinition集合,此时,mybatis会对每一个BeanDefinition做一些特殊的处理,改变BeanDefinition的class对象为一个MapperFactoryBean的FactoryBean,然后指定使用有参构造方法创建bean,并制定注入模型按照类型注入,因为要为MapperFactoryBean的父类属性sqlSessionFactory进行复制。
在完成Mapper bean的创建工作时,会首先创建Mapper bean所依赖的sqlSessionFactory,sqlSessionFactory创建完成就相当于mybatis的配置文件、mapper文件里面都已经解析过了,关键是在解析mapper文件时,会将每一个select、delete、insert、update节点都封装成一个MapperStatement对象然后保存到Configuration对象的一个Map,其中key为namespace + select、delete、insert、update节点的id,例如:it.cast.wechat.mapper.AddressMapper.selectByPrimaryKey, value就为select、delete、insert、update节点对应的MapperStatement对象,然后将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。
在构建sqlSessionFactory完成后,会将sqlSessionFactory返回给依赖它的Mapper bean,继续完成Mapper bean的创建,在构建完成Mapper bean之后,就可以使用,在使用这个Mapper bean的时候,会调用其代理对象的invoke方法,然后根据此方法和Class类,去得到StatementId,然后最终会根据StatementId去查找MapperStatement对象并执行。
需要注意点:
如果我们不使用MapperScan注解的时候,必须要用@Mapper注解修饰mapper接口,因为不使用@MapperScan注解的话,是由AutoConfiguredMapperScannerRegistrar完成扫描的,该类在扫描的时候,指定了includeFilters,是Mapper.class;如果我们的mapper接口没有用@Mapper注解,那么在扫描到包下所有的class之后,第一层过滤的时候,就会过滤掉
如果我们使用的是@MapperScan注解,那就无需添加@Mapper注解,因为@MapperScan注解,会通过import注解引入MapperScannerRegistrar,然后在MapperScannerRegistrar类中,会对接口进行扫描,扫描的时候,并没有在includeFilters中指定必须要用@Mapper注解;所以可以不添加@Mapper注解
参考资料