Mybatis学习

Mybatis学习

在学习之前

学习环境:

  • JDK1.8
  • Mysql8.0.22
  • maven3.6
  • IDEA2020.2.3
  • Mybatis

学习前提:

  • JDBC
  • Mysql
  • Java基础
  • Maven
  • Junit

1、简介

1.1、什么是Mybatis

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

如何获得Mybatis

  • maven仓库:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
  • GitHub:https://github.com/mybatis/mybatis-3/releases
  • 中文文档:https://mybatis.org/mybatis-3/zh/index.html

文档很重要, 文档的内容是非常全面的,包括本篇笔记也是有很多文档上的内容, 可以将笔记与文档比对着看

1.2、持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转换的过程
  • 内存: 断电即失
  • 数据库(jdbc), io文件持久化

为什么数据需要持久化

因为有一些数据,需要长期多次使用,需要保存下来

1.3、持久层

我们了解的层有: Dao层 Service层 Controller层

  • 持久层就是完成持久化工作的代码块
  • 层的界限是否明显

1.4、为什么需要Mybatis

  • 方便
  • 传统的JDBC代码太复杂了, 使用Mybatis将其简化
  • 帮助程序员将数据存入到数据库中
  • Mybatis不是必须的,只是一个框架,不使用Mybatis也可以实现这些功能, 只是使用Mybatis更加方便,简洁
  • 优点:
    • 简单易学
    • 灵活
    • sql与代码分离,提高了可维护性
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签, 支持对象关系组件维护
    • 提供xml标签, 支持编写动态sql

2、第一个Mybatis程序

思路: 搭建环境–>导入Mybatis–>编写代码–>测试

2.1、搭建环境

2.1.1 搭建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

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

INSERT INTO `user`(`id`,`name`,`pwd`)VALUES
(1,'张三',123456),
(2,'李四',234567),
(3,'王五',345678);

2.1.2 新建项目

1.新建一个Maven项目

2.删除src目录(用作父工程)

3.导入Maven依赖:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--父工程-->
    <groupId>com.hassder</groupId>
    <artifactId>MybatisStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--导入依赖-->
    <dependencies>
        <!--mysql驱动-->
        <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.5.2</version>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

</project>

至此,环境搭建完成,如图所示:

在这里插入图片描述

2.2、创建一个模块

创建一个mybatis-01模块

2.2.1 编写mybatis核心配置文件

<?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 核心配置文件-->
<configuration>
    <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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property  name="username" value="root"/>
                <property  name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

在这里插入图片描述

在当前模块下的main目录的resources下创建一个mybatis-config.xml文件, 文件内容如上所示(DataSource中的内容根据自己实际信息填写)

2.2.2 编写mybatis工具类

package com.hassder.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;

// sqlSessionFactory --> sqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory ;

    static {
        try {

            // 使用mybatis第一步:获取sqlSessionFactory
            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();
    }

}

2.3、编写代码

  • User实体类
package com.hassder.pojo;

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

    public User() {
    }

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

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • Dao(Mapper)接口
package com.hassder.dao;

import com.hassder.pojo.User;

import java.util.List;

public interface UserDao {
    List<User> getUserList() ;
}
  • 接口实现类

不再像以前一样需要使用实现类去实现接口了,而是使用Mapper.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">
<!--namespace= 绑定一个对应的Dao(Mapper)接口-->
<mapper namespace="com.hassder.dao.UserDao">

    <!--select 查询语句-->
    <!--select id="方法名" resultType="返回对象类型(全路径)"-->
    <!--parameterType="参数类型" 在需要传入参数的时候需要用到此属性-->
    <select id="getUserList" resultType="com.hassder.pojo.User" >
        /*编写要执行的sql语句*/
        select * from mybatis.user
    </select>
</mapper>

2.4 测试

注意点: 每一个Mapper.xml都需要在Mybatis核心配置文件中注册

在这里插入图片描述

否则报错:

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

还有一个问题:

在这里插入图片描述

这是因为在Maven中, 约定大于配置, 有时候我们写的配置文件可能无法生效或者被导出,需要我们手动添加,下面pom.xml文件的最后一段<build></build>内的内容即是, 这是在父工程进行设置, 若设置完还无法生效, 则将那一段粘贴到对应子工程的pom.xml文件中即可,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--父工程-->
    <groupId>com.hassder</groupId>
    <artifactId>MybatisStudy</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>mybatis-01</module>
    </modules>

    <!--导入依赖-->
    <dependencies>
        <!--mysql驱动-->
        <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.5.2</version>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

    <!--Maven由于他的约定大于配置,我们之后可能会遇到我们写的配置文件无法被导出或者生效的问题-->
    <!--在build中配置resources,来防止我们资源导出的问题-->
    <!--先放在父工程的pm.xml文件内,可能无法生效,如果无法生效,可以将其复制粘贴到我们需要的子工程的pom.xml中去-->
    <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>

</project>

如果添加上述配置之后还没有解决问题,可以尝试着删除target文件,将maven去clean一下(clean的方法与运行结果在同一张图), 或者重启idea后再尝试运行,如果还存在问题,或许是其它代码有问题

junit测试

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {

    @Test
    public void test(){

        // 第一步:获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 执行SQL
        // 方式一: getMapper() (推荐)
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList1 = userDao.getUserList();

        // 方式二: sqlSession.selectList() (不推荐)
        List<User> userList2 = sqlSession.selectList("com.hassder.dao.UserDao.getUserList");

        for(User user : userList1){
            System.out.println(user);
        }
        System.out.println("-------------------------------");
        for(User user : userList2){
            System.out.println(user);
        }
        // 关闭sqlSession
        sqlSession.close();
    }
}

使用第一种方式比第二种方式更加安全, 不需要进行类型强转

运行结果:

在这里插入图片描述

3、CRUD

我们一般使用UserMapper这样的命名方式,而不是UserDao, 在第一个Mybatis程序中,为了方便理解,所以仍然使用了UserDao这样的命名方式, 现在将其改为UserMapper(记住改名之后还有什么地方需要跟着改,全部改掉)

在第一个Mybatis程序中我们实现了对数据库表中所有数据的查询, 接下来我们实现其它的增删改查操作

3.1、查询(select)

使用id查询指定用户

需要修改的地方:

UserMapper接口:

package com.hassder.dao;

import com.hassder.pojo.User;

import java.util.List;

public interface UserMapper {

    // 查询所有用户
    List<User> getUserList() ;

    // 根据id查询指定用户
    User getUserById(int id) ;
}

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">
<!--namespace= 绑定一个对应的Dao(Mapper)接口-->
<mapper namespace="com.hassder.dao.UserMapper">

    <!--select 查询语句-->
    <!--1 查询所有用户-->
    <!--select id="方法名" resultType="返回对象类型(全路径)"-->
    <!--parameterType="参数类型" 在需要传入参数的时候需要用到此属性-->
    <select id="getUserList" resultType="com.hassder.pojo.User" >
        /*编写要执行的sql语句*/
        select * from mybatis.user
    </select>

    <!--2 根据id查询指定用户-->
    <select id="getUserById" parameterType="int"  resultType="com.hassder.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>
</mapper>

后续操作也只是在UserMapper接口中增加方法名和在UserMapper.xml配置文件中的<mapper></mapper>中增加部分内容,所以后续不再给出完整代码,而只给出增加/改变部分

测试:

@Test
public void testGetUserById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User userById = mapper.getUserById(1);
    System.out.println(userById);
    sqlSession.close();
}

3.2、添加(insert)

UserMapper接口:

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

UserMapper.xml:

<!--添加用户-->
<!--对象中的属性, 可以直接取出-->
<!--即可以在#{}中直接填写对象的属性名来获取其对应的值-->
<insert id="addUser" parameterType="com.hassder.pojo.User" >
    insert into mybatis.user (id,name,pwd) value (#{id},#{name},#{pwd})
</insert>

UserTest测试类:

@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User(4, "四号", "456789");
    int num = mapper.addUser(user);
    System.out.println(num);
    sqlSession.close();
}

运行结果:

在这里插入图片描述

为什么代码显示执行成功了,没有任何问题,但是在数据库表中却没有体现呢? 这是因为在增删改操作中,都涉及到了"事务"这个概念, 所以在执行完增删改操作之后还需要进行事务提交, 才能真正实现数据库表中数据的增删改.

那么要如何进行事务提交呢?修改代码如下所示:

@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User(4, "四号", "456789");
    int num = mapper.addUser(user);
    if (num>0){

        // 提交事务
        sqlSession.commit();
        System.out.println("添加成功");
    }
    sqlSession.close();
}

运行结果:

在这里插入图片描述

可以看到, 在我们增加了提交语句之后, 代码执行之后,数据库表中的数据也同步进行了变化

3.3、修改(update)

UserMapper接口:

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

UserMapper.xml:

<!--修改用户-->
<update id="updateUser" parameterType="com.hassder.pojo.User">
    update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}
</update>

UserTest测试类:

@Test
public void testUpdateUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User(4, "新四号", "987654");
    int i = mapper.updateUser(user);
    // 由于此处代码简单,所以就不加判断了 直接提交
    sqlSession.commit();
    sqlSession.close();
}

运行结果:
在这里插入图片描述

3.4、删除(delete)

UserMapper接口:

// 删除用户
int deleteUser(int id);

UserMapper.xml:

<!--删除用户-->
<delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id}
</delete>

UserTest测试类:

@Test
public void testDeleteUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int i = mapper.deleteUser(4);
    sqlSession.commit();
    sqlSession.close();
}

运行结果:

在这里插入图片描述

可以在数据库表中观察到, id为4的user已经被删除了

总结

各个不同功能大体的步骤都是:

1 编写接口(接口中的方法)

2 编写对应的mapper中的sql语句

3 测试(注意增删改需要提交事务)

整体代码:

当前(子)项目的pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>MybatisStudy</artifactId>
        <groupId>com.hassder</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mybatis-01</artifactId>

    <!--Maven由于他的约定大于配置,我们之后可能会遇到我们写的配置文件无法被导出或者生效的问题-->
    <!--在build中配置resources,来防止我们资源导出的问题-->
    <!--先放在主项目的pm.xml文件内,可能无法生效,如果无法生效,可以将其复制粘贴到我们需要的子项目的pom.xml中去-->
    <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>

</project>

当前项目的核心配置文件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 核心配置文件-->
<configuration>
    <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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property  name="username" value="root"/>
                <property  name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

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

工具类MybatisUtils:

package com.hassder.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;

// sqlSessionFactory --> sqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory ;

    static {
        try {

            // 使用mybatis第一步:获取sqlSessionFactory
            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();
    }

}

实体类User:

package com.hassder.pojo;

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

    public User() {
    }

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

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

接口UserMapper:

package com.hassder.dao;

import com.hassder.pojo.User;

import java.util.List;

public interface UserMapper {

    // 查询所有用户
    List<User> getUserList() ;

    // 根据id查询指定用户
    User getUserById(int id) ;

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

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

    // 删除用户
    int deleteUser(int id);

}

具体SQL的xml文件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">
<!--namespace= 绑定一个对应的Dao(Mapper)接口-->
<mapper namespace="com.hassder.dao.UserMapper">

    <!--select 查询语句-->
    <!--1 查询所有用户-->
    <!--select id="方法名" resultType="返回对象类型(全路径)"-->
    <!--parameterType="参数类型" 在需要传入参数的时候需要用到此属性-->
    <select id="getUserList" resultType="com.hassder.pojo.User" >
        /*编写要执行的sql语句*/
        select * from mybatis.user
    </select>

    <!--2 根据id查询指定用户-->
    <select id="getUserById" parameterType="int"  resultType="com.hassder.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>

    <!--添加用户-->
    <!--对象中的属性, 可以直接取出-->
    <!--即可以在#{}中直接填写对象的属性名来获取其对应的值-->
    <insert id="addUser" parameterType="com.hassder.pojo.User" >
        insert into mybatis.user (id,name,pwd) value (#{id},#{name},#{pwd})
    </insert>

    <!--修改用户-->
    <update id="updateUser" parameterType="com.hassder.pojo.User">
        update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}
    </update>

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id = #{id}
    </delete>

</mapper>

测试类UserMapperTest(之前命名为UserDaoTest,现在改正为UserMapperTest):

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

    @Test
    public void test(){

        // 第一步:获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        // 执行SQL
        // 方式一: getMapper() (推荐)
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        List<User> userList1 = userDao.getUserList();

        // 方式二: sqlSession.selectList() (不推荐)
        List<User> userList2 = sqlSession.selectList("com.hassder.dao.UserMapper.getUserList");

        for(User user : userList1){
            System.out.println(user);
        }
        System.out.println("-------------------------------");
        for(User user : userList2){
            System.out.println(user);
        }
        // 关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void testGetUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        System.out.println(userById);
        sqlSession.close();
    }

    @Test
    public void testAddUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(4, "四号", "456789");
        int num = mapper.addUser(user);
        if (num>0){

            // 提交事务
            sqlSession.commit();
            System.out.println("添加成功");
        }
        sqlSession.close();
    }

    @Test
    public void testUpdateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(4, "新四号", "987654");
        int i = mapper.updateUser(user);
        // 由于此处代码简单,所以就不加判断了 直接提交
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testDeleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.deleteUser(4);
        sqlSession.commit();
        sqlSession.close();
    }

}

项目整体结构如下:

在这里插入图片描述

4、Map和模糊查询(拓展)

4.1、Map

以上面用到的修改为例, 在上面的代码中,我们想要修改某一个user在数据库表中的信息时, 需要new一个新的对象,然后将对象作为方法参数传入, 在上面的试验过程中并没有任何问题,但是在实际开发过程中,加入那是一个拥有很多属性的类型, 但是我们只需要修改其中的个别属性, 如果还是按照上面的方法进行操作,那么我们就要new一个新的对象作为参数,这个新的对象中绝大部分属性都是与旧的对象相同,只有要修改的个别值是不同的, 也就是说有很多值都是用不上,不必要存在的,在这种情况下,我们就可以使用Map来进行操作:

代码实现:

UserMapper接口:

// 修改用户 使用Map修改
int updateUser2(Map<String,Object> map) ;

UserMapper.xml:


UserTest测试类:

<!--修改用户 使用Map修改-->
    <update id="updateUser2" parameterType="Map">
    update mybatis.user set pwd = #{userPwd} where id = #{userId}
</update>

运行结果:

在这里插入图片描述

修改测试方法如下:

@Test
public void testUpdateUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("userId",3);
    map.put("use1rPwd","6666766");
    int i = mapper.updateUser2(map);
    sqlSession.commit();
    sqlSession.close();
}

运行结果:

在这里插入图片描述

对比上面两次结果, 可以发现: 当我们使用Map来传入数据的时候,我们应该在Mapper.xml中做出如下修改:

  • parameterType=""的值修改为Map
  • sql语句中#{}内的值应该填写Map中对应的key,

也就是说,在使用Map操作时, sql语句使用Map的key来获取到对应的value,然后将value填入sql中的对应位置,并在数据库表中执行此语句

在第二次测试中,显然可以看出来, sql语句可以通过Map中的"userId"的key来获取到其值3, 但是Map中却没有值为"use1rPwd"的key,所以无法获取到值(也就是没有值,空值), 所以实际执行的sql语句是:

update mybatis.user set pwd = null  where id = 3

因此出现了将id为3的user的pwd修改为null的情况

(测试完成,将id=3的user的pwd改回666666)

在我们使用Map进行操作时,加入出现对象属性值很多但是我们只需要对其中一部分做变更操作时,就不需要写额外的信息了,直接使用Map即可,需要修改多少属性就往Map中添加多少个键值对,键代表属性名(键可以任意取名,只要填写在sql语句中对应正确的位置即可),值代表该属性修改后的值即可

4.2、模糊查询

为了体现模糊查询, 我们在表中添加一条数据(4,“李五”,“111111”),使表的内容如下图所示:

在这里插入图片描述

这种情况使用模糊查询有两种方式:

UserMapper接口内容相同:

// 模糊查询
List<User> getUserLikeList(String value) ;

方式一:

UserMapper.xml:

<!--模糊查询-->
<select id="getUserLikeList" resultType="com.hassder.pojo.User" >
    select * from mybatis.user where name like #{value}
</select>

UserMapperTest:

@Test
public void testGetUserLikeList(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userLikeList = mapper.getUserLikeList("%李%");
    for (User user : userLikeList) {
        System.out.println(user);
    }
    sqlSession.close();
}

测试结果:

在这里插入图片描述

方式二:

UserMapper.xml:

<!--模糊查询-->
<select id="getUserLikeList" resultType="com.hassder.pojo.User" >
    select * from mybatis.user where name like "%"#{value}"%"
</select>

UserMapperTest:

@Test
public void testGetUserLikeList(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userLikeList = mapper.getUserLikeList("李");
    for (User user : userLikeList) {
        System.out.println(user);
    }
    sqlSession.close();
}

测试结果:

在这里插入图片描述

使用这两者方式直接查询的结果是相同的

防止sql注入

我们可以使用concat来防止sql注入(实际上使用#{}就已经可以避免大部分sql注入的问题了),concat的作用是拼接

我们可以使用concat()将我们要传入的值包裹起来,来阻止依赖注入的发生,如下:

UPDATE mybatis.user SET NAME = concat(#{name}),pwd = concat(#{pwd}) where id = concat(#{id})

concat()的作用是无论其括号中是什么值,都将其当做字符串来拼接, 不作任何计算或者其它处理

不知道concat()的可以在sql中尝试运行下面几行sql语句 观察结果

SELECT * FROM mybatis.user WHERE `name` LIKE '%李%' OR 1=1
SELECT * FROM mybatis.user WHERE `name` LIKE '%李%' OR TRUE
SELECT * FROM mybatis.user WHERE `name` LIKE CONCAT('%李%' OR 1=1)
SELECT * FROM mybatis.user WHERE `name` LIKE CONCAT('%李%' OR TRUE)

5、配置解析

5.1、准备工作

之前我们使用的mybatis-01模块已经比较乱了, 为了方便后续的代码测试,我们使用与mybatis-01同样的方式创建一个mybatis-02, 并从mybatis-01中复制部分内容到mybatis-02, 其初始结构如下:

在这里插入图片描述

其中的各个文件内容如下(是从mybatis-01中的同路径同名文件复制,而后进行部分删减后得到的):

接口UserMapper:

package com.hassder.dao;

import com.hassder.pojo.User;

import java.util.List;

public interface UserMapper {

    // 查询所有用户
    List<User> getUserList() ;

    // 根据id查询指定用户
    User getUserById(int id) ;

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

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

    // 删除用户
    int deleteUser(int id);

}

xml配置文件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">
<!--namespace= 绑定一个对应的Dao(Mapper)接口-->
<mapper namespace="com.hassder.dao.UserMapper">

    <!--查询所有用户-->
    <select id="getUserList" resultType="com.hassder.pojo.User" >
        select * from mybatis.user
    </select>

    <!--根据id查询指定用户-->
    <select id="getUserById" parameterType="int"  resultType="com.hassder.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>

    <!--添加用户-->
    <insert id="addUser" parameterType="com.hassder.pojo.User" >
        insert into mybatis.user (id,name,pwd) value (#{id},#{name},#{pwd})
    </insert>

    <!--修改用户-->
    <update id="updateUser" parameterType="com.hassder.pojo.User">
        update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}
    </update>

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id = #{id}
    </delete>

</mapper>

实体类User:

package com.hassder.pojo;

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

    public User() {
    }

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

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

工具类MybatisUtils:

package com.hassder.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;

// sqlSessionFactory --> sqlSession
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();
    }

}

xml配置文件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 核心配置文件-->
<configuration>
    <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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property  name="username" value="root"/>
                <property  name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <mapper resource="com/hassder/dao/UserMapper.xml"/>
    </mappers>
</configuration>

测试类UserMapperTest:

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;


public class UserMapperTest {

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        List<User> userList1 = userDao.getUserList();
        for(User user : userList1){
            System.out.println(user);
        }
        sqlSession.close();
    }
}

首先运行测试类中的测试方法,得到结果:

在这里插入图片描述

确认结果无误后即可进行后续操作测试

5.2、核心配置文件

  • 一般来讲就是mybatis-config.xml
  • Mybatis的配置文件包含了会深深影响Mybatis行为的设置和属性信息
  • configuration(配置)
    • properties(属性)*
    • settings(设置)*
    • typeAliases(类型别名)*
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)*
      • environment(环境变量)*
      • transactionManager(事务管理器)*
      • DataSource(数据源)*
    • databaseldProvider(数据库厂商标识)
    • mappers(映射器)*

5.1.1 环境配置(environments)

Mybatis可以适配成适应多种环境, 但是每个SqlSessionFactory实例只能选择一种环境

Mybatis默认的事务管理器就是JDBC, 连接池(数据源)是POOLED

如何配置多套运行环境:

<?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 核心配置文件-->
<configuration>

    <!--设置默认使用的环境-->
    <!--environments default="环境id"在下面的代码中就是development或者test-->
    <!--environments default="development" 使用第一套环境-->
    <!--environments default="test"使用第二套环境-->
    <environments default="test">

        <!--第一套环境-->
        <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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property  name="username" value="root"/>
                <property  name="password" value="123456"/>
            </dataSource>
        </environment>

        <!--第二套环境-->
        <environment id="test">
            <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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property  name="username" value="root"/>
                <property  name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

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

5.1.2 属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性都是可以在外部配置并且可以动态替换的, 既可以在典型的Java属性文件中配置, 也可以通过properties元素的子元素来传递(现在用到的就是db.properties)

优化:

第一步 在当前子项目的main下的resources下创建一个db.properties文件

并在其中编写内容如下:

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

注意: 此部分内容可以直接在mybatis-config.xml文件中找到, 不要照抄上面的内容,而是要根据自己的实际配置去填写, 如果直接从mybatis-config.xml文件中进行复制,则要注意一点: 在mybatis-config.xml中的"&"需要进行转义,即我们使用的是&amp;但是在db.properties文件中&不需要转义,直接使用&即可,复制之后要将其修改

第二步 在mybatis-config.xml核心配置文件中引入db.properties文件

在核心配置文件中引入db.properties文件需要用到<properties><properties>标签, 那么这个标签应该写在什么位置呢, 首先尝试在任一位置写下此标签,发现有提示报错, 报错提示信息里面提出了不同标签之间的顺序, 因此,以后添加标签都需要按照此顺序进行,而不能在随意位置写

在这里插入图片描述

我们将其写在<configuration></configuration>内的最上部分,内容如下:

<properties resource="db.properties"/>

或者可以这样写:

<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</properties>

即可以在db.properties文件中写一部分, 在properties标签内还可以继续添加属性配置

如果在外部配置文件(db.properties)和properties标签内都写了某一个属性值, 那么将优先使用外部配置文件中的属性值

而后修改原来环境配置中的信息, 将其修改如下:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <!--引入外部配置文件后 可以直接使用外部配置文件中的值-->
            <!--这里的#{}中的内容是外部配置文件中的key(即配置文件中"="之前的部分)-->
            <!--它会自动替换为value(即配置文件中"="之后的部分)-->
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property  name="username" value="${username}"/>
            <property  name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

运行测试类中的测试方法, 发现可以正常获取数据库表中的数据, 因此,此部分优化成功

5.1.3 类型别名(typeAliases)

  • 在我们前面所写的代码中,如下图所示, 画出来的地方都需要使用类的完全限定名, 非常繁琐, 这个时候我们就可以使用别名来进行简化

在这里插入图片描述

  • 类型别名就是为Java类型设置一个短的名字, 其只与XML配置有关,存在的意义就是用来减少类的完全限定名的冗余

取类型别名有两种方式:

方式一, 直接指定类:

<!--类型别名-->
<!--方式1-->
<typeAliases>
    <typeAlias type="com.hassder.pojo.User" alias="User"/>
</typeAliases>

在核心配置文件中进行如此配置之后, 此前在UserMapper.xml中所写的com.hassder.pojo.User就可以全部简化为User, 如下图所示:

在这里插入图片描述

在修改完成之后,运行测试方法,仍然可以查询出数据库表中的数据, 说明修改没有问题

方式二, 指定包名:

<!--方式2-->
<typeAliases>
    <package name="com.hassder.pojo"/>
</typeAliases>

在这种方式下, 系统会将我们所指定的包路径下的所有Java类的类名的首字母小写来作为包下类的别名, 如果在使用第二种方式时,还想要进行自定义别名的操作, 则需要在对应类上添加注解@Alias(“别名”)

5.1.4 设置(settings)

一个完整的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>

设置(settings)的各属性及其取值范围如下:

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true | falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true | falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true | falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION | STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true | falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true | falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB | JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true | falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSql从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)true | falsefalse
defaultSqlProviderTypeSpecifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.A type alias or fully qualified class nameNot set

5.1.5 映射器

我们在编写第一个Mybatis程序的时候就已经接触过它了

在这里插入图片描述

MapperRegistry: 注册或绑定Mapper.xml文件

要实现映射器注册有四种方式, 其中有三种常用方式 如下:

<mappers>
    <!--第一种方式: 使用相对于类路径的资源引用 推荐使用-->
    <mapper resource="com/hassder/dao/UserMapper.xml"/>
    <!--第二种方式: 使用映射器接口实现类的完全限定类名-->
    <mapper class="com.hassder.dao.UserMapper"/>
    <!--第三种方式: 将包内的映射器接口实现全部注册为映射器-->
    <package name="com.hassder.dao"/>
    <!--第四种方式: 使用完全限定资源定位符(URL) 非常不推荐使用!!!-->
    <!--略-->
</mappers>
  • 使用第一种方式最为安全保险
  • 使用第二种方式需要注意以下几点:
    • 接口和它的Mapper.xml文件必须同名
    • 接口和它的Mapper.xml文件必须在同一包下
  • 使用第三种方式与使用第二种方式一样需要注意
    • 接口和它的Mapper.xml文件必须同名
    • 接口和它的Mapper.xml文件必须在同一包下

5.1.6 其它配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)

暂时不需要了解

5.1.7 生命周期和作用域

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

在这里插入图片描述

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory, 就不再需要SqlSessionFactoryBuilder了
  • 作为局部变量即可

SqlSessionFaction:

  • 可以理解为与数据库连接池的相同模式
  • SqlSessionFaction一旦被创建就会在应用的运行期间一直存在,没有任何理由丢弃它或者重新创建另一个SqlSessionFaction实例
  • SqlSessionFaction的最佳作用域就是应用作用域(存在并作用与整个程序存在期间)
  • 最简单的就是使用单例模式或者静态单例模式(保证全局唯一)

SqlSession

  • 连接到连接池的一个请求

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

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

6、ResultMap(结果集映射)

6.1、准备工作

新建一个子项目mybatis-03, 将mybatis-02中的所有内容复制, 运行测试方法,结果无误后开始修改代码 将修改代码如下:

实体类User:

package com.hassder.pojo;

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

接口UserMapper:

package com.hassder.dao;

import com.hassder.pojo.User;

public interface UserMapper {

    // 删除多余方法,仅留有一个用于测试即可
    // 根据id查询指定用户
    User getUserById(int id) ;

}

xml配置文件UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">

    <!--根据id查询指定用户-->
    <select id="getUserById" parameterType="int"  resultType="User">
        select * from mybatis.user where id = #{id}
    </select>

</mapper>

测试类UserMapperTest:

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class UserMapperTest {

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        User user = userDao.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    }
}

运行测试类,查看是否能够成功获取

运行结果:

在这里插入图片描述

可以看到, 程序没有错误,也能查询出结果,但是有一点问题,id值和name值都是对应正确的,password值却显示问null, 显然,这是因为我们修改了User实体类中的属性名,导致属性名与数据库表的字段名不一致而导致的.

怎么解决呢?

方法一: 起别名(sql中的别名)

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">

    <!--根据id查询指定用户-->
    <select id="getUserById" parameterType="int"  resultType="User">
        select id,name,pwd as password from mybatis.user where id = #{id}
    </select>

</mapper>

再次运行测试方法,查看结果,结果如下:

在这里插入图片描述

可以看到,结果恢复正常了, 这说明取别名是可以实现的, 但是如果实体类中有很多属性, 而且都与数据库表的字段名不一致, 难道要我们在sql语句中一个一个取别名吗?

我们将UserMapper.xml文件的内容改回去,有没有办法在不修改sql语句的前提下实现呢?

6.2、ResultMap

可以使用ResultMap来解决这个问题

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">

    <!--结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--colum对应数据库中的字段名 property对应实体类中的属性名-->
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    </resultMap>

    <!--根据id查询指定用户-->
    <select id="getUserById" resultMap="UserMap">
        select * from mybatis.user where id = #{id}
    </select>

</mapper>

在这里插入图片描述

运行测试方法,查看结果:

在这里插入图片描述

可以正确查询出结果

既然这样的话, 在我们的例子里, id和name两个值在数据库表和实体类中是可以对应起来的, 那么我们能不能省略这一部分的配置呢? 我们可以将堆id和name值的配置去掉,在重新测试:

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">

    <!--结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--colum对应数据库中的字段名 property对应实体类中的属性名-->
        <!--<result column="id" property="id"/>
        <result column="name" property="name"/>-->
        <result column="pwd" property="password"/>
    </resultMap>

    <!--根据id查询指定用户-->
    <select id="getUserById" resultMap="UserMap">
        select * from mybatis.user where id = #{id}
    </select>

</mapper>

测试结果:

在这里插入图片描述

我们发现,它依然可以正确的查询到结果并且将结果与实体类的属性对应起来,也就是说,在实际使用过程中,我们只需要在<resultMap></resultMap>中配置那些无法自动对应(不一样的)的属性名与字段名就可以了

  • ResultMap是Mybatis中最重要最强大的元素
  • ResultMap的设计思想是: 对于简单的语句根本不需要配置显示的结果映射, 而对于复杂一点的语句只需要描述它们的关系就行了.

7、日志

6.1 日志工厂

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

曾经我们可能使用过System.out.println()或者debug

但是现在 日志工厂才是最好的选择

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

STDOUT_LOGGING标准日志输出:

在mybatis核心配置文件中,进行日志的配置:

<!--写在properties与typeAliases之间-->
<settings>
    <!--如果想使用其他日志 只需要修改value的值即可-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

运行测试方法.结果如下:

在这里插入图片描述

6.2、Log4j

尝试使用LOG4J日志

<settings>
    <!--如果想使用其他日志 只需要修改value的值即可-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <setting name="logImpl" value="LOG4J"/>
</settings>

运行测试方法, 就会发现 报错了 :

在这里插入图片描述

这是因为我们没有导入LOG4J的包, 因为STDOUT_LOGGING是Mybatis的标准日志输出, 已经自带了, 但是LOG4J以及其它日志是需要我们手动导入包的

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

导入LOG4J依赖:

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

仅仅导入是不够的, 我们还需要一个配置文件log4j.properties(不用记, 网上找就行):

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/hassder.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 = DEBU

而后直接运行测试方法,结果如下:

在这里插入图片描述

现在的LOG4J和之前直接使用的STDOUT_LOGGING看起来似乎差不多, 好像没有什么特殊的,如果只是这样,似乎不必要使用LOG4J吧, 毕竟STDOUT_LOGGING连包都不用导, 更加方便, 既然LOG4J能够流行到现在,那么必然还有其它地方是受到用户喜爱的,那么我们应该怎么去使用它呢?

LOG4J的简单使用:

1 在要使用Log4j的类中,导入包org.apache.log4j.Logger(import org.apache.log4j.Logger;),不要导错!

2 生成日志对象, 参数为当前类的class, 设置为static

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

3 日志级别,常用的3个:

logger.info("info: 进入了infotestLog4j");
logger.debug("debug: 进入了infotestLog4j");
logger.error("error: 进入了infotestLog4j");

修改测试类

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;

public class UserMapperTest {

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

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        User user = userDao.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    }

    @Test
    public void testLog4j(){
        logger.info("info: 进入了infotestLog4j");
        logger.debug("debug: 进入了infotestLog4j");
        logger.error("error: 进入了infotestLog4j");
    }
}

testLog4j()的运行结果为:

在这里插入图片描述

与此同时,由于我们在log4j.properties文件中进行了如下设置:

在这里插入图片描述

所以系统还会将这些日志信息保存到当前的log下的hassder.log文件中,我们可以看看

在这里插入图片描述

可以看到,所有的debug信息都被保存在了这个文件中,如果我们想要保留更多或者更少的信息,则只需要在log4j.properties配置文件中进行修改即可.

在我们的实际开发过程中, 不可能让你一直使用sout语句来输出信息的, 所以要使用日志来完成一些记录

8、分页

学习MySQL的时候, 我们是使用Limit进行分页的

SELECT * FROM user LIMIT startIndex pageSize;

8.1、Limit分页

Limit分页就是直接在我们写的sql语句中添加limit部分来实现分页的效果

接口UserMapper:

// limit分页查询
List<User> getUserByLimit(Map<String,Integer> map) ;

xml配置文件UserMapper.xml:

<!--limit分页查询-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex} , #{pageSize}
</select>

测试方法:

@Test
public void testGetUserByLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper userDao = sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map = new HashMap<>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> users = userDao.getUserByLimit(map);
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}

运行结果:

在这里插入图片描述

从结果中可以看到,确实按照我们所需要的信息,在数据库表中查出了前两个数据, 并且还有日志显示,让我们对程序的运行过程有更加清晰的了解

这个是在sql的层面上实现的分页操作, 那么我们使用Mybatis能不能在Java代码的层次上实现分页操作呢

8.2、RowBounds分页

接口UserMapper:

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

xml配置文件UserMapper.xml:

<!--RowBounds分页查询-->
<select id="getUserByRowBounds" resultMap="UserMap">
    select * from mybatis.user
</select>

测试方法:

@Test
public void testGetUserByRowBounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    // 表示从第1个开始 显示2个
    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("com.hassder.dao.UserMapper.getUserByRowBounds",null,rowBounds);

    for (User user : userList) {
        System.out.println(user);
    }

}

运行结果:

在这里插入图片描述

使用这种方式也可以实现分页的效果, 但是在实际应用过程中, RowBounds方式还是使用较少, 一般都是使用Limit来实现分页效果的,因为相对而言Limit的效率更高, 当然 还可以使用一些其它的插件或者其它方法来实现分页的操作.

8.3、分页插件

在这里插入图片描述

网址: https://pagehelper.github.io/

想要尝试的话可以参考以下网址:https://pagehelper.github.io/docs/howtouse/ ,在这个网址里面已经说的非常详细了,所以这里不再过多说

了解即可, 如果以后在实际开发过程中需要使用, 至少要知道这是什么东西, 能够参考文档写出来就行

9、使用注解开发

9.1、面向接口编程

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

关于接口的理解:

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

三个面向的区别:

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

9.2、使用注解开发

9.2.1 准备工作

新建一个mybatis-04 ,复制之前的部分内容, 其结构如下:

在这里插入图片描述

修改其中部分代码,去掉不需要的部分:

接口UserMapper:

package com.hassder.dao;

public interface UserMapper {

}

实体类User不变

工具类MybatisUtils不变

配置文件db.properties不变

配置文件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>
        <typeAlias type="com.hassder.pojo.User" alias="User"/>
    </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>

</configuration>

准备工作完成, 接下来开始使用注解完成Mybatis工作

9.2.2 注解开发过程

接口UserMapper:

package com.hassder.dao;

import com.hassder.pojo.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {

    // 使用注解完成查询所有User
    // @Select("sql语句")
    @Select("select * from user")
    List<User> getUsers() ;

}

xml配置文件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>
        <typeAlias type="com.hassder.pojo.User" alias="User"/>
    </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.hassder.dao.UserMapper"/>
    </mappers>

</configuration>

按照之前的方法创建测试类,编写测试方法用于测试:

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

    @Test
    public void testGetUsers(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

运行测试方法, 查看运行结果:

在这里插入图片描述

可以看到, 执行效果是没有问题的, 但是问题在于User实体类中的password属性与数据库表中的pwd字段无法对应,所以获取不到值,因此, 甚至Mybatis的官方都表示,注解最好值使用在一些较为简单的查询情况里,而对于复杂一些的需求情况,最好还是使用配置xml配置文件的方式来实现, 也许在其它地方,我们都是使用注解多于配置文件, 但是在这个地方,最好还是使用xml配置文件, 因为相对而言, xml配置文件比注解更加详细准确.

9.3 使用注解完成CRUD

之前在我们进行增删改的操作之后,都需要手动使用sqlSession.commit()来进行事务提交的操作, 但是实际上我们在创建工具类的时候就可以进行自动提交的设置

工具类MybatisUnils:

package com.hassder.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;

// sqlSessionFactory --> sqlSession
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(){
        // 传入参数true
        // 观察源码
        return sqlSessionFactory.openSession(true);
    }

}

如果眼不够尖可能没有看出来哪里不同

在这里插入图片描述

实际上sqlSessionFactory.openSession()方法是有很多重载的, 其中有一个就是带有boolean类型的重载, 其中false表示不自动提交, true表示自动提交(默认为false), 所以我们只需要加一个true的参数即可实现自动提交, 而不需要每次手动提交了.

9.3.1 查询(select)

接口UserMapper:

// 使用注解完成 根据id查询user
@Select("select * from user where id = #{id}")
User getUserById(int id) ;

测试方法:

@Test
public void testGetUserById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

运行测试方法, 查看结果:

在这里插入图片描述

那如果有两个参数的时候该怎么办呢, 首先尝试一下

接口UserMapper:

// 使用注解完成 根据id和name查询user
@Select("select * from user where id = #{id} and name = #{name}")
User getUserByIdAndName(int id , String name) ;

测试方法:

@Test
public void testGetUserByIdAndName(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserByIdAndName(1,"张三");
    System.out.println(user);
    sqlSession.close();
}

运行结果:

在这里插入图片描述

很遗憾, 报错了 明显可以看到提示说"找不到id这个参数", 看样子使用注解开发时不能像xml那样, 根据参数名来自动对应到sql语句中的值, 或者说只有在只有一个参数的时候才能完成对应(这不是废话呢么), 那如果需要传入多个参数, 那该怎么办呢, 别急 我们有@Param()注解,下面看看这个注解该怎么使用吧:

接口UserMapper:

// 使用注解完成 根据id和name查询user
@Select("select * from user where id = #{id} and name = #{name}")
User getUserByIdAndName(@Param("id") int id , @Param("name") String name) ;

测试方法不变. 运行测试方法, 查看结果

在这里插入图片描述

现在没有问题了

  • @Param()注解只能作用在参数上
  • @Param()注解就是用来将方法参数与sql语句里的值对应的

如果不理解的话 可以将接口UserMapper中的方法改成下面这样,再运行:

// 使用注解完成 根据id和name查询user
@Select("select * from user where id = #{cs1} and name = #{cs2}")
User getUserByIdAndName(@Param("cs1") int id , @Param("cs2") String name) ;

上面这段代码也是可以让程序正常运行的, 也就是说, 在多个参数时,我们需要将#Param("")里的值与sql语句中#{}的值一一对应, 这样系统才能知道哪个参数对应哪个位置. 为了规范, 在使用注解方式进行sql操作时, 应该在所有与sql相关的基本类型数据参数前使用@Param()注解.

9.3.2 添加(insert)

在执行添加操作之前, 我们先看一下现在数据库表中的数据:

在这里插入图片描述

接口UserMapper:

// 使用注解完成 添加user
@Insert("insert into user(id,name,pwd) value(#{id},#{name},#{password})")
int addUser(User user) ;

测试方法:

@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(5,"五号","555555"));
    sqlSession.close();
}

运行结果:

在这里插入图片描述

可以看到, 数据被成功插入进去了

并且,在我们使用自己定义的类型(User)时, 是不需要使用@Param()注解的, 只需要在sql语句中的#{}位置写入类中属性名即可

9.3.3 修改(update)

接口UserMapper:

// 使用注解完成 修改user
@Update("update user set name = #{name} , pwd = #{password} where id = #{id}")
int updateUser(User user) ;

测试方法:

@Test
public void testUpdateUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updateUser(new User(5,"新五号","252525"));
    sqlSession.close();
}

运行结果:

在这里插入图片描述

那么之前使用xml配置文件实现的时候,我们用过的Map在这里还能使用吗?尝试一下

接口UserMapper:

// 使用注解和Map完成 修改user的部分值
@Update("update user set name = #{name} where id = #{id}")
int updateUserPart(Map<String,Object> map) ;

测试方法:

@Test
public void testUpdataUserPart(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String ,Object> map = new HashMap<>() ;
    map.put("id",5);
    map.put("name","旧五号");
    mapper.updateUserPart(map) ;
    sqlSession.close();
}

运行结果:

在这里插入图片描述

事实证明,Map在这里还是可以使用的

9.3.4 删除(delete)

接口UserMapper:

// 使用注解完成 删除user
@Delete("delete from user where id = #{id}")
int deleteUserById(@Param("id") int id) ;

测试方法:

@Test
public void testDeleteUserById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.deleteUserById(5) ;
    sqlSession.close();
}

运行结果:

在这里插入图片描述

10、Lombok

使用步骤:

1 在IDEA中安装Lombok插件(安装插件应该都会吧)

2 在项目中导入Lombok的jar包

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

3 使用我们想要的注解

@Getter and @Setter 作用在类上,生成所有属性的get、set(final)方法
@FieldNameConstants 默认生成一个常量,名称为大写字段名,值为字段名
@ToString 作用在类上 生成toString方法
@EqualsAndHashCode 作用在类上 生成equals和hashCode方法
@AllArgsConstructor 作用在类上,生成一个包含所有元素的构造方法
@NoArgsConstructor 作用在类上,生成一个无参构造方法 
@RequiredArgsConstructor 作用在类上,生成私有的一个包含常量,和标识了NotNull的变量的构造方法
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data 作用在类上,在类上, 直接为此类生成无参构造,所有元素的get、set(final)、tostring、hashcode、equals方法
@Builder 作用在类上,生成构造者(Builder)模式
@SuperBuilder 
@Singular 这个注解和@Builder一起使用,为Builder生成字段是集合类型的add方法,字段名不能是单数形式,否则需要指定value值
@Delegate 代理模式,把字段的方法代理给类,默认代理所有方法
@Value
@Accessors
@Wither
@With
@SneakyThrows 作用在方法上,将方法内的所有代码用try{}catch{}捕捉异常
@Synchronized 作用在方法上,将方法内的所有代码用Synchronized(){}包围
@val 作用在变量上,变量声明类型推断(不写变量类型,系统自动判断变量类型)
@var 作用在变量上,和val一样,官方文档中说区别就是var不加final修饰,但测试的效果是一样的
@UtilityClass
@ExtensionMethod 拓展方法,向现有类型“添加”方法,而无需创建新的派生类型。

目前掌握部分即可

  • @Data 作用在类上, 直接为此类生成无参构造,所有元素的get、set、tostring、hashcode、equals方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • @AllArgsConstructor 作用在类上,生成此类所有参数的有参构造(@Data生成的无参构造会消失)
  • @NoArgsConstructor 作用在类上,生成此类的无参构造

在这里插入图片描述

现在对Lombok褒贬不一, 有觉得好的也有觉得不好的, 应该根据自己的实际需求来取舍

11、多对一处理

概念

概念应该在学习mysql的时候就学习过了, 不再多说,简单来说,一个班里有很多学生和一个班主任,那么学生对班主任就是多对一(多个学生关联一个老师),班主任对学生就是一对多(一个老师关联多个学生)

11.1、准备工作

在mybatis数据库中新建表,并插入一些数据 sql代码如下:

USE mybatis
-- 创建一个表teacher 有id和name两个字段 主键为id
CREATE TABLE `teacher`(
	`id` INT(10) NOT NULL ,
	`name` VARCHAR(30) DEFAULT NULL ,
	PRIMARY KEY(`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;

-- 向teacher表中插入一条数据
INSERT INTO `teacher` ( `id` , `name` ) VALUES ( 1 , '王老师' );

-- 创建一个表student 有id name和tid三个字段 主键为id 有一个外键将tid与teacher表中的id绑定
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;

-- 向student表中插入五条数据
INSERT INTO `student` ( `id` , `name` , `tid` ) VALUES
	( '1' , '小明' , '1' ),
	( '2' , '小红' , '1' ),
	( '3' , '小张' , '1' ),
	( '4' , '小李' , '1' ),
	( '5' , '小王' , '1' );

新建一个子项目mybatis-05,结构如下图所示:

在这里插入图片描述

其中工具类MybatisUtils和配置文件db.properties与之前一样,mybatis-config.xml配置文件删除之前的绑定接口,其它不变

测试环境搭建:
在这里插入图片描述

在这里插入图片描述

pojo下的实体类(这里可以用Lombok简化代码, 要用的话自己添加jar包):

Student:

package com.hassder.pojo;

import java.util.Objects;

public class Student {
    private int id ;
    private String name ;

    // 学生需要关联一个老师
    private Teacher teacher ;

    public Student() {
    }

    public Student(int id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }

    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 Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                name.equals(student.name) &&
                teacher.equals(student.teacher);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, teacher);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

Teacher:

package com.hassder.pojo;

import java.util.Objects;

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

    public Teacher() {
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Teacher teacher = (Teacher) o;
        return id == teacher.id &&
                name.equals(teacher.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

dao下的接口:

StudentMapper:

package com.hassder.dao;

public interface StudentMapper {
}

TeacherMapper:

package com.hassder.dao;

public interface TeacherMapper {
}

工具类MybatisUtils不变

Mapper.xml配置文件:

StudentMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.StudentMapper">

</mapper>

teacherMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.TeacherMapper">

</mapper>

db.properties不变

Mybatis核心配置文件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>
        <typeAlias type="com.hassder.pojo.Student" alias="student"/>
        <typeAlias type="com.hassder.pojo.Teacher" alias="teacher"/>
    </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.hassder.dao.TeacherMapper"/>
        <mapper class="com.hassder.dao.StudentMapper"/>
    </mappers>

</configuration>

最后 在随便一个Mapper接口里进行一个查询操作,如果可以可以正常查询,则说明没有问题,环境搭建完成,否则肯定是有与上述不同的地方

我的测试:

接口TeacherMapper

package com.hassder.dao;

import com.hassder.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface TeacherMapper {

    @Select("select * from teacher where id = #{tid}")
    Teacher getTeacherById(@Param("tid")int id) ;
}

测试方法:

package com.hassder;

import com.hassder.dao.TeacherMapper;
import com.hassder.pojo.Teacher;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MyTest {

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

测试结果:

在这里插入图片描述

11.2、按照查询嵌套处理

查询所有学生信息,以及其对应的老师的信息

接口StudentMapper:

// 查询所有的学生信息,以及对应的老师信息
public List<Student> getStudent() ;

xml配置文件StudentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.StudentMapper">
    
    <select id="getStudent" resultMap="studentMap">
        select * from student
    </select>
    
    <resultMap id="studentMap" type="student">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!--复杂的属性,需要单独处理
        如果复杂属性是集合, 就使用collection,如果是其它对象,就使用association-->
        <association column="tid" property="teacher" javaType="teacher" select="getTeacher"/>
    </resultMap>

    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>

测试方法:

@Test
public void getStudentTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.getStudent();
    for (Student student : students) {
        System.out.println(student);
    }
    sqlSession.close();
}

运行结果:

在这里插入图片描述

执行的流程如下:

1 测试方法调用getStudent方法

2 在SudentMapper.xml找到sql语句执行, reaultMap为studentMap

3 在配置文件中找到名为studentMap的resultMap

4 根据studentMap中指定的关系将查询到的数据库表字段与Student类中的属性值一一对应

5 对应到最后一个字段tid, 发现它对应的是Student类中的teacher属性, 它还有javaType值,javaType值为teacher(这个值指的是在java中的类型为teacher,和前面那个teacher不一样,前面那个teacher指的是Student类中的一个属性名, 而这个是一个类型,这个teacher类型指的是com.hassder.pojo.Teacher这个类型, 这是在mybatis-congif.xml配置文件中的typeAliases配置的),后面还有一个select属性,值为getTeacher, 说明还要经过一次查询,所以它会继续在配置文件中找一个id值为getTeacher的查询语句

5 找到getTeacher查询语句之后,执行查询语句,并将这条查询语句的结果封装为teacher类型(java类型),作为Student类中的teacher属性的值

在这里插入图片描述

11.3、按照结果嵌套查询

还有另一种实现上述查询的方式, 如下:

接口StudentMapper:

public List<Student> getStudent2() ;

xml配置文件StudentMapper.xml

<select id="getStudent2" resultMap="studentMap2">
    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="studentMap2" type="student">
    <result column="sid" property="id"/>
    <result column="sname" property="name"/>
    <association property="teacher" javaType="teacher">
        <result column="teaid" property="id"/>
        <result column="tname" property="name"/>
    </association>
</resultMap>

测试方法:

@Test
public void getStudentTest2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.getStudent2();
    for (Student student : students) {
        System.out.println(student);
    }
    sqlSession.close();
}

运行结果:

在这里插入图片描述

可以看到,这种方式也是可以查询出我们想要的结果的,

在第一种方式中,是我们先进行其中一个表的查询,拿到结果之后,再拿其中与另一个表有个的数据部分去进行另一个查询,完成之后进行赋值,转换为Java类型

而在第二种方式中,我们一开始就采取多表查询的方式查询出我们所需要的所有属性值(学生id,学生name,老师id,老师name),然后再使用取别名以及嵌套resultMap的方式将四个值分别赋值给Student与Teacher中的属性(将s.id赋值给Student中的id,s.name赋值给Student中的name ,然后再将t.id赋值给Teacher的id,t.name赋值给Teacher的name,并形成一个teacher对象,然后将这个teacher对象赋值给Student的teacher属性,形成一个student对象)

12、一对多处理

环境搭建: 和多对一一样 , 创建一个mybatis-06子项目,并恢复到多对一处理时的初始状态

修改两个实体类, 其它不变:

Student:

package com.hassder.pojo;

import java.util.Objects;

public class Student {
    private int id ;
    private String name ;
    private int tid ;

    public Student() {
    }

    public Student(int id, String name, int tid) {
        this.id = id;
        this.name = name;
        this.tid = tid;
    }

    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 int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                tid == student.tid &&
                name.equals(student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, tid);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", tid=" + tid +
                '}';
    }
}

Teacher:

package com.hassder.pojo;

import java.util.List;
import java.util.Objects;

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

    // 一个老师拥有多个学生
    private List<Student> students ;

    public Teacher() {
    }

    public Teacher(int id, String name, List<Student> students) {
        this.id = id;
        this.name = name;
        this.students = students;
    }

    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 List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Teacher teacher = (Teacher) o;
        return id == teacher.id &&
                name.equals(teacher.name) &&
                students.equals(teacher.students);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, students);
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
}

只是将两个实体类的属性值变化了一下而已, 之前是Student中包含Teacher对象, 现在改成一个Teacher中包含多个Sudent对象(Student集合) 也就是将关系从多对一变成了一对多

如何根据老师的id获取老师信息以及这个老师的所有学生的信息?

12.1、按照结果嵌套查询

接口TeacherMapper:

// 根据老师id获取老师信息以及此老师的所有学生的信息
Teacher getTeacherById(@Param("tid") int id) ;

xml配置文件TeacherMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.TeacherMapper">
    
    <select id="getTeacherById" resultMap="teacherStudents">
        select s.id sid , s.name sname , t.id tid , t.name tname
        from student s, teacher t
        where s.tid = t.id and t.id =#{tid}
    </select>
    
    <resultMap id="teacherStudents" type="teacher">
        <result column="tid" property="id"/>
        <result column="tname" property="name"/>
        <!--collection需要有ofType这个属性, 其值表示这个集合内属性的类型-->
        <collection property="students" ofType="student">
            <result column="sid" property="id"/>
            <result column="sname" property="name"/>
            <result column="tid" property="tid"/>
        </collection>
    </resultMap>
    
</mapper>

测试方法:

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

运行结果:

在这里插入图片描述

12.2、按照查询嵌套处理

接口TeacherMapper:

Teacher getTeacherById2(@Param("tid") int id) ;

xml配置文件TeacherMapper.xml

<select id="getTeacherById2" resultMap="teacherStudents2">
    select * from teacher where id = #{tid}
</select>

<resultMap id="teacherStudents2" type="teacher">

    <!--不加id 查询结果中teacher的id为0-->
    <result property="id" column="id"/>
    <collection property="students" column="id" ofType="student" select="getStudentByTid"/>
</resultMap>

<select id="getStudentByTid" resultType="student">
    select * from student where tid = #{tid}
</select>

测试方法:

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

运行结果:

在这里插入图片描述

小结

  • 关联 - association
  • 集合 -collection
  • javaType & ofType
    • JavaType 用来指定实体类中属性的类型
    • ofType 用来指定映射到List或者集合中的pojo类型, 泛型中的约束类型

注意点:

  • 保证SQL的可读性, 尽量保证通俗易懂
  • 注意一对多的和多对一中, 属性名和字段的问题
  • 如果问题不好排查错误, 可以使用日志(推荐使用Log4j)

13、动态SQL

**由于在这一部分中 重要的是SQL语句 而不是查询结果 所以不再重复给出结果截图 **

动态SQL就是值根据不同的条件生成不同的SQL语句

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

13.1、搭建环境

创建数据库表:

USE mybatis
-- 在mybatis数据库中创建一个blog表,含有id,title,author,create_time,views五个字段
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

创建一个子项目mybatis-07

1 导包

2 编写配置文件与工具类

3 编写实体类

4 编写实体类对应的Mapper接口和Mapper.xml

mybatis-07 结构如下:

在这里插入图片描述

接口BlogMapper:

package com.hassder.dao;

import com.hassder.pojo.Blog;

public interface BlogMapper {

    // 插入数据
    int addBlog(Blog blog);
}

实体类Blog:

package com.hassder.pojo;

import java.util.Date;
import java.util.Objects;

public class Blog {
    private String id ;
    private String title ;
    private  String author ;

    // 属性名与数据库字段名不一致
    // 在mybatis-config.xml的settings中设置mapUnderscoreToCamelCase值为true来解决
    private Date createTime ;
    private int views ;

    public Blog() {
    }

    public Blog(String id, String title, String author, Date createTime, int views) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.createTime = createTime;
        this.views = views;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getViews() {
        return views;
    }

    public void setViews(int views) {
        this.views = views;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Blog blog = (Blog) o;
        return id == blog.id &&
                Objects.equals(title, blog.title) &&
                Objects.equals(author, blog.author) &&
                Objects.equals(createTime, blog.createTime) &&
                Objects.equals(views, blog.views);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title, author, createTime, views);
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", createTime=" + createTime +
                ", views='" + views + '\'' +
                '}';
    }
}

工具类IDUtils:

package com.hassder.utils;

import java.util.UUID;

public class IDUtils {

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

xml配置文件BlogMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hassder.dao.BlogMapper">

    <insert id="addBlog" parameterType="blog">
        insert into mybatis.blog(id, title, author, create_time, views)
        values ( #{id} , #{title} , #{author} , #{createTime} , #{views});
    </insert>

</mapper>

核心配置文件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"/>
        <!--开启自动将数据库字段名的_转换为驼峰命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--自动起别名-->
    <typeAliases>
        <package name="com.hassder.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.hassder.dao.BlogMapper"/>
    </mappers>
</configuration>

测试类:

package com.hassder.dao;

import com.hassder.pojo.Blog;
import com.hassder.utils.IDUtils;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;

public class TestBlogMapper {

    @Test
    public void addBlogTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog() ;

        blog.setId(IDUtils.getId());
        blog.setTitle("JAVA入门");
        blog.setAuthor("Hassder");
        blog.setCreateTime(new Date());
        blog.setViews(9999);
        mapper.addBlog(blog) ;

        blog.setId(IDUtils.getId());
        blog.setTitle("JAVA从入门到放弃");
        mapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("JAVA程序员的转职之路");
        mapper.addBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("从写AVA到卖炒粉");
        mapper.addBlog(blog);

        sqlSession.close();
    }
}

直接运行测试类中的测试方法, 用于插入数据 ,新增的工具类是用于随机生成id值的, 测试类运行成功后,查看数据库中blog表 若数据已经存储到数据库表中 , 则无误.(数据是后续需要使用的 ,当然 具体内容可以自己改)

13.2、IF语句

如果我们想实现以下效果:

当我们什么查询条件都不给的时候, 查询出所有的书籍信息, 当我们只输入title时,就查询出所有title符合的信息,当我们只输入author时,就查询出author符合的信息, 当我们同时输入title和author时,就输出同时符合两者的信息.

想要实现这个效果,我们该怎么办呢?

接口BlogMapper:

// 查询博客
List<Blog> queryBlogIF(Map map) ;

xml文件BlogMapper.xml

<!--如果 不在where后写1=1 那么 如果title 和author都为null时
sql语句就会变成select * from blog where 这是一条错误的语句
如果把where写在if里 那么 两个if 该把where写在哪里?
所以 用where 1=1 保证where后面必定有判断 即使title和author都为null 语句也不会有错误
而如果传入了title与author中的一个或两个值 1=1也不会对后续追加的条件产生影响
但是 注意 实际开发中是不会出现这种1=1的语句的, 现在只是为了方便测试
-->
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

测试方法:

@Test
public void queryBlogIFTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String, Object> map = new HashMap<>();
    System.out.println("map为null时:");
    System.out.println(mapper.queryBlogIF(map));
    map.put("title","JAVA入门");
    System.out.println("map只有title时");
    System.out.println(mapper.queryBlogIF(map));
    // 限于控制台篇幅 同时展示不了这么多结果, 所以没有测试只输入author时的效果 可以自己测试
    map.put("author","Hassder");
    System.out.println("map中两个都有时");
    System.out.println(mapper.queryBlogIF(map));

    sqlSession.close();
}

运行结果:

在这里插入图片描述

13.3、trim (where, set)

13.3.1 where

如果我们要把上面的sql语句规范一下,把1=1去掉,要怎么样才能保证sql语句不会有问题呢

如果我们单纯去掉1=1 ,会变成什么样呢?

<select id="queryBlogIF" resultType="blog">
    select * from blog where 
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

如果我们什么值都不给 sql语句就是:

select * from blog where 

这显然不是一个可以正常运行的sql语句

如果我们给了title没没有给author:

select * from blog where title = #{title}

这是一个正常的可以执行的sql

那如果我们把title和author都给了:

select * from blog where title = #{title} and author = #{author}

这也是一个正常的语句

那如果我们只给了author 而没有给title

select * from blog where and author = #{author}

这显然也是一个无法正确执行的sql语句

显然,如果我们直接这么写的话,就必须给定title才能正常执行了, 这肯定与我们的需求不符了, 那么mybatis还有什么别的办法来实现我们的需求吗 ,可以在mybatis的文档中找到:

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求

那么这个办法是什么呢? 这就是mybatis中的<where>标签,文档中这样写到:

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

我们可以尝试一下使用<where>标签, 根据文档中的参考,将我们的sql改成如下形式:

<select id="queryBlogIF" resultType="blog">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

这是将sql语句中的where关键字替换成了mybatis中的<where>标签, 其作可以当成是将where修改成了一个可以动态添加的词, 只有在where标签中有if被判断为正确(也就是说sql语句后肯定会接条件时, 才会在现在的语句后加上where关键字,并将后续条件拼接上去, 如果where标签内的所有if都不符合条件, 则sql语句中没有where关键字, 并且,相应的, 为了实现功能,如果if成立的部分语句中含有and或者or关键字, 而前面直接接的是where, 那么它还自动会去掉那个and/or), 正如文档中的这一句话:

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

13.3.2 set

与where类似的, 我们在进行更新操作的时候还需要用到set关键字 ,所以mybatis也提供了一个<set> 其用法与<where>类似, 可以动态的判断是否应该添加<set>, 并且与<where>可以"智能"判断是否需要去掉and/or类似, <set>也可以智能帮助我们判断内置内容后端的","是否需要添加

测试:

接口BlogMapper:

// 更新博客
int updateBlog(Map map) ;

xml文件BlogMapper.xml:

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

运行结果:

在这里插入图片描述

可以看到,在我们只传入title的时候, 原sql语句中,在末尾是有",“的, 但是通过<set>,它可以"智能"的去掉那些多余的”,"

13.3.3 trim

trim相当于where与set的"父亲"

where与set都可以看成是trim中的特例

where相当于:

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

意思是 会"智能"判断如果此标签内有值需要拼接到sql上, 则会在前面加上where 且会判断拼接语句前(prefixOverrides 匹配语句前的)是否有and或者or 并且"智能"的添加或者删掉多余的and或者or

set相当于:

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

意思是 会"智能"判断如果此标签内有值需要拼接到sql上, 则会在前面加上set且会判断拼接语句后(suffixOverrides 匹配语句末尾的)是否有"," 并且"智能"的添加或者删掉多余的","

13.4、choose (when, otherwise)

那么如果我们再次修改需求,改成如果我们输入title,就按title进行查询, 如果我们输入了author 那就按author来查询,如果都没有输入,那么如果输入了浏览量,那么就按照浏览量来查询,否则就查询全部,可以看到文档中有这么一句话:

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

我们来试着学习一下,修改sql:

<select id="queryBlogIF" resultType="blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = '9999'
            </otherwise>
        </choose>
    </where>
</select>

以上语句的意思是 如果我们只传入了title, 那么就按照title来查询, 如果我们只传入了author, 那么就按照author来查询, 如果我们两个都没有传入, 那么就按照views = '9999’的条件来查询, 那么如果我们两个都传入了, 它会怎么执行呢? 它会执行我们写在最上面的一个when里的内容, 因为它是从上到下来判断的, 只要判断过程中,有一个符合条件,其它的就不再判断了

13.5、foreach

在mybatis的文档中对foreach是这么描述的:

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

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

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

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

这一段代码看起来似乎,没有那么好理解,那么我们写一个测试来实际观察效果吧

为了方便测试, 我们先将数据库中biog表中插入的随机id值修改为1,2,3,4,

接口BlogMapper:

// 查询指定id(多个)的博客
List<Blog> queryBlogForeach(Map map) ;

xml文件BlogMapper.xml

<!-- 原sql语句应该为: select * from mybatis.blog where id = #{id} or id = #{id} ...-->
<!--<foreach collection="map中的键(拿到要从map中获取的值,发现是一个list 开始遍历取值)" item="随便取 表示每次取出的值叫什么(就是要与sql语句中的对应)"
     open="and ("(如果有的话 在语句前拼接"and (") close=")"(在结尾拼接")") separator="or"(如果有多个 中间用"or"连接)>-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

测试方法:

@Test
public void queryBlogForeachTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String , Object> map = new HashMap<>() ;
    ArrayList<Integer> ids = new ArrayList() ;

    map.put("ids",ids);
    System.out.println("list为空");
    mapper.queryBlogForeach(map);
    ids.add(1);
    System.out.println("list传入1");
    mapper.queryBlogForeach(map);
    ids.add(2) ;
    System.out.println("list又传入2");
    mapper.queryBlogForeach(map);
    sqlSession.close();
}

运行结果:
在这里插入图片描述

可以看到 使用上面的方法, sql可以自动拼接where 和 or 以及括号

13.6、SQL片段

在一个Mapper.xml配置文件中, 可能存在多个sql语句中都具有相同的sql语句片段, 这个时候,我们可以将这些相同部分提取出来作为一个"SQL片段", 可以用于方便复用

比如说在我们上面几个where的sql中, 就存在重复的语句:

<if test="title != null">
    title = #{title}
</if>
<if test="author != null">
    and author = #{author}
</if>

在这种情况下, 我们就可以将这一部分提取出来,将其修改为这样:

<!--id理论上可以随意取 符合规范-->
<sql id="id1">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

那么我们在想要使用的时候, 就直接引用即可:

<!--使用include标签 refid写我们要引用的sql片段的id-->
<select id="queryBlogIF" resultType="blog">
    select * from blog
    <where>
        <include refid="id1"></include>
        <!--<if test="title != null">
                title = #{title}
            </if>
            <if test="author != null">
                and author = #{author}
            </if>-->
    </where>
</select>

注意事项:

  • 最好基于单表来定义SQL片段
  • 即不要在SQL判断中进行过于复杂的操作
  • 在SQL片段中最好不要使用<where>

14、缓存(了解)

14.1、简介

1 什么是缓存(Cache)

  • 存在内存中的临时数据
  • 将用户经常查询的数据放在缓存(内存)中, 用户去查询数据就不用从磁盘上(关系型数据库文件)查询, 而是从缓存中查询, 从而提高查询效率, 解决了高并发系统的性能问题

2 为什么使用缓存

  • 减少和数据库的交互次数, 减少系统开销, 提高系统效率

3 什么样的数据能使用缓存

  • 经常查询并且不经常改变的数据

14.2、Mybatis缓存

  • Mybatis包含一个非常强大的查询缓存特性, 它可以非常方便地定制和配置缓存, 缓存可以极大的提升查询效率

  • Mybatis系统中默认定义了两极缓存: 一级缓存二级缓存

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

14.3 、一级缓存

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

14.3.1 准备工作

新建一个子项目mybatis-08, 复制部分原项目中的文件,结构如下:

在这里插入图片描述

其中resources目录下的db.properties和utils下的工具类不变 , dao下的UserMapper和UserMapper.xml为初始状态(空)

实体类User:

package com.hassder.pojo;

import java.util.Objects;

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

    public User() {
    }

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

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                name.equals(user.name) &&
                pwd.equals(user.pwd);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, pwd);
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

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.hassder.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.hassder.dao.UserMapper"/>
    </mappers>
</configuration>

准备完毕

14.3.2 测试

接口UserMapper:

package com.hassder.dao;

import com.hassder.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {

    // 根据id查询用户
    User queryUserById(@Param("id") int id) ;

}

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">
    <select id="queryUserById" resultType="user">
        select * from user where id = #{id}
    </select>
</mapper>

测试方法:

package com.hassder.dao;

import com.hassder.pojo.User;
import com.hassder.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestUser {

    @Test
    public void queryUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);

        User user = new User(3, "三号", "333333");
        mapper.updateUser(user) ;
        // 手动清理缓存
//        sqlSession.clearCache();

        System.out.println("======================================================");
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);

        System.out.println(user1==user2);

        sqlSession.close();
    }
}

在测试方法中, 我们调用了两次方法来查询同一个user, 运行测试方法, 查看运行结果:

在这里插入图片描述

从运行结果中我们可以看到(需要开启日志才能看到), 在我们第一次进行id=1的user查询的时候, 的确是进入了数据库,执行了sql语句来进行查询, 但是第二次却没有进入数据库查询, 而是直接输出了结果, 这就是因为在第一次查询之后, 就将结果放在了缓存当中, 所以第二次查询的时候, 就可以直接在缓存中找到结果,而无需再去进行查询操作, 提高了效率.

根据官方文档中的描述:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

我们可以使用增删改语句来进行缓存刷新, 那么我们在两次查询之间, 插入一条更新语句, 会是什么结果呢

我们写一个更新的方法放在两次查询之间, 并运行 查看结果(已经写过很多次了 ,这里就不再多占篇幅):

在这里插入图片描述

可以看到, 当我们在两次对同一条数据进行查询操作之间插入一条更新语句之后 , 第二次查询也是执行了sql语句之后才能获得结果, 这说明在执行更新语句之后, 原来的缓存就刷新了 ,或者说不存在了, 需要重新进行缓存

在一级缓存当中会导致缓存失效的情况

  • 查询不同的东西
  • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存

14.4、二级缓存

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

默认情况下, 只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存, 要启用全局的二级缓存, 只需要在你的SQL映射文件中添加一行:

<chche/>

缓存只作用于 cache 标签所在的映射文件(Mapper.xml)中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。这些属性可以通过 cache 元素的属性来修改。比如:

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

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

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

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

注意: 虽然直接在Mapper.xml中设置<cache/>也可以使用, 但这并不意味着只需要在这一个地方进行设置就可以使用了, 因为在Mybatis的核心配置文件的settings中还有这么一个属性:

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue

虽然这个设置的默认值为true, 也就是说默认就是打开的, 但是一般而言, 还是最好手动显式的将其在文件中显示出来.

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    
    <!--显式将其进行配置-->
    <setting name="cacheEnabled" value="true"/>
</settings>

测试:

接口UserMaper无变化

xml配置文件UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hassder.dao.UserMapper">

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

    <select id="queryUserById" resultType="user">
        select * from user where id = #{id}
    </select>

    <update id="updateUser" parameterType="user">
        update user set name = #{name} , pwd = #{pwd} where id = #{id}
    </update>
</mapper>

mybatis-config.xml中在settings中设置:

<!--显式将其进行配置-->
<setting name="cacheEnabled" value="true"/>

其它无变化

测试方法:

@Test
public void queryUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.queryUserById(1);
    System.out.println(user1);
    sqlSession.close();
    
    System.out.println("======================================================");
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    System.out.println(user1==user2);
    sqlSession2.close();
}

一级缓存的作用域可以理解为仅在一个sqlSession内部, 当一个sqlSession被close的时候, 缓存就没有了, 只有在开启二级缓存的情况下, 系统才会在SQLSession被close的时候将缓存内容提交到二级缓存, 才能将其缓存内容提供给其它sqlSession使用, 结果如图:
在这里插入图片描述

问题: 我们需要将实体类序列化(尤其是在没有手动更改清除策略的时候), 否则就会报以下错误:

Caused by: java.io.NotSerializableException: com.hassder.pojo.User

解决: 实现序列化:

package com.hassder.pojo;

import java.io.Serializable;
import java.util.Objects;

public class User implements Serializable{
	// 内部内容无变化, 将其省略
}

14.5、缓存原理

缓存过程中:

​ 缓存首先是保存在SQLSession中的一级缓存内, 当SQLSession关闭的时候, 如果开启了二级缓存, 则将一级缓存内的内容提交到对应Mapper中的二级缓存中去进行保存; 否则,缓存刷新, 当Mapper结束的时候, 二级缓存也随之刷新. (一级缓存只在单个SQLSession内有效, 只包含单个SQLSession内查询过的内容, 而二级缓存中应该包含当前Mapper下所有SQLSession内查询过的内容,[没有更新等操作来进行缓存刷新时])

​ 当用户进行某项操作, 调用某条语句时, 系统会先寻找对应的Mapper中有没有二级缓存, 若有二级缓存但是在二级缓存中没有找到我们需要的结果,或者没有二级缓存的时候, 则会到其SQLSession中的一级缓存中去寻找,若还是没有找到, 则去数据库中进行对应的操作, 操作完成之后将其按照上述顺序添加到缓存中, 若在某一级缓存中找到了结果, 则无需进行对数据库的操作

14.6、自定义缓存-ehcache

  • Encache是一种广泛的开源Java分布式缓存, 主要面向通用缓存
  • 要在程序中使用ehcache, 先要导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>MybatisStudy</artifactId>
        <groupId>com.hassder</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mybatis-08</artifactId>

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

接下来就可以使用了:

在对应的Mapper.xml文件中进行如下配置:

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

并在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:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.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"/>

    <!--
   defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
 -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统宕机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>

其中各设置值的具体作用可以参考文件内注释

而后可以运行之前的测试方法, 如果运行结果以及日志步骤与之前没有区别, 则说明使用自定义缓存成功

END

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值