Mybatis技能提升

mybatis是什么呢?
mybatis是一个半ORM(对象关系映射)持久层框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。为什么说是半ORM,因为MyBatis需要程序员自己编写 Sql 语句,因此带来的一个优点是可以严格控制 sql 执行性能,灵活度高

mybatis的比较核心的东西就是 SqlSessionFactory,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

//定义全局变量
String resource = "org/mybatis/example/mybatis-config.xml";
//定义输入流 读取全局变量对应的配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//将输入流放在build方法中,返回 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

举个例子:
在这里插入图片描述
对应的BlogMapper.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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

mybatis-config.xml文件如下

<?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">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

mybatis-config.xml 配置文件中主要包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)

输出结果
在这里插入图片描述
上面是导入Mybatis源码后对数据库的操作进行的结果返回,然后我们来进行分析Mybatis是怎么工作的,工作原理是什么,有哪些步骤
我们平时集成Mybatis时通常情况下要做下面三个步骤
获取数据库连接,获取执行SQL语句,对数据库进行操作返回操作结果
在这里插入图片描述
那么怎么拿到数据库连接呢
在这里插入图片描述

我们到源码中查看前两行代码是获取配置文件的,那么获取源码的代码就再这里SqlSessionFactoryBuilder().build(inputStream)方法
bulid方法调用了parse方法
在这里插入图片描述
parse方法又执行了parseConfiguration方法
在这里插入图片描述
parseConfiguration方法就是读取XML文件的,
在这里插入图片描述
因为数据源是配置在environments标签中的所以我们看environmentsElement方法
在这里插入图片描述
environmentsElement方法中读取数据源的方法

在这里插入图片描述
获取对应的参数,并且newInstance数据源对象
在这里插入图片描述
将其set到configuration的Environment 属性中
在这里插入图片描述
整体步骤:
第一步调用SqlSessionFactoryBuilder.build方法
第二步调用XMLConfigBuilder.parse方法
第三步调用XMLConfigBuilder.parseConfiguration方法
第四步调用XMLConfigBuilder.environmentsElement方法
第五步调用XMLConfigBuilder.dataSourceElement方法
第六步调用Configuration.setEnvironment方法

在这里插入图片描述
Configuration是SqlSessionFactory的一个属性,而SqlSessionFactoryBuilder在build方法中实际上就是调用XMLConfigBuilder对xml文件进行解析,然后注入到SqlSessionFactory中。

SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行

拿到数据源了,就看怎么获取SQL语句了
获取SQL语句前先提出一个问题
Mybatis的mappers标签有几种方式可以加载?
在这里插入图片描述

答案是 4种
在这里插入图片描述
文档给出的
在这里插入图片描述
知道了是4种方式,那么这4种方式的优先级又是什么呢?
package > resource > url > mapperClass
首先判断的是package,然后判断resource,其次判断Url 最后才是Class在这里插入图片描述
在这里我使用的是resource所以我看这个判断方法
在这里插入图片描述
依旧是读取配置文件,将配置文件转换未流,然后调用parse方法,不同的是此次调用的是XMLMapperBuilder实例调用的
这个也是读取标签的方法在这里插入图片描述
在这里插入图片描述
接着往下走
在这里插入图片描述
生成完实例后调用了在这里插入图片描述
在这里插入图片描述

最后将其添加至configuration的addIncompleteStatement属性中
在这里插入图片描述
大概步骤
第一步调用SqlSessionFactoryBuilder.build方法
第二步调用XMLConfigBuilder.parse方法
第三步调用XMLConfigBuilder.parseConfiguration方法
第四步调用XMLConfigBuilder.mapperElement方法
第五步调用XMLConfigBuilder.configurationElement方法
第六步调用XMLStatementBuilder.parseStatementNode方法
第七步调用Configuration.addMappedStatement方法

然后就是对结果集的处理了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在查询数据库之前还会查一个缓存
在这里插入图片描述
然后执行selectOne方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里还是又一个判断缓存的逻辑的
在这里插入图片描述
如果缓存中没有数据的话就走这个方法
在这里插入图片描述
调用doQuery方法
在这里插入图片描述
然后调用query方法设置参数读取结果集在这里插入图片描述
大概步骤
第一步调用DefaultSqlSessionFactory.openSession方法
第二步调用Configuration.newExecutor方法
第三步调用executor.SimpleExecutor方法
第四步调用DefaultSqlSessionFactory.selectOne方法
第五步调用DefaultSqlSessionFactory.selectList方法
第六步调用CachingExecutor.query方法
第七步调用BaseExecutor.doQuery方法
第八步调用PreparedStatementHandler.query方法
第九步调用DefaultResultSetHandler.handleResultSets方法
在这里插入图片描述
然后就是对Mybatis中缓存的理解
一级缓存怎么开启的?
一级缓存是默认就是开启的
开启后是怎么使用的?
一级缓存使用过程简介:查询学生表数据,程序发起SQL指令,查询张三同学的个人信息,这个时候会先查缓存中有没有数据,如果没有数据,就会查数据库,从数据查到数据以后,会将数据保存到缓存中,返回给用户,等待下次调用,如果第二次调用还是查张三同学信息,这个时候,直接查缓存中的数据,就不用在去查询数据库了
使用中有什么问题?
会存在数据一致性的问题 比如缓存中张三同学的年龄是18 后台运维同事将其年龄改为了28,这个时候就存在缓存与数据库数据不一致的问题了
问题如何解决?
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应,更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存?
原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。
比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?
举个栗子
一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存

二级缓存
一级缓存就是只能在同一个SqlSession中有效,脱离了同一个SqlSession就没法使用这个缓存了,有的时候我们可能希望能够跨SqlSession进行数据缓存。那么这个时候需要我们进行手动开启二级缓存。
二级缓存的开启方式其实很简单,只需要我们在userMapper.xml中配置

<cache/>

节点即可。如下:

<?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="org.sang.db.UserMapper">
    <cache/>
    <select id="getUser" resultType="org.sang.bean.User" parameterType="Long">
        select * from user where id = #{id}
    </select>
    <insert id="insertUser" parameterType="org.sang.bean.User">
        INSERT INTO user(username,password,address) VALUES (#{username},#{password},#{address})
    </insert>
    <delete id="deleteUser" parameterType="Long">
        DELETE FROM user where id=#{id}
    </delete>
    <select id="getAll" resultType="u">
        SELECT * from user
    </select>
</mapper>

这样简单配置之后,二级缓存就算开启了,这样的配置中,许多东西都是默认的,比如所有的select语句都会被缓存,所有的delete、insert和update则都会将缓存刷新,还比如缓存将使用LRU算法进行内存回收等。那么这些东西如果需要配置的话,我们可以按如下方式进行配置:

<cache eviction="LRU" flushInterval="20000" size="1024" readOnly="true"/>

这里的eviction表示缓存策略,除了LRU之外还有先进先出(FIFO)、软引用(SOFT)、弱引用(WEAK)等,flushInterval则表示刷新时间,表示缓存的对象个数,readOnly为true则表示缓存只可以读取不可以修改
这样的话二级缓存就配置成功了
如果本地要使用二级缓存的话,要求实体类序列化,我们的实体类实现Serializable接口即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值