Mybatis

Mybatis

Mybatis 文档 mybatis – MyBatis 3 | 入门

1.什么是Mybatis

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

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。
  • 2013年11月迁移到Github。

maven仓库中获取

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

Github: Releases · mybatis/mybatis-3 · GitHub

Mybatis的优点

(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解 除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。

(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。

(3)很好的与各种数据库兼容。

(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。

(4)能够与Spring很好的集成。

(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

Mybatis的缺点

(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。

(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

2.第一个Mybatis程序

搭建环境

搭建数据库

CREATE DATABASE `Mybalits`;
USE `Mybalits`;

CREATE TABLE `user`(
	`id` INT(20) NOT NULL PRIMARY KEY,
	`name` VARCHAR(20) DEFAULT NULL,
	`password` varchar(20) DEFAULT NULL
)ENGINE = INNODB DEFAULT CHARSET=UTF8;

INSERT INTO `user` (`id`,`name`,`password`)
VALUE(1,'张三','123456'),(2,'李四','111111'),(3,'王五','222222')

新建项目:新建一个普通的maven项目,删除src目录

导入maven依赖

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

创建模块

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 中构建 SqlSessionFactory

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration 核心配置文件-->
<configuration>
    <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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
<!--每一个Mapper.xml都需要在mappers中注册    -->
    <mappers>
        <mapper resource="com/taiyuan/dao/UserDaoMapper.xml"/>
    </mappers>
</configuration>

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

编写mybatis工具类

//SqlSessionFactory——>sqlsession
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        InputStream inputStream = null;
        try {
            //使用mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    private static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

编写程序

实体类

public class User {
    private int id;
    private String name;
    private String password;

    public User() {
    }
    public User(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Dao接口

public interface UserDao {
    //查询所有用户
    List<User> getUserList();
}

Mapper配置文件

<?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:命名空间,指定一个Dao接口或Mapper接口-->
<!--namespace 中的包名要和 Dao/Mapper接口的包名一致-->
<mapper namespace="com.taiyuan.dao.UserDao">
    <select id="getUserList" resultType="com.taiyuan.pojo.User">
    select * from mybatis.user
  </select>
</mapper>

测试

public class UserDaoTest {
    @Test
    public void test(){
        //1.获得SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.执行sql
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> listUser = mapper.getUserList();
        for(User user : listUser) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

3.CRUD

select

编写接口

//根据id 查询用户
User getUserByid(int id);

实现Mapper中的sql

<select id="getUserByid" parameterType="int" resultType="com.taiyuan.pojo.User">
    select * from mybatis.user where id = #{id};
</select>

测试

@Test
public void getUserByid(){
    //1.获得SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //2.执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User userByid = mapper.getUserByid(1);

    System.out.println(userByid);
    sqlSession.close();
}

insert

<!--对象中的属性,可以直接取出来-->
<insert id="addUser" parameterType="com.taiyuan.pojo.User" >
    insert into mybatis.user (id,name,password) values (#{id},#{name},#{password})
</insert>

update

<update id="updateUser" parameterType="com.taiyuan.pojo.User">
    update mybatis.user set name=#{name},password=#{password} where id = #{id}
</update>

delete

<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id}
</delete>

注意点:增删改需要提交事务

sqlSession.commit();

万能Map

假设,我们的实体类或者数据库中的类、字段或者参数过多,我们可以考虑使用Map

编写接口

//添加用户
int addUser2(Map<Object, Object> map);

实现Mapper中的sql

<!--此时对象中的属性为Map中的key-->
<insert id="addUser2" parameterType="map" >
    insert into mybatis.user (id,name,password)values(#{userid},#{username},#{password})
</insert>

测试

@Test
public void addUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<Object, Object> map = new HashMap<Object, Object>();

    map.put("userid",5);
    map.put("username","小天");
    map.put("password","120120");

    mapper.addUser2(map);
    sqlSession.commit();
    sqlSession.close();
}

模糊查询

编写接口

List<User> getUserLike(String s);

实现Mapper中的sql

<select id="getUserLike" resultType="com.taiyuan.pojo.User">
    select * from mybatis.user where name like #{value}
</select>
<!--在sql 拼接中使用通配符!-->
<!--select * from mybatis.user where name like "%"#{value}"%"-->

测试

@Test
public void getuserlike(){
    //1.获得SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //2.执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userLike = mapper.getUserLike("%李%");

    for(User user : userLike){
        System.out.println(user);
    }
    sqlSession.close();
}

4.配置解析

核心配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

环境配置(environments)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例.

environments 元素定义了如何配置环境。

<environments default="development">
  <environment id="development">
    <!--事务管理器-->
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <!--数据源-->
    <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>

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。

注意:如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源(dataSource)

有三种内建的数据源类型(也就是 type=“[UNPOOLED|POOLED|JNDI]”):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

编写一个配置文件【db.properties】

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8
username=root
password=cpx147258

在核心配置文件中引入

<!--引入外部配置文件-->
<!--<properties resource="do.properties"/>-->
<properties resource="do.properties">
    <property name="username" value="root"/>
    <property name="password" value="cpx147258"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

注意:如果两个文件有同一个字段,优先使用外部配置文件!

类型别名(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="domain.blog"/>
</typeAliases>

在实体类比较少时,使用第一种方式。

在实体类比较多时,使用第二种方式。

第一种可以DIY(自定义)别名,第二种则不行,如果非要改,需要在实体上添加注解。

@Alias("user")
public class User{}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置

映射器(mappers)

MapperRegistry, 注册绑定我们的Mapper文件。

方式一:【推荐】

<mappers>
    <mapper resource="com/taiyuan/dao/UserDaoMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<mappers>
    <mapper class="com.taiyuan.dao.UserDaoMapper"/>
</mappers>

注意:使用方式二时,接口和他的mapper配置文件必须同名,接口和他的mapper配置文件必须在同一个包下

方式三:使用扫描包进行绑定

<mappers>
    <package name="com.taiyuan.dao"/>
</mappers>

注意:方式三和方式二一样要注意要求

5.生命周期和作用域

作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory

可以想象为:数据库连接池

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。SqlSessionFactory 的最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式

SqlSession

可以想象为:连接到连接池的一个请求!

SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

用完之后需要赶紧关闭,否则资源被占用

6.属性名和字段名不一致

数据库中的字段

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

而实体类中的属性:

public class User {
    private int id;
    private String name;
    private String password;
}

测试出现问题:

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

探究:

<!--select * from mybatis.user where id = #{id};-->
<!--它的本质其实是:-->
<!--select id,name,pwd from mybatis.user where id = #{id};-->

所以,我们可以在mapper配置文件中对不一致的字段起别名

<select id="getUserByid"  resultType="com.taiyuan.pojo.user">
    select id,name,pwd as password from mybatis.user where id = #{id}
</select>

当然,除了可以起别名,我们还可以使用resultMap来解决问题

<mapper namespace="com.taiyuan.dao.UserMapper">
    <!--resultMap : 结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--column:数据库中的字段,property:实体类中的属性-->
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    </resultMap>
    <select id="getUserByid" parameterType="int" resultMap="UserMap">
    select * from mybatis.user where id = #{id};
    </select>
</mapper>

resultMap 元素是 MyBatis 中最重要最强大的元素.

7.日志

日志工厂

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手。

曾经我们使用sout, debug,现在:日志工厂

设置名描述有效值默认值
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
  • SLF4J
  • LOG4J【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】
  • NO_LOGGING

在Mybatis中具体使用那个日志实现,在核心配置文件中配置设置!

STDOUT_LOGGING 标准日志输出

<!--此处name和value不能错,也不能有空格,必须是规定的名字-->
<settings>
    <!--标准的日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

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

Log4j

什么是Log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

先导入Log4j的包

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/shun.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

配置日志的实现

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

简单使用

  1. 在要使用log4j的类中,导入包import org.apache.log4j.Logger;
  2. 日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
  1. 日志级别
logger.info("info:进入了testlog4j");
logger.debug("debug:进入了testlog4j");
logger.error("error:进入了testlog4j");

8.分页

Limit

select * from user limit startIndex,pageSize;

使用Mybatis实现分页,核心SQL

  1. 接口
List<User> getUserByLimit(Map<Object,Object> map);
  1. Mapper.xml
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
  1. 测试
@Test
public void testLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<Object, Object> map = new HashMap<Object, Object>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> user = mapper.getUserByLimit(map);
    for(User userlimit :user){
        System.out.println(userlimit);
    }
    sqlSession.close();
}

分页插件:PageHelper

官网:MyBatis 分页插件 PageHelper

9.面向接口编程

到底什么是面向接口编程?

在介绍什么是面向接口编程之前,让我们先来认识下什么是接口,这样才能够"知其然,知其所以然"。

接口,英文名叫Interface,可以理解成是一种标准(规范),在广义泛指一组标准的集合,它规定了实现该接口的类或者接口必须也拥有这一组规则

在一个面向对象的系统中,系统之间的各项功能小到类之间,大到模块、系统之间的交互实际上是通过不同对象的相互协作来实现的,在设计阶段,并不会太关注内部的实现细节,而是着重于设计对象之间的协作关系,尽量达到高内聚、低耦合的目的。

接口作为实体抽象出来的一种表现形式,用于抽离内部实现进行外部沟通,最终实现内部变动而不影响外部与其他实现交互,可以理解成按照这种思想来设计编程的方式就可以称为面向接口编程

举个例子: 就像电脑上的USB插口,如果你觉的有线鼠标用着不过瘾,完全可以购买一个无线鼠标插入原来有线鼠标的插口即可实现无缝切换,而不需要修改计算机中的任何地方,无论你的无线鼠标是否和有线鼠标是同种类型,因为这个插口就是一种标准,只要你购买的鼠标实现了这个标准,它就可以通过这个插口使用对应的鼠标。

10.注解开发

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句

  1. 注解在接口上实现
public interface UserMapper {
    @Select("select * from user")
    List<User> getUserList();
}
  1. 需要在核心配置文件上绑定
<!--绑定接口-->
<mappers>
    <mapper class="com.taiyuan.dao.UserMapper"/>
</mappers>
  1. 测试
@Test
public void testUserList(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserList();
    for(User user : userList){
        System.out.println(user);
    }
    sqlSession.close();
}

本质:反射机制实现

底层:动态代理

CRUD

查询用户

//方法存在多个基本数据类型参数,每个参数必须加上@Param
@Select("select * from user where id = #{id} and name = #{name}")
User getUserByid(@Param("id")int id,@Param("name") String name);

添加用户

public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);//在工具类中可以自动提交事务
}
@Insert("insert into user(id,name,password) values(#{id},#{name},#{psw})")
int addUser(User user);

修改用户

@Update("update user set name = #{name},password = #{psw} where id = #{id}")
int updateUser(User user);

删除用户

@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id") int id);

@Param()注解

  • 基本数据类型或者String类型,需要加上。
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议加上
  • 我们在SQL中引用的就是我们这里的@Param()中设定的属性名!

11.mybatis执行过程

说到MyBatis执行流程,估计80%的程序员可能每天都沉浸在一个接口方法、一条SQL快乐的coding中,也可能是没有时间研究;也可能觉得使用起来很简单,不就是加载配置文件,执行SQL吗,soeasy;但是作为一个励志成为优秀工程师的程序员,仅仅停留在这个层面还远远不够,它根本满足不了我们对技术的渴望。

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

我们都知道MyBtis是对JDBC的简易封装,它的出现某种程度了是为了消除所有的JDBC代码和参数的手工设置以及结果集的封装问题;不管怎样,JDBC的那一套还是不会变的,只是做了抽象、封装、归类等;

MyBatis执行过程

  1. 读取MyBatis的核心配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接、属性、类型别名、类型处理器、插件、环境配置、映射器(mapper.xml)等信息,这个过程中有一个比较重要的部分就是映射文件其实是配在这里的;这个核心配置文件最终会被封装成一个Configuration对象

  2. 加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,映射文件是在mybatis-config.xml中加载;并可以加载多个映射文件。

  3. 构造会话工厂获取SqlSessionFactory

    SqlSessionFactory 有两个实现类,一个是 SqlSessionManager 类,一个是 DefaultSqlSessionFactory 类:

    • DefaultSqlSessionFactory : SqlSessionFactory 的默认实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。
    • SqlSessionManager :已被废弃,原因大概是: SqlSessionManager 中需要维护一个自己的线程池,而使用MyBatis 更多的是要与 Spring 进行集成,并不会单独使用,所以维护自己的 ThreadLocal 并没有什么意义,所以 SqlSessionManager 已经不再使用。

    这个过程其实是用建造者设计模式使用SqlSessionFactoryBuilder对象构建的,SqlSessionFactory的最佳作用域是应用作用域。

    然后调用 SqlSessionFactoryBuilder 中的 build 方法传递一个InputStream 输入流,Inputstream 输入流中就是你传过来的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根据传入的 InputStream 输入流和environment、properties属性创建一个XMLConfigBuilder对象。

    SqlSessionFactory builder = new	SqlSessionFactoryBuilder().build(inputStream);
    
    //源码
        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            SqlSessionFactory var5;
            try {
                XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                var5 = this.build(parser.parse());
            } catch (Exception var14) {
                throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
            } finally {
                ErrorContext.instance().reset();
    
                try {
                    inputStream.close();
                } catch (IOException var13) {
                }
    
            }
            return var5;
        }
    
  4. 创建会话对象SqlSession。由会话工厂创建SqlSession对象,然后就可以执行 SQL 语句了。在 SqlSessionFactory.openSession 过程中,它会调用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource 方法,这个方法主要创建了两个与我们分析执行流程重要的对象,一个是 Executor 执行器对象,一个是 SqlSession 对象。

    SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口能够让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你能够执行简单的 CRUD 操作,也可以通过 getMapper 获取 Mapper 层,执行自定义 SQL 语句,因为 SqlSession 在执行 SQL 语句之前是需要先开启一个会话,涉及到事务操作,所以还会有 commit、 rollback、close 等方法。这也是模版设计模式的一种应用。

    SqlSession sqlSession = builder.openSession();
    
    //源码
        private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
            Transaction tx = null;
    
            DefaultSqlSession var8;
            try {
                Environment environment = this.configuration.getEnvironment();
                TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                Executor executor = this.configuration.newExecutor(tx, execType);
                var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
            } catch (Exception var12) {
                this.closeTransaction(tx);
                throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
            } finally {
                ErrorContext.instance().reset();
            }
    
            return var8;
        }
    
  5. MapperProxy。MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy 来和对应的 SQL 语句进行绑定的。

    //源码
    //SqlSession
    public interface SqlSession extends Closeable {
            <T> T getMapper(Class<T> var1);
    }
    
    //DefaultSqlSession
    public class DefaultSqlSession implements SqlSession {
        public <T> T getMapper(Class<T> type) {
            return this.configuration.getMapper(type, this);
        }
    }
    
    //configuration
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
    
    //mapperRegistry
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
    
    //mapperProxyFactory
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
    

    这就是 MyBatis 的核心绑定流程,

    MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute逻辑比较简单,就是判断是 插入、更新、删除 还是 查询 语句。

    //源码
    //MapperProxy
    public class MapperProxy<T> implements InvocationHandler, Serializable {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
    
                if (method.isDefault()) {
                    return this.invokeDefaultMethod(proxy, method, args);
                }
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
    
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    
        private MapperMethod cachedMapperMethod(Method method) {
            return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
                return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            });
        }
    }
    
    //mapperMethod
    //execute
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch (this.command.getType()) {
            case INSERT:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                    if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }
    
        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
    

    绕了一圈,其实就是通过sqlSession一层层拿到MapperProxyFactory,然后拿到MapperProxy,最后调用SQL操作接口时,其实是通过MapperProxy中封装的execute()来执行的,最后其实是走到了Executor中的逻辑。

  6. Executor执行器。是MyBatis的核心,每一个 SqlSession 都会拥有一个 Executor 对象,负责SQL语句的生成和查询缓存的维护,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。

  7. StatementHandler 。Executor 会把后续的工作交给 StatementHandler 继续执行。StatementHandler 是四大组件中最重要的一个对象,负责操作 Statement 对象与数据库进行交互,

    • SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。
    • PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句。
    • CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。
  8. 输入参数映射。输入参数类型可以是基本数据类型,也可以是Map、List、POJO类型复杂数据类型,这个过程类似于JDBC的预编译处理参数的过程,

  9. 封装结果集。可以封装成多种类型可以是基本数据类型,也可以是Map、List、POJO类型复杂数据类型。封装结果集的过程就和JDBC封装结果集是一样的

总结:

  1. 通过MyBatis XML配置文件生成source
  2. 通过source中的数据生成并生成Executor和SqlSessionFactoryBuilder
  3. 通过SqlSessionFactoryBuilder得到SqlSessionFactory
  4. 通过SqlSessionFactory得到SqlSession
  5. 通过SqlSession拿到MapperProxyFactory
  6. 通过MapperProxyFactory拿到MapperProxy
  7. 通过MapperProxy操作具体的SQL接口
  8. SQL接口的执行逻辑交给Executor执行(Executor之前就已经通过配置生成好了)
  9. Executor的执行逻辑交给StatementHandler执行
  10. StatementHandler会根据SQL类型选择SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三者之一去执行SQL,比如先执行PreparedStatementHandler
  11. 通过PreparedStatementHandler处理数据前,通过ParameterHandler给SQL语句动态赋值
  12. 继续通过StatementHandler处理SQL逻辑
  13. SQL逻辑全部处理完后,通过ResultSetHandler生成结果集,返回数据给客户端

12.Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
  1. 在IDEA中安装Lombok插件

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

  2. 导入Lombok的jar包

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>
  1. 在实体类中加入注解
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
@Data:无参构造,get、set、tostring、hashcoode、equals
@AllArgsConstructor
@NoArgsConstructor

13.多对一

关联… 多个学生——关联一个老师【多对一】

集合… 一个老师——教许多学生【一对多】

association – 一个复杂类型的关联;许多结果将包装成这种类型

  • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用

collection– 一个复杂类型的集合

  • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用

环境搭建:

CREATE TABLE `teacher`(
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) NOT NULL,
	PRIMARY KEY(`id`)
) ENGINE = INNODB DEFAULT CHARSET=utf8;

INSERT INTO teacher(`id`,`name`) VALUES(1,'曹老师');

CREATE TABLE `student`(
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	`tid` INT(10) DEFAULT NULL,
	PRIMARY KEY(`id`)
 	KEY`fktid`(`tid`),
 	CONSTRAINT `fktid` FOREIGN KEY(`tid`) REFERENCES`teacher`(`id`)
) ENGINE = INNODB DEFAULT CHARSET=utf8;

INSERT INTO student(`id`,`name`,`tid`) VALUES(1,'小魂',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES(2,'小吕',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES(3,'小蓝',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES(4,'小名',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES(5,'小天',1);

pojo

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

按照查询嵌套处理

  1. 接口
public interface StudentMapper{
    //获取所有的学生信息和对应的老师信息
	public List<Student> getStudent();
}
  1. 编写Mapper.xml
<mapper namespace="com.taiyuan.dao.StudentMapper">
    <!--思路:1.查出学生信息-->
    <!--2. 查出学生的tid 查询老师名字-->
    <select id="getStudent" 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="getTeacher"/>
    </resultMap>
    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>
  1. 测试
public void testgetstudent(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> student = mapper.getStudent();
    for(Student student1:student){
        System.out.println(student1);
    }
    sqlSession.close();
}

按照结果嵌套处理

<mapper namespace="com.taiyuan.dao.StudentMapper">
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname
        from student s,teacher t
        where s.tid = t.id
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>

14.一对多

pojo

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
}

按照结果嵌套处理

  1. 接口
public interface TeacherMapper{
    //获取老师及其学生信息
    List<Teacher> getTeacher2(@Param("tid") int id);
}
  1. 编写Mapper.xml
<mapper namespace="com.taiyuan.dao.TeacherMapper">
    <select id="getTeacher2" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from teacher t ,student s
        where t.id = s.tid and t.id = #{tid}
    </select>
    <!--集合中的泛型信息,我们通过ofType获取-->
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
</mapper>

按照查询嵌套处理

<mapper namespace="com.taiyuan.dao.TeacherMapper">
    <!--思路:1. 查出老师信息-->
    <!--2. 通过老师i即学生tid查询学生信息-->
    <select id="getTeacher2" resultMap="TeacherStudent">
        select * from teacher where id = #{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <collection property="students"  javaType="ArrayList" ofType="Studnet" select="getStudent" column="id"/>
    </resultMap>
    <select id="getStudent" resultType="Student">
        select * from student where tid = #{tid}
    </select>
</mapper>

小结

  1. 关联:association 【多对一】
  2. 集合:collection【一对多】
  3. javaType & ofType
    • javaType:用来指定实体类中属性的类型
    • ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

15.动态SQL

动态SQL就是指不同的条件下生成不同的SQL语句

搭建环境

CREATE TABLE `blog`(
	`id` VARCHAR(50) NOT NULL COMMENT'博客id',
	`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
	`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
	`creat_time` DATETIME NOT NULL COMMENT '创建时间',
	`views` INT(30) NOT NULL COMMENT '浏览器'
) ENGINE= INNODB DEFAULT CHARSET=utf8

pojo

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date creattime;//属性名和字段名不一致
    private int views;
}

Utils

//获取UUID
private static String getId(){
    return UUID.randomUUID().toString().replaceAll("-","");
}

if

  1. 接口
//查询博客
List<Blog> getBlog(Map map);
  1. Mapper.xml

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<select id="getBlog" resultType="Blog" parameterType="map">
    select * from Blog
    <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

choose (when, otherwise)

我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句.

<select id="getBlog2" parameterType="map" resultType="Blog">
    select * from Blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

trim (where, set)

set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id = #{id}
</update>

所谓的动态SQL,只是在SQL层面,可执行的一个逻辑代码

SQL片段

有的时候,我们会将一些功能部分抽取出来,方便使用!

  1. 使用SQL标签抽取公共的部分
<sql id="id-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>
  1. 在需要使用的地方使用include标签引用即可
<select id="getBlog" resultType="Blog" parameterType="map">
    select * from Blog
    <where>
        <include refid="id-title-author"></include>
    </where>
</select>

Foreach

<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>

16.缓存

什么是缓存?

缓存就是数据交换的缓冲区(称作Cache),是存贮数据(使用频繁的数据)的临时地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行。如果找不到,则去数据库中查找。

为什么要用缓存?

缓存的本质就是用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库读取最新的数据,减少数据库IO,减轻服务器压力,减少网络延迟,加快页面打开速度。

Mybatis缓存

  • Mybatis系统默认定义了两级缓存,一级缓存二级缓存
  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,基于namespace级别的缓存
  • 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存

一级缓存

用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。如果获取相同的数据,直接从缓存中拿,不在查询数据库。

缓存失效的情况:

  1. 查询不同的东西
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  3. 查询不同的Mapper.xml
  4. 手动清理缓存
sqlSession.clearCache(); //手动清理缓存

二级缓存(全局缓存)

由于一级缓存的作用域太低了,所以诞生了二级缓存,他是基于namespace级别的缓存,作用域超出 session 范围之外,可以被所有 SqlSession 共享。

步骤:

  1. 开启全局缓存
<!--显示的开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
  1. 在Mapper.xml中开启
<!--在当前Mapper.xml中使用二级缓存-->
<cache/>

可以自定义参数

  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

以上属性说明:

属性说明
eviction代表的是缓存回收策略,目前 MyBatis 提供以下策略。LRU:使用较少,移除最长时间不用的对象;FIFO:先进先出,按对象进入缓存的顺序来移除它们;SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象;WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval刷新间隔时间,单位为毫秒,这里配置的是 100 秒刷新,如果省略该配置,那么只有当 SQL 被执行的时候才会刷新缓存。
size引用数目,正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是 1024 个对象。
readOnly只读,默认值为 false,意味着缓存数据只能读取而不能修改,这样设置的好处是可以快速读取缓存,缺点是没有办法修改缓存。

参考链接:1、什么是MyBatis_哔哩哔哩_bilibili

参考链接:Mybatis框架知识详解 - 知乎 (zhihu.com)

参考链接:面试官的窒息逼问: 到底什么是面向接口编程? - 知乎 (zhihu.com)

参考链接:【MyBatis系列3】MyBatis SQL执行流程 - 知乎 (zhihu.com)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值