mybatis篇(三):mybatis整合扩展

mybatis

涉及到的设计模式

  1. 装饰器模式
    1. Cache->PerpetualCache->LruCache
    2. Executor->SimpleExecutor->CacheExecutor

image-20200629174258186

  1. 工厂方法模式
    1. SqlSessionFactory
    2. MapperProxyFactory
    3. ObjectProxy
  2. 单例模式:
    1. ErrorContext:

image-20200629155450299

  1. 代理模式:MapperProxy、Plugin、SqlSessionInterceptor等
  2. 模版方法模式:BaseExecutor和SimpleExecutor等
  3. 建造者模式:
    1. XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder
  4. 适配器模式:Log4j
  5. 责任链模式:InterceptorChain

PageHelper插件原理

pageHelper插件是作用在Executor层的代理,在构建Executor的时候生成代理类,若有多个插件,则嵌套生成代理类(责任链模式)

image-20200629170726627

接着我们看下mybatis中Plugin类构建的代理类逻辑

image-20200629171031036

可以看到执行的是inteceptor的intercept方法,而inteceptor就是配置文件中声明的interceptor参数的实例化对象,在初始化xml解析的时候放入configuration当中InterceptorChain数组当中

image-20200629171559690

因此方法调用实际上是调用了PageInterceptor的intercept方法

该方法中持有传入的方法名、参数、sql等信息,就可以进行sql查询了,包括缓存处理也是在这里面,因为该方法持有存二级缓存的statement对象以及存一级缓存的Exetutor对象

内部机制:分页插件内部实际上执行了两个sql,一个count语句,一个获取实际数据的语句,在源码或者实操中都可以验证

image-20200629173112848
image-20200629173025547

那么分页参数是怎么来的呢,因为PageInterceptor物理分页,因此需要page等参数,执行sql前,若用分页插件,我们都会添加该语句PageHelper.startPage(1,2)

image-20200629172546172

可以看出分页参数是放在了ThreadLocal当中,那么有个问题,若ThreadLocal没有及时清除的话,会影响该线程后续sql查询。我们看下PageInterceptor类当中是否有释放逻辑

image-20200629172706516

image-20200629172722422

可以看出在afterAll方法里面执行了Threadlocal的remove操作,因此线程后续操作需要分页,需要重新执行PageHelper.startPage(1,2)

  • 多个插件使用其实就是通过责任链模式生成了多层代理

如何实现自定义插件

看了PageHelper的原理分析,那么最快的了解插件原理的方式,便是查看PageHelper的写法,首先是继承interceptor接口

image-20200629173315377

然后是生命插件的作用域,因为插件可以作用在多个地方。PageHelper作用在Executor上面

image-20200629173425010
实现interceptor的三个接口方法(mybatis约定的接口实现类)

  1. intercept:查询实际调用的方法
  2. plugin:用于生成代理类的方法
  3. setProperties:mybatis-config.xml声明插件可以指定properties参数,这边用于设值

image-20200629173518189

最后需要在mybatis-config.xml中注册插件

应用场景

  1. 日志输出
  2. 自定义一个注解,指定分表逻辑
  3. 数据加密:与mysql交互前后进行加密
  4. 权限控制

整合spring

SqlSessionFactoryBean源码分析

该类实现了FactoryBean、InitializingBean接口,在类初始化bean属性值设置完后会调用afterPropertiesSet方法

image-20200629220105559

image-20200629220119018

该方法就是原生mybatis中sqlsessionFactory的构建过程

接下来就是构建DefaultSqlSession,与原生mybatis不同的是,spring对其封装了

常问的重点问题

为什么原生mybatis中DefaultSqlSession是非线程安全的?

主要问题还是在一级缓存当中

image-20200629222221280

如图,线程执行到到第一步的时候,localCache存入的是EXECUTION_PLACEHOLDER,之后执行到第二个红框内的时候才是list,那么我们在来看一下外层的判断

image-20200629222354494
可以看出,并发下,多个线程如果持有同一个Sqlsession,可能导致这里转换异常。还有就是若是同个Sqlsession下。一个线程插入未完成,另外个线程写入缓存,就会导致数据的不一致性。还有就是多个Sqlsession共享一个connection的线程安全性

并发下,若每个请求创建一个Sqlsession是没有线程安全性问题,这样就导致Sqlsession无法作为单例,也没法复用的问题

spring做的处理就是持有一个单例sqlsessionTemplate,然后通过threadLocal维护线程的sqlsession

image-20200629230757216

mybatis原生不用SqlSessionManager的原因是:未集成spring的情况下管理动态代理效率不高,因此直接分开管理。每次请求都自己创建sqlsession。

SqlSessionTemplate更为高效,有做引用计数等复用

SqlSessionTemplate源码分析

  1. spring是通过构造注入SqlSessionTemplate的
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

image-20200630094032135

  1. 可以看到SqlSessionTemplate当中的select()都是通过sqlSessionProxy去操作的

那么我们看sqlSessionProxy是如何生成的

image-20200630094131714

  1. 在SqlSessionTemplate的构造方法可以看出,实际操作是通过SqlSessionInterceptor这个代理类去处理的,SqlSessionInterceptor.invoke方法中通过getSqlSession方法获取SqlSession

image-20200630094829485

  1. TransactionSynchronizationManager.getResource里面调用的doGetResource方法。可以找到存储SqlSessionHolder的对象resources,通过ThreadLocal保证线程安全

image-20200630095334296

basedao的基本原理

basedao的两种实现思路

  1. 若是自己定义basedao然后xml的save、insert、delete是自动生成的,底层可通过sqlsessionTemplate去获取操作的

那么我们要如何拿到sqlsessionTemplate这个对象呢:

mybatis提供了一个扩展类SqlSessionDaoSupport,里面持有sqlsessionTemplate对象

image-20200630095918112

有了这个。我们就可以自定义basedao通过反射去组装statementId,然后通过SqlSessionTemplate去查询

  1. 若是基本操作的xml也不写的话,可结合@SelectProvider和@RegisterMapper去做

image-20200629194959269

image-20200629195027369

原理上是自己组成sql语句,mybatis解析的话跟xml解析的时候一致的。这种方式的sql底层也是通过调用@SelectProvider中type对象中相应的方法生成的sql语句。

总结:

  1. 插件的原理就是通过代理模式去作用在mybatis不同组件上
  2. SqlSessionTemplate线程安全是通过线程池管理每个线程下的SqlSession
  3. 原生mybatis的DefaultSqlSession线程不安全:因为若多个线程持有同一个SqlSession,就没法控制session.commit事务提交的时机。当然还有其他创建的线程安全性问题
  4. basedao的原理:两种方式,通过持有SqlSessionTemplate去封装;或者通过@SelectProvider加上反射去实现sql语句的生成

mybatis主流问题

#{}和${}的区别:

  1. #{}是生成预编译sql(?替换占位符):PreparedStatement的set来赋值

  2. ${}就是替换变量的值,直接的静态string替换,可能88会有sql注入

mybatis dao层如何找到xml文件中对应的sql:

约定里,接口的权限定名称以及方法名要对应xml中的namespace以及MappedStatement的id值。那么根据反射就能获取到接口名+方法名就能获取对应的mybatis解析xml文件的MappedStatement对象。里面包括了sql语句。方法调用是通过代理模式去做的

mybatis如何分页:

分为物理分页和逻辑分页

  1. 物理分页:即通过插件修改执行的sql语句,添加limit等。常用的pageHelper插件就是物理分页,原理是通过代理Executor去修改sql语句,分页参数存储在ThreadLocal当中
  2. 逻辑分页:内存分页,查出数据后在进行分页处理。mybatis通过RowBounds对象进行逻辑分页

mybatis插件原理:

mybatis可通过插件进行干预的4个对象有:ParameterHandler、ResultSetHandler、StatementHandler、Executor

实际执行的是mybatis的Interceptor接口,需要实现的方法有3个

mybatis延迟加载原理:

mybatis关联查询有association和collection:区别在与一对一和一对多

延迟加载的原理:可对比hibernate的延迟加载

mybatis的延迟加载时通过代理模式。当获取对象的成员属性的时候,若为空,会去执行事先存储的sql

mybatis的xml映射文件当中:xml文件的id是否能一致:

根据mybatis源码存储MapperStatement对象的map来看,key是nameSpace+id组成。因此相同id不影响数据存入

mybatis批处理原理:

mybatis的3个处理器:simple、reuse(重复利用Statement)、batch,其中批处理采用batchExecutor,通过一个对象存储批处理sql

List statementList

mybatis可不可以映射成一些特殊类型,枚举、json等:

mybaits可以自定义typeHandler的,通过集成BaseTypeHandler可以实现


参考资料:之前的源码自己debug跑通一遍的话,主流问题也是很容易理解的

主流问题参考地址:https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/mybatis/mybatis-interview.md


穷尽所有可以提升自己的机会。—共勉

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以按照以下步骤来实现Spring Boot与MyBatis整合,并实现省市区级查询: 1. 首先,在你的Spring Boot项目中添加MyBatisMyBatis-Spring的依赖。你可以在项目的pom.xml文件中添加以下依赖: ```xml <dependencies> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!-- MySQL 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <!-- 其他依赖... --> </dependencies> ``` 2. 在你的Spring Boot配置文件application.properties或application.yml中添加数据库连接配置,例如: ```properties # 数据库连接配置 spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name spring.datasource.username=your_username spring.datasource.password=your_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MyBatis 配置 mybatis.mapper-locations=classpath:mapper/**/*.xml mybatis.type-aliases-package=com.example.yourproject.model ``` 3. 创建省市区的数据表,并定义对应的实体类。 4. 创建MyBatis的Mapper接口,用于定义省市区数据表的增删改查操作。例如,创建一个CityMapper接口: ```java public interface CityMapper { City getById(int id); List<City> getByProvinceId(int provinceId); // 其他查询方法... } ``` 5. 创建MyBatis的Mapper XML文件,对应CityMapper接口的方法。例如,创建一个cityMapper.xml文件: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.yourproject.mapper.CityMapper"> <resultMap id="BaseResultMap" type="com.example.yourproject.model.City"> <id column="id" property="id" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="province_id" property="provinceId" jdbcType="INTEGER"/> <!-- 其他字段映射... --> </resultMap> <select id="getById" resultMap="BaseResultMap"> SELECT * FROM city WHERE id = #{id} </select> <select id="getByProvinceId" resultMap="BaseResultMap"> SELECT * FROM city WHERE province_id = #{provinceId} </select> <!-- 其他查询语句... --> </mapper> ``` 6. 创建Service层和Controller层,用于调用MyBatis的Mapper接口进行查询操作。例如,创建一个CityService和CityController: ```java @Service public class CityService { @Autowired private CityMapper cityMapper; public City getById(int id) { return cityMapper.getById(id); } public List<City> getByProvinceId(int provinceId) { return cityMapper.getByProvinceId(provinceId); } // 其他查询方法... } @RestController public class CityController { @Autowired private CityService cityService; @GetMapping("/city/{id}") public City getCityById(@PathVariable int id) { return cityService.getById(id); } @GetMapping("/city/province/{provinceId}") public List<City> getCitiesByProvinceId(@PathVariable int provinceId) { return cityService.getByProvinceId(provinceId); } // 其他接口... } ``` 7. 启动Spring Boot应用程序,并测试接口的调用。例如,通过访问`http://localhost:8080/city/1`可以获取id为1的城市信息,通过访问`http://localhost:8080/city/province/1`可以获取省份id为1的所有城市信息。 以上是整合Spring Boot和MyBatis实现省市区级查询的基本步骤,你可以根据自己的需求进行修改和扩展。希望对你有帮助!如果你有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值