【狂神说Java】Mybatis笔记

Mybatis01:Mybatis简介

1、什么是MyBatis

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通 Java 对象)为数据库中的记录。
  • MyBatis 是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github 。
  • Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html
  • GitHub : https://github.com/mybatis/mybatis-3

2、持久化

什么是持久化?

  • 持久化是将程序数据在持久状态和瞬时状态间转换的机制。
  • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
  • JDBC就是一种持久化机制。文件IO也是一种持久化机制。
  • 在生活中 : 将鲜肉冷藏吃的时候再解冻的方法;将水果做成罐头的方法都是持久化。

为什么需要持久化服务?

  • 是由于内存本身的缺陷引起的。
  • 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,而且无法保证内存永不断电。
  • 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

3、持久层

什么是持久层?

  • 完成持久化工作的代码块:dao层 【DAO (Data Access Object) 数据访问对象】
  • 大多数情况下特别是企业级应用,数据持久化也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系型数据库来完成。
  • 对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专著于数据持久化逻辑的实现。
  • 与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。 【说白了就是用来操作数据库存在的!】

4、为什么需要MyBatis

  • MyBatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 .

  • 传统的jdbc操作,有很多重复代码块。比如:数据取出时的封装,数据库的建立连接等等,通过框架可以减少重复代码,提高开发效率。

  • MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) —>对象关系映射。

  • 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别。

5、MyBatis的优点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis02:MyBatis第一个程序

1、代码演示

1.1、安装(导入MyBatis)

使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

Maven依赖查找地址:https://mvnrepository.com/

<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatisartifactId>
  <version>x.x.xversion>
dependency>

1.2、搭建环境(数据库)

CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(20) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert  into `user`(`id`,`name`,`pwd`) values (1,'张三','abcdef'),(2,'李四','987654'),(3,'王五','987654');

1.3、编写mybatis核心配置文件mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>
    <mappers>
        <mapper resource="com/study/Mapper/UserMapper.xml"/>
    mappers>
configuration>

1.4、编写MyBatis工具类

从 XML 中构建 SqlSessionFactory

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

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

package com.study.utils;

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

import java.io.IOException;
import java.io.InputStream;

public class mybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

1.5、创建实体类

  • 构造:有参、无参;
  • set、get;
  • toString();
package com.study.pojo;

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

1.6、编写mapper接口类

完成简单的CRUD操作

package com.study.mapper;

import com.study.pojo.User;

import java.util.List;
import java.util.Map;

public interface UserMapper {
    // 查询全部用户
    List<User> getUserList();
}

1.7、编写mapper.xml配置文件


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.UserMapper">
mapper>

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.UserMapper">
    <select id="getUserList" resultType="com.study.pojo.User">
        select * from user
    select>
mapper>
  • MyBatis 提供的所有特性都可以利用基于 XML 的映射语言来实现。

  • namespace 十分重要,不能写错。

  • 命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

  • **命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

    • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
    • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

8、导入Junit 包测试


<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.13.2version>
    <scope>testscope>
dependency>
package com.study.mapper;

import com.study.pojo.User;
import com.study.utils.mybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
    @Test
    public void getUserList(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

2、可能出现的问题

Maven静态资源过滤问题

<resources>
    <resource>
        <directory>src/main/javadirectory>
        <includes>
            <include>**/*.propertiesinclude>
            <include>**/*.xmlinclude>
        includes>
        <filtering>falsefiltering>
    resource>
    <resource>
        <directory>src/main/resourcesdirectory>
        <includes>
            <include>**/*.propertiesinclude>
            <include>**/*.xmlinclude>
        includes>
        <filtering>falsefiltering>
    resource>
resources>

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis03:CRUD操作

1、select

  • select标签是mybatis中最常用的标签之一
  • select语句有很多属性可以详细配置每一条SQL语句
    • id
      • 命名空间中唯一的标识符
      • 接口中的方法名与映射文件中的SQL语句ID 一一对应
    • parameterType
      • 传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】
    • resultType
      • SQL语句返回值类型。【完整的类名或者别名】
    • 如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可
User getUserById(int id);
List<User> getUserById2(Map<String,Object> map);
<select id="getUserById" parameterType="int" resultType="com.study.pojo.User">
	select * from user where id = #{id}
select>
<select id="getUserById2" parameterType="map" resultType="com.study.pojo.User">
	select * from user where pwd = #{pwd} and id <= #{id}
select>
@Test
public void getUserById(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}
@Test
public void getUserById2(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new HashMap<String,Object>();
    map.put("id",2);
    map.put("name","2");
    map.put("pwd","qwerty");
    List<User> userList = userMapper.getUserById2(map);
    for (User user : userList) {
    	System.out.println(user);
    }
    sqlSession.close();
}

2、模糊查询

List<User> getUserLike(String value);

<select id="getUserLike" resultType="com.study.pojo.User">
    select * from user where name like #{value}
select>

<select id="getUserLike" resultType="com.study.pojo.User">
    select * from user where name like "%"#{value}"%"
select>
@Test
public void getUserLike(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.getUserLike("%李%");
    // List userList = userMapper.getUserLike("李");
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

3、insert

int AddUser(User user);
int AddUser2(Map<String,Object> map);
<insert id="AddUser" parameterType="com.study.pojo.User">
	insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})
insert>
<insert id="AddUser2" parameterType="map">
	insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})
insert>
@Test
public void AddUser(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    userMapper.AddUser(new User(4,"4","243324"));
    sqlSession.commit();
    sqlSession.close();
}
@Test
public void AddUser2(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new HashMap<String,Object>();
    map.put("id",4);
    map.put("name","q7w");
    map.put("pwd","df8sre9r");
    userMapper.AddUser2(map);
    sqlSession.commit();
    sqlSession.close();
}

4、update

int UpdateUser(User user);
<update id="UpdateUser" parameterType="com.study.pojo.User">
	update user set name=#{name},pwd=#{pwd} where id=#{id}
update>
@Test
public void UpdateUser(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    userMapper.UpdateUser(new User(4,"回复","123123"));
    sqlSession.commit();
    sqlSession.close();
}

5、delete

int DeleteUser(int id);
<delete id="DeleteUser" parameterType="int">
	delete from user where id = #{id}
delete>
@Test
public void DeleteUser(){
    SqlSession sqlSession = mybatisUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    userMapper.DeleteUser(4);
    sqlSession.commit();
    sqlSession.close();
}

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis04:MyBatis配置解析

1、配置文档的顶层结构

  • mybatis-config.xml 系统核心配置文件

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

  • configuration(配置)

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

1.1、环境配置(environments)

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

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

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推

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

注意一些关键点:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

事务管理器(transactionManager)

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

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

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

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

  • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
  • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
  • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
  • 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等。

1.2、映射器(mappers)

  • 映射器 : 定义映射SQL语句文件

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

  • 引入资源方式


<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
mappers>

<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
mappers>

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
mappers>

<mappers>
  <package name="org.mybatis.builder"/>
mappers>

这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件

  • Mapper文件

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.UserMapper">
    
mapper>

namespace中文意思:命名空间,作用如下:

  • namespace和子元素的id联合保证唯一 , 区别不同的mapper
  • 绑定DAO接口
    • namespace的命名必须跟某个接口同名
    • 接口中的方法与映射文件中sql语句id应该一一对应
  • namespace命名规则 : 包名+类名

MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。

1.3、Properties优化

数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。官方文档

我们来优化我们的配置文件

第一步 ; 在资源目录下新建一个db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true
username=root
password=root

第二步 : 将文件导入properties 配置文件


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="db.properties" />
    <typeAliases>
        <package name="com.study.pojo"/>
    typeAliases>
    <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="com/study/Mapper/UserMapper.xml"/>
    mappers>
configuration>

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:

<dataSource type="POOLED">
  
  <property name="username" value="${username:ut_user}"/> 
dataSource>

这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。例如:

<properties resource="org/mybatis/example/config.properties">
  
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> 
properties>

1.4、类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases
   alias="Blog" type="domain.blog.Blog"/>
typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

1.5、设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。详细描述见官方文档。

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>

1.6、类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。详细描述见官方文档。

你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。【了解即可】

使用类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
  • 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。

可以通过两种方式来指定关联的 JDBC 类型:

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
  • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的作用范围,并且可以确保,除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐式地使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。 然而从 Mybatis 3.4.0 开始,如果某个 Java 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true,那么这个类型处理器也会是 ResultMap 使用 Java 类型时的默认处理器。

1.7、对象工厂(objectFactory)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。

默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。

如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。详细描述见官方文档。

1.8、生命周期和作用域

作用域(Scope)和生命周期

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

可以通过流程图,分析一下Mybatis的执行过程!

【狂神说Java】Mybatis笔记_第1张图片

作用域理解

SqlSessionFactoryBuilder

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • 一旦创建了SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory

  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
  • SqlSessionFactory 占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域(ApplicationContext)。
  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
  • 最简单的就是使用单例模式或静态单例模式。

SqlSession

  • 所以 SqlSession 的最佳的作用域是请求或方法作用域。
  • 每个线程都应该有它自己的 SqlSession 实例。
  • SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
  • 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保SqlSession 关闭的标准模式:
  • 【狂神说Java】Mybatis笔记_第2张图片

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis05:XML 映射器(基础使用)

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。

MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

1、select

查询语句是 MyBatis 中最常用的元素之一,光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。

MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
select>

这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

2、insert, update 和 delete

数据变更语句 insert,update 和 delete 的实现非常接近:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

下面是 insert,update 和 delete 语句的示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
delete>

3、结果映射resultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

解决的问题:属性名和字段名不一致

  1. Java中的实体类设计

    • 构造:有参、无参;
    • set、get;
    • toString();
    public class User {
        private int id;  			//id    
        private String name;   		//name    
        private String password;   	//pwd
    }
    
  2. 接口

    //根据id查询用户
    User selectUserById(int id);
    
  3. mapper映射文件

    <select id="selectUserById" resultType="user">
    	select * from user where id = #{id}
    select>
    
  4. 测试

    @Test
    public void testSelectUserById() {
        SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.selectUserById(1);
        System.out.println(user);
        session.close();
    }
    

结果:

  • User{id=1, name=’张三’, password=’null’}
  • 查询出来发现 password 为空 . 说明出现了问题

解决方案

方案一:为列名指定别名 , 别名和java实体类的属性名一致

<select id="selectUserById" resultType="User">
	select id , name , pwd as password from user where id = #{id}
select>

方案二:使用结果集映射->ResultMap 【推荐】

<resultMap id="UserMap" type="User">
    
    <id column="id" property="id"/>
    
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
resultMap>
<select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
select>

结果映射(resultMap)

  • constructor- 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection– 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator– 使用结果值来决定使用哪个resultMap
    • case- 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射。

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis06:日志工厂

当我们在测试SQL的时候,通过日志在控制台输出 SQL ,能够有更快的排错效率。

如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题。

对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用Mybatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。

1、日志配置Log4j

简介:

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

你可以通过在包、映射类的全限定名、命名空间或全限定语句名上开启日志功能,来查看 MyBatis 的日志语句。

再次提醒,具体配置步骤取决于日志实现。接下来我们会以 Log4J 作为示范。配置日志功能非常简单:添加一个或多个配置文件(如 log4j.properties),有时还需要添加 jar 包(如 log4j.jar)。下面的例子将使用 Log4J 来配置完整的日志服务。

1.1、添加 Log4J 的 jar 包

<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

1.2、配置 Log4J

配置 Log4J 比较简单。假设你需要记录这个映射器的日志:

package org.mybatis.example;
public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

创建一个名为 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/test.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

1.3、setting设置日志实现

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

1.4、在程序中使用Log4j进行输出

static Logger logger = Logger.getLogger(MyTest.class);
@Test
public void selectUser() {
    logger.info("info:进入selectUser方法");
    logger.debug("debug:进入selectUser方法");
    logger.error("error: 进入selectUser方法");
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser();
    for (User user: users){
        System.out.println(user);
    }
    session.close();
}

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis07:MyBatis分页的实现

1、limit实现分页

为什么需要分页?

在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

使用Limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize

SELECT * FROM table LIMIT 5,10; #检索记录行 6-15

#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1: 
SELECT * FROM table LIMIT 95,-1; #检索记录行 96-last.
#注:这种写法本身就是错的,虽然它可以在之前的版本中运行(低优先级),新版本的mysql对此做出了修复,现在的替代方法是第二个参数用一个较大的正数代替。
SELECT * FROM table LIMIT 95,10000; #检索记录行 96-last.

#如果只给定一个参数,它表示返回最大的记录行数目:换句话说,LIMIT n 等价于 LIMIT 0,n。  

步骤:

  1. 修改Mapper文件

    <select id="selectUser" parameterType="map" resultType="user">
    	select * from user limit #{startIndex},#{pageSize}
    select>
    
  2. Mapper接口,参数为map

    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
    
  3. 在测试类中传入参数测试

    • 推断:起始位置 = (当前页面 - 1 ) * 页面大小
    //分页查询 , 两个参数startIndex , pageSize
    @Test
    public void testSelectUser() {
        SqlSession session = MybatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        int currentPage = 1;  //第几页
        int pageSize = 2;  //每页显示几个
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("startIndex",(currentPage-1)*pageSize);
        map.put("pageSize",pageSize);
        List<User> users = mapper.selectUser(map);
        for (User user: users){
            System.out.println(user);
        }
        session.close();
    }
    

2、RowBounds分页

我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页,当然此种方式作为了解即可。我们来看下如何实现的!

步骤:

  1. 引入github相关包

    <dependency>
        <groupId>com.github.pagehelpergroupId>
        <artifactId>pagehelperartifactId>
        <version>4.1.6version>
    dependency>
    
  2. mapper接口

    //选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();
    
  3. mapper文件

    <select id="getUserByRowBounds" resultType="user">
    	select * from user
    select>
    
  4. 测试类

    在这里,我们需要使用RowBounds类

    @Test
    public void testUserByRowBounds() {
       SqlSession session = MybatisUtils.getSession();
       int currentPage = 2;  //第几页
       int pageSize = 2;  //每页显示几个
       RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
       //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
       List<User> users = session.selectList("com.study.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
       for (User user: users){
           System.out.println(user);
       }
       session.close();
    }
    

3、PageHelper

了解即可,可以自己尝试使用:官方文档:https://pagehelper.github.io/

使用方法

  1. 引入分页插件:推荐使用 Maven 方式。

在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelperartifactId>
    <version>最新版本version>
dependency>

最新版本号可以从首页查看。

  1. 在 MyBatis 配置 xml 中配置拦截器插件

    
    <plugins>
        
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            
            <property name="param1" value="value1"/>
    	plugin>
    plugins>
    
  2. 如何在代码中使用

    //第一种,RowBounds方式的调用
    List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
    
    //第二种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.startPage(1, 10);
    List<Country> list = countryMapper.selectIf(1);
    
    //第三种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.offsetPage(1, 10);
    List<Country> list = countryMapper.selectIf(1);
    
    //第四种,参数方法调用
    //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List<Country> selectByPageNumSize(
                @Param("user") User user,
                @Param("pageNum") int pageNum,
                @Param("pageSize") int pageSize);
    }
    //配置supportMethodsArguments=true
    //在代码中直接调用:
    List<Country> list = countryMapper.selectByPageNumSize(user, 1, 10);
    

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis08:使用注解开发

1、面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

2、利用注解开发

mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建

常用注解分为三大类:SQL语句映射,结果集映射和关系映射。【注意】利用注解开发就不需要mapper.xml映射文件了 。

2.1、SQL语句映射

@Select注解:实现查询功能

@Select("Select * from user")
@Results({
    @Result(id = true, column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "sex", property = "sex"),
    @Result(column = "age", property = "age")
})
List<User> queryAllUser();

@SelectKey注解:插入后,获取id的值

以mysql为例,mysql在插入一条数据后,如何能获得到这个自增id的值呢?使用select last_insert_id() 可以取到最后生成的主键。

@Insert("insert into user(id,name) values(#{id},#{name})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
@SelectKey(statement = "select last_insert_id()" ,keyProperty = "id",keyColumn = "id",resultType = int.class,before = false) 
public int insert(User user);

备注:before属性,默认是true,在执行插入语句之前,执行select last_insert_id()。如果设置为flase,则在插入这个语句之后,执行select last_insert_id()

@Insert注解:实现插入功能

@Insert("insert into user(name,sex,age) values(#{name},#{sex},#{age}")
int saveUser(User user);

@Update注解:实现更新功能

@Update("update user set name= #{name},sex = #{sex},age =#{age} where id = #{id}")
void updateUserById(User user);

@Delete注解:实现删除功能

@Delete("delete from  user  where id =#{id}")
void deleteById(Integer id);

【注意】确保实体类和数据库字段对应

【注意】增删改一定记得对事务的处理

2.2、结果集映射

@Result,@Results,@ResultMap是结果集映射的三大注解。

首先说明一下@Results各个属性的含义,id为当前结果集声明唯一标识,value值为结果集映射关系,@Result代表一个字段的映射关系,column指定数据库字段的名称,property指定实体类属性的名称,jdbcType数据库字段类型,@Result里的id值为true表明主键,默认false;使用@ResultMap来引用映射结果集,其中value可省略。

声明结果集映射关系代码:

@Select({"select id, name, class_id from student"})
@Results(id="studentMap", value={
    @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
    @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
    @Result(column="class_id ", property="classId", jdbcType=JdbcType.INTEGER)
})
List<Student> selectAll();

引用结果集代码:

@Select({"select id, name, class_id from student where id = #{id}"})
@ResultMap(value="studentMap")
Student selectById(integer id);

这样就不用每次需要声明结果集映射的时候都复制冗余代码,简化开发,提高了代码的复用性。

2.3、关系映射

@one注解:用于一对一关系映射

@Select("select * from student")  
@Results({  
    @Result(id=true,property="id",column="id"),  
    @Result(property="name",column="name"),  
    @Result(property="age",column="age"),  
    @Result(property="address",column="address_id",one=@One(select="cn.mybatis.mydemo.mappers.AddressMapper.getAddress"))  
})  
public List<Student> getAllStudents();  

@many注解:用于一对多关系映射

@Select("select * from t_class where id=#{id}")  
@Results({  
    @Result(id=true,column="id",property="id"),  
    @Result(column="class_name",property="className"),  
    @Result(property="students", column="id", many=@Many(select="cn.mybatis.mydemo.mappers.StudentMapper.getStudentsByClassId"))  
    })  
public Class getClass(int id); 

3、Mybatis详细的执行流程

【狂神说Java】Mybatis笔记_第3张图片

4、关于@Param

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

5、#与$的区别

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  • ${} 的作用是直接进行字符串替换

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('张三');
    

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis09:XML 映射器(多对一的处理)

多对一的理解:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!

1、数据库设计

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT 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');

2、搭建测试环境

2.1、编写实体类

  • 构造:有参、无参;
  • set、get;
  • toString();

Student

package com.study.pojo;

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

Teacher

package com.study.pojo;

public class Teacher {
    private int id;
    private String name;
}

2.2、编写实体类对应的Mapper接口

// Student
public interface StudentMapper {
}
// Teache
public interface TeacherMapper {
}

2.3、编写Mapper接口对应的 mapper.xml配置文件

Student


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.StudentMapper">
 
mapper>

Teacher


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.TeacherMapper">

mapper>

3、按结果嵌套处理

  1. 接口方法编写

    public List<Student> getStudents2();
    
  2. 编写对应的mapper文件

    
    <select id="getStudents2" 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">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        association>
    resultMap>
    
  3. 测试

    @Test
    public void getStudentList2(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = studentMapper.getStudentList2();
        for (Student student : studentList){
            System.out.println(student);
        }
        sqlSession.close();
    }
    

4、按查询嵌套处理

  1. 接口方法编写

    public List<Student> getStudents();
    
  2. 编写对应的mapper文件

    <resultMap id="StudentTeacher" type="Student">
        
        <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
    resultMap>
    
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id} and name = #{name}
    select>
    
  3. 测试

    @Test
    public void getStudentList(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = studentMapper.getStudentList();
        for (Student student : studentList){
            System.out.println(student);
        }
        sqlSession.close();
    }
    

5、小结

  • 按照查询进行嵌套处理就像SQL中的子查询
  • 按照结果进行嵌套处理就像SQL中的联表查询

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis10:XML 映射器(一对多的处理)

一对多的理解:

  • 一个老师拥有多个学生
  • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

1、实体类编写

  • 构造:有参、无参;
  • set、get;
  • toString();
package com.study.pojo;

public class Student {
    private int id;
    private String name;
    private int tid;
}
package com.study.pojo;

import java.util.List;

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

2、按结果嵌套处理

  1. TeacherMapper接口编写方法

    //获取指定老师,及老师下的所有学生
    public Teacher getTeacher(int id);
    
  2. 编写接口对应的Mapper配置文件

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.study.mapper.TeacherMapper">
        <select id="getTeacherById" resultMap="TeacherStudent">
            select s.tid tid,t.name tname,s.id sid,s.name sname
            from student s,teacher t
            where tid = #{id}
        select>
        <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>
    
  3. 将Mapper文件注册到MyBatis-config文件中

    <mappers>
        <mapper class="com.study.mapper.TeacherMapper"/>
        <mapper class="com.study.mapper.StudentMapper"/>
    mappers>
    
  4. 测试

    @Test
    public void getTeacherById(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = teacherMapper.getTeacherById(1);
        System.out.println(teacher);
        sqlSession.close();
    }
    

3、按查询嵌套处理

  1. TeacherMapper接口编写方法

    public Teacher getTeacher2(int id);
    
  2. 编写接口对应的Mapper配置文件

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.study.mapper.TeacherMapper">
        <select id="getTeacherById2" resultMap="TeacherStudent2">
            select id,name from teacher where id = #{id}
        select>
        <resultMap id="TeacherStudent2" type="Teacher">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentListByTeacherId"/>
        resultMap>
        <select id="getStudentListByTeacherId" resultType="Student">
            select * from student where tid = #{id}
        select>
    mapper>
    
  3. 将Mapper文件注册到MyBatis-config文件中

  4. 测试

    @Test
    public void getTeacherById2(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        Teacher teacher = teacherMapper.getTeacherById2(1);
        System.out.println(teacher);
        sqlSession.close();
    }
    

9.4、小结

  1. 关联-association
  2. 集合-collection
  3. 所以association是用于一对一和多对一,而collection是用于一对多的关系
  4. JavaType和ofType都是用来指定对象类型的
    • JavaType是用来指定pojo中属性的类型
    • ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

  1. 保证SQL的可读性,尽量通俗易懂
  2. 根据实际要求,尽量编写性能更高的SQL语句
  3. 注意属性名和字段不一致的问题
  4. 注意一对多和多对一 中:字段和属性对应的问题
  5. 尽量使用Log4j,通过日志来查看自己的错误

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis11:动态 SQL

什么是动态SQL?

  • 动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句。
  • 动态 SQL 是 MyBatis 的强大特性之一。
  • 如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
  • 使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
  • 如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
  • 使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

1、搭建环境

1.1、创建数据库

CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

1.2、创建项目

【狂神说Java】Mybatis笔记_第4张图片

1.3、编写mybatis核心配置文件


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="db.properties" />
    <settings>
        
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        
        
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>
    <typeAliases>
        <package name="com.study.pojo"/>
    typeAliases>
    <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 class="com.study.mapper.BlogMapper"/>
    mappers>
configuration>

1.4、编写db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true
username=root
password=root

1.5、编写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/ph.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

1.6、实体类编写

  • 构造:有参、无参;
  • set、get;
  • toString();
import java.util.Date;
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
    // 有参、无参、set、get、tostring....
}

1.7、编写Mapper接口

public interface BlogMapper {

}

1.8、编写Mapper.xml文件


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.BlogMapper">

mapper>

2、if 语句

需求:如果不传入“title” 和 “author”,那么所有的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果;如果传入了 “author” 参数,那么就会对 “author” 一列进行模糊查找并返回对应的 BLOG 结果

编写接口类

List<Blog> QueryBlogIF(Map map);

编写SQL语句

<select id="QueryBlogIF" resultType="Blog">
  SELECT * FROM BLOG WHERE
  <if test="title != null">
    title like #{title}
  if>
  <if test="author != null">
    AND author like #{author}
  if>
select>

【注意】这样写我们可以看到,如果 author 等于 null,那么查询语句为 select from user where title=#{title},但是如果title为空呢?那么查询语句为 select from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

3、where 语句

需求:如果不传入“title” 和 “author”,那么所有的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果;如果传入了 “author” 参数,那么就会对 “author” 一列进行模糊查找并返回对应的 BLOG 结果

编写SQL语句

<select id="QueryBlogIF" resultType="Blog">
  SELECT * FROM BLOG
  <where>
      <if test="title != null">
        title like #{title}
      if>
      <if test="author != null">
        AND author like #{author}
      if>
  where>
select>

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

4、set语句

编写接口类

int UpdateBlogSET(Map map);

编写SQL语句


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

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

5、trim


<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
trim>

<trim prefix="SET" suffixOverrides=",">
  ...
trim>

6、choose、when、otherwise

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

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回“views”>500的blog。

编写接口类

List<Blog> QueryBlogChoose(Map map);

编写SQL语句

<select id="QueryBlogChoose" resultType="blog" parameterType="map">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                and title like #{title}
            when>
            <when test="author != null">
                and author = #{author}
            when>
            <otherwise>
                and views > 500
            otherwise>
        choose>
    where>
select>

7、Foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

  1. 编写接口

    List<Blog> queryBlogForeach(Map map);
    
  2. 编写SQL语句

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            
            <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
                id=#{id}
            foreach>
        where>
    select>
    

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

8、小结

其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

Mybatis12:MyBatis缓存

1、简介

  1. 什么是缓存 [ Cache ]?
    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据。
  4. Mybatis缓存
    • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
    • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
      • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地的会话缓存)
      • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。需要在你的 SQL 映射文件中添加一行
      • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
  5. 可用的清除策略有:
    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

2、一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

2.1、测试

  1. 在mybatis中加入日志,方便测试结果

  2. 编写接口方法

    //根据id查询用户
    User queryUserById(@Param("id") int id);
    
  3. 接口对应的Mapper文件

    <select id="queryUserById" resultType="user">
    	select * from user where id = #{id}
    select>
    
  4. 测试

    @Test
    public void testQueryUserById(){
        SqlSession session = MybatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user);
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session.close();
    }
    
  5. 结果分析

【狂神说Java】Mybatis笔记_第5张图片

2.2、一级缓存失效的四种情况

  • 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

  • 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

    1. sqlSession不同

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          SqlSession session2 = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          UserMapper mapper2 = session2.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          User user2 = mapper2.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
          session2.close();
      }
      

      观察结果:发现发送了两条SQL语句!

      结论:每个sqlSession中的缓存相互独立

    2. sqlSession相同,查询条件不同

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          UserMapper mapper2 = session.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          User user2 = mapper2.queryUserById(2);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
      }
      

      观察结果:发现发送了两条SQL语句!很正常的理解

      结论:当前缓存中,不存在这个数据

    3. sqlSession相同,两次查询之间执行了增删改操作!

      • 增加方法

        //修改用户
        int updateUser(Map map);
        
      • 编写SQL

        <update id="updateUser" parameterType="map">
            update user set name = #{name} where id = #{id}
        update>
        
      • 测试

        @Test
        public void testQueryUserById(){
            SqlSession session = MybatisUtils.getSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.queryUserById(1);
            System.out.println(user);
            HashMap map = new HashMap();
            map.put("name","kuangshen");
            map.put("id",4);
            mapper.updateUser(map);
            User user2 = mapper.queryUserById(1);
            System.out.println(user2);
            System.out.println(user==user2);
            session.close();
        }
        

        观察结果:查询在中间执行了增删改操作后,重新执行了

        结论:因为增删改操作可能会对当前数据产生影响

    4. sqlSession相同,手动清除一级缓存

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          session.clearCache();//手动清除缓存
          User user2 = mapper.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session.close();
      }
      

一级缓存就是一个map

3、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

3.1、使用步骤

官方文档

  1. 开启全局缓存 【mybatis-config.xml】

    <setting name="cacheEnabled" value="true"/>
    
  2. 去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

    <cache/>
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
    
  3. 代码测试

    • 所有的实体类先实现序列化接口

    • 测试代码

      @Test
      public void testQueryUserById(){
          SqlSession session = MybatisUtils.getSession();
          SqlSession session2 = MybatisUtils.getSession();
          UserMapper mapper = session.getMapper(UserMapper.class);
          UserMapper mapper2 = session2.getMapper(UserMapper.class);
          User user = mapper.queryUserById(1);
          System.out.println(user);
          session.close();
          User user2 = mapper2.queryUserById(1);
          System.out.println(user2);
          System.out.println(user==user2);
          session2.close();
      }
      

3.2、结论

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

4、缓存原理

【狂神说Java】Mybatis笔记_第6张图片

5、EhCache

第三方缓存实现—EhCache: 查看百度百科

  • 官方文档

  • Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;

  • 要在应用程序中使用Ehcache,需要引入依赖的jar包

    
    <dependency>
        <groupId>org.mybatis.cachesgroupId>
        <artifactId>mybatis-ehcacheartifactId>
        <version>1.1.0version>
    dependency>
    
  • 在mapper.xml中使用对应的缓存即可

    <mapper namespace = “org.acme.FooMapper” > 
        <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> 
    mapper>
    
  • 编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

    
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        
        <diskStore path="./tmpdir/Tmp_EhCache"/>
        <defaultCache
                eternal="false"
                maxElementsInMemory="10000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="259200"
                memoryStoreEvictionPolicy="LRU"/>
        <cache
                name="cloud_user"
                eternal="false"
                maxElementsInMemory="5000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="1800"
                memoryStoreEvictionPolicy="LRU"/>
        
        
    ehcache>
    

    【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

你可能感兴趣的:(java,mybatis)