【MyBatis】初学记录2w字笔记

        大家好。我是一位大学在校生,在校期间学习了Mybatis框架,认为学习完一门东西,一定要做一些笔记好重复去体会实现的过程,于是花了几天时间整理了Mybatis框架知识。有参考书籍、教学视频、文章做统一的整理,文章挺长的,两万字左右。建议先收藏后面有空慢慢看,一起加油吧。

目录

一、概述

二、初学者快速入门

三、深入了解Mybatis框架

1、关于全局配置文件SqlMapConfig.xml

2、关于数据库访问层接口和映射文件(CRUD)

3、关于mapper.xml中的占位符"#{}"和"${}"

4、模糊查询以及主键

5、动态SQL

6、指定参数的位置

7、表与表之间的关联关系

四、缓存机制


一、概述

1、首先Mybatis属于框架,那么我们需要先了解什么是框架?

        框架是一个半成品软件,将所有的公共的,重复的功能解决掉,帮助程序快速高效的进行开发。它是可复用、可扩展的。

2、什么是Mybatis框架?

        MyBatis 本是 apache 的一个开源项目iBatis, 2010 年这个项目由 apache software foundation 迁移到了google code,并且改名为 MyBatis。它是一款优秀的、半自动的ORM持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。下图即是Mybatis的架构原理(蓝色圆柱体为数据库)。

3、什么是ORM?

        ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。 MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。

4、什么是持久化?

        数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。例如,文件的存储、数据的读取等都是数据持久化操作。数据模型可以是任何数据结构或对象的模型、XML、二进制流等。 当我们编写应用程序操作数据库,对表数据进行增删改查的操作的时候就是数据持久化的操作。

二、初学者快速入门

1、创建项目,选择Maven,从archetype中创建。

注意:普通的Java统一创建为:org.apache.maven.archetypes:maven-archetype-quickstart

而如果是Web项目的话统一为:org.apache.maven.archetypes:maven-archetype-webapp

2、就是在创建好后的pom.xml中添加以下依赖

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--添加Mybatis框架的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <!--添加MySQL框架的依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.29</version>
    </dependency>
  </dependencies>

  <!--添加资源文件的指定-->
  <!--记得在加入资源文件后再右上角的Maven中刷新一遍-->
  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
    </resources>
  </build>

3、目录结构如图。在resources中去创建一个jdbc.properties的属性文件(数据库的配置)

 4、编写全局配置信息,resources下添加SqlMapConfig.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>
    <!--第一步:先读取jdbc.properties属性文件-->
    <properties resource="jdbc.properties" />
    <!--第二步:设置日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--第三步:注册实体类的别名-->
    <typeAliases>
        <package name="com.chf.pojo"/>
    </typeAliases>
    <!--第四步:配置环境变量-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--第五步:注册mapper.xml文件-->
    <mappers>
        <!--批量注册-->
        <package name="com.chf.mapper" />
    </mappers>
</configuration>

 5、创建实体类Users类,用来封装数据。

package com.chf.pojo;

import java.util.Date;

//以下省略无参有参构造、重写toString方法、所有属性的Set及Get方法
public class Users {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

6、在mapper下创建UsersMapper接口,定义方法。

package com.chf.mapper;

import com.chf.pojo.Users;

public interface UsersMapper {
    List<Users> getAll();//查询所有用户的信息
}

7、接着继续在mapper目录下创建UsersMapper.xml文件,用来编写Sql

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

<!--绑定dao接口,面向接口编程-->
<mapper namespace="com.chf.mapper.UsersMapper">
    <select id="getAll" resultType="users">
        select id,username,birthday,sex,address
        from users
    </select>
</mapper>

8、紧接着在测试类Test中的java下创建MyTest类,在@Test左边即可完成运行。

package com.chf;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest01 {
    @Test
    public void testGetAll() throws IOException {
        //使用文件流读取核心配置文件SqlMapConfig.xml
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory工厂(框架初始化)
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //取出SqlSession的对象
        SqlSession sqlSession = factory.openSession();
        //完成查询操作
        List<Users> list = sqlSession.selectList("users.getAll");
        list.forEach(users -> System.out.println(users));
        //关闭SqlSession
        sqlSession.close();
    }
}

三、深入了解Mybatis框架

1、关于全局配置文件SqlMapConfig.xml

其<configuration></configuration>中,中间的标签是用严格的顺序的,否则无法执行/执行报错。

到源码的mybatis-3-config-dtd中,我们可以发现有这么一条语句,即是对标签的顺序做出的要求:

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, 
objectFactory?, objectWrapperFactory?, reflectorFactory?,
 plugins?, environments?, databaseIdProvider?, mappers?)>

//由于太长我就不截图了,当做代码块放出来给大家看

以下只介绍学习过程中所使用到的一些标签

configuration(配置)
properties(属性):实现引用配置文件
settings(设置):日志形式输出到控制台
typeAliases(类型别名):用来减少类完全限定名的冗余
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置):用来配置数据源
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器):用来配置mapper.xml映射文件且这些xml文件里都是SQL语句

1.1、属性<properties>

其用处就是用来读取我们所需要的配置文件的信息的。比如jdbc.properties,就不多介绍了。

1.2、设置<setting>

当设置了将输出到控制台上,我们就可以多读取到这些信息,还可以查看到Sql语句是否有误。

1.3、类型别名<typeAliases>,用法主要有两种:

  • 在实体类比较少的时候,使用第一种方式。
  • 如果实体类十分多,建议使用第二种方式。

1.4、环境配置<environment>

注意的是在这里面使用的是"${}"占位符

<!--配置数据库的环境变量(数据库连接配置)
        default:使用下面的environment标签的id属性进行指定配置
-->
<environments default="development">
        <!--id:就是提供给environments的default属性使用-->
        <environment id="development">
            <!--配置事务管理器
                type:指定事务管理的方式
                    JDBC:事务的控制交给程序员处理
                    MANAGED:由容器(Spring)来管理事务
            -->
            <transactionManager type="JDBC"/>
            <!--配置数据源
                type:指定不同的配置方式
                    JNDI:java命名目录接口,在服务器端进行数据库连接池的管理
                    POOLED:使用数据库连接池
                    UNPOOLED:不使用数据库连接池
            -->
            <dataSource type="POOLED">
                <!--配置数据库连接的基本参数-->
                <property name="driver" value="${jdbc.driverClassName}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
</environments>

 1.5、映射器<mappers>

mappers中有四种注册方式,选择一种即可,优先级分别是package > resource > url > class。

<!--注册mapper.xml文件-->
    <mappers>
        <!--动态代理方式下的单个mapper.xml文件注册-->
        <mapper class="com.chf.mapper.UsersMapper"></mapper>
        <!--绝对路径注册-->
        <mapper url="/"></mapper>
        <!--非动态代理方式下的注册-->
        <mapper resource="StudentMapper.xml"></mapper>
        <!--批量注册-->
        <package name="com.chf.mapper"></package>
    </mappers> 

2、关于数据库访问层接口和映射文件(CRUD)

2.1、Select、Insert、Update、Delete

①根据数据库中的Users表去pojo包下写关于Users的实体类。实体类代码在上面的快速入门中。

②然后在mapper目录下接口中定义想要对数据库操作的方法

public interface UsersMapper {
    List<Users> getAll();//查询所有用户的信息

    int update(Users user);//用户的更新

    int insert(Users user);//新增用户

    int delete(Integer id);//删除用户
}

 ③相同目录下绑定该接口,创建mapper文件。

其中的namespace中的包名要和mapper接口的包名保持一致。

id:就是对应的namespace中的方法名;

resultType:Sql语句执行的返回值

parameterType:传入测试类中的参数类型

注意:在这里使用的是"#{}"占位符,在后面我会讲解${}和#{}两者的区别和用法

<mapper namespace="com.chf.mapper.UsersMapper">
    <select id="getAll" resultType="users">
        select id,username,birthday,sex,address
        from users
    </select>
    <insert id="insert" parameterType="users">
        insert into users (username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>
    <update id="update" parameterType="users">
        update users set username=#{username},birthday=#{birthday},
        sex=#{sex},address=#{address}
    </update>
    <delete id="delete" parameterType="users">
        delete from users
        where id=#{id}
    </delete>
</mapper>

 ④在test包下创建一个测试类去完成对数据库访问的测试

注意:只要我们进行完增删改操作后一定还要有提交的操作即commit。

public class MyTest0 {
    @Test
    public void testGetAll() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        List<Users> list = sqlSession.selectList("users.getAll");
        list.forEach(user -> System.out.println(users));
        sqlSession.close();
    }
    @Test
    public void testInsert() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        sqlSession.insert("users.insert","王小八",new Date(),"2","福建省");
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testUpdate() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        sqlSession.update("users.update",7,"王五",new Date(),"2","泉州市");
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testDelete() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession();
        sqlSession.delete("users.delete",6);
        sqlSession.commit();
        sqlSession.close();
    }
}

        当你看到这里时,你会发现测试类的代码重复写肯定是很不正常的,于是就会引入注解来对其测试进行优化代码的重复量。

@Before:在执行Test前一定会先执行的操作

@After:在执行完Test后一定会执行的操作

public class MyTest{
    SqlSession sqlSession;
    
    @Before
    public void openSqlSession() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        sqlSession = factory.openSession();
    }

    @After
    public void closeSqlSession(){
        sqlSession.close();
    }

    @Test
    public void testGetAll(){
        List<Users> list = sqlSession.selectList("users.getAll");
        list.forEach(user -> System.out.println(users));
    }

    @Test
    public void testInsert(){
        sqlSession.insert("users.insert","王小八",new Date(),"2","福建省");
        sqlSession.commit();
    }

    @Test
    public void testUpdate(){
        sqlSession.update("users.update",7,"王五",new Date(),"2","泉州市");
        sqlSession.commit();
    }

    @Test
    public void testDelete(){
        sqlSession.update("users.delete",6);
        sqlSession.commit();
    }
}

        看到这里,有没有想到一个问题:当接口内的方法变多了的话,测试类中每次测试方法都需要写上"resultType.方法",如果方法名改变的话或者新增方法的话,改变起来会很复杂。这并不符合程序扩展的OCP原则。于是我们就需要采用动态代理:即使接口中的业务发生了改变 此处也不需要做改变。我们需要进一步的对上面的测试类进行改变。

2.2、动态代理的规范

1)UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下.
2)UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不管.
3)UserMapper.xml文件中标签的id值与与UserMapper.java的接口中方法的名称完全一致.
4)UserMapper.xml文件中标签的parameterType属性值与与UserMapper.java的接口中方法的参数类型完全一致.
5)UserMapper.xml文件中标签的resultType值与与UserMapper.java的接口中方法的返回值类型完全一致.
6)UserMapper.xml文件中namespace属性必须是接口的完全限定名com.chf.mapper.UsersMapper
7)在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名com.chf.mapper.UsersMapper.

public class MyTest{
    SqlSession sqlSession;
    UsersMapper usersMapper;//动态代理对象

    @Before
    public void openSqlSession() throws IOException {
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        sqlSession = factory.openSession();
        //取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相应的标签的功能
        usersMapper = sqlSession.getMapper(UsersMapper.class);
    }

    @After
    public void closeSqlSession(){
        sqlSession.close();
    }

    @Test
    public void testGetAll(){
        List<Users> list = usersMapper.getAll();
        list.forEach(user -> System.out.println(users));
    }

    @Test
    public void testInsert(){
        Users users = new Users("王小八",new Date(),"2","福建省");
        usersMapper.insert(users);
        sqlSession.commit();
    }

    @Test
    public void testUpdate(){
        Users users = new Users(7,"王五",new Date(),"2","泉州市");
        usersMapper.update(users);
        sqlSession.commit();
    }

    @Test
    public void testDelete(){
        usersMapper.delete(8);
        sqlSession.commit();
    }
}

3、关于mapper.xml中的占位符"#{}"和"${}"

1、#{}占位符是为了获取值,通常在where语句后。

2、若在Sql代码中语句为select * from users where id=#{id},执行后自动翻译为select * from users where id=?。这就相当于JDBC中的PreparedStatement预编译阶段,可以有效的防止Sql注入。

3、#{}里如何写,看parameterType参数的类型。如果parameterType的类型是简单类型(8种基本(封装)+String),则#{}里随便写;如果是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写。

4、${}字符串用于拼接或字符串替换。一般用于模糊查询中,建议少用,因为有Sql注入的风险。同样分两种情况:如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只能写value;如果是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用)。

4、模糊查询以及主键

        前面我们看过"#{}"和"${}"的区别,所以我们就能知道为了防止Sql注入的风险,就需要使用到"#{}"的占位符,而不需要使用"${}"占位符。

<select id="getByName" parameterType="string" resultType="users">
        select id,username,birthday,sex,address
        from users
        where username like concat('%',#{username},'%')
</select>
    @Test
    public void testGetByName(){
        List<Users> list = usersMapper.getByName("小");
        list.forEach(users -> System.out.println(users));
    }

        当我们需要查询不止一项的时候,就需要写好几个模糊查询的语句,这样代码量就很大,于是需要将模糊查询进行优化,变成高级模糊查询。这里我们需要在接口中定义方法的时候对其形参属性使用注解@Param。这个注解是为了SQL语句赋值参数而服务的。

    <!--
        高级模糊查询:模糊查询多个的话,parameterType就不需要写
         List<Users> getByNameOrAdd(
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue);
    -->
    <select id="getByNameOrAdd" resultType="users">
        select id,username,birthday,sex,address
        from users
        where ${columnName} like concat('%',#{columnValue},'%')
    </select>
    @Test
    public void testGetByNameOrAdd(){
        List<Users> list1 = usersMapper.getByNameOrAdd("username","小");
        List<Users> list2 = usersMapper.getByNameOrAdd("address","市");
        list1.forEach(users -> System.out.println(users));
        System.out.println("===========================================");
        list2.forEach(users -> System.out.println(users));
    }

        通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢? 使用<selectKey>子标签

    <insert id="insert" parameterType="users">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id()
        </selectKey>
        insert into users (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>

返回主键值,插入语句结束后,返回自增的主键值传到入参的users对象的id属性中
keyProperty:users对象的哪个属性来接返回的属性值
resultType:返回的主键的类型
order:Before是在插入语句前执行,After是在插入语句后执行

上面的last_insert_id()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。

5、动态SQL

  我们需要了解什么是动态SQL:

  可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更为简单.
  ①<sql>:用来定义代码片段,可以将所有的列名,或复杂的条件定义为代码片段,供使用时调用.
  ②<include>:用来引用<sql>定义的代码片段.

  ③<if>:进行条件判断
    test条件判断的取值可以是实体类的成员变量,可以是map的key,可以是@Param注解的名称。

  ④<where>:进行多条件拼接,在查询,删除,更新中使用.

  ⑤<set>:有选择的进行更新处理,至少更新一列.能够保证如果没有传值进来,则数据库中的数据保持不变.

  ⑥<foreach>:用来进行循环遍历,完成循环条件查询,批量删除,批量增加,批量更新.

<mapper namespace="com.chf.mapper.UsersMapper">
    <!--定义代码片段-->
    <sql id="Columns">
        id,username,birthday,sex,address
    </sql>

    <select id="getAll" resultType="users">
        select <include refid="Columns" />
        from users
    </select>

    <!--按指定的条件进行多条件查询-->
    <select id="getByCondition" parameterType="users" resultType="users">
        select <include refid="Columns" />
        from users
        <where>
            <if test="username != null and username != ''">
                and username like concat('%',#{username},'%')
            </if>
            <if test="birthday != null">
                and birthday=#{birthday}
            </if>
            <if test="sex != null and sex != ''">
                and sex=#{sex}
            </if>
            <if test="address != null and address != ''">
                and address like concat('%',#{address},'%')
            </if>
        </where>
    </select>

    <select id="getByIds" resultType="users">
        select <include refid="Columns" />
        from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </select>
    
    <update id="update" parameterType="users">
        update users set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
    </update>

    <update id="updateBySet" parameterType="users">
        update users
        <set>
            <if test="username != null and username != ''">
                username=#{username},
            </if>
            <if test="birthday != null">
                birthday=#{birthday},
            </if>
            <if test="sex != null and sex != ''">
                sex=#{sex},
            </if>
            <if test="address != null and address != ''">
                address=#{address}
            </if>
        </set>
        where id=#{id}
    </update>

    <!--批量增加-->
    <insert id="insertBatch">
        insert into users(username,birthday,sex,address) values
        <foreach collection="list" item="u" separator=",">
            (#{u.username},#{u.birthday},#{u.sex},#{u.address})
        </foreach>
    </insert>

    <delete id="delete" parameterType="users">
        delete from users
        where id=#{id}
    </delete>
    
    <!--批量删除-->
    <delete id="deleteBatch">
        delete from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

        其中<foreach>标签的东西比较多,就一一进行说明一下:
        collection:用来指定入参的类型即paramterType,如果是List集合则为list,如果是Map集合则为map,如果是数组则是array
        item:每次循环遍历出来的值或者对象,随便取
        separator:多个值之间的分隔符
        open:在<foreach>前面
        close:在</foreach>后面

6、指定参数的位置

        如果入参是多个,可以通过指定参数位置进行传参.,是实体包含不住的条件。实体类只能封装住成员变量的条件。如果某个成员变量要有区间范围内的判断,或者有两个值进行处理,则实体类包不住。

    <!--
        查询指定日期范围内的用户:List<Users> getByBirthday(Date begin,Date end);
    -->
    <select id="getByBirthday" resultType="users">
        select <include refid="Columns" />
        from users
        where birthday between #{arg0} and #{arg1}
    </select>

6.1、入参为Map

如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确。

    <!--入参是Map:List<Users> getByMap(Map map)
        #{birthdayBegin}和#{birthdayEnd}:就是map中的key
    -->
    <select id="getByMap" resultType="users">
        select <include refid="Columns" />
        from users
        where birthday between #{birthdayBegin} and #{birthdayEnd}
    </select>

测试类中

@Test
    public void testGetByMap() throws ParseException {
        Map map = new HashMap<>();
        map.put("birthdayBegin",new Date());
        map.put("birthdayEnd", new Date());
        List<Users> list = usersMapper.getByMap(map);
        list.forEach(users -> System.out.println(users));
    }

6.2、返回值为Map

        如果返回的数据实体类无法包含,可以使用map返回多张表中的若干数据。返回后这些数据之间没有任何关系,就是Object类型,返回的map的key就是列名或别名。

    <!--
        返回值是一行的map:Map getReturnMap(Integer id);
    -->
    <select id="getReturnMap" parameterType="int" resultType="map">
        select username nam,address a
        from users
        where id=#{id}
    </select>
    
    <!--
        返回多行的map:List<Map> getMulMap();
    -->
    <select id="getMulMap" resultType="map">
        select username,address
        from users
    </select>

7、表与表之间的关联关系

        关联关系是有方向的。以下我只展示一对多和多对一的代码。
①一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方,就是一对多关联。
②多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生方,就是多对一关联。
③一对一关联:一个老师辅导一个学生,一个学生只请教一个老师,学生和老师是一对一。
④多对多关联:园区划线的车位和园区的每一辆车,任意一个车位可以停任意一辆车,任意一车辆车可以停在任意一个车位上。

以下是一对多的实例

public class Customer {
    private Integer id;
    private String name;
    private Integer age;

    //该客户名下所有订单的信息
    private List<Orders> ordersList;

    //以下我还是省略有参无参构造、所有属性的setget方法、重写toString方法
}
<mapper namespace="com.chf.mapper.CustomerMapper">
    <resultMap id="customermap" type="customer">
        <id property="id" column="cid" />
        <result property="name" column="name" />
        <result property="age" column="age" />
        <!--collection是用于一对多的关系-->
        <collection property="ordersList" ofType="orders">
            <id property="id" column="oid" />
            <result property="orderNumber" column="orderNumber" />
            <result property="orderPrice" column="orderPrice" />
        </collection>
    </resultMap>
    <select id="getById" parameterType="int" resultMap="customermap">
        select c.id cid,name,age,o.id oid,orderNumber,orderPrice
        from customer c
        left join orders o
        on c.id=o.customer_id
        where c.id=#{id}
    </select>
</mapper>
    @Test
    public void testGetAll(){
        List<Book> list = bookMapper.getAll();
        list.forEach(book -> System.out.println(book));
    }

以下是多对一的实例

public class Orders {
    private Integer id;
    private String orderNumber;
    private Double orderPrice;

    //关联此订单的客户信息
    private Customer customer;

    //以下依旧省略所有属性的setget方法、重写toString方法、有参无参构造方法
}
<mapper namespace="com.chf.mapper.OrdersMapper">
    <!--
        Orders getById(Integer id);

        private Integer id;
        private String orderNumber;
        private Double orderPrice;
        private Customer customer;
    -->
    <resultMap id="ordersmap" type="orders">
        <id property="id" column="oid" />
        <result property="orderNumber" column="orderNumber" />
        <result property="orderPrice" column="orderPrice" />
        <!--association是用于一对一或者多对一的情况-->
        <association property="customer" javaType="customer">
            <id property="id" column="cid" />
            <result property="name" column="name" />
            <result property="age" column="age" />
        </association>
    </resultMap>
    <select id="getByIdTwo" parameterType="int" resultMap="ordersmap">
        select o.id oid,orderNumber,orderPrice,customer_id,c.id cid,name,age
        from orders o
        inner join customer c
        on c.id=o.customer_id
        where o.id=#{id}
    </select>
</mapper>
    @Test
    public void testGetByIdTwo(){
        Orders orders = ordersMapper.getByIdTwo(11);
        System.out.println(orders);
    }

总结:无论什么关联关系,如果某方持有另一方的集合(一对多),则使用<collection>标签完成映射;如果某方持有另一方的对象(多对一或者一对一),则使用<association>标签完成映射。

四、缓存机制

        首先我们需要知道:MyBatis框架提供两级缓存,一级缓存和二级缓存,并且默认开启一级缓存,缓存就是为了提交查询效率。 使用缓存后,查询的流程为:查询时先到缓存里查,如果没有则查询数据库,再放缓存一份,再返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库,如果数据库中发生commit()操作,则清空缓存。

        一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据。

1、如果在mapper映射文件中的CRUD标签中加入flushCache="true",会导致一二级缓存机制都消失

2、也可以在测试类中直接手动清理缓存。

3、也可以在全局配置文件设置<settings name="localCacheScope" value="STATEMENT" />这样会使一级缓存失效,二级缓存不受影响。

1、

2、

3、

        二级缓存也叫做全局缓存,基于namespace级别的缓存。使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域。需要在全局配置文件中设置<setting name="cacheEnabled" value="true" />,然后在具体的mapper.xml中添加<cache />即可。

以上就是我在学习Mybatis中的笔记了,谢谢大家观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值