MyBatis总结

简介

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

一、第一个MyBatis程序

首先打开IDEA新建一个maven项目
编写maven依赖pom.xml,导入相对应的jar包

<?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>org.example</groupId>
    <artifactId>MyBatis-Study</artifactId>
    <version>1.0-SNAPSHOT</version>


    <!--导入依赖-->

    <dependencies>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.11</version>
    </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>


    </dependencies>

</project>

在这里插入图片描述

1、新建一个模块

在这里插入图片描述
在子模块的src/main/resources目录下创建一个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>
  <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>

编写对应测试数据库的地址用户名称以及密码

2、编写对应的测试代码

(1)首先我们在数据库中新建测试user表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'imperfect', '123456');
INSERT INTO `user` VALUES (2, 'sam', '1234');

SET FOREIGN_KEY_CHECKS = 1;

(2)整体代码的结构
在这里插入图片描述
(3)编写代码
MyBatis-Study\mybatis-01\pom.xml,我们在子模块中的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>MyBatis-Study</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mybatis-01</artifactId>

    <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配置文件
src/main/resources

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://数据库地址/数据库名?userUnicode=true&amp;characterEncoding=utf8&amp;userSSL=true"/>
                <property name="username" value="用户名称"/>
                <property name="password" value="用户密码"/>
            </dataSource>
        </environment>
    </environments>

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

编写用于接收数据库的实体类
src/main/java/com/imperfect/pojo/User.java

package com.imperfect.pojo;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:36
 */
public class User {

    private int id;
    private String name;
    private String password;

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

    public User() {
    }

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

根据MyBatis官方中文文档指引编写对应的mapper接口
com/imperfect/dao/UserMapper.java

package com.imperfect.dao;

import com.imperfect.pojo.User;

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:38
 */
public interface UserMapper {

    List<User> getUserList();
}

编写接口对应的实现SQL代码

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imperfect.dao.UserMapper">
    <select id="getUserList" resultType="com.imperfect.pojo.User">
    select * from user
  </select>
</mapper>

编写对应获取sqlSession的工具类
com/imperfect/utils/MybatisUtils.java

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

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:27
 */
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            //通过流获取myBatis配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //获取sqlSessionFactory对象
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //从官方文档可以得知,这里如果需要获取sqlSession对象是通过sqlSessionFactory.openSession()方法进行获取的
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

编写单元测试类代码
com/imperfect/dao/UserMapperTest.java

package com.imperfect.dao;

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

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:52
 */

    public class UserMapperTest {

        @Test
        public void test(){

            //获得sqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();

            //执行SQL
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            for (User user : userList) {
                System.out.println(user);
            }

            sqlSession.close();
        }

    }

这里我们直接执行测试的方法,可以看到能够正常获取到user表中的字段
在这里插入图片描述
(4)编写增删改业务
编写单元测试类

package com.imperfect.dao;

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

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:52
 */

public class UserMapperTest {

    @Test
    public void test() {

        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }

        sqlSession.close();
    }

    @Test
    public void getUserById() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        System.out.println(userById);
        sqlSession.close();
    }

    @Test
    public void addUser() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int darling = mapper.addUser(new User(3, "darling", "123456"));
        System.out.println(darling);

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

    @Test
    public void updateUser() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int darling = mapper.updateUser(new User(3, "darling", "123"));
        System.out.println(darling);

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

    @Test
    public void deleteUser() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int darling = mapper.deleteUser(3);
        System.out.println(darling);

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

注意这里进行增删改操作的时候务必加上提交事务
编写Mapper接口类

package com.imperfect.dao;

import com.imperfect.pojo.User;

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:38
 */
public interface UserMapper {
    //获取全部用户
    List<User> getUserList();
    //根据id查询用户
    User getUserById(int id);

    //insert一个用户
    int addUser(User user);

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

    //删除一个用户
    int deleteUser(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">
<mapper namespace="com.imperfect.dao.UserMapper">
    <select id="getUserList" resultType="com.imperfect.pojo.User">
    select * from user
  </select>

    <select id="getUserById" resultType="com.imperfect.pojo.User" parameterType="int">
    select * from user where id= #{id}
  </select>

    <insert id="addUser" parameterType="com.imperfect.pojo.User">
    insert into user (id,name,password) values (#{id},#{name},#{password})
    </insert>

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

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

二、作用域和生命周期

摘录自MyBatis官方文档
  理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

1、SqlSessionFactoryBuilder

  这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

2、SqlSessionFactory

  SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

3、SqlSession

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

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

4、整体调用的思维导图
在这里插入图片描述

三、ResultMap

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

这里我们为了方便理解这个概念,我们直接在第一章节(第一个MyBatis程序)中的代码基础上。

  这里我们思考一个问题当数据库中的字段名和我们接收的实体类名字不同的时候,我们应该如何进行映射呢?

带着这个疑问我们直接修改一下com.imperfect.pojo.User中的代码

package com.imperfect.pojo;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:36
 */
public class User {

    private int id;
    private String name;
    private String 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;
    }

    public User() {
    }

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

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

在这里插入图片描述
很显然这里我们通过sql去查询的时候,pwd这个字段会为null
在这里插入图片描述
对于实体类与数据库字段的映射MyBatis提供了resultMap结果映射来进行解决
这里我们直接在com/imperfect/dao/UserMapper.xml添加resultMap

   <resultMap id="userMap" type="com.imperfect.pojo.User">
      <result column="id" property="id"></result>
      <result column="name" property="name"></result>
      <result column="password" property="pwd"></result>
    </resultMap>

id:能够被xml中其他代码调用的标识
type:代表对应实体类的位置
result:对应结果集的映射关系
column:数据库列的名称
property:实体类变量

其中的getUserById这个方法我们改变一下接收到的出参

<select id="getUserById" resultMap="userMap" parameterType="int">
    select * from user where id= #{id}
  </select>

在这里插入图片描述
重新调试代码后发现能够接收到pwd这个出参

四、log4j

  Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

这里我们穿插地了解一下关于日志管理的log4j
在这里插入图片描述
首先我们在maven导入一下log4j的jar包

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

然后在mybatis-config.xml中加上配置使用log4j进行日志管理

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

我们再配置一下log4j的配置文件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/imperfect.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

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

我们首先测试一下log4j是否已经成功导入

package com.imperfect.dao;

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

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2022/12/26  17:52
 */

public class UserMapperTest {

    private final Logger log= Logger.getLogger(this.getClass());

 
    @Test
    public void testLog4j(){
        log.debug("进入log4j debug代码");
        log.info("进入log4j info代码");
        log.error("进入log4j error代码");
    }
}

在这里插入图片描述
测试成功,能够打印出log4j日志

然后我们再执行一下sql的代码
在这里插入图片描述
这里我们可以看到通过log4j日志的日志管理,打印出了详细的sql信息

五、limit实现分页

首先我们创建一下测试数据库表user
对应的sql如下:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'imperfect', '123456');
INSERT INTO `user` VALUES (2, 'sam', '1234');
INSERT INTO `user` VALUES (3, 'sun', '456789');
INSERT INTO `user` VALUES (4, 'boy', '456789');

这里我们编写一下对应的代码
com/imperfect/dao/UserMapper.xml中添加进行分页查询的SQL

  <select id="getUserByLimit" parameterType="map" resultMap="userMap">
    select * from user limit #{startIndex},#{pageIndex}
    </select>

com/imperfect/dao/UserMapper.java

List<User> getUserByLimit(Map<String,Integer> map);

com/imperfect/dao/UserMapperTest.java

@Test
public void getUserByLimit() {
    //获得sqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //执行SQL
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map map = new HashMap<String, Integer>();
    map.put("startIndex", 0);
    map.put("pageIndex", 2);
    List<User> userList = mapper.getUserByLimit(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

这里我们的入参为map,通过传入map键值对能够实现对mapper.xm中的数值进行定制化编写,所以是比较方便的。
执行结果:
在这里插入图片描述
这里我们传入的startIndex为0,pageIndex为2,意思是我们从user表中的第0条数据开始,查找紧跟着后面的两条数据,以上结果验证了成功地进行了分页的查询。

六、复杂场景(多对一、一对多)

在这里插入图片描述
这里我们通过学生与老师的关系构造出多对一和一对多的场景。
对于学生而言:这里多个学生对应一个老师是多对一的关系。
对于老师而言:这里一个老师对应多个学生是一对多的关系。

我们先构建一下这个关系的两个数据库表
请执行以下sql

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

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

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

1、多对一场景

这里我们先模拟一下这个场景,如果我们想通过多个学生与一个老师的关系我们可以使用如下sql进行查询

SELECT * FROM student,teacher where student.tid=teacher.id

在这里插入图片描述
那么对于mapper.xml 中的映射关系我们应该如何实现呢?
我们首先了解一下mybatis xml中的association标签,根据官方文档的介绍如下:
  关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
  关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

首先我们创建一下Student和Teacher两个实体类
Student实体类

package com.imperfect.pojo;

public class Student {

    private int id;
    private String name;
    private int tid;
    private Teacher teacher;

    public Student() {
    }

    public Student(int id, String name, int tid, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.tid = tid;
        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 int getTid() {
        return tid;
    }

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

    public Teacher getTeacher() {
        return teacher;
    }

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

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

Teacher实体类

package com.imperfect.pojo;

import java.util.List;

public class Teacher {

    private int id;
    private String name;
    private List<Student> stduentList;

    public Teacher() {
    }

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

    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> getStduentList() {
        return stduentList;
    }

    public void setStduentList(List<Student> stduentList) {
        this.stduentList = stduentList;
    }

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

我们在mapper.xml文件中编写一下xml的语句
com/imperfect/dao/UserMapper.xml

   <resultMap id="studentTeacher" type="com.imperfect.pojo.Student">
        <result property="id" column="sid"></result>
        <result property="name" column="sname"></result>
        <association property="teacher" javaType="com.imperfect.pojo.Teacher">
            <result property="name" column="tname"></result>
        </association>
    </resultMap>
       <!--按照结果嵌套处理 -->
    <select id="getStudentTeacher" resultMap="studentTeacher">
            select student.id sid,student.name sname,teacher.name tname
            from student,teacher
             where student.tid=teacher.id
    </select>

com/imperfect/dao/UserMapper.java

List<Student> getStudentTeacher();

com/imperfect/dao/UserMapperTest.java

@Test
    public void getStudentTeacher() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<Student> studentList = mapper.getStudentTeacher();
        for (Student student : studentList) {
            System.out.println(student);
        }
        sqlSession.close();
    }

执行结果如下:
在这里插入图片描述

2、一对多场景

首先我们需要了解一下mybatis当中的collection标签
从mybatis官方文档可得知
collection是 一个复杂类型的集合
在这里插入图片描述
编写com/imperfect/dao/UserMapper.xml

 <!--    指定属性的类型我们使用javaType-->
    <!--    集合中的泛型我们使用ofType-->
    <resultMap id="teacherStudent" type="com.imperfect.pojo.Teacher">
        <result property="id" column="tid"></result>
        <result property="name" column="tname"></result>
        <collection property="stduentList" ofType="com.imperfect.pojo.Student">
            <result property="id" column="sid"></result>
            <result property="name" column="sname"></result>
        </collection>
    </resultMap>
    <select id="getTeacherStudent" resultMap="teacherStudent">
            select student.id sid,student.name sname,teacher.name tname,teacher.id tid
            from student,teacher
             where student.tid=teacher.id
    </select>

com/imperfect/dao/UserMapper.java

List<Teacher> getTeacherStudent();

com/imperfect/dao/UserMapperTest.java

   @Test
    public void getTeacherStudent() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<Teacher> teacherStudent = mapper.getTeacherStudent();
        for (Teacher teacher : teacherStudent) {
            System.out.println(teacher);
        }
        sqlSession.close();
    }

执行结果如下
在这里插入图片描述

七、动态SQL场景(if标签、foreach标签)

  动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

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

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

首先构建一下动态SQL的场景
执行以下SQL

DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog`  (
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '博客id',
  `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '博客标题',
  `author` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '博客作者',
  `create_time` datetime(0) NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of blog
-- ----------------------------
INSERT INTO `blog` VALUES ('1', 'Mybatis', '狂神说', '2023-01-01 12:34:25', 9999);
INSERT INTO `blog` VALUES ('2', 'Java', '狂神说', '2023-01-01 12:34:25', 9999);
INSERT INTO `blog` VALUES ('3', 'Spring', '狂神说', '2023-01-01 12:34:25', 9999);
INSERT INTO `blog` VALUES ('4', '微服务', '狂神说', '2023-01-01 12:34:25', 9999);

com/imperfect/pojo/Blog.java

package com.imperfect.pojo;

import lombok.Data;

import java.util.Date;
@Data
public class Blog {

    private int id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}

1、if标签场景

com/imperfect/dao/UserMapper.xml

<select id="queryBlogIf" resultType="com.imperfect.pojo.Blog" parameterType="map">
        select * from blog where 1=1
        <if test="title!=null">
            and title =#{title}
        </if>
    <if test="author!=null">
        and author =#{author}
    </if>
</select>

com/imperfect/dao/UserMapper.java

List<Blog> queryBlogIf(Map<String,String> map);

com/imperfect/dao/UserMapperTest.java

  @Test
    public void queryBlogIf() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        HashMap map=new HashMap<String,String>();
        map.put("title","Mybatis");
        map.put("author","狂神说");
        List<Blog> blogs = mapper.queryBlogIf(map);
        System.out.println(blogs);

        sqlSession.close();
    }

查询结果如下
在这里插入图片描述
如果我们注释掉代码

   // map.put("title","Mybatis");

查询结果如下:
[Blog(id=1, title=Mybatis, author=狂神说, createTime=Sun Jan 01 12:34:25 CST 2023, views=9999), Blog(id=2, title=Java, author=狂神说, createTime=Sun Jan 01 12:34:25 CST 2023, views=9999), Blog(id=3, title=Spring, author=狂神说, createTime=Sun Jan 01 12:34:25 CST 2023, views=9999), Blog(id=4, title=微服务, author=狂神说, createTime=Sun Jan 01 12:34:25 CST 2023, views=9999)]

这样子我们就实现了动态SQL的效果

2、foreach标签场景

  动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
  foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

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

我们下面就来构建一下foreach的应用场景
com/imperfect/dao/UserMapper.xml

 <select id="queryBlogForEach" parameterType="map" resultType="com.imperfect.pojo.Blog">
    select * from blog
    <where>
    <foreach collection="ids" item="id" open="and (" close=")" separator="or">
        id=#{id}
    </foreach>
    </where>
    </select>

com/imperfect/dao/UserMapper.java

List<Blog> queryBlogForEach(Map map);

com/imperfect/dao/UserMapperTest.java

   @Test
    public void queryBlogForEach() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        HashMap map=new HashMap();
        // map.put("title","Mybatis");
        ArrayList<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        List<Blog> blogs = mapper.queryBlogForEach(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }

查询结果如下:
在这里插入图片描述

八、缓存

1、缓存简介

根据MyBatis官方文档的介绍
  MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

  默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存。

2、一级缓存

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

(1)一级缓存测试用例

测试用例:
com/imperfect/dao/UserMapper.xml

   <select id="getUserById" resultMap="userMap" parameterType="int">
    select * from user where id= #{id}
  </select>

com/imperfect/dao/UserMapper.java

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

com/imperfect/dao/UserMapperTest.java

@Test

public void getUserById() {
    //获得sqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //执行SQL
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User userById = mapper.getUserById(1);//这里我们两次都查询同一个用户
    User userById1 = mapper.getUserById(1);//这里我们两次都查询同一个用户
    sqlSession.close();
}

在这里插入图片描述
我们可以看到从开始会话到结束,对数据库只执行了一条SQL。
从日志可以看出这里我们执行了两次SQL

(2)一级缓存失效的情况

1、查询不同的东西
   @Test
    public void getUserById() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(1);
        User userById1 = mapper.getUserById(2);
        sqlSession.close();
    }

当我们对两个不同的用户进行查询的时候
在这里插入图片描述

2、映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存:
@Test
    public void getUserById() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查询
        User userById = mapper.getUserById(1);
        //映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存:
        int darling = mapper.addUser(new User(5, "love", "123456"));
        //第二次再去查询
        User userById1 = mapper.getUserById(1);


        sqlSession.close();
    }

执行结果如下
在这里插入图片描述
这里执行了三次SQL,其中最后一条的查询语句由于刷新了缓存,并没有从缓存中直接获取。

3、手动清理缓存
  @Test
    public void getUserById() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查询
        User userById = mapper.getUserById(1);
        //手动去清理缓存
        sqlSession.clearCache();
        //第二次再去查询
        User userById1 = mapper.getUserById(1);

        sqlSession.close();
    }

查询结果如下
在这里插入图片描述
对于同一条数据,我们在手动清理缓存后再去查询的时候会发现又再去执行了一次SQL。

3、二级缓存

(1)二级缓存简介

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

(2)如何配置二级缓存

<cache/>

基本上就是这样。这个简单语句的效果如下:

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

这些属性可以通过 cache 元素的属性来修改。比如:

<cache

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

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

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

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

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

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

  提示:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

(3)测试用例

@Test
    public void getUserById() {
        //获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查询
        User userById = mapper.getUserById(1);
        sqlSession.close();


//下面我们又开启了另外一次的sqlSession
        //获得sqlSession2对象
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        //执行SQL
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        //第一次查询
        User userById2 = mapper2.getUserById(1);
        sqlSession2.close();
    }

在未开启二级缓存的时候,执行结果如下
在这里插入图片描述
在mapper.xml文件中开启二级缓存后

<cache/>

执行结果如下:
在这里插入图片描述
这里没有再去创建新的jdbc连接的原因是我们从二级缓存当中已经读取到我们想要的数据了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值