MyBatis

MyBatis是JDBC的封装,先回顾下JDBC!它的核心API接口包括了
1、DriverManager类 (管理和注册数据库驱动)
2、Connection接口:一个连接对象,创建(Prepared)Statement对象
3、Statement接口 4、ResultSet接口
基本步骤:
1)注册驱动
2)获取连接
3)Connection获取Statement对象
4)使用Statement对象执行SQL语句
5)返回结果集
6)释放资源
SQL注入:select * from user where name =‘username’and password =‘renyi’or‘1’=‘1’早期网页登录可以随意登录,如早期京东。
减少SQL注入,提高效率,数据库优化Statement变为预编译PreparedStatement。JDBC连接数据库操作:

public class TEST {
public static void main(String[] args) {
Connection coon = null;
PapredStatement pstmt = null;
ResultSet rs = null;
	try {
		//加载MYSQL驱动,反射机制需要处理类未找到异常:
		Class.forName("com.mysql.cj.jdbc.Driver");
		//建立连接
		coon = DriverManager.getConnection(url,username,password);
    pstmt = coon.prepareStatement("select score.*from score,student"+"
            where score.stuID=student.id and student.name = ?");
    pstmt.setString(1,studentName);
   ResultSet rs = pstmt.execute.Query();//查询涉及结果集。
       while(rs.next())  //next()将光标从当前位置向前移一行。
   {
       System.out.println(rs.getInt("subject") + "" + rs.getFloat("score"));
   }             
}catch(Exception e){e.printStackTrace();}
finally  //代码块finally,先开后关,后开先关原则。
{
    if(rs != null)try{rs.close();}catch(exception e){e.printStackTrace();}
    if(pstmt != null)try{pstmt.close();}catch(exception e){e.printStackTrace();}
    if(coon != null)try{coon.close();}catch(exception e){e.printStackTrace();}
}

——————————————————————分割线——————————————————————
1-1 什么是MyBatis
一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。将SQL语句放在映射文件XML中,自动将输入参数映射到SQL语句的动态参数中,自动将SQL语句执行的结果映射为java对象。中文学习文档地址:https://mybatis.org/mybatis-3/zh/index.html
Maven依赖的导入:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>  //3.5.2
</dependency>

MyBatis配置文件中包含系统的核心设置,包含获取数据库连接实例的数据源和决定事务范围和控制方式的事务管理器。
数据库名:db_pet;账号密码都为root。每一个mapper.xml
都需要在Mybatis核心配置文件中配置。

<configuration>
        <environments default="development"> //默认环境
        <environment id="development">
            <transactionManager type="JDBC"/> //jdbc事务管理
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db_pet?useSSL=true&amp;
                useUnicode=true&amp;characterEncoding=UTF-8"/>//如果是mysql8.0以上还需要加上时区设置。
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        </environments>
    <mappers>  
        <mapper resource="org/mybatis/petmapper.xml"/>  
    </mappers>
</configuration>        

1-2 怎么拿到工厂SqlSessionFactory与SqlSession
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
/通过读取配置文件,把它加载成流,通过build加载这个流构建成工厂

有了SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。

SqlSession session = sqlSessionFactory.openSession();// 通过SqlSessionFactory对象来获取

1-3 MyBatis中SqlSession的重要性:
SqlSession是MyBatis的关键对象,是持久化操作的独享,类似于jdbc中的Connection,它是应用程序与持久层之间执行交互操作的一个单线程对象。SqlSession对象包含全部的以数据库为背景的SQL操作方法,底层封装jdbc连接,可以用SqlSession实例来直接执行被映射的SQL语句,即执行mapper.xml中的语句。但是记住,SqlSession应该是线程私有的,因为它不具备线程安全性
我们可以把构建SqlSessionFactory再获取Session封装成一个工具类,以便直接使用。

SqlSessionFactory-->SqlSession工具类
public class MybatisUtils{
private static SqlSessionFactory sqlSessionFactory;//提升作用域
 static {
      try{
           String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          }catch(IOException e){
          e.printStackTrance();
          }
 }
 public static SqlSession getSqlSession(){}
 SqlSession session = sqlSessionFactory.openSession();
 return sqlsession; //即return sqlSessionFactory.openSession();
}

但是在Spring这样的框架或者更高级的框架如SpringBoot中我们不需要再创建,可以开箱即用。
在spring-Mybatis中,使用SqlSessionFactoryBean 来替代:

<bean id="sqlSessionFactory" 
 class="org.mybatis.spring.SqlSessionFactoryBean">

2-1 MyBatis的应用(搭建环境–>导入mybatis–>编写代码–>测试)
那SqlSession和Mapper(Dao)到底执行了什么操作呢?

```java
(0)创建数据库关系表,添加数据
    CREATE TABLE db_pet.pet(id INT(10),NAME VARCHAR(32),age INT(10));
    INSERT INTO pet VALUE(001,"wxh",23);
    INSERT INTO pet VALUE(002,"lpl",24);
    INSERT INTO pet VALUE(003,"sbw",25);
    INSERT INTO pet VALUE(004,"dsg",26);
    INSERT INTO pet VALUE(005,"bml",27);
(1)创建项目,搭建包结构
(2)导入jar包
    a.导入MyBatis相关的jar包
    b.导入MySQL的驱动包
    c.导入log4j相关的jar包
(3)在src的根目录下创建MyBatis主配置文件mybatis-config.xml,搭建配置文件结构
(4)创建mapper包结构,创建SQL映射文件XxxMapper.xml
(5)在src根目录下引入log4j文件
(6)搭建测试环境,测试根据ID查询数据库中所有记录

创建mapper接口,PetMapper以及对应的xml文件PetMapper.xml.

import com.kmbdqn.pet.entity.Pet;
public interface PetMapper {
	/** 查询所有宠物.  * @return 宠物列表.*/
	List<Pet> findAll(Pet pet);
	/** 插入宠物信息. * @param pet 宠物信息.* @return >0 代表插入成功 */
	int insert(Pet pet);
/** 根据ID查询对象.* @param petId 宠物ID* @return 宠物对象. */
	Pet findById(Int petId);
/** 无条件查询全部.* @return 全部列表. */
	List<Pet> findAll();
}

我们在mapper.xml中书写sql语句,需要注意:
1)命名空间namespace:
不同的mapper映射文件使用namespace区分,一个namespace绑定一个对应的Dao/Mapper接口;不同的mapper映射文件使用的namespace不允许出现重复;使用命名空间,sqlId的形式来找到我们想要执行的sql语句。
2) sql语句必须要写在相应的标签中,实现crud。

parameterType:为sql语句传递的参数的类型
resultType:如果返回值是多条记录,那么resultType的返回值类型,应该写为集合的泛型
注意:在未来开发中
        a.所有的标签都必须要写ID属性
        b.<select>标签parameterType属性可以省略,resultType必须写
        c.对于<insert><update><delete>这3个标签,通常我们只写ID属性,其他的一概不写
        查询的SQL语句,基于xml的映射语句的实例。

注意我们在mapper中的id相当于mapper中的crud方法,mapper.xml相当于mapper接口的实现类。重申我们要在mybatis.xml中注册mapper,即MapperRegistry。方式有多种。

<?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">
  //namespace=绑定一个对应的mapper接口
<mapper namespace="com.kmbdqn.pet.mapper.PetMapper">
  <select id="findAll"resultType="Pet">
    select * from pet where id = #{id}
  </select>
</map

测试!

public class Test{
/第一步,获得SqlSession对象
SqlSession sqlsession =MyBatisUnit.getSqlSession();
/第二步:getmapper
PetMapper mapper = sqlSession.getMapper(PetMapper.class);
//在这我们只需要写方法名就可以了,
List<Pet> findAll = mapper.findAll(pet);
} finally {
  session.close();
}

3-1MyBatis Config.xml文件(配置解析)
3.1 我们可以通过properties属性来实现引用配置文件(db.properties)

(1)直接引入mybatis-config.xml文件,并且加载数据库驱动的方式为
        <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db1"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        </environment>
    
(2)直接引入mybatis-config.xml文件,但是加载数据驱动的方式为引入properties文件的形式
   
    db.propertise文件放到src根目录的resource下,配置文件里的内容大概为
        driver=com.mysql.jdbc.Driver 
        url=jdbc:mysql://localhost:3306/db_pet
        username=root
        password=root
        
    mybatis-config.xml配置文件里的内容变为
        <properties resource="db.properties"/>  //引入properties文件
        <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/> //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>

3.2设置方面
** 3.2.1 typeHandlers(类型处理器)**:无论是 MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。比如:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class JunliTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, s + "LIJUN");
    }
    @Override
    public String getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return resultSet.getString(s)+"LIJUN";
    }
  @Override
    public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return resultSet.getString(i);
    }
    @Override
    public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return callableStatement.getString(i);
    }
}
<!-- mybatis-config.xml -->
   <typeHandlers>
        <typeHandler handler="com.junli.mybatis.demo.mybatis.JunliTypeHandler"/>
    </typeHandlers>

3.2.2 插件(plugins)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
<plugins>
      <plugin interceptor="com.junli.mybatis.demo.mybatis.JunliPlugin"/>
  </plugins>

3.2.3 typeAliases 类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

也可以指定一个包名,MyBatis会在包名下搜索需要的java bean,比如扫描实体类的包,它默认的别名就为这个类的类名,首字母小写!

<typeAliases>
    <package name="com.kmbdqn.pet.entity"/>
</typeAliases>  //实体类较多的时候可以使用扫包,第一种可以自定义别名

3.2.4 映射器(mappers)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

<mappers>  //最佳
    <mapper resource="xml/TestMapper.xml"/>
    <mapper resource="xml/PostsMapper.xml"/>
</mappers> 

使用类路径:时接口和Mapper配置文件必须同名,且在同一个包下。
使用扫描包:<mappers/>时接口和Mapper配置文件必须同名,且在同一个包下。
3.2.5 引入MyBatis日志打印SQL语句具体操作(LOG4J/LOG4J2)
1.maven中导入包
2.log4j2.properties
3 xml中配置

<settings>
        <setting name="logImpl" value="LOG4J"/>
 </settings>

4-1关于Mapper xml映射等问题
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
主要的是resultMap,它包含的元素中有很多子元素和一个值得讨论的结构。
constructor - 用于在实例化类时,注入结果到构造方法中
idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg - 将被注入到构造方法的一个普通结果
id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以指定为一个 resultMap 元素,或者引用一个
collection – 一个复杂类型的集合嵌套结果映射 – 集合可以指定为一个 resultMap 元素,或者引用一个
discriminator – 使用结果值来决定使用哪个 resultMap.

**结果集映射**先对要返回的结果集的id和type说明
<resultMap id ="petMap" type="pet">
**column是数据库中的列字段,property是实体类pet中的属性。**
<result column="id" property="petId"> **解决数据库与实体类中字段名和属性名不一致**
<result column="name" property="petName">
<result column="age" property="age">  //省略

resultMap元素是MyBatis中最重要的最强大的元素,它的设计思想是“对于简单的语句根本不需要配置显示的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了”(什么不一样映射什么就是了)。
4-2测试环境搭建(多对一,一对多)
1.导入lonbok
2.新建实体类Teacher,Student
3.建立Mapper接口
4.建立Mapper.xml文件
5.在核心配置文件中绑定我们的Mapper接口或者文件
6.测试查询是否能够成功!
通俗易懂学生与老师之间的关系(多对一);实体类中:

Student{                      Teacher{
private int id;                          private int id;
private String name;                     private String name;    
//学生需要关联一个老师                  }
private Teacher teacher;
}

思路:1.查询所有的学生信息 2.根据查询出来的学生tid,寻找对应的老师。

方式一:按照查询进行嵌套处理,相关子查询
!--嵌套查询--
<select id="findAll"(id对应方法)resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
!--复杂的属性,我们需要单独处理,对象:association 集合:collection--
<association property="teacher"column="tid" javaType="teacher" select="getTercher"/> !--类型Teacher teacher,select关联下一个查询,相关子查询
<select id="getTeacher" resultMap="Teacher">
select * from teacher where id =#{id}
</select>
方式二:按照结果嵌套处理
<select id="findAll" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname from student s,teacher t where s.id = t.id;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">//column="无"
!--处理老师--<result property="name" column="tname"/>

一对多(一个老师有多个学生)

Student{                      Teacher{
private int id;                          private int id;
private String name;                     private String name;                      
private int tid;                      //一个老师拥有多个学生  
}                                   private List<Student>students  
方式一:按照结果嵌套处理
<select id="findAll" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname from student s,teacher t where s.id = t.id and t.id=#{tid};
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
集合:collection javaType=""指定属性的类型,这里是集合使用ofType获取
<collection property="students" ofType="Student">//column="无"
!--处理学生--<result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"
方式二:子查询
<select id="findAll"(id对应方法)resultMap="TeacherStudent">
select * from teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
!--复杂的属性,我们需要单独处理,对象:association 集合:collection--
<collection property="Students" javaType="ArrayList" ofType="student" select="getStudentTeacherId" column="id"/> 
</resultMap>
<select id="getStudentTeacherId" resultMap="Student">
select * from student where tid =#{tid}
</select>

5-1动态SQL
什么是动态SQL?
根据不同的条件生成不同的sql语句。
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以摆脱这种痛苦。
where-if的条件判断:

    <select id="findAll" resultType="Pet">
        select * from db_pet.pet
        where
            <if test="id!=null">
                id=#{id}
            </if>
            <if test="petName!=null and petName!=''">
                and petname like #{petName}
            </if>
            <if test="age!=null">
                and age=#{age}
            </if> 
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
     </select>

如果第一条sql语句失败时,即id值为null,而petName、age和gender判断成功后,最后sql语句就会变为:

select * from db_pet.pet where and petName like #{petName} and age=#{age} and gender=#{gender}
会出现一个where后面多一个and,执行时会失败。

在where条件后面加了一条判断1=1,然后在id的判断后加上and关键字,这样当下面if条件中的任何一个判断失败后,都不会影响整个sql语句。

  <select id="findAll" resultType="Pet">
        select * from db_pet.pet
        where 1=1
            <if test="id!=null">
                and id=#{id}
            </if>
            <if test="petName!=null and petName!=''">
                and petname like #{petName}
            </if>
            <if test="age!=null">
                and age=#{age}
            </if> 
            <if test="gender==0 or gender==1">
                and gender=#{gender}
            </if>
     </select>

建议写法是《where》和《if》进行组合,当条件不成立时,if条件后的内容包括and也不会存在,因此不会对整个sql语句产生影响。
上方petName二者在一起写,,这种是最常用的。这个取决于数据库中,该字段的约束。否则会报错。
1、 如果为NOT NULL,那么mapper.xml中必须要验证;
2、 如果为非空字符串,则mapper.xml中必须要验证。
choose, when, otherwise
有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

 <select id="findAll" resultType="Pet">
        select * from db_pet.pet
 <where>
<choose>
    <when test="id!=null">
     id = #{id}
    </when>
    <when test="petName!= null and petName !=''">
      AND petName=#{petName}
    </when>
    <otherwise>
      AND age = #{age} 
    </otherwise>
  </choose>
  </where>

trim
如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。
set
类似的用于动态更新语句的解决方案叫做 set。set 元素可以用于动态包含需要更新的列,而舍去其它的。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这里,set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。(译者注:因为用的是“if”元素,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留)
若你对 set 元素等价的自定义 trim 元素的代码感兴趣,那这就是它的真面目:<trim prefix="SET" suffixOverrides=","> ... </trim>注意这里我们删去的是后缀值,同时添加了前缀值
foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
6-1MyBatis的执行流程
{Resources获取加载全局配置文件}······>{实例化SqlSessionFactoryBuilder构造器}······>{解析配置文件流XMLConfigBuilder}······>{Configuration所有的配置信息}······>{实例化SqlSessionFactory}······>{transactional事务管理}······>{创建executor执行器}······>{创建SqlSession}执行mapper······>{实现crud(rollback)}
Mybatis中进行SQL查询是通过org.apache.ibatis.executor.Executor接口进行的。
7-1 MyBatis缓存机制
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,只有一个默认的实现类PerpetualCache,它是用HashMap 实现的。除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。

所有的缓存实现类总体上可分为三类:基本缓存、淘汰算法缓存、装饰器缓存。
在这里插入图片描述
一级缓存(本地缓存):
一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。首先我们必须去弄清楚一个问题,在MyBatis 执行的流程里面,涉及到这么多的对象,那么缓存PerpetualCache 应该放在哪个对象里面去维护?如果要在同一个会话里面共享一级缓存,这个对象肯定是在SqlSession 里面创建的,作为SqlSession 的一个属性。

DefaultSqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在Executor 里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor 的父类BaseExecutor 的构造函数中持有了PerpetualCache。在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。

在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

如下图所示,MyBatis会在一次会话的表示----一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
在这里插入图片描述
一级缓存的生命周期有多长?
1.MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
2.如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
3.如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
4.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
SqlSession 一级缓存的工作流程:
1.对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果
2.判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
3.如果命中,则直接将缓存结果返回;
4.如果没命中:
去数据库中查询数据,得到查询结果;​ 将key和查询到的结果分别作为key,value对存储到Cache中; 将查询结果返回;

@Test /查询两次相等user,关注sql走了几次
public void test(){
SqlSession sqlsession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
sqlSession.close(); 
}

我们发现只执行了一次sql语句。

一级缓存的不足:
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选:
1.session 级别的缓存,在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中。2.statement 级别的缓存,避坑: 为了避免这个问题,可以将一级缓存的级别设为 statement 级别的,这样每次查询结束都会清掉一级缓存。
二级缓存:
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。
作为一个作用范围更广的缓存,它肯定是在SqlSession 的外层,否则不可能被多个SqlSession 共享。而一级缓存是在SqlSession 内部的,所以第一个问题,肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。第二个问题,二级缓存放在哪个对象中维护呢? 要跨会话共享的话,SqlSession 本身和它里面的BaseExecutor 已经满足不了需求了,那我们应该在BaseExecutor 之外创建一个对象。
实际上MyBatis 用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,MyBatis 在创建Executor 对象的时候会对Executor 进行装饰。CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器Executor 实现类,比如SimpleExecutor 来执行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户。
在这里插入图片描述先走二级缓存,再走一级没有再到数据库
事务不提交,二级缓存不存在

为什么事务不提交,二级缓存不生效?因为二级缓存使用TransactionalCacheManager(TCM)来管理,最后又调用了TransactionalCache 的getObject()、putObject 和commit()方法,TransactionalCache里面又持有了真正的Cache 对象,比如是经过层层装饰的PerpetualCache。 为什么层层装饰呢?因为

这里使用了下图的装饰器嵌套 每个cache的getObject方法依次调用
在这里插入图片描述
在putObject 的时候,只是添加到了entriesToAddOnCommit 里面,只有它的commit()方法被调用的时候才会调用flushPendingEntries()真正写入缓存。它就是在DefaultSqlSession 调用commit()的时候被调用的。

为什么增删改操作会清空缓存?在CachingExecutor 的update()方法里面会调用flushCacheIfRequired(ms),isFlushCacheRequired 就是从标签里面渠道的flushCache 的值。而增删改操作的flushCache 属性默认为true。

二级缓存 写在硬盘中
在这里插入图片描述
所以需要序列化

什么时候开启二级缓存?

一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问题,在什么情况下才有必要去开启二级缓存?

因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
如果多个namespace 中有针对于同一个表的操作,比如Blog 表,如果在一个namespace 中刷新了缓存,另一个namespace 中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个Mapper 里面只操作单表的情况使用。
如果要让多个namespace 共享一个二级缓存,应该怎么做?

跨namespace 的缓存共享的问题,可以使用来解决:

<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />

cache-ref 代表引用别的命名空间的Cache 配置,两个命名空间的操作使用的是同一个Cache。在关联的表比较少,或者按照业务可以对表进行分组的时候可以使用。
注意:在这种情况下,多个Mapper 的操作都会引起缓存刷新,缓存的意义已经不大了。
第三方缓存做二级缓存

除了MyBatis 自带的二级缓存之外,我们也可以通过实现Cache 接口来自定义二级缓存。MyBatis 官方提供了一些第三方缓存集成方式,比如ehcache 和redis:https://github.com/mybatis/redis-cache ,这里就不过多介绍了。当然,我们也可以使用独立的缓存服务,不使用MyBatis 自带的二级缓存。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值