MyBatis

MyBatis

概述

企业级框架 数据库管理

Hibernate、MyBatis、Spring Data JPA、MyBatis Plus

  • mybatis是什么?有什么特点?

它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低

  • ORM:Object Relationship Mapping 对象关系映射

把面向对象的编程语言和关系性数据库进行映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据。

把 Java 和 MySQL 进行映射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QIEt0Nj6-1650442930280)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210818154850934.png)]

MyBatis 以前叫 iBatis

MyBatis 的优势

  • 极大简化 JDBC 代码的开发
  • 简单易上手、具有很强的灵活性
  • SQL 写到 XML 中,实现和 Java 代码的解耦合
  • 支持动态 SQL,可以根据具体业务灵活实现需求

MyBatis 的缺点

  • 相比于 Hibernate,开发者需要完成更多的工作,定义 SQL,完成结果集和实体类的匹配。
  • 要求开发人员具备一定的 SQL 编写能力。
  • 数据库移植性较差,因为 SQL 依赖于底层数据库的,如果要进行数据库迁移,部分 SQL 需要重新编写。

MyBatis 的使用

Maven:管理 jar 的工具

Maven 提供了一个远程仓库,Java 生态中所有框架的 jar 包依赖都会上传到远程仓库,

开发者需要哪个 jar 只需要通过简单的配置从远程仓库下载 jar 到本地即可使用。

导入 jar 的顺序:

1、pom.xml 配置你需要的 jar

2、会在本地仓库进行查找,如果本地仓库存在,则直接导入

3、如果本地仓库没有这个 jar,则需要联网去远程仓库下载

首先需要在本地配置 Maven 环境

1、下载 Maven 文件

2、配置环境变量

Maven 换源 更换远程仓库数据源

更换为国内的阿里云镜像仓库,下载速度会很快

基于原生接口的实例

pom:project object model 工程对象模型

jar 包管理全部通过修改 pom.xml 来实现

1、pom.xml 添加依赖

<dependencies>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>

    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
</dependencies>

2、创建实体类

package com.southwind.entity;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String name;
    private Integer money;
}

3、配置数据源,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="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test2"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

4、调用 API 完成操作

  • 使用原生接口

第一步、创建 Mapper 文件 UserMapper.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.southwind.mapper.UserMapper">

    <select id="get" parameterType="int" resultType="com.southwind.entity.User">
        select * from user where id = #{id}
    </select>

</mapper>

第二步、config.xml 注册 UserMapper.xml

<!-- 注册UserMapper.xml -->
<mappers>
    <mapper resource="com/southwind/mapper/UserMapper.xml"></mapper>
</mappers>

第三步、调用原生接口执行 SQL 获取结果

package com.southwind.test;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        String statement = "com.southwind.mapper.UserMapper.get";
        Object o = sqlSession.selectOne(statement, 1);
        System.out.println(o);
    }
}

注意:

1、Maven 默认 JDK 是 1.5,所以需要手动进行升级

<plugins>
    <!-- 设置jdk版本 -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <encoding>utf-8</encoding>
        </configuration>
    </plugin>
</plugins>

2、默认无法读取 src 路径下的 XML 文件,需要手动进行修改

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
    </resource>
</resources>
  • 总结:
    1. 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数
    2. 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件
    3. 通过全局配置文件,创建SqlSessionFactory
    4. 每次进行CRUD时,通过SqlSessionFactory创建一个SqlSession
    5. 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数

这个mapper接口,mybatis会自动的找到对应的mapper.xml,然后对mapper接口使用动态代理的方式生成一个代理类。

定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.yogurt.po.Student,这太长了,所以可以用别名来简化书写,比如

一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息

用来开启或关闭mybatis的一些特性,比如可以用来开启延迟加载,可以用来开启二级缓存

在mapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.yogurt.po.Student,这太长了,所以可以用别名来简化书写,比如

之后就可以在resultType上直接写student,mybatis会根据别名配置自动找到对应的类。

当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式

如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名

另外,对于基本的Java类型 -> 8大基本类型以及包装类,以及String类型,mybatis提供了默认的别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string

用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。这个标签用的不多

mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多

可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。在mybatis底层,运用了责任链模式+动态代理去实现插件的功能

用来配置数据源

用来配置mapper.xml映射文件,这些xml文件里都是SQL语句

parameterType

设置参数数据类型,支持基本数据类型、包装类、String、多个参数、Java 对象

1、基本数据类型

public User findById(int id);
<select id="findById" parameterType="int" resultMap="userMap">
    select * from user where id = #{id}
</select>

#{id} 可以任意替换,因为只有一个参数,所以无论映射名是什么都可以完成映射

2、包装类

public User findById(Integer id);
<select id="findById" parameterType="java.lang.Integer" resultMap="userMap">
    select * from user where id = #{abc}
</select>

3、String

public User findByName(String name);
<select id="findByName" parameterType="java.lang.String" resultMap="userMap">
    select * from user where name = #{abc}
</select>

4、多个参数

多个参数的情况下,无法通过映射名来进行映射,而应该采用下标进行映射

arg0、arg1、param1、param2

public User findByIdAndName(Integer id,String name);
<select id="findByIdAndName" resultMap="userMap">
    select * from user where id = #{param1} and name = #{param2}
</select>

5、Java 对象

参数列表中的映射名就是实体类的属性名

public User findByUser(User user);
<select id="findByUser" parameterType="com.southwind.entity.User" resultMap="userMap">
    select * from user where name = #{userName} and money = #{userMoney}
</select>

resultType

1、基本数据类型

public int count();
<select id="count" resultType="int">
    select count(id) from user
</select>

2、包装类

public Integer count();
<select id="count" resultType="java.lang.Integer">
    select count(id) from user
</select>

3、String

public String findNameById(Integer id);
<select id="findNameById" parameterType="java.lang.Integer" resultType="java.lang.String">
    select name from user where id = #{id}
</select>

4、Java 对象

public User findById(Integer id);
<select id="findById" parameterType="java.lang.Integer" resultMap="userMap">
    select * from user where id = #{abc}
</select>

mapper.xml的SQL语句中的占位符${}和#{}

一般会采用#{},#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类。

那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射来做的,这不同于 , {}, {}取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式

, 一 般 会 用 在 模 糊 查 询 的 情 景 , 比 如 S E L E C T ∗ F R O M s t u d e n t W H E R E n a m e l i k e ′ {},一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '% SELECTFROMstudentWHEREnamelike{name}%';

它的处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like ‘%zhangsan%’;

而如果此时用的是SELECT * FROM student WHERE name like ‘%#{name}%’; 这条SQL最终就会变成

SELECT * FROM student WHERE name like ‘%‘zhangsan’%’; 所以模糊查询只能用 , 虽 然 普 通 的 入 参 也 可 以 用 {},虽然普通的入参也可以用 ,{},但由于${}不会做类型解析,就存在SQL注入的风险,比如

SELECT * FROM user WHERE name = ‘ n a m e ′ A N D p a s s w o r d = ′ {name}' AND password = ' nameANDpassword={password}’

我可以让一个user对象的password属性为’OR ‘1’ = '1,最终的SQL就变成了

SELECT * FROM user WHERE name = ‘yogurt’ AND password = ''OR ‘1’ = ‘1’,因为OR ‘1’ = '1’恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证

另外,对于pojo的入参,KaTeX parse error: Expected 'EOF', got '#' at position 14: {}中获取对象属性的语法和#̲{}几乎一样,但{}在mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}的反射处理有所不同

对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value

基于Mapper代理的实例

推荐使用,解决原生接口所带来的问题

Free MyBatis 很实用的 MyBatis 插件

1、自定义接口

package com.southwind.mapper;

import com.southwind.entity.User;

import java.util.List;

public interface UserMapper {
    public User findById(Integer id);
    public List<User> findAll();
    public int save(User user);
    public int deleteById(Integer id);
    public int update(User user);
}

2、接口对应的 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.southwind.mapper.UserMapper">

    <insert id="save" parameterType="com.southwind.entity.User">
        insert into user(name,money) values(#{name},#{money})
    </insert>

    <update id="update" parameterType="com.southwind.entity.User">
        update user set name = #{name},money = #{money} where id = #{id}
    </update>

    <delete id="deleteById" parameterType="java.lang.Integer">
        delete from user where id = #{id}
    </delete>

    <select id="findById" parameterType="java.lang.Integer" resultType="com.southwind.entity.User">
        select * from user where id = #{id}
    </select>

    <select id="findAll" resultType="com.southwind.entity.User">
        select * from user
    </select>

</mapper>

XML 的命名空间为接口的全类名

每一个 statement 的 id 对应接口的方法名,参数和返回值类型也需要匹配

3、config.xml 中注册 Mapper.xml

<mappers>
    <mapper resource="com/southwind/mapper/UserMapper.xml"></mapper>
</mappers>

4、调用 API

System.out.println(mapper.findById(1));

List<User> all = mapper.findAll();
for (User user : all) {
    System.out.println(user);
}

User user = new User();
user.setUserName("小王");
user.setUserMoney(600);
int save = mapper.save(user);
System.out.println(save);
//增删改需要提交事务,查询不需要
sqlSession.commit();

User user = mapper.findById(1);
user.setUserName("小黄");
user.setUserMoney(2000);
int update = mapper.update(user);
System.out.println(update);
sqlSession.commit();

System.out.println(mapper.deleteById(4));
sqlSession.commit();

#后面就是与实体类进行对应。

MyBatis 底层实现

功能:

  • 自定义接口
  • XML 中定义接口方法对应的 SQL 语句
  • 根据 XML,通过 JDK 动态代理(反射)完成自定义接口的具体实现
  • 自动解析结果集,映射成 XML 中配置的 JavaBean

技术:

  • XML 解析
  • Mapper 代理机制通过 JDK 动态代理,掌握反射

注意事项

1、Mapper.xml(SQL语句所在的文件) 必须要在 config.xml(全局环境配置)中进行注册,才可以使用,否则会抛出异常。

2、statement 值必须是 Mapper 接口的命名空间+方法id

3、需要修改 pom.xml ,让 src 路径下的 XML 可以被读取

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
    </resource>
</resources>

Maven 工程中默认只能读取 resources 路径下的 XML,src 路径下的 XML 无法读取,所以会报错,所以需要手动进行设置。

应用场景

主键返回

通常我们会将数据库表中的主键id设为自增,在插入一条记录的时候,我们不设置其主键id,而是让数据库自动的生成该条记录的主键id,而让数据库自动的生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键Id了?

  1. 使用useGeneratedKeys和keyProperty

    <insert id = 'insert' parameterType = "com.cuntou.entity.user" userGeneratedKeys = "true"keyProperty = "id">
    	INSERT INTO user (name,age,score) VALUES(#{name},#{age},#{score});
    	</insert>
    	
    

    2.使用子标签

    <insert id = "insert" parameterType ="com.cuntou.entity.user">
    	INSERT INTO user (name,age,score) VALUES(#{name},#{age},#{score})
    	<selectKey keyProperty = "id" order = "AFTER" resultType = "int">
    		SELECT LAST_INSERT_ID()
    	</insert>
    

    ​ 如果使用的是mysql这样的支持自增主键的数据库,可以简单的使用第一种方式.标签其实就是一条SQL,这条SQL的执行,可以放在主SQL执行之前或之后,并且会将其执行得到的结果封装到入参的Java对象的指定属性上。注意子标签只能用在和标签中。上面的LAST_INSERT_ID()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。

    批量查询

    主要是动态SQL标签的使用,如果parametertype是List的话,则在标签体内引用这个List,只能用变量名为list,如果parameterType是数组,则只能使用变量名array

    <select id = "batchFind" resultType = "user" parameterType = "java.util.List ">
    		SELECT * FROM student
    		<where>
    			<if test = "list != null and list.size() > 0">
    				AND id in
    				<foreach collection = "list" item ="id" open = "(" separtor = "," close =")">
    					#{id}
    				</foreach>
    			</if>
    		</where>
    </select>
    
    @Test
    public void testBatchQuery(){
    	SqlSession sqlSession = sqlSessionFactory.openSession();
    	UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    	List<User> users = mapper.batchFind(Array.asList(1,2,3,7,9))
    	users.forEach(System.out.println)''
    }
    
    
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fB5qefjy-1650442930282)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210827205941636.png)]

动态sql

可以根据具体的参数条件,来对SQL语句进行动态拼接。

比如在以前的开发中,由于不确定参数查询是否存在,许多人会使用类似where 1= 1,然后后面就用AND来拼接查询的参数,这样就是查询的参数为空,也能够正确的执行查询,如果不加1=1,则如果查询参数为空,SQL语句就会变成SELECT * FROM student where ,SQL不合法

  • id 和 username 查询

  • username 和 password 查询

  • password 和 age 查询

    <mapper namespace = "com.mapper.UserMapper">
    
    	<select id = "findByUser1" resultType = "com.domain.User">
    		selcet * from user from where id = #{id} and username = #{username}
    	</select>
    	
    	<selcet id = "findByUser2" resultType = "com.domain.User">
    		selcet*from user where password = #{password} and username = #{username}
    	</select>
    	
    	<selet id = "findByUser3" resultType = "com.domain.User">
    		select * from user where password = #{password} and age = #{age}
    	</select>
    	
    </mapper>
    
    

mybatis里的动态标签主要有

判断条件是否成立,如果成立则就追加if标签的内容, 如果不成立,则不追加内容

自动判断where后面是否直接跟着and,如果是则删除and,如果不是则就保留and,这个用于处理比如id不查询的时候。

<select id = 'find' resultType = 'com.cuntou.entity.student' parameterType = 'com.cuntou.entity.student'
	SELECT * FROM user 
	<where>
		<if test = "id != null">
			id = #{id}
		</if>
		<if test = "username != null">
			and username = #{username}
		</if>
		<if test = "password != null">
			and password = #{password}
		</if>
		<if test ="age != null">
			and test = #{test}
		</if>
	</where>
</select>

作用和if标签是一致的

<select id="findByUser" resultType="com.domain.User">
    select * from user
    <where>
        <choose>
            <when test="id != null">
                id = #{id}
            </when>
            <when test="username != null">
                and username = #{username}
            </when>
            <when test="password != null">
                and password = #{password}
            </when>
            <when test="age != null">
                and age = #{age}
            </when>
         </choose>
    </where>
</select>

prefix中的值,如果和prefixOverrides 中的值直接拼接,则自动删除prefixOverrides中的值

<select id = "findByUser" result = "com.domain.User">
	select * from user
	<trim prefix = "where" prefixOverrides = "and">
		<if test = "id != null">
			id = #{id}
		</if>
		<if test = "password != null">
			and password = #{password}
		</if>
		<if test = "username != null">
			and username = #{username}
		</if>
		
		<if test = "age != null">
			and age = #{age}
		</if>
		
	</trim>
</select>		

用于update操作,会自动根据参数来选择生成sql语句

<update id="update">
    update user
    <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="password != null">
            password = #{password},
        </if>
        <if test="age != null">
            age = #{age}
        </if>
    </set>
    where id = #{id}
</update>

可以迭代生成一系列的值,这个标签主要用于 SQL 的 in 语句。

<select id = "findByUser" parameterType = "com.domain.User" resultType = "com.domain.User">
	select * from user
	<where>
		<foreach collection="ids" item="id" open="id in (" close=")" separator=",">
               #{id}
        </foreach>
    </where>
</select>

延迟加载

将某些加载(查询数据库)进行滞后操作,尽量减少SQL的执行,从而达到提高速度的目的,是对数据库操作的一种优化。

延迟加载(按需加载,懒加载)只存在于级联查询中,单表查询没有延迟加载的功能。

查询学生–》班级

  • mybatis中打印sql

    <settings>
    	<!--打印SQ-->
    	<settings name = "logImpl" value = "STDOUT_LOGGING" />
    </settings>
    

    同样的结果,但是SQL语句不同,尽量选择效率最高的sql去执行。

  • 多表关联查询的SQL拆分成两个SQL

    ​ 单独sql查询student

    ​ 单独sql查询class

    select * from student where id = 1 ;
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWRCfYSj-1650442930282)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828140542558.png)]

    查询出student的相关信息,再去查询级联的class信息

    select * from class where id = 1
    

    第二条sql的查询条件,就是第一条Sql中的cid.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruy5tFnc-1650442930283)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828140752606.png)]

开启延时加载之前,查询name 需要执行两条sql

  • config.xml配置文件中开启延时加载。

    开启延时加载,如果只需要获取student的信息,那么只需要执行第一条sql,就足够了。

    <!-- 延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sv8CoKT6-1650442930283)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828141403606.png)]

    如果要获取的级联的class信息,则第一条sql不够用,此时再去执行第二条SQl,这就是按需加载。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AioyUlhG-1650442930283)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828141559805.png)]

总结:

MyBatis 框架的延迟加载,是实际开发中使用频录较高的功能,正确的使用延迟加载,可以有效的减少Java程序和数据库的交互次数,从而提升整个系统的运行效率,延迟加载适用于多表关联查询的业务场景。

缓存

MyBatis 延迟加载,解决的是多表关联查询情况下的效率问题,但是对于单表查询,延迟加载没有作用,MyBatis 提供了缓存机制来解决单表查询情况下的效率问题。

使用缓存也是通过减少 Java 程序和数据库交互次数来提高查询效率,第一次查询出某个对象之后,MyBatis 会自动将它存入缓存,当下一次查询该对象的时候,可以直接从缓存中获取,不必再次访问数据库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wzhBo6Cz-1650442930283)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828142038907.png)]

如果执行删除、修改操作,MyBatis 会自动清楚缓存,从而保证数据的时效性。

MyBatis 有两种缓存:一级缓存和二级缓存,作用域不同

一级缓存

默认开启,同一个sqlsession级别共享的缓存,在一个sqlsession的生命周期内,执行两次相同的sql的查询,则第二次sql查询会被直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之前,执行了DML(INSERT/UPDATE/DALETE),则一级缓存会被清空,第二次查询相同的SQL仍然会走数据库。

  • 作用范围:SqlSession(默认)

  • 持有者:BaseExecutor

  • 默认开启(其实是无法关闭的,但可以通过一些方法来使一级缓存失效)

  • 清除缓存:

    • ​ 在同一个SqlSession里执行 增 删 改 操作时(不必提交),会清除一级缓存

    • SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存

    • 对mapper.xml中的某个CRUD标签设置属性flushCache=true (这样会导致该标签的一级缓存和二级缓存都失效)

    • 在全局配置文件中设置 ,这样会使一级缓存失效,二级缓存不受影响

      一级缓存的作用范围仅在同一个SqlSession,它是通过BaseExecutor中的一个localCache属性来实现的,这个localCache其实是一个PerpetualCache类的实例,其内部就是一个普通的HashMap。下面通过一次查询来具体解释

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2IN8IVGF-1650442930284)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828143218035.png)]

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //通过sqlSession获取代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectById(1);
        System.out.println(student);
        //获取新的sqlSession
        sqlSession = factory.openSession();
        mapper = sqlSession.getMapper(StudentMapper.class);
        Student student1 = mapper.selectById(1);
        System.out.println(student1);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nFKgoe9H-1650442930284)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828143328634.png)]

二级缓存

  • 作用范围:mapper级别(可以跨SqlSession),一个mapper.xml即对应一个二级缓存,每个二级缓存,以mapper文件的namespace作为唯一标识

  • 持有者:MappedStatement

  • 默认关闭(其实全局配置文件中的cacheEnabled默认是true,这个是二级缓存总开关,默认是已经打开了的,然而必须要在mapper.xml中配置 标签,才能开启该mapper.xml的二级缓存)

  • 对于SELECT节点对应的MappedStatement,默认是开启二级缓存,对其他节点(INSERT/DELETE/UPDATE),默认是关闭(这是由MappedStatement里的useCache属性控制的)

一个mapper.xml中的每个crud标签,都会被封装为MappedStatement,一个mapper.xml只有一个二级缓存,二级缓存是被MappedStatement(准确来说是SELECT节点)持有的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11i8GF3n-1650442930284)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828144703381.png)]

1、config.xml 配置文件中开启二级缓存。

<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>

2、Mapper.xml 中配置二级缓存

<cache></cache>

3、实体类实现 Serializable 接口

package com.southwind.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class Student implements Serializable {
    private Integer id;
    private String name;
    private Class aClass;
}

4、测试

package com.southwind.test;

import com.southwind.entity.Class;
import com.southwind.entity.Student;
import com.southwind.entity.User;
import com.southwind.mapper.*;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //通过sqlSession获取代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectById(1);
        System.out.println(student);
        sqlSession.close();
        //获取新的sqlSession
        sqlSession = factory.openSession();
        mapper = sqlSession.getMapper(StudentMapper.class);
        Student student1 = mapper.selectById(1);
        System.out.println(student1);
    }
}

要使用二级缓存,第一次使用的 SqlSession必须关闭,否则二级缓存无法开启。

SqlSession 在没有提交的情况下,不会将数据同步到二级缓存中,所以会导致第二次查询仍然要执行 SQL,所以一般执行完毕之后需要关闭 SqlSession,关闭的同时会自动提交。

总结

MyBatis 框架的缓存分为两种:一级缓存和二级缓存,一级缓存是 SqlSession 级别的,二级缓存是 Mapper 级别的,使用的时候需要注意两种缓存的区别,作用域不同,一级缓存是默认开启且无法关闭的,二级缓存需要手动开启。

缓存机制和延迟加载功能类似,都是通过减少 Java 程序和数据库的交互次数来提高系统的运行速度,缓存机制更多针对于单表查询,延迟加载更多针对多表关联查询。

缓存机制使用的前提是两次查询直接没有修改、删除的操作,如果有修改、删除的操作,那么缓存就会自动清空,从而保证数据的时效性。

关联查询

使用<resultMap> 标签以及<association><collection> 子标签,进行关联查询

  1. 一对多

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gy7KUq3f-1650442930285)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828145124168.png)]

如果查询出来的结果是 null

1、数据确实不存在

2、数据存在,但是字段无法和实体类属性名进行映射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IdBMTwd-1650442930285)(C:\Users\村头\AppData\Roaming\Typora\typora-user-images\image-20210828145151351.png)]

@Data
public class Student{
	private Integer id;
	private String name;
	private Class aClass;
}
@Data 
public class Class{
	private Integer id;
	private String name;
	
}
public interface StudentMapper{
	public Student findById(Integer id);
}
<?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.southwind.mapper.StudentMapper">

    <resultMap id="studentMap" type="com.southwind.entity.Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
        <association property="aClass" javaType="com.southwind.entity.Class">
            <id column="cid" property="id"></id>
            <result column="cname" property="name"></result>
        </association>
    </resultMap>

    <select id="findById" resultMap="studentMap">
        select s.id sid,s.name sname,c.id cid,c.name cname
        from student s,class c where s.cid = c.id and s.id = #{id}
    </select>

</mapper>

TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2

查询出来的结果集和实体类无法映射,接口方法返回值要求是一个对象,但是查出来多条结果。

package com.southwind.entity;

import lombok.Data;

import java.util.List;

@Data
public class Class {
    private Integer id;
    private String name;
    private List<Student> students;
}
package com.southwind.mapper;

import com.southwind.entity.Class;

public interface ClassMapper {
    public Class findById(Integer id);
}

2、多对多关系

package com.southwind.m2mentity;

import lombok.Data;

import java.util.List;

@Data
public class Course {
    private Integer id;
    private String name;
    private List<User> users;
}
package com.southwind.m2mentity;

import lombok.Data;

import java.util.List;

@Data
public class User {
    private Integer id;
    private String name;
    private List<Course> courses;
}
package com.southwind.mapper;

import com.southwind.m2mentity.Course;

public interface CourseMapper {
    public Course findById(Integer id);
}
<?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.southwind.mapper.CourseMapper">

    <resultMap id="courseMap" type="com.southwind.m2mentity.Course">
        <id column="cid" property="id"></id>
        <result column="cname" property="name"></result>
        <collection property="users" ofType="com.southwind.m2mentity.User">
            <id column="uid" property="id"></id>
            <result column="uname" property="name"></result>
        </collection>
    </resultMap>

    <select id="findById" parameterType="java.lang.Integer" resultMap="courseMap">
        select u.id uid,u.name uname,
        c.id cid,c.name cname from user u,course c,user_course uc
        where u.id = uc.uid and c.id = uc.cid and c.id = #{id}
    </select>

</mapper>

name;
private List courses;
}


```java
package com.southwind.mapper;

import com.southwind.m2mentity.Course;

public interface CourseMapper {
    public Course findById(Integer id);
}
<?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.southwind.mapper.CourseMapper">

    <resultMap id="courseMap" type="com.southwind.m2mentity.Course">
        <id column="cid" property="id"></id>
        <result column="cname" property="name"></result>
        <collection property="users" ofType="com.southwind.m2mentity.User">
            <id column="uid" property="id"></id>
            <result column="uname" property="name"></result>
        </collection>
    </resultMap>

    <select id="findById" parameterType="java.lang.Integer" resultMap="courseMap">
        select u.id uid,u.name uname,
        c.id cid,c.name cname from user u,course c,user_course uc
        where u.id = uc.uid and c.id = uc.cid and c.id = #{id}
    </select>

</mapper>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值