【Java】Mybatis学习笔记

发布于:2025-03-20 ⋅ 阅读:(25) ⋅ 点赞:(0)

目录

一.搭建Mybatis

二.Mybatis核心配置文件解析

1.environment标签

2.typeAliases

3.mappers

三.Mybatis获取参数值

四.Mybatis查询功能

五.特殊的SQL执行

1.模糊查询

2.批量删除

3.动态设置表名

4.添加功能获取自增的主键

六.自定义映射ResultMap

1.配置文件处理字段名和属性名的映射关系

2.使用ResultMap自定义映射处理映射关系

1)处理一对一的映射关系

2)处理多对一的映射关系

3)处理一对多的映射关系

七.动态SQL

1.if标签  

2.where标签

3.trim标签

4.choose,when,otherwise标签

5.foreach标签

6.sql标签 

八.Mybatis缓存

1.一级缓存

2.Mybatis二级缓存


Mybatis下载地址:GitHub - mybatis/mybatis-3: MyBatis SQL mapper framework for Java

Mybatis的优点:SQL语句加载在Java代码中耦合度高,导致硬编码内伤,不宜维护,Mybatis将SQL和Java编码分开,功能边界清晰;

一.搭建Mybatis

  • 创建maven工程
  • 创建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>
        <!--配置连接数据库的环境-->
    <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/ssm?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入mybatis的映射文件-->
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>

mysql不同版本注意事项:

1.驱动类driver-class-name

MYSQL5启动类使用:com.mysql.jdbc.Driver

MYSQL8驱动类使用:com.mysql.cj.jdbc.Driver

2.连接地址url

MYSQL5:"jdbc:mysql://localhost:3306/数据库名

MYSQL8:"jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC

  • 创建数据库表User,实体类User;-
  • 创建mapper接口:相当于dao,但只需要创建mapper接口,不需要提供实现类,接口命名要和数据库表、实体类名称一一对应即UserMapper;
  • public interface UserMapper {
        int insertUser ();
    }
    
  • 创建mybatis映射文件;

命名规则:表对应的实体类类名 + Mapper.xml

  • 一个映射文件对应一个实体类,对应一张表的操作;
  • Mybatis的映射文件用于编写SQL,访问及操作表中的数据;
  • 映射文件存放的位置是:src/main/resources/mappers目录下

mapper接口和映射文件要保持两个一致:

  • mapper接口的全类名要和映射文件的namespace一致;
  • mapper接口中的方法的方法名要和映射文件中的sql的id保持一致;

<?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.fz.mybatis.mapper.UserMapper">
    <!--int insertUser ()-->
    <insert id="insertUser">
        insert into user values (null,"admin","123456",23,"男","123456@qq.com")
    </insert>
</mapper>
  • 在mybaits核心配置文件中引入mybatis映射文件
<!--引入mybatis的映射文件-->
<mappers>
    <mapper resource="mappers/UserMapper.xml"/>
</mappers>

创建测试文件

使用myBatis提供的操作数据库的对象sqlSession通过调用getMapper方法创建mapper接口的代理实现类,直接调用mapper接口中的方法,从而定位到sql语句执行;

public class MybatisTest {
    @Test
    public void testInsert () throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取sqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 获取sqlSessionFactory
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        // 获取sql的会话对象sqlSession,是myBatis提供的操作数据库的对象
        SqlSession sqlSession = build.openSession();
        // 获取UserMapper的代理实现类的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 调用mapper接口中的方法
        int result = mapper.insertUser();
        // 提交事务
        sqlSession.commit();
        System.out.println(result);
        // 关闭sqlSession对象
        sqlSession.close();
    }
}

或创建mapper接口的代理实现类对象,重写接口中的抽象方法,通过mapper接口的全类名找到映射文件,通过mapper接口中的方法找到sql语句执行

int result = sqlSession.insert("com.fz.mybatis.mapper.UserMapper.insertUser");

加入log4j日志功能:

首先加入依赖

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

配置log4j配置文件

<?xml version="1.0" encoding="UTF-8"?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n"/>
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug"/>
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info"/>
    </logger>
    <root>
        <level value="debug"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

查询功能

需要在select标签上设置resultType和reaultMap

resultType:设置结果类型,即查询的数据需要转换的java类型;

reaultMap:自定义类型,处理多对一或一对多二点映射关系;

public interface UserMapper {
    User getUserById ();
}
<select id="getUserById" resultType="com.fz.mybatis.pojo.User" >
    select * from user where id = 1
</select>
@Test
public void testSelect () {
    try {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        SqlSession sqlSession = build.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById();
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

二.Mybatis核心配置文件解析

1.environment标签

environments标签:设置连接数据库的环境,属性default设置默认使用的环境的id,在该标签内部使用environment标签设置具体的环境;

environment标签:设置一个具体的连接数据库的环境,属性id用来设置环境的唯一标识不能重复。每一个环境又分为两个子标签

  • transcationManger用来设置事务的管理方式,属性type设置事务管理的方式,值为JDBC(使用JDBC原生的事务管理的方式即自动提交事务可以手动的开启和关闭,或手动提交事务或回滚事务)和MANAGED(被管理的,例如spring整合Mybatis时其事务管理交由spring管理)
  • dataSource用于设置数据源,属性type用于设置数据源的类型,一共三个值(POOLED|UNPOOLED|JNDI)

POOLED:表示使用数据库连接池

UNPOOLED:表示不适用数据库连接池,即每一个获取连接都需要重新创建一个链接

JNDI:表示使用上下文中的数据源

<!--配置连接数据库的环境-->
    <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/ssm?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

此外在核心配置文件中使用properties标签,其resource属性指定外部的properties文件路径,引入properties配置文件,此后可以在当前文件中使用${key}的方式访问配置文件中的value;

2.typeAliases

Mybatis核心配置文件中的标签必须要使用指定的顺序去配置,顺序如下:

properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?, databaseIdProvider?,mappers?

typeAliases标签用于设置类型别名,即为某一个具体的类型设置一个别名,在Mybatis的范围中,就可以使用别名表示一个具体的类型;

其中的type属性用于设置取别名的类型,alias用于设置某个类型的别名,该属性可以省略不写,当设置了type属性后,就有了一个默认的别名为该类的类名且不区分大小写;

<typeAliases>
    <typeAlias type="com.fz.mybatis.pojo.User" alias="U1"></typeAlias>
</typeAliases>

还可以通过包来设置类型的别名,指定包下的所有类型将全部拥有默认的别名;

    <typeAliases>
        <package name="com.fz.mybatis.pojo"/>
    </typeAliases>

3.mappers

mappers标签用于引入Mybatis的映射文件。可以使用mapper标签引入一个映射文件,但往往映射文件数量较多,所以可以通过包的方式即package标签来引入,需要满足两个条件:

mapper接口和映射文件所在的包必须一致;

mapper接口的名字和映射文件的名字必须一致;

在resources目录下创建文件com/fz/mybatis/mapper,将所有映射文件放在该文件夹下,而所有的mapper接口在java文件下的com/fz/mybatis/mapper;

这样当项目打包以后形成的target文件中这连个文件就会在同一个目录下;

这样就可以在在配置文件中使用package标签映入映射文件了

<mappers>
    <package name="com.fz.mybatis.mapper"/>
</mappers>

 使用idea创建mybatis配置文件的模板:File->Settings

三.Mybatis获取参数值

Mybatis获取参数值的方式有:${}字符串拼接(手动加引号)和#{}占位符赋值(自动加引号)

1.若mapper接口获取参数的类型为单个字面量

此时可以通过${}和#{}以任意的内容获取参数值,一定要注意${}的单引号问题

示例:根据用户名查询用户信息

首先创建mapper接口

public interface UserMapper {
    // 根据用户名查询用户信息
    User getUserByUsername (String username);
}

映射文件中添加sql语句,使用任意内容username获取参数,当然也可以是其他的命名

<select id="getUserByUsername" resultType="User">
    select * from user where username = #{username}
</select>

创建测试类,获取用户名为admin1的用户信息

@Test
public void getUserByUsername () {
    try {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        SqlSession sqlSession = build.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserByUsername("admin1");
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2.若mapper接口的方法的参数为多个字面量类型

此时mybatis会将参数放在map集合中,以两种方式存储数据

以arg0,arg1...为键,以参数为值

以param1,param2...为键,以参数为值

因此只需要通过#{}或${}来访问map集合的键,就可以获取对应的值

public interface UserMapper {
    // 用户登录
    User checkLoginIn (String username,String password);
}
<select id="checkLoginIn" resultType="User">
    select * from user where username = #{arg0} and password =#{arg1}
</select>
@Test
public void checkLoginIn () {
    try {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        SqlSession sqlSession = build.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.checkLoginIn("admin1","123456");
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3.mapper接口方法的参数为map集合类型

因此只需要通过#{}或${}来访问map集合的键,就可以获取对应的值

public interface UserMapper {
    User checkLoginInMap (Map<String,String> map);
}
<select id="checkLoginInMap" resultType="User">
    select * from user where username = #{username} and password =#{password}
</select>
@Test
public void checkLoginInMap () {
    try {
        .......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, String> map = new HashMap<>();
        map.put("username","admin1");
        map.put("password","123456");
        User user = mapper.checkLoginInMap(map);
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

4.mapper接口方法的参数为实体类类型的参数

只需要通过#{}或${}来访问实体类中的属性名,就可以获取相对应的属性值

创建实体类,实体类的属性必须要有对应的getXxx和setXxx方法,这样才能作为一个实体类的属性被Mybatis获取到,反之,如果不声明属性但存在getXxx和setXxx方法,依旧会被认作实体类中的一个属性;

public class User {
    private Integer id;
    private String username;
    private String password;
    private int age;
    private String gender;
    private String email;
    ......
}
public interface UserMapper {
    void insertUserByUser (User user);
}
<insert id="insertUserByUser">
    insert into user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>
@Test
public void testInsertUser () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null,"root","123456",33,"女","123@qq.com");
        mapper.insertUserByUser(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

5.在mapper接口方法的参数上设置@Param注解

@Param注解的value属性值为键,以参数为值;

以param1,param2......为键,以参数为值

只需要通过#{}或${}来访问实体类中的属性名,就可以获取相对应的属性值

示例:在mapper接口中以username和password为键,以方法的实参为值获取参数

public interface UserMapper {
    User checkLoginInByParam (@Param("username") String username,@Param("password") String password);
}

在mapper映射文件中使用上面声明的键放在#{}中作为占位符赋值;

<select id="checkLoginInByParam" resultType="User">
    select * from user where username = #{username} and password =#{password}
</select>

添加测试类:

@Test
public void checkLoginInParam () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.checkLoginInByParam("root", "123456");
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

四.Mybatis查询功能

若sql查询的结果为多条时,一定不能以实体类类型作为mapper接口方法的返回值,否则会抛出TooManyResultsException;

若sql查询的结果为一条时,此时可以使用实体类类型或list集合作为方法的返回值;

示例1:查询单行单列的数据,查询表中的用户总数

public interface UserMapper {
    // 查询用户的总数量
    Integer getCount ();
}
<select id="getCount" resultType="java.lang.Integer">
    select count(*) from user
</select>

需要将查询出的类型设置在select标签的resultType中设置类型别名,而Mybatis中为Java中常用的数据类型设置了类型别名,例如:

Integer:Integer,int

int:_int,_integer

Map:map

String:string

示例2:查询的结果没有实体类对应时,需要使用Map集合为返回的类型

查询得到的结果是一个map集合,且查询到的内容是以键值对的方式呈现的,若查询到的数据的某个字段为null,则该字段不会出现在map集合中,如下所示:

public interface UserMapper {
    // 根据id查询用户的信息的map集合
    Map<String,Object> getUserByIdToMap (@Param("id") Integer id);
}

resultType的结果为一个Map集合,可以使用Mybatis中提供的Map集合的类型别名map

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

查询中的结果以键值对的方式出现在返回的Map集合中 

@Test
public void testGetUserByIdToMap () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> userByIdToMap = mapper.getUserByIdToMap(14);
        // {password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}
        System.out.println(userByIdToMap);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

示例3:查询的结果没有实体类对应时,将查询到的多条数据保存在集合中返回

1)将mapper接口方法的返回值设置为泛型是map的list集合

public interface UserMapper {
    // 获取所有用户信息
    List<Map<String,Object>> getAllUserByList ();
}
<select id="getAllUserByList" resultType="map">
    select * from user
</select>

测试代码略,最终的返回结果如下: 

[{password=123456, gender=男, id=1, age=23, email=123456@qq.com, username=admin1}, {password=123456, gender=女, id=12, age=33, email=123@qq.com, username=root}, {password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}]

2)可以将每条数据转换为Map集合放在一个大的Map中,但是必须要通过@MapKey注解,在该注解中要声明是将查询的哪个字段的值作为大Map的键;

public interface UserMapper {
    @MapKey("id")
    Map<String,Object> getAllUserToMap ();
}
<select id="getAllUserToMap" resultType="map">
    select * from user
</select>
uploading.4e448015.gif正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消

最终的返回结果如下:

{1={password=123456, gender=男, id=1, age=23, email=123456@qq.com, username=admin1}, 12={password=123456, gender=女, id=12, age=33, email=123@qq.com, username=root}, 14={password=123456, gender=男, id=14, age=23, email=123456@qq.com, username=admin}}

五.特殊的SQL执行

1.模糊查询

前置知识:使用LIKE关键字进行模糊查询,可以提高%表示任意个数的任意字符,用_表示任意的单个字符;

在Mybatis中有三种方式进行模糊查询,建议使用第三种

<select id="getUserByLike" resultType="User">
    select * from user where username like '%${Like}%'
    select * from user where username like concat('%',#{Like},'%')
    select * from user where username like "%"#{Like}"%"
</select>

2.批量删除

可以使用

DELETE FROM 表名 WHERE 条件1 OR 条件2

DELETE FROM 表名 WHERE 字段名 IN (XX,XX,...)

在Mybaits中使用${}的方式进行批量删除

public interface UserMapper {
    // 批量删除
    void deleteMoreUser (@Param("ids") String ids);
}
<delete id="deleteMoreUser">
    delete from user where id in (${ids})
</delete>
@Test
public void testDeleteMoreUser () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteMoreUser("12,14");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3.动态设置表名

必须使用${}的方式,不能使用#{},因为它是占位符赋值,会自动加单引号‘’,而sql语句中表名是不能加单引号的;

public interface UserMapper {
    // 动态设置表名获取查询信息
    List<User> getUserList (@Param("tableName") String tableName);
}
<select id="getUserList" resultType="User">
    select * from ${tableName}
</select>
@Test
public void testGetUserList () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList("user");
        System.out.println(userList);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

4.添加功能获取自增的主键

当向表中添加一条记录时,一般记录的主键是数据库生成的(这里设置为自增主键),往往需要立刻获取,一般会认为新添加的主键一定是当前表中值最大的主键,直接获取最大主键即可,但实际场景中会存在多个线程操作数据库的情况,所以通过主键最大值获取到的主键很可能不是我们刚刚添加的。而JDBC底层提供了获取自增主键的方式,Mybatis中也封装了JDBC中的功能;

创建mapper接口方法,由于是添加数据,返回值为空void(固定的返回值),参数类型为实体类User类型

public interface UserMapper {
    // 添加用户信息并获取自增的主键
    void insertUserAndGetKey (User user);
}

创建映射文件,需要添加两个属性:

useGeneratedKeys:表示当前添加功能使用自增的主键

keyProperty:将添加的数据的自增主键为实体类类型的参数的属性赋值

<insert id="insertUserAndGetKey" useGeneratedKeys="true" keyProperty="id">
    insert into user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

由于增加记录方法的返回值固定是void类型,所以不能将主键作为方法的返回值返回,只能返回到创建的实体类中的指定属性,所以使用keyProperty属性指定返回到实体类的哪个属性中;

创建测试方法,添加记录:

@Test
public void testInsertUserAndGetKey () {
    try {
        ......
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null, "蒙奇·D·路飞", "123456", 23, "男", "123456@qq.com");
        mapper.insertUserAndGetKey(user);
        System.out.println(user);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

此时打印实体类实参就会获取到id主键值了:

User{id=25, username='蒙奇·D·路飞', password='123456', age=23, gender='男', email='123456@qq.com'}

六.自定义映射ResultMap

使用全局配置解决数据库字段名和pojo类属性名不一致的情况。往往数据库的字段名使用下划线的方式命名,而对应Java类则以驼峰命名,字段名和属性名不一致会导致无法获取字段的值;

1.配置文件处理字段名和属性名的映射关系

1)为查询的字段设置别名,和属性名保持一致

<select id="getEmpByEmpId" resultType="Emp">
    select emp_id empId,emp_name empName,age,gender from emp where emp_id = #{empId}
</select>

2)当字段符合sql的要求使用_,属性符合Java的要求使用驼峰,可以在Mybatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰:

<settings>
    <!--将下划线映射为驼峰-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

映射文件

<select id="getEmpByEmpId" resultType="Emp">
    select * from emp where emp_id = #{empId}
</select>

测试代码

@Test
public void testGetEmpByEmpId () {
    try {
        ......
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empByEmpId = mapper.getEmpByEmpId(1);
        System.out.println(empByEmpId);  // Emp{empId=1, empName='张三', age=12, gender='男'}
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2.使用ResultMap自定义映射处理映射关系

1)处理一对一的映射关系

需要使用resultMap标签,用于自定义映射关系,其包括以下属性:

id:唯一标识;

type:处理映射关系的实体类的类型;

其中包含常用的子标签有:

id: 处理主键和实体类中的属性实现映射关系;

result:处理普通字段和实体类中属性的映射关系;

子标签的常用属性有:

property: 设置映射关系中属性的属性名,必须是处理实体类类型的属性名;

column:设置映射关系中的字段名,必须是sql查询出的某个字段;

随后在select标签的resultMap属性加上对应的resultMap标签的id值

<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
</resultMap>
<select id="getEmpByEmpId" resultMap="empResultMap">
    select * from emp where emp_id = #{empId}
</select>

2)处理多对一的映射关系

创建员工类Emp,每个员工都对应一个部门dept,添加有参无参构造器,getter和setter和toString方法;

public class Emp {
    private Integer empId;
    private String empName;
    private Integer age;
    private String gender;
    private Dept dept;
}

创建部门类Dept;

public class Dept {
    private Integer deptId;
    private String deptName;
}

1.使用级联处理

处理好字段和实体类中的哪些属性进行映射

<resultMap id="empAndDeptResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
    select emp.*,dept.* from emp left join dept on emp.dept_id = dept.dept_id where emp.emp_id = 1
</select>

测试方法

@Test
public void testGetEmpAndDept () {
    try {
        ......
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empByEmpId = mapper.getEmpAndDept(1);
        // Emp{empId=1, empName='张三', age=12, gender='男', dept=Dept{deptId=1, deptName='A'}}
        System.out.println(empByEmpId); 
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2.使用association标签

association标签用来处理多对一的映射关系,处理实体类类型的属性,该标签包含以下常用属性:

property:设置需要处理映射关系的属性的熟悉那个名;

javaType:设置要处理的属性的类型;

<resultMap id="empAndDeptResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <association property="dept" javaType="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
    </association>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
    select emp.*,dept.* from emp left join dept on emp.dept_id = dept.dept_id where emp.emp_id = 1
</select>

3.分步查询

可以处理多对一和一对多的关系。第一步先查询员工,第二部查询员工对应的部门信息;依旧需要子ResultMap中设置association标签,但需要设置如下属性:

property:需要处理的映射关系的属性名;

select:设置分步查询的sql的唯一标识,及当前的属性值property由哪个sql查询而来;

column:设置查询出的某个字段作为分布查询sql的条件

分步查询时最好对不同的表查询创建不同的mapper接口和映射文件。首先创建emp,dept表的接口

public interface EmpMapper {
    // 分布查询员工及对应部门的信息1
    Emp getEmpAndDeptOne (@Param("empId")Integer empId);
}
public interface DeptMapper {
    // 分布查询员工及对应部门的信息2
    Dept getEmpAndDeptByStepTwo (@Param("deptId") Integer deptId);
}

创建emp映射文件进行第一步查询,分步查询需要创建accociation标签,由于第二部需要查询dept表获取部门信息dept属性的值返回给第一步查询,所以property属性值为dept类型;此外要指定下一步查询是由哪个sql完成的,需要在select属性中指定第二部查询的方法的全类名;最后第二部查询是根据部门id获取部门信息的,即查询条件是第一步查询获取员工信息中的dept_id字段,故将其作为column属性值;

<resultMap id="getEmpAndDeptOneResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <association property="dept" select="com.fz.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="dept_id">
    </association>
</resultMap>
<select id="getEmpAndDeptOne" resultMap="getEmpAndDeptOneResultMap">
    select * from emp where emp_id = #{empId}
</select>

创建dept表的映射文件: 

<mapper namespace="com.fz.mybatis.mapper.DeptMapper">
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from dept where dept_id = #{deptId}
    </select>
</mapper>

创建测试类:注意此时需要将核心配置文件中添加settings标签将下划线映射为驼峰,这样才能获取dept属性的对象值

@Test
public void testGetEmpAndDeptByStep () {
    try {
        ......
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empByEmpId = mapper.getEmpAndDeptOne(1);
        // Emp{empId=1, empName='张三', age=12, gender='男', dept=Dept{deptId=1, deptName='A'}}
        System.out.println(empByEmpId); 
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

分步查询的优势?

可以实现延迟加载,需要开启延迟加载功能,在全局配置文件中做如下添加:

<settings>
    <!--开启延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

在上述的mybaits-config文件中延迟加载的配置会对当前项目中所有的分布查询生效,此时如果需要对某一个分布查询进行完整加载,需要在当前的association标签加载fetchType属性,值为eager(立即加载),此外还有值lazy(延迟加载)

<association property="dept" select="com.fz.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="dept_id" fetchType="eager">
</association>

3)处理一对多的映射关系

查询部门信息时,一个部门对应多个员工,即一对多的映射关系,使用多表联查获取员工字段需要将员工信息放在一个集合中;

1.Collection方式

设置实体类属性,由于是一对多,员工信息为一个集合类型

public class Dept {
    private Integer deptId;
    private String deptName;
    private List<Emp> emps;
}

在ResultMap标签中添加Collection标签 ,其作用是处理一对多的映射关系(处理集合类型的属性),其中ofType属性用来设置集合类属性中存储数据的类型;

<resultMap id="deptAndEmpResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps" ofType="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
    </collection>
</resultMap>
<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
    select * from dept left join emp on dept.dept_id = emp.dept_id where dept.dept_id = #{deptId}
</select>
@Test
public void testGetDeptAndEmpByDeptId () {
    try {
        ......
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept deptAndEmpByDeptId = mapper.getDeptAndEmpByDeptId(1);
        // Dept{deptId=1, deptName='A', emps=[Emp{empId=1, empName='张三', age=12, gender='男', dept=null}, Emp{empId=4, empName='赵六', age=16, gender='男', dept=null}]}
        System.out.println(deptAndEmpByDeptId); 
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2.分步查询

设置部门mapper接口和映射文件

public interface DeptMapper {
    // 获取部门及其员工信息第一步
    Dept getDeptAndEmpByStepOne (@Param("deptId") Integer deptId);
}
<resultMap id="deptAndEmpResultMapByStep" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps" select="com.fz.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" column="dept_id"></collection>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
    select * from dept where dept_id = #{deptId}
</select>

设置员工mapper接口和映射文件

public interface EmpMapper {
    // 获取部门及其员工信息第二步
    List<Emp> getDeptAndEmpByStepTwo (@Param("deptId")Integer deptId);
}
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
    select * from emp where dept_id = #{deptId}
</select>

测试代码

@Test
public void testGetDeptAndEmpByStep () {
    try {
        ......
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept deptAndEmpByStep = mapper.getDeptAndEmpByStepOne(1);
        System.out.println(deptAndEmpByStep);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

总结:ResultMap有三个功能。设置字段和属性的映射关系;处理多对一的映射;处理一对多的映射。其中对一对应对象(一般是在实体类中设置对象类型的属性,使用association标签),对多对应集合(一般在实体类中设置集合类型的属性,使用collection标签)。

七.动态SQL

根据特定条件动态拼接SQL语句的功能,解决拼接SQL时的痛点问题;

1.if标签  

提交表单时,文本框什么都没有输入,提交到服务器中的就是空字符串;没有向服务器中提交某个请求参数,服务器获取的是null。如果出现以上两类情况,就不需要将条件拼接到sql语句中。

此时可以使用if标签,通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到sql中)

创建mapper接口:

public interface DynamicSqlMapper {
    // 根据条件查询员工的信息
    List<Emp> getEmpByCondition (Emp emp);
}

mapper映射文件: 

<mapper namespace="com.fz.mybatis.mapper.DynamicSqlMapper">
    <select id="getEmpByCondition" resultType="Emp">
        select * from emp where
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender = #{gender}
        </if>
    </select>
</mapper>

测试文件:直接传递一个实体类参数作为客户端提交的数据

    @Test
    public void testGetEmpByCondition () {
        try {
            ......
            DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
            Emp emp = new Emp(null,"张三",12,"男");
            List<Emp> empByCondition = mapper.getEmpByCondition(emp);
            System.out.println(empByCondition);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

2.where标签

上述案例中,如果where关键字后的if标签没有一个成立,此时where关键字依旧会存在,会报错。可以在where关键字后添加横成立条件 1=1 或者使用<where>标签,它有如下的作用:

如果当前where标签内有条件成立,会自动生成where关键字;

将标签内内容前多余的and关键字去掉,但是其中内容后对于的and不能去掉;

where标签中没有任何一个条件成立,则where标签没有任何功能;

对上述映射文件做如下修改:

<select id="getEmpByCondition" resultType="Emp">
    select * from emp
        <where>
            <if test="empName != null and empName != ''">
                emp_name = #{empName}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="gender != null and gender != ''">
                and gender = #{gender}
            </if>
        </where>
</select>

3.trim标签

用于截取,可以在标签中内容的前面或后面天添加指定内容或去掉指定内容,其中有如下常用属性:

prefix,suffix:在标签中内容的前面或后面添加指定内容;

prefixOverrides,suffixOverrides:在标签中内容的前面或后面去掉指定内容;

<select id="getEmpByCondition" resultType="Emp">
    select * from emp
    <trim prefix="where" suffixOverrides="and">
        <if test="empName != null and empName != ''">
            emp_name = #{empName} and
        </if>
        <if test="age != null and age != ''">
            age = #{age} and
        </if>
        <if test="gender != null and gender != ''">
            gender = #{gender}
        </if>
    </trim>
</select>

例如添加了如下的测试代码,此时设置性别gender字段传入值为空:

DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
Emp emp = new Emp(null,"张三",12,"");
List<Emp> empByCondition = mapper.getEmpByCondition(emp);
System.out.println(empByCondition);

此时生成的sql语句如:select * from emp where emp_name = ? and age = ?

4.choose,when,otherwise标签

相当于java中的if...else if...else,when至少设置一个,otherwise最多设置一个(它标识else)

mapper接口:

public interface DynamicSqlMapper {
    // 使用choose查询员工信息
    List<Emp> getEmpByChoose (Emp emp);
}

映射文件: 

<select id="getEmpByChoose" resultType="Emp">
    select * from emp
    <where>
        <choose>
            <when test="empName != null and empName != ''">
                emp_Name = #{empName}
            </when>
            <when test="age != null and age != ''">
                age = #{age}
            </when>
            <when test="gender != null and gender != ''">
                gender = #{gender}  
            </when>
        </choose>
    </where>
</select>

测试代码:

DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
Emp emp = new Emp(null,"张三",12,"男");
List<Emp> empByCondition = mapper.getEmpByChoose(emp);
System.out.println(empByCondition);

此时生成的sql语句是:select * from emp WHERE emp_Name = ? ,显然满足if...else的逻辑,当员工姓名emp_Name符合条件时,其后面的所有条件都不会判断,所以sql语句中只有emp_Name = ?。

5.foreach标签

用于批量操作,常用的有批量添加和批量删除。

示例1:批量添加用户

mapper接口设置形参为List类型,此时Mybatis会将其以list为键,参数值为值放在一个Map中;如果形参为Array类型,此时Mybatis会将其以array为键,参数值为值放在一个Map中;为了简单起见,在参数前添加@Param注解;

public interface DynamicSqlMapper {
    // 批量添加员工信息
    void insertMoreEmp(@Param("emps") List<Emp> emps);
}

映射文件,使用sql语句insert into emp values (),(),()的方式添加多条记录,使用foreach标签循环小括号遍历集合emps ,使用item="emp"表示集合内的每一个员工信息,使用separator添加每一次循环括号之间的分隔符" , ",如下:

<insert id="insertMoreEmp">
    insert into emp values
    <!--emp表示集合中的每一个员工信息,所以访问员工属性需要使用emp.xxx的方式-->
    <foreach collection="emps" item="emp" separator=",">
        (null,#{emp.empName},#{emp.age},#{emp.gender},null)
    </foreach>
</insert>

 测试代码:

    @Test
    public void testInsertMoreEmp () {
        try {
            ......
            DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
            Emp emp1 = new Emp(null,"宇智波止水",12,"男");
            Emp emp2 = new Emp(null,"宇智波带土",12,"男");
            Emp emp3 = new Emp(null,"宇智波鼬",12,"男");
            List<Emp> list = Arrays.asList(emp1, emp2, emp3);
            mapper.insertMoreEmp(list);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

示例2:批量删除用户

设置mapper接口

public interface DynamicSqlMapper {
    // 批量删除员工
    void deleteMoreEmp(@Param("empIds") Integer[] empIds);
}

设置映射文件,使用delete from emp where emp_id in (1,2,3)删除指定的多条记录,在()中使用foreach标签遍历数组中的empId,open和close设置当前循环以什么开始和结束;

<delete id="deleteMoreEmp">
    delete from emp where emp_id in
        <foreach collection="empIds" item="empId" separator="," open="(" close=")">
            #{empId}
        </foreach>
</delete>

或使用 delete from emp where emp_id = 1 or 2 or 3的方式删除多条记录

<delete id="deleteMoreEmp">
    delete from emp where
    <foreach collection="empIds" item="empId" separator="or">
        emp_id = #{empId}
    </foreach>
</delete>

测试代码如下:

@Test
public void testDeleteMoreEmp () {
    try {
        ......
        DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
        Integer[] empIds = new Integer[]{5,6,7};
        mapper.deleteMoreEmp(empIds);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

总结:foreach标签常用属性

collection:设置要循环的数组或集合

item:用一个字符串表示数组或集合中的每一个数据;

separator:设置每次循环的数组之间的分隔符;

open:循环所有内容以什么开始;

close:循环的所有内容以什么结束;

6.sql标签 

sql标签将常用的sql片段进行记录,随后就可以在需要使用的地方使用include标签进行引用。

<sql id="empColumns">
    emp_id,emp_name,age,gender,dept_id
</sql>
<select id="getEmpByCondition" resultType="Emp">
    select <include refid="empColumns"></include> from emp
</select>

7.set标签

<set>标签可以帮助我们生成set关键字,去除字段后多余的。

<update id="xxx">
    update xxx
    <set>
        <if test="username != null and username != ''">username=#{username},</if>
        <if test="password!= null and password!= ''">password=#{password},</if>
    </set>
    where id=#{id}
</update>

 

八.Mybatis缓存

Mybitis缓存主要针对的都是查询功能,可以将查询出的数据进行缓存,重复的数据再次查询就可以从缓存中获取。分为一级缓存和二级缓存;

1.一级缓存

默认开启的缓存级别。Mybatis的一级缓存是sqlSession级别的,及通过同一个SqlSession查询的数据会被缓存,再次使用同一个SqlSession查询同一条数据会从缓存中获取;

一级失效的四种情况:

不同的SqlSession对应不同的一级缓存;

同一个SqlSession但查询条件不同;

同一个SqlSession两次查询期间执行了任何一次增删改操作;

同一个SqlSession两次查询期间手动清空了缓存;

2.Mybatis二级缓存

Mybatis的二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory所获取的SqlSession对象查询的数据会被缓存,再次通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取;

Mybatis二级缓存开启的条件:

  • 在核心配置文件,设置全局配置属性cacheEnabled=“true”,默认为true所以不需要设置;
  • 在映射文件中设置标签<cache/>;
  • 二级缓存必须存在SqlSession关闭或提交之后才会生效,即执行sqlSession.close()方法;
  • 查询的数据所转换的实体类类型必须实现序列化的接口,即public class Xxx implements Serializable

二级缓存失效的条件:

两次查询之间执行任意的增删改,会使一级二级缓存同时失效(手动清空缓存不会使二级缓存失效);

Mybatis缓存查询的顺序:

  • 先查询二级缓存,因为二级缓存中可能有其他程序已经查出的数据,可以直接拿来使用;
  • 如果二级缓存没命中,再查询一级缓存;
  • 如果一级缓存也没有命中,则查询数据库;
  • SqlSession关闭以后,一级缓存中的数据会写入二级缓存;