ORM框架MyBatis
数据库操作框架历程
1.JDBC
java database connection(java数据库连接)是一种执行sql语句的核心java api,用java语言编写,为多种关系型数据库提供统一访问接口。
优点是在运行期快捷高效。缺点是在编译器,代码量打,繁琐异常处理,不支持数据库应用跨平台。
调用数据库流程:应用程序->jdbc api->jdbc driver Manager->各数据库jdbc driver->各数据库(Oracle,SqlServer,Mysql)。所以一般使用jdbc或orm的时候,一般要配一个jdbc和driver.
2.DBUtils,一个工具栏,对jdbc的封装
3.Hibernate
一个ORM框架 object relational mapping对象关系映射
只需定义pojo和对应的xml对应关系即可,无需写sql代码
全自动的ORM
缺点是代码耦合度高,全自动化的缺点是不够灵活。
4.JDBC Template,spring框架提供的工具,基于AOP声明式事务
5.mybatis
mybatis是一个持久层框架/半自动的ORM框架,对jdbc进行了封装
每个基于mybatis的应用都是以一个sqlsessionfactory的实例为核心的,sqlsessionfactory实例由sqlsessionFacotryBuilder创建,sqlsessionFacotryBuilder由xml配置文件的configuration元素或一个预先配置的Configuration是来构建。
全局文件和Mapper.xml文件都会解析到Configuration
mybatis单独用时提交事务需要自己提交,其用的自带事务是jdbc事务,需要自己提交。如果和spring集成的话用声明事务就无需自己提交。
openSession()方法调用后会创建一个sqlSession,这个sqlSession具备几个特性:事务作用域会开启,也就是不会自动提交事务、根据当前环境配置的DataSource实例获取Connection对象、事务隔离级别使用驱动或数据源的默认配置、预处理语句不会被复用也不会批量处理更新。这些都是可配置更改的,可以在openSession的时候设置参数。
try(sqlSessionFactory.openSession())这样的写法报错后会自动回滚,且会自动关闭sqlSession。
mybatis内置了jndi,pooled,unpooled三种数据源,但一般不使用内置的数据源,会使用druid连接池
事务隔离级别默认用的是jdbc的事务,如果和spring一起使用的话会使用spring的事务。
mybatis在getMapper会给我们创建jdk动态代理来实现相关数据库操作逻辑
sql参数预解析可以防止sql注入
< select id=“getUserById” resultType=“User”>
SELECT * FROM users WHERE id = #{userId}
</ select>
#{userId} 是一个参数占位符,MyBatis 在执行这个 SQL 语句时会将 userId 参数的值预先设置到 SQL 语句中。这样可以避免 SQL 注入攻击,并且提高 SQL 语句的执行效率。
resultType是自动映射,resultMap是自定义映射
嵌套查询编写更麻烦,一般使用嵌套结果,但是一些情况嵌套结果做不到,比如分页,所以分页需要用到嵌套查询来查,比如查询前面5个员工
<?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.mybatis.vo.Teacher">
<!-- 嵌套结果 -->
<select id="getTeacherByJoin" parameterType="int" resultMap="getTeacherByJoinMap">
SELECT teacher.*,user.id AS userId,user.UserName,user.Sex FROM Teacher
LEFT OUTER JOIN USER ON user.TeacherID = teacher.id where teacher.id = #{id}
</select>
<resultMap type="Teacher" id="getTeacherByJoinMap" autoMapping="true">
<id column="id" property="id"/>
<collection property="users" ofType="User" autoMapping="true">
<id property="id" column="userId"/>
</collection>
</resultMap>
<!-- 一对多嵌套查询 包含子查询 -->
<select id="getTeacherById" parameterType="int" resultMap="getTeacherByIdMap">
select * from Teacher where id = #{id}
</select>
<select id="getUserByTeacherId" parameterType="int" resultType="User">
select * from User where teacherId = #{teacherId}
</select>
<resultMap type="Teacher" id="getTeacherByIdMap" autoMapping="true">
<id column="id" property="id"/>
<collection property="users" column="id" select="getUserByTeacherId" autoMapping="true"></collection>
</resultMap>
</mapper>
缓存分一级缓存和二级缓存。一级缓存基于sqlSession,默认开启,在一次会话中有效。二级缓存也默认开启,但需要自己实现。
一级缓存 (Local Cache):
一级缓存是基于 SqlSession 的缓存,它默认开启,而且是在一次会话中有效。
当你执行一次查询之后,查询结果会被存储在 SqlSession 的缓存中,之后如果再次执行相同的查询,MyBatis 会首先检查一级缓存,如果缓存中有相同的查询结果,则直接返回缓存中的结果,而不会再次发送查询请求到数据库。
二级缓存 (Global Cache):
二级缓存是基于 Mapper 级别的缓存,它默认也是开启的,但是需要手动配置和实现。
二级缓存的作用范围是跨 SqlSession 的,也就是说,不同的 SqlSession 之间可以共享同一个 Mapper 的二级缓存。
为了启用二级缓存,你需要在映射文件中的 标签中添加 cache 属性,并指定一个二级缓存实现类,比如 ,同时你还需要确保相应的实体类实现了序列化接口。
一级缓存默认实现类为PerpetualCache。缓存key:hashcode+sqlid+sql语句。二级缓存有两级map 一级map key为每个mapper.xml的 namespace value为PerpetualCache的map。
一级缓存在查询完就会进行存储到缓存,二级缓存在事务提交或sqlSession关闭的时候存储到缓存。先读二级缓存再读一级缓存
一级缓存介质——内存,二级缓存介质——内存,硬盘。二级缓存由SqlSessionFactory进行管理的。缓存底层实现就是通过HashMap实现的。SqlSessionFactory对象是进程级别的。可以被多个SqlSession所共享。
二级缓存可以存储在内存中,也可以通过第三方缓存框架(例如 EhCache、Redis 等)存储在硬盘上。
数据查找过程:二级缓存 -> 一级缓存 -> 数据库
先查询二级缓存、因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
一般不使用二级缓存 因为二级缓存会被多个会话共享,多个会话同时修改同一条数据或者缓存更新不及时会造成数据脏读
mybatis setting参数无法在spring-ioc配置,所以需要保留mybatis-config.xml
druid 德鲁一
mybatis可以单独使用,可以不用结合spring来用
Mybatis 源码分析
spring整合mybatis原理:通过BeanFactoryPostProcessor和jdk动态代理。通过BeanFactoryPostProcessor扫描所有含有@Mapper注解的接口,通过FactoryBean生成代理类,代理是JDK动态代理机制。JDK动态代理在运行时动态生成代理对象。
spring整合mybatis sqlSession失效: MyBatis 中的SqlSession不是线程安全的,要使sqlSession线程安全,可以用ThreadLocal,让每个线程都有单独的sqlSession。但是这样会导致一级缓存失效,因为不同线程拥有不同的 SqlSession 实例。
如果希望在一个事务中保持一级缓存的有效性,可以考虑使用 @Transactional 注解来管理事务。加@Transactional注解解决缓存失效的原理主要是通过 Spring 的事务管理机制来确保在同一个事务内共享同一个SqlSession 实例,从而保持一级缓存的有效性。
mybatis不建议使用一级缓存,隔离级别优先级更高。
使用JDBC时,可以不显示通过代码加载驱动,可以通过SPI规范在文件中设置声明驱动类,当调用DriverManager.getConnection时会自动加载驱动。
JDBC没有缓存,mybatis有
mybatis架构分为三层:接口层、核心数据处理层(配置解析、参数处理、sql执行、结果集映射)、基础支撑层。
SqlSessionFactoryBuilder().build(reader)读取全局配置文件后
由XMLConfigBuilder来解析全局配置文件mybatis-config.xml(包括setting、数据库信息、数据库环境、类型处理器、别名解析器、插件等)
由XmlMapperBuilder来解析UserMapper.xml这些Mapper.xml文件(crud,resultMap等)。
UNPOOLED表示没有使用连接池,POOLED表示使用连接池。
类型处理器 用于传参和解析resultMap字段类型
mapper扫描配置主要包含
1.基于 XML 配置的映射器扫描,在 MyBatis 的主配置文件中使用 标签配置
2.基于包路径的自动扫描
3.基于 Java 注解的扫描:在 Mapper 接口上使用 @Mapper 注解
MapperAnnotationBuilder
解析mapper配置文件是在MapperAnnotationBuilder类的parse方法里完成的,该方法先解析配置文件(这里会使用XMLMapperBuilder来解析),然后再解析接口里的注解配置,且注解里的配置会覆盖配置文件里的配置,也就是说注解的优先级高于配置文件。
接口的注解如@Insert、@Delete、@Update、@Select。
一级缓存默认实现类为PerpetualCache,默认缓存策略是LRU
SqlSessionFactory的CacheBuilder是用来构建二级缓存的,它的实现原理是使用装饰器模式,一层包一层,职责非常单一。最先包装的缓存是PerpetualCache,然后一层一层包装。二级缓存id为mapper.xml的namespace。一级缓存是SqlSession级别的缓存,二级缓存是整个应用级别的缓存(跨越多个 SqlSession 的长期全局缓存需求),Application级别的缓存。但是找的缓存的时候,会先从TransactionCacheManager中的事务缓存中找,然后一层一层找,最后找的是PerpetualCache。
要使用二级缓存需要在全局配置文件中指定cacheEnabled=true(默认=true),且需要在mapper文件中使用cache节点才会使用二级缓存。一般不使用二级缓存 因为只能指定一个关联namespace,会造成数据脏读(二级缓存会被多个会话共享,多个会话同时修改同一条数据或者缓存更新不及时会造成数据脏读)。另外在执行具体的语句时如<select…/>可以配置useCache来控制具体的语句是否使用缓存。
每个CRUD最终都会封装成一个MapperdStatement,每个MapperdStatement是由XmlStatementBuilder来解析的。
SqlSource:在 MyBatis 中,SqlSource 是用于封装 SQL 语句及其参数映射的接口。它是执行 SQL 语句的核心组件之一,负责将 SQL 语句和参数信息组合成可执行的 SQL 对象。
SqlSource分动态(DynamicSqlSource)和静态的(RawSqlSource)
DynamicSqlSource是带动态参数的。RawSqlSource是不带动态参数的,能立马解析的。RawSqlSource 最终会在解析过程中被转换成 StaticSqlSource 的实例。
动态DynamicSqlSouce解析
会将每个sql标签解析成一个一个的SqlNode,然后使用组合设计模式来进行组装。
动态SqlSource在使用SqlNode包装完后还不是最终的sql语句,它是在执行sql语句时确定参数后解析成BoundSql之后才能确定。
SqlSession利用门面模式向外提供接口,最终做事的是Executor。
SqlSession 的设计使用了门面模式,通过将复杂的底层操作封装在一个简单的接口中,提供了更加方便的数据库访问方式。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne(“getUserById”, 1);
执行器分以下3种,SIMPLE、REUSE、BATCH。区别是是否会重用预处理语句和批量更新。
二级缓存执行器是CachingExecutor,里面通过一个delegate引用一级缓存执行器BaseExecutor。使用了装饰器模式。
MyBatis的缓存分为一级缓存和二级缓存,一级缓存默认是开启的,而且不能关闭。
如果全局配置文件设置了cacheEnabled=true,则会先调用二级缓存Executor(CachingExecutor),在CachingExecutor执行的时候会再判断mapper.xml文件是否使用了,如果使用了则从二级缓存中找,如果二级缓存没有找到,就执行一级缓存Executor(BaseExecutor),从一级缓存中找。
CachingExecutor从二级缓存获取缓存(调用query方法)的时候,首先会先调用TransactionalCacheManager从事务缓存中开始找起,如果没找到就从他的delegate一层一层找。
事务缓存在使用事务的时候会用到,会先将缓存保存到TransactionalCacheManager临时缓存Map中,如果事务提交,对二级缓存的操作才会生效,如果事务回滚或者不提交事务,则不对缓存产生影响。
mybatis插件
mybatis可以自定义插件,基于插件机制可以开发出分页插件等。
mybatis的插件可以在不修改原来的代码的情况下,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理SQL,处理结果。
mybatis插件的原理是动态代理。
mybatis插件可以有多个,插件实现了Interceptor拦截器接口(插件其实就是一个拦截器),通过责任链模式执行每一个插件。
如果当前的插件增强的是执行器才会为执行器创建动态代理。
插件使用了装饰器模式,将每个插件包装集成到执行器。
mapper.xml Sql执行解析过程就是解析每个SqlNode得到一个sql语句,然后交给jdbc执行,然后解析封装结果,然后返回结果。执行前后,如果有插件,会回调插件方法。
spring sqlSession是由SqlSessionTemplate通过sqlSessionTemplate.getSqlSessionFactory().openSession来创建。而mybatis sqlSession是直接由SqlSessionFactory创建的。
SpringBoot多数据源
多数据源使用场景:1.一个应用,业务复杂,数据量大,数据分布在不同的数据库中,数据拆了,应用没拆 2.读写分离,解决读性能瓶颈(写锁会影响读阻塞,从而影响读的性能)
读写分离有很多中间件可以实现,下面我们探讨如果要自己实现的话要怎么实现,其实现原理。spring-jdbc提供了、AbstractRoutingDataSource,其内部可以包含多个数据源,每次只能使用一个,可以动态指定哪一个。如果有多个数据源可以使用@Primary来指定主数据源。
多数据源的原理是:1.继承AbstractRoutingDataSource,初始化所有数据源,通过模板方法返回当前数据源标识。2.对于不同的业务场景提供不同的切换数据源方式:基于AOP+自定义注解(这种适合业务复杂(数据量大))、mybatis插件(适合读写分离)
更简单实现多数据源方式,集成多个MyBatis框架 实现多数据源,也就是一个应用集成多个mybatis,上面是在一个mybatis框架配置多个数据源
多数据源事务控制:事务控制只能针对一个数据源进行控制。可以通过spring的声明式事务来进行多个数据源事务控制。但是正确应该是用分布式事务思想来控制,比如seata、rocketmq。也可以使用开源DynamicDataSource多数据源框架(baomidou dynamic-datasource),它是springboot的多数据源组件,支持seata分布式事务。