小羽

10 minute read

I. 简介

项目代码

1. MyBatis是什么

MyBatis是一款优秀的持久层框架。其通过使用简单的XML(或注解)和映射原生信息来取代了几乎所有的JDBC代码、手动设置参数和获取结果集的过程。为了理解什么是持久层框架,我们需要首先了解什么是持久化以及持久层

持久化

持久化是将程序数据在持久状态和瞬时状态间转化的机制。也就是说将数据从RAM(内存)中保存到ROM(存储设备比如磁盘)中。常见的持久化机制如JDBC, IO等。持久化的本质类似于将鲜肉冷藏,吃的时候拿出来解冻。

持久层

持久层是完成持久化工作的代码块(DAO层[Data Access Object, 数据访问对象])。数据持久化意味着将内存中的数据保存到磁盘加以固化,而持久化的实现过程则大多通过各种关系型数据库完成。

在我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现,也就是我们的持久层。(操作数据库的层)

2. 为什么要使用MyBatis

如上所述,MyBatis是一款用于持久层的ORM框架(Object Relationship Mapping, 对象关系映射)。框架的使用其目的往往是避免繁琐的重复代码块,减少程序员的工作量,从而提高开发效率。MyBatis也是一样,使用它可以完全代替JDBC来实现持久层的所有功能。

MyBatis的优点

  • 使用的人多

  • *简单易学*。不需要任何第三方依赖,只需要安装两个jar文件并配置几个SQL映射文件就好。通过文档和原代码,可以很好的掌握其设计思路和实现

  • *灵活*。myBatis不会对应用冲虚或者数据库的现有设计强加任何影响。SQL写在XML里,便于统一管理和优化

  • *解除SQL与程序代码的耦合*。通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更以维护和做单元测试

  • 提供XML标签,支持编写动态SQL

3. 文章概述

在II章节中本文会首先介绍MyBatis的工作原理。接着在III章节中介绍MyBatis项目的开发流程。第四章中会详细介绍MyBatis核心配置文件。随后的第五章节会讲解如何编写配置文件。第六章节会介绍更为简洁的注解开发。第七章介绍关系型数据库中常见的多对一、一对多处理。之后的两个章节分别介绍动态SQL缓存

本文环境说明:

  • JDK 8+
  • MySQL 5.7.19
  • Maven-3.6.0
  • IDEA

学习前需要掌握:

  • JDBC
  • MySQL
  • Java基础
  • Maven
  • JUnit

II. Mybatis工作原理

mybatis原理

III. Mybatis项目开发流程

1. 准备工作

a. 搭建Maven环境

这一步没有什么好说的,用Idea自动生成Maven项目即可。

b. 导入MyBatis依赖

找到MyBatis依赖,并配置到pom.xml中:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

c. 编写核心配置文件

编写核心配置文件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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/kuang/dao/userMapper.xml"/>
    </mappers>
</configuration>

d. 编写MyBatis工具类

类似于JDBC中的工具类:

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

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

  static { 
    try {
                // 使用Mybatis获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
                } 
  }
  
  //获取SqlSession连接, 类似JDBC的Connection对象
  public static SqlSession getSession(){
          return sqlSessionFactory.openSession();
      }
  }

2. 编写一个Mybatis功能

a. 创建实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 可以使用lombok来简化代码
@Data   
@AllArgsConstructor
@NoArgsConstructor
public class User {
  
    private int id; //id 
  private String name; //姓名 
  private String pwd; //密码
  
//构造,有参,无参 //set/get //toString()
}

b. 编写Mapper接口

Mapper接口相当于JDBC中DAO层的接口,用于定义操作数据库的方法:

import com.kuang.pojo.User;
import java.util.List;
public interface UserMapper {
    List<User> selectUser();
}

c. 编写Mapper.xml配置文件

Mapper.xml文件相当于JDBC中DAO层的接口实现类,用于定义实际操作数据库的配置:

<?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.kuang.dao.UserMapper">
  <select id="selectUser" resultType="com.kuang.pojo.User">
    select * from user
  </select>
</mapper>

Note: namespace对应DAO层接口,非常重要,不能写错。

d. 编写JUnit单元测试

public class MyTest {
  @Test
    public void selectUser() {
        SqlSession session = MybatisUtils.getSession(); 
    //方法一:
        //List<User> users = session.selectList("com.kuang.mapper.UserMapper.selectUser"); 
    
    //方法二:
    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser();
    for (User user: users){
      System.out.println(user);
        }
    session.close();
    }
}

e. 运行测试

IV. 核心配置文件详解

mybatis-config.xml是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>
    <!--引入外部配置文件-->
    <properties resource="db.properties"></properties>
    <typeAliases>
        <package name="com.soul.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>   <!--默认JDBC, type = JDBC | MANAGED-->
            <dataSource type="POOLED">  <!--数据库连接池 type= UNPOOLED| POOLED -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        <!--第二个环境,想切就换environments的default-->
        <environment id="test">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>
    </environments>

    <!--绑定接口-->
    <mappers>
        <mapper class="com.soul.dao.TeacherMapper"/>
        <mapper class="com.soul.dao.StudentMapper"/>
    </mappers>
</configuration>

Note: 配置文件的元素顺序不能错!!!

mybatis-config.xml主要元素包括:

  1. environments

    • 配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行 环境(通过default指定)
    • 子元素节点:environment 具体的一套环境,通过设置id进行区别,id保证唯一!
  2. properties

    • 数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过

      properties 元素的子元素来传递:

      • 第一步 ; 在资源目录下新建一个db.properties:
      driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://localhost:3306/mybatis?
      useSSL=true&useUnicode=true&characterEncoding=utf8
      username=root
      password=123456
      
      • 第二步 : 将文件导入properties 配置文件
      <!--导入properties文件-->
      <properties resource="db.properties"/>
      
  3. mappers

    mappers中可定义多个映射器mapper,即映射SQL语句的文件,其目的是为了告诉MyBatis到哪里能够找到这些mapper文件。

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

    <!-- 引用资源的各种方式 -->
        
    <!-- 使用相对于类路径的资源引用 --> 
    <mappers>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
        
    <!-- 使用完全限定资源定位符(URL) --> <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
        
    <!-- 使用映射器接口实现类的完全限定类名 需要配置文件名称和接口名称一致,并且位于同一目录下 -->
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
        
    <!-- 将包内的映射器接口实现全部注册为映射器 但是需要配置文件名称和接口名称一致,并且位于同一目录下 -->
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    
  4. typeAliases

    类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

    <!--配置别名,注意顺序--> 
    <typeAliases>
        <!--当这样配置时, User 可以用在任何使用 com.kuang.pojo.User 的地方。--> 
        <typeAlias type="com.kuang.pojo.User" alias="User"/>
    </typeAliases>
        
    <!-- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean -->
    <!-- 每一个在包 com.kuang.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的 非限定类名来作为它的别名。 -->
    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>
    
  5. settings

可以设置各种设置。在这里介绍一种使用Log4j格式的log用于Debug:

Log4j

使用步骤:

​ 第一步:导入log4j包

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

​ 第二步:配置文件log4j.properties编写

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

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

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender 
log4j.appender.file.File=./log/soul.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

​ 第三步:setting设置日志实现:

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

​ 第四步:在程序中使用Log4j进行输出:

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

V. 配置文件

1. CRUD操作

a. select

select语句有很多属性可以详细配置每一条SQL语句:

  • id
    • 命名空间中唯一的标识符
    • 接口中的方法名与映射文件中的SQL语句ID 一一对应
  • parameterType
    • 传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】
  • resultType
    • SQL语句返回值类型。【完整的类名或者别名】

b. insert

c. update

d. delete

具体流程

第一步:编写DAO层接口中的方法:

package com.soul.dao;

import com.soul.pojo.User;

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

public interface UserMapper {
    // 获取全部用户
    List<User> getUserList();

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

    User getUserByMap(Map<String, Object> map);

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

    // 用Mapinsert用户
    int addUserByMap(Map<String, Object> map);

    int updateUser(User user);

    int deleteById(int id);
}

第二步:编写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.soul.dao.UserMapper">
    <!--id 对应方法名-->
    <select id="getUserList" resultType="hello">
        select * from user;
    </select>

    <select id="getUserById" parameterType="int" resultType="User">
        select * from user where id = #{id};
    </select>

    <select id="getUserByMap" parameterType="map" resultType="User" >
        select * from user where id = #{id} and name = #{name};
    </select>

    <!--属性的对象可以直接取出-->
    <insert id="addUser" parameterType="User">
        insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd});
    </insert>

    <insert id="addUserByMap" parameterType="map">
        insert into user (id, name, pwd) values (#{userId}, #{userName}, #{userPwd});
    </insert>

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

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

第三步:在mybatis-config.properties中配置mapper:

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

第四步:编写JUnit测试类测试:

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

        @Test
    public void testGetUserByMap() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("id", 3);
        map.put("name", "李四");
        User user = userMapper.getUserByMap(map);
        System.out.println(user);
        sqlSession.close();
    }

    @Test
    public void testAddUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(4, "小红", "123");
        int res = userMapper.addUser(user);
        if (res > 0) {
            System.out.println("插入成功");
        } else {
            System.out.println("插入失败");
        }
        // 增删改需要提交事务
        sqlSession.commit();
        sqlSession.close();
    }

2. ResultMap

当数据库中返回的结果对应的是Java中的一个对象时,使用ResultMap可以避免Field对应不上从而导致值为空的情况。ResultMap用来指定返回结果的类型和格式。

a. 自动映射

  • ResultMap元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来
  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同 等功能的长达数千行的代码
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语 句只需要描述它们的关系就行了
<select id="selectUserById" resultType="map">
    select id , name , pwd
    from user
    where id = #{id}
</select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在 大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。

b. 手动映射

1). 返回值类型为resultMap:

<select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
</select>

2) 编写resultMap, 实现手动映射:

<resultMap id="UserMap" type="User"> 
  <!-- id为主键 -->
  <id column="id" property="id"/>
  <!-- column是数据库表的列名 , property是对应实体类的属性名 --> 
  <result column="name" property="name"/>
  <result column="pwd" property="password"/>
</resultMap>

此外,在数据库中,存在一对多,多对一的情况,我们之后会 使用到一些高级的结果集映射,association,collection这些,我们将在之后讲解,今天你们需要把这 些知识都消化掉才是最重要的!理解结果集映射的这个概念!

VI. 注解开发(代替配置文件)

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

sql 类型主要分成 :

  • @select ()
  • @update ()
  • @Insert ()
  • @delete ()

Note: 利用注解开发就不需要mapper.xml映射文件了。

注解开发步骤:

  1. 在接口中添加注解

    package com.soul.dao;
        
    import com.soul.pojo.User;
    import org.apache.ibatis.annotations.*;
        
    import java.util.List;
        
    public interface UserMapper {
        // 获取全部用户
        @Select("select * from user")
        List<User> getUserList();
        
        // 方法存在多个参数,所有参数前面必须加上@Param("var")注解
        @Select("select * from user where id = #{id}")
        User getUserByID(@Param("id") int id, @Param("name") String name);
        
        @Insert("insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd});")
        int addUser(User user);
        
        @Update("update user set name = #{name}, pwd = #{pwd} where id = #{id};")
        int updateUser(User user);
        
        @Delete("delete from user where id = #{id};")
        int deleteUser(@Param("id") int id);
        
    }
    
  2. 在MyBatis的核心配置文件中注入Mapper(注入接口)

        <!--使用class绑定接口-->    
        <mappers>
        <mapper class="com.soul.dao.UserMapper"/>
        </mappers>
    
  3. JUnit测试

  @Test
  public void testGetAllUser() {
    SqlSession session = MybatisUtils.getSession(); //本质上利用了jvm的动态代理机制
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    List<User> users = mapper.getAllUser(); 
    for (User user : users){
      System.out.println(user);
    }
    session.close();
  }

关于@Param

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

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

#与$的区别

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
INSERT INTO user(name) VALUES (#{name}); 
INSERT INTO user(name) VALUES (?);
  • \${} 的作用是直接进行字符串替换(${} 不安全,无法防止sql注入)

VII.多对一、一对多处理

在调用数据库数据时,我们往往会遇到多对一和一对多的处理,这时候就要在mapper.xml中使用到<association><collection>标签。我们通过下面老师和学生的例子来详解如何实现此功能。

多对一

  • 多个学生对应一个老师。
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联(association)一个老师!
  1. 我们首先编写实体类:

    @Data //GET,SET,ToString,有参,无参构造 public class Teacher {
        private int id;
        private String name;
    }
        
    @Data
    public class Student {
      private int id;
        private String name; 
      //多个学生可以是同一个老师,即多对一 
      private Teacher teacher;
    }
    
  2. 编写实体类对应的Mapper接口

    package com.soul.dao;
        
    import com.soul.pojo.Student;
    import java.util.List;
        
    public interface StudentMapper {
        List<Student> getAllStudent();
        
        List<Student> getAllStudent2();
    }
    
    package com.soul.dao;
        
    import com.soul.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 getTeacher(@Param("tid") int id);
        
    }
    
  3. 编写对应的两个mapper.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.soul.dao.StudentMapper">
      <!--方式一-->
        <!--思路
        1. 查询所有的学生信息
        2. 根据查询出来的学生的tid,寻找对应的老师-->
        <select id="getAllStudent" resultMap="StudentTeacher">
            select * from student;
        </select>
        <resultMap id="StudentTeacher" type="student">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <!--复杂的实行,我们需要单独处理
                对象: association
                集合: collection-->
            <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
        </resultMap>
        <select id="getTeacher" resultType="teacher">
            select * from teacher where id = #{id};
        </select>
        
        <!--方式二,推荐!!!-->
        <!--按照结果嵌套处理-->
        <select id = "getAllStudent2" resultMap="StudentTeacher2">
            select s.id as sid, s.name as sname, t.id as tid, t.name as tname from student s, teacher t where s.tid = t.id;
        </select>
        <resultMap id="StudentTeacher2" type="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <association property="teacher" javaType="Teacher">
                <result property="id" column="tid"/>
                <result property="name" column="tname"/>
            </association>
        </resultMap>
    </mapper>
    
  4. 配置mapper, JUnit测试

###一对多

  • 一个老师拥有多个学生
  • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(collection)!
  1. 编写实体类:

    package com.soul.pojo;
        
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
        
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
        private int id;
        private String name;
        private int tid;
    }
    
    package com.soul.pojo;
        
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
        
    import java.util.List;
        
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Teacher {
        private int id;
        private String name;
        private List<Student> students;
    }
    
  2. 编写DAO层接口:

    package com.soul.dao;
        
    import com.soul.pojo.Teacher;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
        
    public interface TeacherMapper {
        
        @Select("select * from teacher;")
        Teacher getAllTeacher();
        
        // 获取指定老师下的所有学生及老师的信息
        Teacher getTeacher(@Param("tid") int id);
    }
    
  3. 编写Mapper.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.soul.dao.TeacherMapper">
        
        <!--按照结果嵌套查询-->
        <select id="getTeacher" resultMap="TeacherStudent">
            select s.id as sid, s.name as sname, t.id as tid, t.name as tname
            from student s, teacher t
            where s.tid = t.id and tid = #{tid}
        </select>
        <resultMap id="TeacherStudent" type="Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tid"/>
            <!--JavaType="" 制定属性的类型
                集合中的泛型信息,我们使用ofType获取-->
            <collection property="students" ofType="Student">
                <result property="id" column="sid"/>
                <result property="name" column="sname"/>
                <result property="tid" column="tid"/>
            </collection>
        </resultMap>
    </mapper>
    
  4. 配置Mapper,JUnit测试

VIII. 动态SQL

动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句。

官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你
就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意 去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 􏰀供了可以被用在任意 SQL 映射语 句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有 很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。 MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
    -------------------------------
    - if
    - choose (when, otherwise)
    - trim (where, set)
    - foreach
    -------------------------------

我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需 要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同 时,也大大提高了开发人员的效率。

1. if

Dao层接口:

//需求1
List<Blog> queryBlogIf(Map map);

mapper.xml:

<!--需求1:
根据作者名字和博客名字来查询博客! 如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author} -->
<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog where
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

JUnit测试:

@Test
public void testQueryBlogIf(){
  SqlSession session = MybatisUtils.getSession();
  BlogMapper mapper = session.getMapper(BlogMapper.class);
    HashMap<String, String> map = new HashMap<String, String>(); 
  map.put("title","Mybatis如此简单");       
  map.put("author","狂神说");
    List<Blog> blogs = mapper.queryBlogIf(map);
  System.out.println(blogs);
  session.close();
}

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

2. where

mapper.xml:

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

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

3. Set (用于Update)

Dao层接口:

int updateBlog(Map map);

mapper.xml:

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
    update blog
  <set>
    <if test="title != null">
      title = #{title},
    </if>
    <if test="author != null">
      author = #{author}
        </if> 
  </set>
    where id = #{id};
</update>

JUnit测试:

@Test
public void testUpdateBlog(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    HashMap<String, String> map = new HashMap<String, String>(); 
    map.put("title","动态SQL");
    map.put("author","秦疆"); 
    map.put("id","9d6a763f5e1347cebda43e2a32687a77");
    mapper.updateBlog(map);
    session.close();
}

4. choose

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

Dao层接口:

List<Blog> queryBlogChoose(Map map);

mapper.xml:

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

JUnit测试:

@Test
public void testQueryBlogChoose(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>(); 
    map.put("title","Java如此简单");
        map.put("author","狂神说");
        map.put("views",9999);
    List<Blog> blogs = mapper.queryBlogChoose(map);
    System.out.println(blogs);
    session.close();
}

5. Foreach

将数据库中前三个数据的id修改为1,2,3;

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

DAO层接口:

List<Blog> queryBlogForeach(Map map);

mapper.xml:

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <!--
        collection:指定输入对象中的集合属性
        item:每次遍历生成的对象
        open:开始遍历时的拼接字符串
        close:结束时拼接的字符串
        separator:遍历对象之间需要拼接的字符串
        select * from blog where 1=1 and (id=1 or id=2 or id=3)
        -->
        <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
                        id=#{id}
        </foreach>
    </where>
</select>

JUnit测试:

@Test
public void testQueryBlogForeach(){
    SqlSession session = MybatisUtils.getSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    List<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    map.put("ids",ids);
    List<Blog> blogs = mapper.queryBlogForeach(map);
    System.out.println(blogs);
    session.close();
}

小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生 的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它 的技巧

SQL片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。

提取SQL片段:

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

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
      <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上namespace -->
      <include refid="if-title-author"></include>
            <!-- 在这里还可以引用其他的 sql 片段 --> 
    </where>
</select>

Note:

- 最好基于 单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where

IX. 缓存

1. 定义

  • 什么是缓存 [ Cache ]?

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

  • 为什么使用缓存?

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

  • 什么样的数据能使用缓存?

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

2. MyBatis中的缓存

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

  • 提升查询效率。 MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

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

3. 一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
  • 一级缓存的四种情况:
    • 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
    • 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
      • 1) sqlSession不同 (每个sqlSession中的缓存相互独立)
      • 2) sqlSession相同,查询条件不同(当前缓存中,不存在这个数据)
      • 3) sqlSession相同,两次查询之间执行了增删改操作! (因为增删改操作可能会对当前数据产生影响)
      • 4) sqlSession相同,手动清除一级缓存

4. 二级缓存

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

5. 缓存原理

mybatis原理

X. 坑

  1. 绑定接口错误:org.apache.ibatis.binding.BindingException

    • 每一个mapper.xml都需要在mybatis-config.xml中配置
    • 命名空间一定要正确(mapper.xml中的namespace标签和接口类名称对应)
  2. 方法名不对

    • mapper.xml中每个SQL描述标记的id对应的方法名必须和接口中一致
  3. 返回类型不对

    • mapper.xml中每个SQL描述标记的resultType/resultMap标签对应返回类型
  4. Maven导出资源问题:java文件夹下除了.java文件没有被编译进target

    • Maven配置文件需要在build中配置
  5. 事务:增删改后数据没有更新

    • 增删改需要提交事务

XI. 总结

MyBatis是一款优秀的持久层框架。使用它让我们代替了繁琐的JDBC代码,其灵活性、简单易学等各种特性帮助我们在开发中省时省力。

MyBatis是SSM框架中的一部分,在大量的实战开发中被广泛使用。在理解好JavaWeb的前提下,使用MyBatis使得我们的开发更加高效,所以说MyBatis是程序员从理论到应用的重要一步。

XII. 参考

狂神说Java MyBatis