IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 遇见狂神说JAVA笔记 --- Mybatis 学习 -> 正文阅读

[大数据]遇见狂神说JAVA笔记 --- Mybatis 学习

?传送门==>B站遇见狂神说–Mybatis教程


感谢狂神,学习视频真的是通俗易懂???

笔记和练习只是跟着视频整理的;有的知识点并没有整理进来

ML


1.什么是 Mybatis

首先学习Mybatis; 可以使用到的参考Mybatis中文文档
https://mybatis.org/mybatis-3/zh/index.html
MyBatis中文文档

MyBatis 持久层框架,支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
MyBatis 可以通过简单的 XML注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

简单来说就是封装了一些方法;之前写项目的某些步骤可以省略.

历史由来

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis2013年11月迁移到Github

在这里插入图片描述

本人使用的版本情况
jdk — 1.8版本;
MySql — 8.0.22 版本
Maven — 3.8.1 版本
Tomcat — 9.0.48 版本


1.1如何获取/下载Mybatis

(1)可以在GitHub下载
GitHub官网==>https://github.com/

搜索Mybatis即可

https://github.com/search?q=Mybatis

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(2) 可以在Maven仓库下载
maven仓库官网:==>https://mvnrepository.com/

搜索Mybatis即可;

https://mvnrepository.com/search?q=Mybatis

在这里插入图片描述

一般选择使用人数较多的版本;比较稳定

在这里插入图片描述

我选择了3.4.6 版本的
Maven依赖如下所示

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

1.2 什么是持久化,持久层

也就是数据持久化;将程序的数据在 持久状态和瞬时状态之间转化;

内存有个特点就是 断电即丢失;有的重要对象或者信息,如果丢失就很危险.

数据库,JDBC,IO 文件都有持久化.

持久层就是完成持久化工作的代码块;在项目中,层与层之间的界限是比较明显的.


1.3 为什么需要Mybatis

由于传统的JDBC优点复杂;那么简化它 ;Mybatis就出现了;
可以帮助程序员将数据存到数据库.
其实主要是懂原理,再使用这些框架就比较容易;

它比较简单;导入两个jar文件;配置几个sql映射文件就能上手了;
sql写在xml里,便于统一管理


2. 入门Mybatis框架,先写个查询用户

2.1 首先是搭建环境

2.1.1先创建一个数据库;建个数据表,一会儿要对数据库进行操作;顺便存入几行数据

-- 创建数据库;
CREATE DATABASE IF NOT EXISTS `day2021_9_6_studyMybatis_db`;
-- 创建用户表;
CREATE TABLE `user`(
       `id` INT(20) NOT NULL PRIMARY KEY,
       `name` VARCHAR(10) DEFAULT NULL,
       `password` VARCHAR(10) DEFAULT NULL
       )ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 插入数据;
INSERT INTO `user`
VALUES
  (1, '小智', '123456'),
  (2, '杰哥', '111111'),
  (3, '阿猫', '222222'),
  (4, '点金手','666666'),
  (5, '特工', '555555');

2.1.2 新建一个普通的maven项目;

在这里插入图片描述

注意项目名尽量不要用中文;之前有次我创建maven项目;用中文写项目名;进去爆红了…

在这里插入图片描述

记得在Filesetting设置中检查maven的地址;有时候它会切到 IDEA 自带的maven地址;
(好像之前改过默认地址了…,有时候还是会自动切换到IDEA的地址)

在这里插入图片描述


2.1.3 把创建的项目中的src目录删掉;那么这个项目就作为父级工程

在这里插入图片描述


2.1.4 导入项目需要的相关依赖;

在父级工程导入依赖;子级工程也能继承使用过去.

数据库连接驱动(我使用的是自己的8.0.22对应的版本);mybatis持久层框架;测试使用的junit包

pom.xml文件

<!--数据库连接驱动;mybatis持久层框架;测试使用的junit包-->
    <dependencies>
        <!--数据库连接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <!--Mybatis-->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!--测试使用junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

2.2 创建一个模块,子级工程

在之前创建的项目处新建一个 Moudle 模块;仍然创建的是一个普通的maven项目;

在这里插入图片描述

在这里插入图片描述

那么,项目创建好了;作为子级项目,不用再去写依赖导包了,嘿嘿;

pom.xml文件还有显示它的父级

在这里插入图片描述

点击右边的maven;可以看到;子级项目study01继承了刚才在Study-Mybatis01中导入的包

在这里插入图片描述


2.2.1需要编写Mybatis的核心配置文件

在项目study01的src下的resources目录下创建文件mybatis-config.xml文件;

注意在写url的时候,后面设置的属性连接符号&需要转义;&amp;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
    <!--environments:配置的环境,可以配置多个-->
    <environments default="development">
        <environment id="development">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--加载连接数据库的重要字段属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--每个mapper都需要在核心配置文件中注册-->
    <mappers>
        <mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
    </mappers>
</configuration>

2.2.2 编写需要的工具类

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
也可理解为工厂模式;

如果说每次操作都要去读取一下配置文件;是比较麻烦的,那么就写个工具类把要使用的的功能封装起来吧;每次操作去调用工具类;

utils目录下创建工具类MybatisUtils

在这里插入图片描述

好了,写工具类吧

package com.xiaozhi.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=null;
    //在调用工具类时就执行;
    static {
        try {
            //获取SqlSessionFactory对象;

            //获得配置文件;
            String resource = "mybatis-config.xml";
            //使用流读取资源;
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //加载资源流;
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //从 SqlSessionFactory 中获取 SqlSession;
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }  
}

2.3 编写代码吧

2.3.1 首先需要创建实体类.

一般来说,将实体类存放在创建的pojo目录或者eneity目录中;
实体类的属性对应数据库的对应数据表的对应字段;
由于要和数据库对应,那么就注意属性的数据类型要一致;可不要把数值型的字段给写成字符串类型的或者别的一些错误.
基本上这些方法足够使用了;

package com.xiaozhi.eneity;

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 + '\'' +
                '}';
    }
}

2.3.2创建Dao持久层;

这里用的接口对应配置文件的方式;
先用UserDao命名吧;这个和之后要用的Mapper是一样的;

package com.xiaozhi.dao;
import com.xiaozhi.eneity.User;
import java.util.List;

public interface UserDao {
    //先定义个查询所有用户的方法;
    List<User> findAllUser();
}

需要注意的是,现在不用去写实现类去实现接口的方式,那样子比较麻烦;
比说说写个查询方法把;要获取结果集就得需要一个一个去写setXXX,getXXX对应属性;而且每次写一个方法,就得写对应的;

好了,接着回到这个简单的项目;创建个UserMapper.xml配置文件;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--这里mapper的空间对应创建的持久层接口;-->
<mapper namespace="com.xiaozhi.dao.UserDao">
    <!--比如说,现在要写个查询语句,id就对应接口的方法;-->
    <!--resultType: 查询的结果类型-->
    <select id="findAllUser" resultType="com.xiaozhi.eneity.User">
        select * from day2021_9_6_studyMybatis_db.user
    </select>
</mapper>

2.4 测试

使用junit测试;注意要使用@Test注解.

一般情况下,这个测试代码是要在test测试类中去编写的;和开发的主程序分开;

package com.xiaozhi.dao;
import com.xiaozhi.eneity.User;
import com.xiaozhi.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class UserDaoTest {
    //测试查询所有用户的方法;
    @Test
    public void testFindAllUser(){
        //首先,回去SqlSession对象;
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //第一种方式;getmapper;
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> allUser = userDao.findAllUser();
        //遍历结果;
        for (User user : allUser) {
            System.out.println(user);
        }

        //关闭sqlSession;
        sqlSession.close();
    }
}

报错提示
(1)该错误表示没有在mybatis核心配置文件中注册需要的UserMapper.xml

org.apache.ibatis.binding.BindingException: Type interface com.xiaozhi.dao.UserDao is not known to the MapperRegistry.

(2)虽然说已经回到核心配置文件mybatis-config.xml,把UserMapper.xml添加注册进去了;

<!--每个mapper都需要在核心配置文件中注册-->
<mappers>
    <mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
</mappers>

但是又爆出找不到资源的错误;如下

java.lang.ExceptionInInitializerError
	at com.xiaozhi.dao.UserDaoTest.testFindAllUser(UserDaoTest.java:18)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in com/xiaozhi/dao/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/xiaozhi/dao/UserMapper.xml
.........................................

这段错误中提示找不到UserMapper.xml;那么,想想之前学maven时,就学过遇到资源/配置文件找不到的情况时,要在项目的pom.xml配置文件中写这样一段build;

其实把UserMapper.xml配置文件复制粘贴到target目录下也行,但是每次资源找不到都这样做,比较麻烦,所以就直接在pom.xml文件中一次性搞定吧.

<!--为防止资源导出失败;在build中需要配置resources-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

在这里插入图片描述

添加后,刷新一下maven;再次运行测试类UserDaoTest;可发现结果已经查询出来了,且旁边的UserMapper.xml配置文件已加载到target目录下;

在这里插入图片描述

在测试类中,这个查询还有第二种方式,直接select定位到对应的持久层接口的方法中,但是可能需要强制转换类型为User, 返回结果;但是这种方式已经不常用了;

public class UserDaoTest {
    //测试查询所有用户的方法;
    @Test
    public void testFindAllUser(){
        //首先,回去SqlSession对象;
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //第二种方式;
        List<User> list = sqlSession.selectList("com.xiaozhi.dao.UserDao.findAllUser");
        for (User user : list) {
            System.out.println(user);
        }
        //关闭sqlSession;
        sqlSession.close();
    }
}

运行,也可以查询到;

在这里插入图片描述


3. 试试基础的增删改查(CRUD)

在这里插入图片描述

首先,看看刚才在mapper.xml配置文件用到的select语句中的属性;

<mapper namespace="com.xiaozhi.dao.UserMapper">
    <select id="findAllUser" resultType="com.xiaozhi.eneity.User">
        select * from day2021_9_6_studyMybatis_db.user
    </select>
</mapper>

要注意的是,在mapper.xml文件中,namespace的对应地址名要与持久层的接口名一致.

这里的id 对应namespace中的方法名;
resultType :表示SQL语句的返回值类型;
当然还有其他的属性;
parameterType 表示参数类型,


3.1写个根据ID查询用户;

先在UserMapper定义方法;

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

UserMapper.xml配置文件的mapper标签内写查询语句;注意parameterType 表示参数类型,查询语句中参数用#{ 字段}

<!--根据Id查询用户-->
<select id="findUserById" resultType="com.xiaozhi.eneity.User" parameterType="_int">
    select * from day2021_9_6_studyMybatis_db.user where id= #{id}
</select>

在测试类中试试;

//测试根据Id查询用户;
 @Test
 public void testFindUserById(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     User user = mapper.findUserById(1);
     System.out.println(user);
     //关闭sqlSession;
     sqlSession.close();
 }

运行,查询得出结果

在这里插入图片描述


3.2 写个添加用户

先在UserMapper定义方法;

//添加用户;
int  addUser(User user);

UserMapper.xml配置文件的mapper标签内写添加语句;

<!--添加用户-->
<insert id="addUser" parameterType="com.xiaozhi.eneity.User">
   insert  into day2021_9_6_studyMybatis_db.user(id,name,password)values (#{id},#{name},#{password});
</insert>

在测试类中试试;注意增删改需要提交事务哦

//添加用户;
  @Test
  public void testAddUser(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      int i = mapper.addUser(new User(6, "阿虎", "145698"));
      if(i>0){
          System.out.println("添加数据条数=>"+i);
      }

      //提交事务;
      sqlSession.commit();
      //关闭sqlSession;
      sqlSession.close();
  }

在这里插入图片描述


3.3 写个修改用户

先在UserMapper定义方法;

//修改用户;
int updateUser(User user);

UserMapper.xml配置文件的mapper标签内写修改语句;

<!--修改用户-->
<update id="updateUser" parameterType="com.xiaozhi.eneity.User">
    update day2021_9_6_studyMybatis_db.user set name=#{name},password=#{password} where id=#{id};
</update>

在测试类中试试;

//修改用户;
 @Test
 public void testUpdateUser(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     int i = mapper.updateUser(new User(6, "虎子", "999999"));
     if(i>0){
         System.out.println("修改数据条数=>"+i);
     }
     //提交事务;
     sqlSession.commit();
     //关闭;
     sqlSession.close();
 }

在这里插入图片描述


3.4 写个删除用户

先在UserMapper定义方法;

//根据Id删除用户;
int deleteUser(int id);

UserMapper.xml配置文件的mapper标签内写删除语句;

<!--删除用户-->
<update id="deleteUser" parameterType="_int">
   delete from day2021_9_6_studyMybatis_db.user where id=#{id};
</update>

在测试类中测试;

//根据Id删除用户;
@Test
public void testDeleteUser(){
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   UserMapper mapper = sqlSession.getMapper(UserMapper.class);
   int i = mapper.deleteUser(6);
   if(i>0){
       System.out.println("删除数据条数=>"+i);
   }
   //提交事务;
   sqlSession.commit();
   //关闭;
   sqlSession.close();
}

在这里插入图片描述


4. 使用Map

上面在写sql语句的时候,由于参数或返回值要对应实体类;那么每次sql语句都要准确地对应实体类的属性;

万能的Map来了,那么每次写SQL语句的时候,就可以直接参数写map的键;(而且使用map的好处就是,键不能重复) ; 然后再实际调用的时候,再对map的键进行赋值.

UserMapper中定义方法;

//万能的Map;
int allPowerfulMapToAdd(Map<String,Object> map);

UserMapper.xml配置文件的mapper标签内写添加语句;

<!--添加用户;注意这里的参数就是 map的键-->
    <insert id="allPowerfulMapToAdd" parameterType="map">
        insert into day2021_9_6_studyMybatis_db.user(id,name,password)values (#{useId},#{Name},#{password});
    </insert>

测试一下;

@Test
public void testAddUserForMap(){
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   UserMapper mapper = sqlSession.getMapper(UserMapper.class);

   //这里新建一个Map;可以存多个用户对象;然后调用添加方法;
   HashMap<String, Object> map = new HashMap<String, Object>();
   //向map的键赋值;
   map.put("useId",7);
   map.put("Name","小鸡战士");
   map.put("password","55555");
   int i = mapper.allPowerfulMapToAdd(map);
   if(i>0){
       System.out.println("添加数据条数=>"+i);
   }
   //提交事务;
   sqlSession.commit();
   //关闭资源;
   sqlSession.close();
}

继续在UserMapper中写个方法,准备去查询数据;

//Map 查询;
User allPowerfulMapToGet(Map<String,Object> map);

UserMapper.xml配置文件的mapper添加查询语句;

<select id="allPowerfulMapToGet" parameterType="map" resultType="com.xiaozhi.eneity.User">
        select * from user where id= #{id} and name=#{name};
</select>

测试使用;

@Test
 public void testSelUserForMap(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     HashMap<String, Object> map = new HashMap<String, Object>();
     map.put("id",5);
     map.put("name","特工");
     User user = mapper.allPowerfulMapToGet(map);
     System.out.println(user);
     //提交事务;
     sqlSession.commit();
     //关闭资源;
     sqlSession.close();
 }

5. 模糊查询

UserMapper中声明方法;

//模糊查询;
List<User> findUserByName(String name);

UserMapper.xml中添加文件;

<select id="findUserByName" resultType="com.xiaozhi.eneity.User" parameterType="String">
        select * from user where name like #{name};
</select>

测试类调用方法;传参时使用%通配符匹配;

@Test
 public void testFindByName(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     List<User> userByName = mapper.findUserByName("%鱼%");
     for (User user : userByName) {
         System.out.println(user);
     }

     //关闭资源;
     sqlSession.close();
 }

在这里插入图片描述

还可以在sql拼接时 使用通配符
例如

<!--或者在SQL语句中拼接 通配符-->
<select id="findUserByName" resultType="com.xiaozhi.eneity.User" parameterType="String">
    select * from user where name like "%"#{name}"%";
</select>

6.一些配置优化学习

查看下官方文档给出的配置文件说明.Mybatis中文文档在线

在这里插入图片描述


MyBatis 可以配置成适应多种环境,尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

比如说要写个别的环境;想要使用它,就把environments标签的 default指向那个环境.

<!--environments:配置的环境,可以配置多个-->
<!--要切换到test环境,就把default默认指向的环境切换为test-->
    <environments default="test">
        
        <environment id="development">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--加载连接数据库的重要字段属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        
        <!--比如说,配了个测试使用的环境-->
        <environment id="test">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--加载连接数据库的重要字段属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

transactionManager :事务管理器 ;
有两种类型事务管理器([JDBC和MANAGED);默认使用JDBC; 这个MANAGED不常用;

dataSource 数据源 ;用于连接数据库.

有三种数据源;UNPOOLED不使用数据库连接池;POOLED (默认) 使用数据库连接池;JNDI应用服务器类使用;


6.1属性优化(properties)

还记着之前使用连接池时,读取properties文件的配置;还是比较方便的;
数据库连接池学习—DBCP;C3P0连接池

resources目录下编写db.properties文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day2021_9_6_studyMybatis_db?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username=root
password=123456

在核心配置文件mybatis-config.xml配置文件中(引入)数据源;
注意properties标签要写在核心配置文件的首位;
在环境标签下的dataSource下就能获取到了;就像之前那个el表达式的格式一样;${属性名} 就可读取到了;而且,由于配置文件是写在外部的,可以动态修改;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>

    <!--引入外部配置文件-->
    <properties resource="db.properties"/>


    <!--environments:配置的环境,可以配置多个-->
    <!--想要切换别的环境,就修改默认加载的环境-->
    <environments default="development">
        <environment id="development">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <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>
    
    <!--每个mapper都需要在核心配置文件中注册-->
    <mappers>
        <mapper resource="com/xiaozhi/dao/UserMapper.xml"/>
    </mappers>
</configuration>

properties 除了这种直接自闭合;

 <properties resource="db.properties"/>

还可以在标签体添加属性;有时可能外部配置文件中的属性要被其他的文件读取;那么把这些属性写在外部配置文件就不合适;将这个文件需要的属性添加到properties的标签体即可;
例如:

<properties resource="db.properties">
    <property name="password" value="13245"/>
    <property name="df" value="deqwq"/>
</properties>

需要注意的是:当外部配置文件和properties内定义的属性出现重名的情况;默认优先使用的是引入的外部文件内容!!!


6.2 别名优化(typeAliases)

使用别名可以减少代码的冗余,看起来也比较清晰明了

注意IDEA给出的提示,标签不能乱放位置

在这里插入图片描述

为实体类User配置别名

<!--配置别名-->
<typeAliases>
   <typeAlias type="com.xiaozhi.eneity.User" alias="User"/>
</typeAliases>

在之前的UserMapper.xml的查询语句返回值类型使用配置后的别名;测试,查询方法;可使用;

<select id="findAllUser" resultType="User">
    select * from day2021_9_6_studyMybatis_db.user;
</select>

其实,在配置别名时,还可以使用扫描包的方式;
扫描存放实体类的包;别名默认为实体类的小写,
比如说配置扫描存放实例类User的包eneity;那么User类的别名就是user

<!--使用包扫描的方式 ;实体类的别名默认为首字母小写的类名-->
<typeAliases>
    <package name="com.xiaozhi.eneity"/>
</typeAliases>

UserMapper.xml的查询语句返回值类型使用别名user;测试,查询成功;

<select id="findAllUser" resultType="user">
    select * from day2021_9_6_studyMybatis_db.user;
</select>

在使用扫描包的方式;之后;如果不想用默认的小写开头的别名,
还可以直接在实体类使用注解的方式;
比如说在实体类User上面使用注解@Alias("别名")
注意导包为import org.apache.ibatis.type.Alias.

import org.apache.ibatis.type.Alias;
//实体类User;
//使用注解的方式配置别名;
@Alias("myUser")  

UserMapper.xml那个查询语句处使用注解写的别名;

<select id="findAllUser" resultType="myUser">
    select * from day2021_9_6_studyMybatis_db.user;
</select>

需要注意的是,自带的一些别名,如果类型是基本类型的,就在原先的基础上前面加_;比如说,有个返回值为int的;那么,你在配置类型的时候就给他用别名_int ;这是为了和他们的包装类区分;比如规定的Integer类型的 别名 就是int.

其他类型的话,一般来说用小写就行;比如Date 别名就是date;Float别名就是float;


6.3 设置(settings)

需要注意的几个;

mapUnderscoreToCamelCase

是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
默认为false关闭状态的;

比如说在数据库写了个字段名为 res_time;然后在java实体类里面用的是驼峰命名法resTime;把这个属性一设置,它就能成功映射到对应的属性了;

logImpl; 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

在下半部分会用到日志;

cacheEnabled :全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认是开启的;
lazyLoadingEnabled 懒加载的全局开关。开启后所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。


6.4 映射器(mappers)

之前刚开始配好mybatis后,运行测试代码;出现的mapper映射问题;

org.apache.ibatis.binding.BindingException: Type interface com.xiaozhi.dao.UserDao is not known to the MapperRegistry.

MapperRegistry :注册绑定mapper文件;

需要在核心配置文件mybatis-config.xml,把UserMapper.xml添加注册;

主要有三种方式配置mapper
(1)使用相对于类路径的资源引用;

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

(2)使用映射器接口实现类的完全限定类名

<mappers>
    <mapper class="com.xiaozhi.dao.UserMapper"/>
</mappers>

但使用这种方式有时可能会出错;
错误一:如果说把Mapper.xml配置文件放在了别的包下;会出现找不到mapper的错误;
错误二:例如说有时候不注意把接口命名为UserDao;而映射文件还是UserMapper.xml,这也会出现找不到mapper的情况.
name怎么避免呢?==>
保证接口和xml配置文件在同一个包下;且是同名的.
(3)进行包扫描;将包下的所有映射器接口实现注册;
包扫描时也要保证接口和xml配置文件在同一个包下;且是同名的.

<mappers>
    <package name="com.xiaozhi.dao.UserMapper"/>
</mappers>

7. 生命周期,作用域

生命周期与作用域使用不当会导致并发问题;

在这里插入图片描述

SqlSessionFactoryBuilder在 创建sqlsessionfactory后就不需要了;

SqlSessionFactory;可以理解为一个数据库连接池; 被创建就应在应用的运行期一直存在,不需要销毁或重新创建实例。适用于应用作用域,可使用单例模式或者静态单例模式.

SqlSession 是连接到数据库的一个请求,它的实例不是线程安全的,所以无法被共享,作用于请求或方法的作用域;用完就关闭,防止资源浪费.
SqlSession可以去对应多个映射.


8. ResultMap 结果集映射

试试数据库字段名和实体类属性不一致的状况;
比如这里实体类的姓名username ;数据库为name

在这里插入图片描述

在这里插入图片描述

执行根据Id查询的方法;查询出的姓名为null,空值;(因为实体类的属性和数据库的字段名不一致了.)

在这里插入图片描述

解决方式一:在SQL查询语句使用别名;

<!--解决方式1:在sql字段使用别名-->
<select id="findUserById" resultType="myUser" parameterType="_int">
    select id,name as username,password from user where id= #{id};
</select>

在这里插入图片描述

解决方式二: 通过结果集映射

<!--将实体类属性和数据库字段对应起来-->
<resultMap id="UserMap" type="myUser">
    <result property="id" column="id"/>
    <result property="username" column="name"/>
    <result property="password" column="password"/>
</resultMap>

<!--返回值类型的 UserMap 引用上面的设置-->
<select id="findUserById" resultMap="UserMap" parameterType="_int">
    select * from user where id= #{id};
</select>

在这里插入图片描述


9. 日志

在运行项目的时候,出现异常就需要依次排除错误,以前常用的System.out控制台输出或者debug调试显然是比较麻烦的;使用日志无疑是一个比较好的方式.

9.1使用标准日志工厂

注意,setting中设置日志时,name必须为logImpl;

比如说,使用标准日志 STDOUT_LOGGING
mybatis-config.xml中设置开启日志;

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

测试执行根据Id查询的方法;

在这里插入图片描述


9.2 log4j 日志

可以控制打印的日志输出位置;控制台,文件或者GUI组件;
可控制输出格式;可定义日志的级别;可在配置文件中单独配置.

先把log4j的包导入到项目中;
pom.xml配置文件导入依赖;这里用的是log4j-1.2.17版本

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

resources目录 下创建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/xiaozhi.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

mybatis-config.xml 设置日志;

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

测试运行;比之前的标准日志更详细;

在这里插入图片描述


使用
在需要使用log4j的类;导入包;
import org.apache.log4j.Logger;
创建日志对象;参数为当前类的class;

static Logger logger= Logger.getLogger(UserDaoTest.class);

尝试在测试类中创建一个方法;调用日志方法;

static  Logger logger= Logger.getLogger(UserDaoTest.class);

@Test
public void testForLog(){
    logger.info("info:info信息==>");
    logger.debug("debug:debug调试==>");
    logger.error("error:异常==>");
}

查看控制台打印信息;

在这里插入图片描述

之前在lo4j.properties文件中已经设置在文件目录生成日志信息;

在这里插入图片描述

在这里插入图片描述


10.分页

10.1 直接在SQL语句中用limit分页,然后传参.

UserMapper 中定义方法;

//查询用户且分页;
List<User> findUserToLimit(Map<String ,Integer> map);

UserMapper.xml中写sql语句;

<!--直接在sql语句中使用limit分页-->
<select id="findUserToLimit" resultMap="UserMap" parameterType="map">
    select * from user limit #{pageNum},#{pageSize};
</select>

测试使用方法;

//使用limit分页;
@Test
public void testLimit(){
    SqlSession sqlSession=MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String ,Integer> map=new HashMap<String ,Integer>();
    //查询第二页,且每页5行数据;
    map.put("pageNum",5);
    map.put("pageSize",5);
    List<User> userToLimit = mapper.findUserToLimit(map);
    for (User user : userToLimit) {
        System.out.println(user);
    }
    //关闭sqlSession;
    sqlSession.close();
}

在这里插入图片描述


10.2 使用 RowBounds分页(不推荐使用)

UserMapper定义方法;

//查询分页;
List<User> findUserRowBounds();

UserMapper.xml中写sql语句,sql语句不写分页

<!--使用RowBounds的方法分页-->
<select id="findUserRowBounds" resultMap="UserMap">
    select * from user
</select>

测试,

//RowBounds ==> 已经不推荐使用;
    @Test
    public void testRowBounds(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();
        //实现分页;
        RowBounds rowBounds = new RowBounds(5,5);

        //此方式已不推荐使用;
        List<User> selectList = sqlSession.selectList("com.xiaozhi.dao.UserMapper.findUserRowBounds",null,rowBounds);
        for (User user : selectList) {
            System.out.println(user);
        }
        //关闭sqlSession;
        sqlSession.close();
    }

了解一下分页插件PageHelper

https://pagehelper.github.io/


11.使用注解开发

首先整个设置是面向接口编写程序的.

UserMapper接口的定义方法处直接使用注解;放入SQL语句;

可使用@Param注解,标明参数;sql传入的参数就是@Param注解中的参数名;
引用对象参数不用写@Param注解

package com.xiaozhi.dao;
import com.xiaozhi.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface UserMapper {

    //直接使用注解的方式;
    @Select("select * from user where id=#{id} and name=#{name}")
    //根据Id查询用户; 可以在方法处使用@Param注解,标明参数;sql传入的参数就是@Param注解中的参数名;
    User findUserById(@Param("id") int id,@Param("name") String name);

}

需要在mybatis-config.xml中绑定接口;

<!--绑定接口-->
 <mappers>
     <mapper class="com.xiaozhi.dao.UserMapper"/>
 </mappers>

在测试时,其实已经通过反射得到接口的定义方法返回值,

//根据Id查询用户;
 @Test
 public void testFindUserById(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     User user = mapper.findUserById(1, "小智");
     System.out.println(user);
     //关闭sqlSession;
     sqlSession.close();
 }

Mybatis开发的流程

把之前写的工具类详细看看;

public class MybatisUtils {
    private  static SqlSessionFactory sqlSessionFactory=null;
    //在调用工具类时就执行;
    static {
        try {
            //获取SqlSessionFactory对象;

            //获得配置文件;
            String resource = "mybatis-config.xml";
            //使用流读取资源;
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //加载资源流;
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //从 SqlSessionFactory 中获取 SqlSession;
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}

首先是由Resources获取且加载全局的配置文件,然后是实例化SqlSessionFactoryBuilder;
点进build方法的源码; 里面是创建XMLConfigBuilder来解析配置文件流的;

在这里插入图片描述

接着是Configuration传入所有的配置文件信息;

实例化sqlSessionFactory;
先通过事务管理transaction;
然后创建executor执行器;

创建sqlSession ===>实现增删改查;如果出了问题,就需要事务回滚,回到事务管理;

若执行成功,提交事务;关闭资源.


在写工具类的时候,使用方法openSession() 来获取sqlSession;

在这里插入图片描述

进入源码查看;该方法有很多重载的;注意到有个以布尔值为参数的方法;查看它的实现;

SqlSession openSession(boolean var1);

在这里插入图片描述

在这里插入图片描述

是否开启自动提交事务;

public SqlSession openSession(boolean autoCommit) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

然后呢,在工具类获取sqlSession的时候,给openSession()传入参数true;;也就是自动提交事务;那么就不用commit手动提交事务;

 //从 SqlSessionFactory 中获取 SqlSession;
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        return sqlSession;
    }

用注解完成一个添加方法;
UserMapper定义方法;且用注解完成sql语句;

//添加用户;
@Insert("insert into user values(#{id},#{name},#{password})")
void addUser(User user);

mybatis-config.xml上面已经绑定过UserMapper了,
测试即可,无需手动提交事务;

//添加用户;
@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(13,"蕉宝","846662"));
    //无需手动提交事务;
    //关闭sqlSession;
    sqlSession.close();
}

在这里插入图片描述


扩展小知识,在写sql语句时,#{}可防止sql注入, ${}无法防止sql注入;


12. Lombok插件的使用

目前不建议使用

找到lombok的jar包;导入项目

https://mvnrepository.com/artifact/org.projectlombok/lombok

本人使用 1.18.20 版本.

pom.xml配置文件导入依赖,

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>

在idea安装插件 lombok,插件的注解还是比较多的.

在这里插入图片描述

使用@Data注解;生成无参构造,get,set方法,equals方法,hashCode方法,toString方法.

在这里插入图片描述

注解@AllArgsConstructor生成有参构造方法

在这里插入图片描述

注解@NoArgsConstructor生成无参构造方法.

在这里插入图片描述

注解@ToString 生成toString方法;
注解@EqualsAndHashCode生成equals方法和hashCode方法

在这里插入图片描述

注解@Getter ,注解@Setter 放在类上,生成所有非静态的属性get,set方法;
放在属性上,仅生成该属性的get,set方法.

在这里插入图片描述


13. 复杂的查询

13.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);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (6, '臧绫', 1);

构建实体类;

//教师实体类;
@Data
public class Teacher {
    private int id;
    private String name;
}
//学生类;
@Data    
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

mybatis-config.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties"/>

    <!--设置标准日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--使用包扫描的方式 ;实体类的别名默认为首字母小写的类名-->
    <typeAliases>
        <package name="com.xiaozhi.pojo"/>
    </typeAliases>

    <!--environments:配置的环境,可以配置多个-->
    <environments default="development">
        <environment id="development">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <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.xiaozhi.dao.StudentMapper"/>
        <mapper class="com.xiaozhi.dao.TeacherMapper"/>
    </mappers>
</configuration>

13.2 多对一情况

比如说,想要查询出所有学生,且包括教师的信息;
先在StudentMapper接口定义方法;

List<Student> finAllStudent();

然后去StudengMapper.xml编写sql语句;

<select id="finAllStudent" resultType="student">
  select * from student s ,teacher t where s.tid=t.id;
</select>

在测试目录下执行测试;

@Test
public void testFindStudent(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> studentList = mapper.finAllStudent();
    for (Student student : studentList) {
        System.out.println(student);
    }
    //关闭;
    sqlSession.close();
}

出现的问题就是查询到教师为null;空值;

在这里插入图片描述


按照类似子查询的方式, 查询嵌套

那么先把这个问题拆分开来;
用两段SQL语句;
先查询学生,再根据Id查询教师表;

在写sql之前;需要了解结果集Map映射
association 是表示对象; collection表示集合.
javaType;表示指定属性的类型;一般来说,在集合中的泛型类型用ofType指定

定义的方法不变,在StudentMapper.xml配置文件 中修改SQL,且使用resultMap安排结果集映射,将教师实体类的属性作为集合,整体传进在学生实体类中的教师属性;

<!--分布思路;先查询所有学生;在根据学生,查询教师-->
<select id="finAllStudent" resultMap="studentMap">
  select * from student 
</select>
<resultMap id="studentMap" type="student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>

    <association property="teacher" column="tid" javaType="teacher" select="findTeacherById"/>
</resultMap>

<!--根据Id查询教师-->
<select id="findTeacherById" resultType="teacher">
  select * from teacher where id=#{tid};
</select>

执行测试;查询成功;

在这里插入图片描述

查询结果嵌套

在查询的结果集类型中将教师属性关联;

  <select id="finAllStudent" resultMap="studentRMap2">
        select s.id sid ,s.name sname , t.id teaid, t.name tname
        from student s ,teacher t
        where s.tid=t.id;
    </select>

    <resultMap id="studentRMap2" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>

        <association property="teacher" javaType="teacher">
            <result property="name" column="tname"/>
            <result property="id" column="teaid"/>
        </association>
    </resultMap>

亦可查询

在这里插入图片描述


13.3 一对多的情况

一个教师对应查询多个学生

实体类需要做部分改动,学生类中不用封装教师类作为属性;在教师类中需要封装学生类集合;

学生类;

@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;
}

按照查询语句嵌套

查询指定的教师对应的学生;
TeacherMapper接口中定义方法;

//查询指定教师的对应学生;
Teacher findStuByTeaId(@Param("tid") int id);

association 是表示对象; collection表示集合.
javaType;表示指定属性的类型;一般来说,在集合中的泛型类型用ofType指定

TeacherMapper.xml配置文件中写入sql语句,以及它的对应结果集类型映射;
先根据Id查询教师,把查询的教师Id传入查询对应学生.

 <!--先根据 id查询教师-->
    <select id="findStuByTeaId" resultMap="TeacherMap2">
        select * from teacher where id=#{tid};
    </select>
    <resultMap id="TeacherMap2" type="teacher">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!--这里的javaType表示 List集合类型,ofType 表示集合中的泛型 student类型-->
        <collection property="students" javaType="ArrayList" ofType="student" column="id" select="findStuById"/>
    </resultMap>

    <!--根据教师Id查询对应的学生-->
    <select id="findStuById" resultType="student">
        select * from student where tid=#{tid};
    </select>

测试查询

@Test
public void testFindTeacher(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    Teacher stuByTeaId = mapper.findStuByTeaId(1);
    System.out.println(stuByTeaId);
    sqlSession.close();
}

结果

在这里插入图片描述


按照结果嵌套查询

TeacherMapper.xml配置文件中写入sql语句,以及它的对应结果集类型映射;

<select id="findStuByTeaId" resultMap="TeacherMap">
  select s.name sname , s.id sid ,t.id tid , t.name tname
  from teacher t,student s where
  t.id=s.tid and tid=#{tid}
</select>

<resultMap id="TeacherMap" type="teacher">
   <result property="id" column="tid"/>
   <result property="name" column="tname"/>
   <!--这里使用 collection 集合来映射学生表的属性和数据库查询字段-->
   <collection property="students" ofType="student">
      <result property="id" column="sid"/>
      <result property="name" column="sname"/>
      <result property="tid" column="tid"/>
   </collection>
</resultMap>

测试查询结果;

在这里插入图片描述


14. 动态SQL的使用

根据不同的条件生成不同的sql语句


14.1首先搭建动态sql环境

首先创建个数据表;

-- 博客表;
CREATE TABLE `blog`(
       id VARCHAR(200) NOT NULL COMMENT 'ID号',
       `title` VARCHAR(50) 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;

去idea创建项目;导包,写mybatis-config.xml配置文件;写工具类;和实体类;创建mapper层接口,以及mapper.xml配置文件.

在这里插入图片描述

实体类Blog

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    //注意有个属性名与数据库的字段名不一致;数据库字段为 create_time ;
    private Date createTime;
    private int views;
}

做个生成随机Id的工具类;
使用UUID;

UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。

public class IdUtils {
    public  static String  getId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}   

对于刚才的实体类中属性createTime和数据库名的字段create_time 命名不一致,可在mybatis的核心配置文件中更改settings设置为开启驼峰命名自动转换.

mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties"/>
    <settings>
        <!--设置为标准日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--设置为开启驼峰命名匹配-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--使用包扫描的方式 ;实体类的别名默认为首字母小写的类名-->
    <typeAliases>
        <package name="com.xiaozhi.pojo"/>
    </typeAliases>
    <!--environments:配置的环境,可以配置多个-->
    <environments default="development">
        <environment id="development">
            <!--transactionManager:事务管理;这里默认使用JDBC-->
            <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.xiaozhi.dao.BlogMapper"/>
    </mappers>
</configuration>

定义方法为数据表添加数据,顺便测试搭建环境;
BlogMapper接口定义方法

//写方法,添加数据;
void addBlog(Blog blog);

BlogMapper.xml配置文件写sql语句;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--这里mapper的空间对应创建的持久层接口;-->
<mapper namespace="com.xiaozhi.dao.BlogMapper">

    <!--添加博客的sql-->
    <insert id="addBlog" parameterType="blog" >
        insert into blog(id,title,author,create_time,views)
        values (#{id},#{title},#{author},#{createTime},#{views})
    </insert>
</mapper>

测试执行;

public class TestBlog {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //使用类的反射机制获得mapper映射;
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        //新建博客对象;
        Blog blog =new Blog();

        //就近原则;
        blog.setId(IdUtils.getId());
        blog.setTitle("学习JavaSE基础");
        blog.setAuthor("JAVA大师");
        blog.setCreateTime(new Date());
        blog.setViews(10000);
        mapper.addBlog(blog);

        blog.setId(IdUtils.getId());
        blog.setTitle("学习Mysql");
        blog.setAuthor("CRUD专家");
        blog.setViews(50000);
        mapper.addBlog(blog);

        blog.setId(IdUtils.getId());
        blog.setTitle("学习框架");
        blog.setAuthor("CV大法师");
        blog.setViews(90000);
        mapper.addBlog(blog);

        blog.setId(IdUtils.getId());
        blog.setTitle("学习多线程");
        blog.setAuthor("杰斯");
        blog.setViews(2000);
        mapper.addBlog(blog);
        //关闭sqlSession;
        sqlSession.close();
    }
}

执行结果,成功;搭建的基本环境暂时没有问题;

在这里插入图片描述

在这里插入图片描述


14.2 动态SQL -->IF标签使用

BlogMapper接口定义查询方法

//查询语句;有if条件;
List<Blog> findBlogHaveIf(Map map);

BlogMapper.xml中 编写sql语句;
注意会用到if判断;前面的 1=1 防止后面的条件若都不符合就查询输出所有的数据;

只要对需要查询的输入对应参数即可进行查询;不用去手动改变SQL语句;

<!--查询博客-->
<select id="findBlogHaveIf" resultType="blog" parameterType="map">
    select * from blog where 1=1
    <if test="title!=null">
        and title =#{title}
    </if>
    <if test="author !=null">
        and author =#{author}
    </if>
    <if test="views != null">
        and views =#{views}
    </if>
</select>

测试执行;

@Test
public void testFindBlogHaveIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map=new HashMap();
    List<Blog> blogs = mapper.findBlogHaveIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

在这里插入图片描述

若在执行时,对title属性进行赋值;就要查询这个指定的属性对应结果;只需在测试执行时添加;

 @Test
public void testFindBlogHaveIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map=new HashMap();
    map.put("title","学习Mysql");
    List<Blog> blogs = mapper.findBlogHaveIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

查询结果 ;它会自动地调整SQL语句.

在这里插入图片描述

若要添加title与views为过滤条件

@Test
public void testFindBlogHaveIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map=new HashMap();
    map.put("title","学习Mysql");
    map.put("views","50000");
    List<Blog> blogs = mapper.findBlogHaveIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

执行;

在这里插入图片描述


14.3 动态SQL的常用标签

where 标签;

在上面使用 if 标签时 ,注意到当时在 where 后拼接了一个 1=1;但是实际很多使用中 加这个1=1看起来不是很规范;
那么用<where> 标签替代where,且把需要执行的语句包裹起来,如果说where标签内没用到任何一个条件,那么where就不会拼接到SQL上去; 还有就是,如果where 后的条件开头有and 或者 or,where标签都会把他们去掉.

动手试试吧.修改一下刚才的SQL语句;

<!--修改一下,用where标签-->
<select id="findBlogHaveIf" resultType="blog" parameterType="map">
    select * from blog
    <where>
    <if test="title!=null">
        title =#{title}
    </if>
    <if test="author !=null">
        and author =#{author}
    </if>
    <if test="views != null">
        and views =#{views}
    </if>
</where>
</select>

什么条件参数也不传入,看看是否会自动地不加where

@Test
 public void testFindBlogHaveIf(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
     HashMap map=new HashMap();
    /* map.put("title","学习Mysql");*/
     /*map.put("views","50000");*/
     List<Blog> blogs = mapper.findBlogHaveIf(map);
     for (Blog blog : blogs) {
         System.out.println(blog);
     }
     sqlSession.close();
 }

执行时,确实没有出现where

在这里插入图片描述

再通过条件浏览量 views去查询;注意看看它是否会去掉前面的 and

@Test
 public void testFindBlogHaveIf(){
     SqlSession sqlSession = MybatisUtils.getSqlSession();
     BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
     HashMap map=new HashMap();
     map.put("views","50000");
     List<Blog> blogs = mapper.findBlogHaveIf(map);
     for (Blog blog : blogs) {
         System.out.println(blog);
     }
     sqlSession.close();
 }

确实自动去掉了and

在这里插入图片描述


choose (when, otherwise)标签
若有多个条件,可选择其中的一个,类似于java的判断switch语句.
otherwise 其他条件

还是查询方法;不过这次用choose进行判断过滤
BlogMapper接口定义方法;

//查询语句;用choose条件;
List<Blog> findBlogByChoose(Map map);

BlogMapper.xml中 编写sql语句;

<select id="findBlogByChoose" resultType="blog" parameterType="map">
    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>

输入title,author,views参数进行查询;

@Test
public  void testFindBlogByChoose(){
	   SqlSession sqlSession = MybatisUtils.getSqlSession();
	   BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
	   HashMap map=new HashMap();
	   map.put("title","学习Mysql");
	   map.put("author","大师");
	   map.put("views","50000");
	   List<Blog> blogs = mapper.findBlogByChoose(map);
	   for (Blog blog : blogs) {
	       System.out.println(blog);
	   }
	   sqlSession.close();
}

执行时,可看到,仅选择了title参数作为条件;

在这里插入图片描述


set
set 标签可动态地在行首插入 SET 关键字,删掉额外的逗号

比如说写个修改方法;
BlogMapper接口中定义方法;

void updateBlogBySet(Map map);

BlogMapper.xml 中编写sql语句;

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

注意执行时,它自动去掉了 author 后的逗号

@Test
public  void testUpdateBlogBySet(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map=new HashMap();
    map.put("title","关于转生那件事");
    map.put("author","阿猫");
    map.put("id","7de2fa32bb1f42649bbe3d04e2afd796");
    mapper.updateBlogBySet(map);
    sqlSession.close();
}

在这里插入图片描述


14.4 sql片段

SQL片段也就是把一些公用的SQL语句提取出来;方便使用;避免代码冗余.

可使用 <sql > 标签中写入公用的SQL语句;然后用<include> 标签去引用;
<include> 标签 的refid 对应 <sql>标签的id;

建议不要将复杂SQL作为SQL片段.

比如说,在查询方法的SQL语句中,提取出SQL片段;用inclued标签引入.

<select id="findBlogHaveIf" resultType="blog" parameterType="map">
    select * from blog
    <where>
    <include refid="isPublicCode"></include>
    </where>
</select>

<!--使用sql标签取出公用的代码片段-->
<sql id="isPublicCode">
    <if test="title!=null">
        title =#{title}
    </if>
    <if test="author !=null">
        and author =#{author}
    </if>
    <if test="views != null">
        and views =#{views}
    </if>
</sql>

14.5 动态SQL之foreach标签.

在这里插入图片描述

遍历时,遍历的是一个集合,索引为index, 每次遍历取出的元素就是 item; 用open表示开头 ;close表示结尾;使用separator作为分隔符,将 SQL语句拼接起来.


练习;

先将数据表的ID修改一下;UUID太长了;

在这里插入图片描述

BlogMapper接口定义根据 id查询方法;

//根据Id查询,
List<Blog> findBlogForEach(Map map);

BlogMapper.xml中编写SQL语句;注意在foreach标签中拼接SQL语句;

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

测试执行;

@Test
public  void testFindBlogForEach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map=new HashMap();
    //定义存放 id的集合;
    List<Integer> list=new ArrayList<Integer>();
    list.add(1);
    list.add(3);
    //将存有id的集合,放入map;
    map.put("allId",list);

    List<Blog> blogForEach = mapper.findBlogForEach(map);
    for (Blog blog : blogForEach) {
        System.out.println(blog);
    }
    sqlSession.close();
}

注意到id的参数动态地拼接到where条件后

在这里插入图片描述


15.缓存

进行查询时,需要与数据库连接,会耗费资源,如果说可以把一次操作查询到的数据,先给他暂时放到一个地方,下次要用的时候直接去暂存的地方取,就不去数据库了;提高性能;也不耗费过多资源.

缓存就是在内存中的临时数据;可以把用户常用的查询数据放到缓存,可提高效率,解决高并发性能问题.

经常要查询不经常改变的数据适合放入缓存.

15.1一级缓存

一级缓存是默认开启的,它是sqlSession级别的.

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

可以进行测试;先开启日志,然后将一个用户查询两次,看看是否来自同一个地址;

这次就用之前的用户表,
先在UserMapper接口定义查询方法,

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

UserMapper.xml 配置文件中编写sql

<!--根据Id查询用户-->
<select id="findUserById" resultType="user" parameterType="_int">
    select * from user  where id=#{id}
</select>

测试,再一次测试中根据Id查询同一个用户

public class TestUser {
    @Test
    public void testFindUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = mapper.findUserById(9);
        System.out.println(user);

        System.out.println("==============================");
        //在查询一次id为9 的用户;
        User user1=mapper.findUserById(9);
        System.out.println(user1);
        //判断两个查询结果是否来自同一个地址;
        System.out.println(user==user1);
        sqlSession.close();
    }
}

查看执行结果;
可看到sql语句与数据库仅交互了一次;且查询到的结果是来自同一个地址.

在这里插入图片描述


关于缓存失效

如果经过了添加,删除,或者更改,就刷新缓存.

试试看,在UserMapper接口定义修改方法;

//修改用户;
void updateUser(User user);

UserMapper.xml配置文件中编写sql

<!--修改用户-->
<update id="updateUser" parameterType="user">
    update user set name=#{name},password=#{password} where id=#{id};
</update>

同样滴,这次虽然还是在一次测试中查询两次Id为9的用户;但是在第二次查询之前;执行一下修改语句;比如说修改Id 为4的 用户 ;注意观察,这次执行出的结果;两次查询 id为9 的用户,地址是否来自同一个.

public class TestUser {
    @Test
    public void testFindUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = mapper.findUserById(9);
        System.out.println(user);

        //这里先执行一次修改操作;
        mapper.updateUser(new User(4,"修改","666666"));
        System.out.println("==============================");
        //在查询一次id为9 的用户;
        User user1=mapper.findUserById(9);
        System.out.println(user1);
        //判断两个查询结果是否来自同一个地址;
        System.out.println(user==user1);
        sqlSession.close();
    }
}

注意查看执行日志,由于经过了修改语句,所以第一次查询到的结果缓存失效了;第二次又去重新进行了一次查询,最终两次得到的地址不是同一个位置.

在这里插入图片描述


查询不同的数据,上次的缓存也会失效,

查询不同的xml;

手动清除缓存时,缓存失效;

试试手动清除缓存的方法;clearCache()
同样的,在第二次查询id为9的用户之前,手动清除缓存;

public class TestUser {
    @Test
    public void testFindUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user = mapper.findUserById(9);
        System.out.println(user);

        //这里手动清除缓存;
        sqlSession.clearCache();
        System.out.println("==============================");
        //在查询一次id为9 的用户;
        User user1=mapper.findUserById(9);
        System.out.println(user1);
        //判断两个查询结果是否来自同一个地址;
        System.out.println(user==user1);
        sqlSession.close();
    }
}

执行;由于手动清除了缓存,第一次查询到的结果缓存被清除了;所以会在第二次查询时,又去与数据库交互.

在这里插入图片描述


15.2 二级缓存

在核心配置文件的settings设置中的属性
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认是开启的

二级缓存也就是全局缓存, 它是namespace级别的缓存,即一个名称空间就会对应一个二级缓存;

一级缓存是sqlSession级别的;若关闭sqlSession,一级缓存失效就没了;
在它关闭前要是把缓存转移到二级缓存;那么再开启一段新的sqlSession,二级缓存的数据还能用;
不同的mapper查出的数据会放在自己对应的缓存(map) 中


Mapper.xml配置文件中 写入<cache/> 即开启了二级缓存.

当然,在<select> 标签处 可以使用 useCache 选择是否使用缓存,使用 flushCache 选择是否刷新缓存;

<update>,<delete> ,<insert> 标签处 可使用 flushCache 选择是否刷新缓存.


<cache中还可配置参数>
用参数eviction 选择清除策略;一下是几种清除策略

LRU –移除最长时间不被使用的对象。
FIFO – (先进先出):按对象进入缓存的顺序来移除。
SOFT – (软引用):基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

用参数flushInterval 可设置缓存刷新时间,以毫秒为单位;

用参数以毫秒为单位 设置可存放的引用的个数,默认1024

用参数readOnly 设置是否只读,默认为false;只读的缓存会给所有调用者返回缓存对象的相同实例


试试练习;
首先不开二级缓存;执行查询方法;
(还是之前那个查询方法,再写一次吧)
UserMapper接口定义的查询方法

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

还是UserMapper.xml中的语句

<!--根据Id查询用户-->
<select id="findUserById" resultType="user" parameterType="_int" >
    select * from user  where id=#{id}
</select>

这次的测试执行,首先开启两个 sqlSession ;也就是有两个mapper ;之前说过了不同的mapper查出的数据会放在自己对应的缓存(map) 中;那么试试看,在两个mapper中分别调用查询同一个用户的方法;

public class TestUser {
    @Test
    public void testFindUserById(){
        //开启两个sqlSession;
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        //有两个mapper;
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession1.getMapper(UserMapper.class);

        User user1 = mapper1.findUserById(9);
        System.out.println(user1);

        System.out.println("==============================");

        User user2 = mapper2.findUserById(9);
        System.out.println(user2);
        System.out.println("查询的两个用户是否来自同一地址=>"+(user1==user2));

        //关闭sqlSession;
        sqlSession1.close();
        sqlSession2.close();
    }
}

执行结果;可看到,SQL语句执行了两次,两次结果不是同一地址.

在这里插入图片描述

那么;现在试试开启二级缓存后的效果;
UserMapper.xml配置文件中开启二级缓存.

<!--开启二级缓存-->
<cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

在第一个sqlSession关闭前,查询到的Id为9的用户数据被存到二级缓存中;

public class TestUser {
    @Test
    public void testFindUserById(){
        //开启两个sqlSession;
        SqlSession sqlSession1 = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        //有两个mapper;
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

        User user1 = mapper1.findUserById(9);
        System.out.println(user1);
        //关闭sqlSession;
        sqlSession1.close();
        System.out.println("==============================");

        User user2 = mapper2.findUserById(9);
        System.out.println(user2);

        System.out.println("查询的两个用户是否来自同一地址=>"+(user1==user2));

        //关闭sqlSession
        sqlSession2.close();
    }
}

执行;可看到sql语句仅执行了一次,且两次查询到的结果来自同一地址.

在这里插入图片描述


现在不给二级缓存设置任何参数
即在UserMapper.xml配置文件中的二级缓存改为

<!--开启二级缓存-->
<cache/>

再次执行刚才的测试程序;执行报错了.提示对象没有序列化.

Error serializing object. Cause: java.io.NotSerializableException: com.xiaozhi.pojo.User

在这里插入图片描述

好,那就去实体类实现Serializable 接口;给他开启序列化;
实体类User

//这里使用Lombok插件的注解;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String password;
}

再次执行测试程序,sql语句执行了一次;
通过序列化返回缓存对象的拷贝,所以不是一个对象了

在这里插入图片描述


15.3 Mybatis缓存的原理

数据缓存的时候;先被存到一级缓存中,若开启了二级缓存;那么在sqlSession关闭之前就把数据存入二级缓存.

而进行访问查询的时候;第一次查询时,先看二级缓存中是否存在数据;然后才去二级缓存中查询;如果都没找到,去数据库查询;

15.4 自定义缓存 ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

首先要用它,去找jar包,话不多说,这就去Maven仓库查询;

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

resources目录下创建 ehcache.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<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>

<cache> 标签的type参数指定的类;

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

查看EhcacheCache

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.mybatis.caches.ehcache;

public class EhcacheCache extends AbstractEhcacheCache {
    public EhcacheCache(String id) {
        super(id);
        if (!CACHE_MANAGER.cacheExists(id)) {
            CACHE_MANAGER.addCache(id);
        }

        this.cache = CACHE_MANAGER.getEhcache(id);
    }
}

也可以自定义模仿写个缓存类;实现Cache 接口即可


  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-09-18 10:14:40  更:2021-09-18 10:14:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:16:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码