一、MyBatis简介

MyBatis是ORM框架,即对象关系映射框架。

二、搭建MyBatis

不同的MySQL版本使用的JDBC不同

com.mysql.jdbc.Driver // MySQL 5
com.mysql.cj.jdbc.Driver // MySQL 8

不同版本的MySQL的url也不同

jdbc:mysql://localhost:3306/ssm // MySQL 5
jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC // MySQL 8

三、快速开始

1、引入相关依赖

<dependencies>
    <!-- mybatis 核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>

    <!-- junit 测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
</dependencies>

2、编写MyBatis配置文件

作用:1. 配置链接数据库的环境 2. 配置MyBatis

放置位置:src/main/resources

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://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="lukeewin"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 引入MyBatis的映射文件 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

3、Mapper接口

相当于DAO,但是不用创建实现类,MyBatis会创建代理类,并执行映射文件当中的SQL。

起名规则:POJO+Mapper

public interface UserMapper {

    int insertUser();
}

4、创建MyBatis的映射文件

Mapper 接口当中的一个抽象方法对应映射文件当中的一个SQL语句。

起名规则:POJO名字 + Mapper.xml

放置位置:src/main/resources/mappers/UserMapper.xml

映射关系:

image-20221123174559021

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.lukeewin.mybatis.mapper.UserMapper">
    <!--
        mapper 接口和映射文件要保持两个一致
        1. mapper 接口的全类名和映射文件的 namespace 一致
        2. mapper 接口中的方法的方法名要和映射文件中的 sql 的 id 保持一致
    -->
    <!-- int insertUser() -->
    <insert id="insertUser">
        insert into t_user values(null, 'admin', '123456', 23, '男', '123456@qq.com')
    </insert>
</mapper>

5、测试

openSession() 获得 SqlSession默认是不会自动提交事务,因此需要自己手动提交。

openSession(true) 获得 SqlSession默认自动提交事务。

public class MyBatisTest {

    @Test
    public void testInsert() throws IOException {
        // 1. 获取核心配置文件的输入流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 2. 获取 SqlSessionFactoryBuilder 对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 3. 获取 SqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 4. 获取 sql 的会话对象 SqlSession ,是 MyBatis 提供的操作数据库的对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 5. 获取 UserMapper 的代理实现类对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 6. 调用 mapper 接口中的方法,实现添加用户信息的功能
        int result = mapper.insertUser();
        System.out.println("结果:" + result);
        // 7. 提交事务,事务是不会自动提交的,需要手动提交事务
        sqlSession.commit();
        // 8. 关闭 sqlSession,最后一定要记得关闭会话
        sqlSession.close();
    }
}

6、添加日志功能

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

resources/log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<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="%-5d %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>

    <!-- 默认配置,级别为debug 且根据name为log.console和 log.file两个appender输出-->
    <root>
        <level value="debug"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

在MyBatis核心配置文件中设置开启日志功能

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

四、核心配置文件详解

核心配置文件必须按照一定的顺序写,否则会报错。

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

1、引入properties文件

使用properties标签引入properties文件,此后可以在当前文件中使用$的方式访问properties中的value

<properties resource="jdbc.properties"/>

2、起别名

使用typeAliases标签可以给实体类起别名。

方式一:可以使用typeAlias中的type的值是指定给哪个类起别名,而alias的值是指别名,如果不写alias,那么有默认别名,默认别名为不区分大小写的类名。

方式二:可以使用package指定给哪个包路径下的类起别名。

<typeAliases>
        <!--
            alias的值为别名,可以不设置,如果不设置则有默认的别名,默认别名分为类名且不区分大小写
         -->
        <!--<typeAlias type="top.lukeewin.mybatis.pojo.User" alias="abc"/>-->
        <!--<typeAlias type="top.lukeewin.mybatis.pojo.User"/>-->
        <!-- 通过包设置类型别名,则指定包下的所有类将拥有默认别名 -->
        <package name="top.lukeewin.mybatis.pojo"/>
</typeAliases>

3、配置连接数据库环境

可以定义多个环境,切换时在emvironments中通过default来指定。

<environments default="development">
        <!-- 可以定义多个环境 -->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>

        <environment id="test">
            <!-- 定义事务管理器类型 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
                <property name="username" value="lukeewin"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
</environments>

transactionManager: 定义事务管理器

​ type: 指定使用哪种事务管理器,有JDBC和MANGAED两种。

​ 其中JDBC是原生的事务管理方式。

​ MANGAED是被管理的意思,比如被Spring管理。

dataSource: 数据源

​ type: 设置数据源的类型

​ type="POOLED|UNPOOLED|JNDI"

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

​ UNPOOLED: 不使用数据库连接池

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

4、引入映射文件

有两种方式:

方式一:单个文件引入,不推荐。

方式二:通过包路径方式引入,推荐。

​ 通过包路径的方式引入需要注意包路径必须和 mapper 接口路径一致,并且名字也必须要和 mapper接口的名字一致。

<mappers>
    <!--<mapper resource="mappers/UserMapper.xml"/>-->
    <!--
        以包的方式引入映射文件,但是必须要满足两个条件:
        1. mapper 接口和映射文件所在的包名必须一致
        2. mapper 接口的名字和映射文件的名字必须一致
    -->
    <package name="top.lukeewin.mybatis.mapper"/>
</mappers>

五、MyBatis获取参数的方式

有两种方式:${}和#{}

${}字符串拼接

#{}占位符

推荐使用#{}

因为占位符能避免SQL注入,同时可以为字符串和日期自动添加单引号。

1、单个字面量类型

若 mapper 接口方式的参数为单个的字面量类型(字符串、基本数据类型、包装类)

#{}${}:任意内容

注意:${}不会自动添加单引号

User getUserByUsername(String username);
<select id="getUserByUsername" resultType="User">
	select * from t_user where username = #{username}
</select>
<select id="getUserByUsername" resultType="User">
	select * from t_user where username = '${username}'
</select>

2、多个字面量类型

当 Mapper 接口中有多个参数的时候,MyBatis 会创建Map ,并使用 paramX 或 argX 为Key,参数值为 Value , 且两者均可混合使用。

User checkLogin(String username, String password);
<select id="checkLogin" resultType="User">
	select * from t_user where username = #{arg0} and password = #{arg1}
</select>
<select id="checkLogin" resultType="User">
	select * from t_user where username = #{param1} and password = #{param2}
</select>

3、以Map集合的方式传值

/**
 * {username: "xxxx"}
 * {password: "xxxx"}
 * @param map
 * @return
 */
User checkLoginByMap(Map<String, Object> map);
<!-- User checkLoginByMap(Map<String, Object> map) -->
<select id="checkLoginByMap" resultType="user">
    SELECT * FROM t_user WHERE username = #{username} and password = #{password};
</select>

4、参数为实体类(常用)

通过#{}或${}访问实体类中的属性

<!-- void insertUser(User user) -->
<insert id="insertUser">
	insert into t_user values(null, #{username}, #{password}, #{age}, #{gender}, #{email})
</insert>

5、通过@Param注解方式传参(常用)

在 mapper 接口中的方法形参中添加@Param注解指定map中的key

会替换原来的argX,以@Param中的值作为map中的key

User checkLoginByParam(@Param("username") String username, @Param("password") String password);
<select id="checkLoginByParam" resultType="User">
	select * from t_user where username = #{username} and password = #{password}
</select>
<select id="checkLoginByParam" resultType="User">
	select * from t_user where username = #{param1} and password = #{param2}
</select>

推荐:除了实体类型为,推荐使用@Param

六、MyBatis中的各种查询功能

1、返回单个实体类对象

User getUserById(@Param("id") Integer id);
<select id="getUserById" resultType="User">
	select * from t_user where id = #{id}
</select>

如返回了多条记录,则会报错。

image-20221126213114416

2、返回多条记录

若SQL语句查询的结果为多条时,一定不能使用实体类型接收,否则会报异常。

要有集合接收。

若SQL语句查询的结果返回一条时,可以使用集合接收。

<!-- List<User> getAllUser() -->
<select id="getAllUser" resultType="User">
    select * from t_user
</select>

3、查询单个数据

类型别名

java.lang.Integer-->int|integer

int-->_int|_integer

Map-->map

List-->list

Integer getCount();
<select id="getCount" resultType="int">
    select count(0) from t_user
</select>

4、查询一条记录

查询的结果没有对应的实体类的时候,就可以使用Map集合。

resultType 设置成 map 即可。

查询为null 的字段是不会放到Map集合里面。

Map<String, Object> getUserByIdToMap(@Param("id") Integer id);
<!-- Map<String, Object> getUserByIdToMap(@Param("id") Integer id) -->
<select id="getUserByIdToMap" resultType="map">
	select * from t_user where id = #{id}
</select>

5、查询多条记录

5.1、使用List集合

把Map集合放到List集合中。

List<Map<String, Object>> getAllUserToMap();
<select id="getAllUserToMap" resultType="map">
    select * from t_user
</select>

5.2、使用@MapKey

可以将每条数据转换为 map 集合放在一个大的 map 中,但必须要通过 @MapKey 注解将查询的某个字段的值作为大的 map 的键。

@MapKey("id")
Map<String, Object> getAllUserToMap();
<select id="getAllUserToMap" resultType="map">
    SELECT * FROM t_user
</select>
{2={password=123456, gender=男, id=2, age=23, email=123456@qq.com, username=admin},
4={password=123456, gender=男, id=4, age=23, email=123456@qq.com, username=admin}, 
5={password=123456, gender=男, id=5, age=23, email=123456@qq.com, username=admin},
6={password=123456, gender=男, id=6, age=23, email=123456@qq.com, username=admin},
7={password=123456, gender=男, id=7, age=23, email=123456@qq.com, username=admin},
8={password=123456, gender=男, id=8, age=23, email=123456@qq.com, username=admin},
9={password=123456, gender=男, id=9, age=23, email=123456@qq.com, username=admin},
10={password=123456, gender=男, id=10, age=23, email=123456@qq.com, username=admin},
11={password=123456, gender=男, id=11, age=23, email=123456@qq.com, username=admin},
12={password=123456, gender=男, id=12, age=23, email=123456@qq.com, username=admin},
15={password=123456, gender=女, id=15, age=25, email=12345678@qq.com, username=赵丽颖},
16={password=123, gender=女, id=16, age=25, email=12345678@qq.com, username=迪丽热巴},
17={password=123456, gender=女, id=17, age=25, email=12345678@qq.com, username=高圆圆},
18={password=123456, gender=男, id=18, age=23, email=123456@qq.com, username=admin},
19={password=123456, gender=女, id=19, age=33, email=123456@qq.com, username=root}}

6、模糊查询

有三种方案

方案一:使用${}

方案二:使用concat('%', #, '%')函数进行字符串拼接

方案三:使用"%"#"%"

<!-- List<User> getUserByLike(@Param("mohu") String mohu -->
<select id="getUserByLike" resultType="User">
    <!--select * from t_user where username like '%${mohu}%'-->
    <!--select * from t_user where username like concat('%', #{mohu}, '%')-->
    select * from t_user where username like "%"#{mohu}"%"
</select>

七、批量删除

<!-- void deleteUserByBatch(@Param("ids") String ids) -->
<delete id="deleteMoreUser">
	delete from t_user where id in(${ids})
</delete>

八、动态设置表名

表名不能用单引号,所以不能用#{}

<!-- List<User> getUserList(@Param("tablename") String tablename) -->
<select id="getUserList" resultType="User">
    select * from ${tablename}
</select>

九、获取自增主键

void insertUser()返回的是受影响的行数,不能使用返回值返回主键

useGeneratedKeys:表示是否使用自增主键

keyProperty:指定获取到的主键存储到实体中的哪个属性中

void insertUser(User user);
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null, #{username}, #{password}, #{age}, #{gender}, #{email})
</insert>
@Test
public void testInsertUser() {
	SqlSession sqlSession = SqlSessionUtil.getSqlSession();
	SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);
	User user = new User(null, "xiaoming", "123456", 23, "男", "123456@qq.com");
	mapper.insertUser(user);
	System.out.println(user);
}

十、自定义映射

字段名和属性名不一致

方式一:使用别名

Emp getEmpByEmpId(@Param("empId") Integer empId);
<select id="getEmpByEmpId" resultType="Emp">
    select emp_id empId, emp_name empName, age, gender from t_emp where emp_id = #{empId}
</select>

方式二:在 MyBatis核心配置文件中配置字段下划线自动转属性驼峰

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

方式三:使用resultMap

id: 唯一标识

type: 映射到哪个实体类

id标签: 主键的映射关系

result标签: 普通字段和属性之间的映射关系

column: 查询出来对应的字段名

property: 映射到实体中的属性名

<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
</resultMap>

<select id="getEmpByEmpId" resultMap="empResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

1、多对一

多个 emp 对一个 dept

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

    private Dept dept;
    
    // getter/setter/toString
}
Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);

1.1、使用级联的方式

<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
    <result column="dept_id" property="dept.deptId"/>
    <result column="dept_name" property="dept.deptName"/>
</resultMap>

<!-- Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId) -->
<select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
    select t_emp.*, t_dept.dept_name
    from t_emp
    left join t_dept
    on t_emp.dept_id = t_dept.dept_id
    where t_emp.emp_id = #{empId}
</select>

1.2、使用association标签

association:专门处理多对一的映射关系(处理实体类类型的属性)

property:属性名

javaType:属性的类型

<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
    <association property="dept" javaType="Dept">
        <id column="dept_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
    </association>
</resultMap>

1.3、分步查询

property:属性名

select:第二步查询的唯一标识

column:上一步查询出来的字段传递到下一步

注意:必须要在 MyBatis 核心配置文件中开启下划线自动映射驼峰,如果不开启,那么获取不了部门信息

/**
 * 通过分步查询来查询员工以及员工所对应的部门信息的第一步
 * @param empId
 * @return
 */
Emp getEmpAndDeptByEmpIdOne(@Param("empId") Integer empId);
/**
 * 通过分步查询员工以及员工所对应的部门信息的第二步
 * @param deptId
 * @return
 */
Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);

第一步:

<resultMap id="empAndDeptByStepResultMap" type="Emp">
    <id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
    <association property="dept"
                 select="top.lukeewin.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                 column="dept_id"/>
</resultMap>
<!-- Emp getEmpAndDeptByEmpIdOne(@Param("empId") Integer empId) -->
<select id="getEmpAndDeptByEmpIdOne" resultMap="empAndDeptByStepResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

第二步:

<!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId) -->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
    select * from t_dept where dept_id = #{dept_id}
</select>

1.3.1、延迟加载

在 MyBatis 核心配置文件中开启延迟加载

全局性

aggressiveLazyLoading和lazyLoadingEnabled共同决定延迟加载

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

局部性

fetchType="eager|lazy"

eager:立即迟加载

lazy:延迟加载

<resultMap id="empAndDeptByStepResultMap" type="Emp">
    <id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
    <association property="dept"
                 select="top.lukeewin.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                 column="dept_id"
                 fetchType="lazy"
    />
</resultMap>

2、一对多

2.1、使用collection标签

collection:一对多(集合)

ofType:对应集合中泛型的类型

public class Dept {

    private Integer deptId;

    private String deptName;

    private List<Emp> emps;
    
    // 省略构造器,setter,getter,toString方法
}
Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);
<resultMap id="empResultMap" type="Dept">
    <id column="dept_id" property="deptId"/>
    <result column="dept_name" property="deptName"/>
    <collection property="emps" ofType="Emp">
        <id column="emp_id" property="empId"/>
        <result column="emp_name" property="empName"/>
        <result column="age" property="age"/>
        <result column="gender" property="gender"/>
    </collection>
</resultMap>
<!-- Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId) -->
<select id="getDeptAndEmpByDeptId" resultMap="empResultMap">
    select *
    from t_dept
    left join t_emp
    on t_dept.dept_id = t_emp.dept_id
    where t_dept.dept_id = #{deptId}
</select>

2.2、使用分步查询

先根据传递过来的部门 id 在部门表中查询,如果查到了就根据查询出来的部门 id 在用户表中查询对应有哪些用户

DeptMapper.java

/**
 * 通过分步查询查询部门以及部门的员工信息第一步
 * @param deptId
 * @return
 */
Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);

EmpMapper.java

/**
 * 通过分步查询查询部门以及部门的员工信息第二步
 * @param deptId
 * @return
 */
List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);

DeptMapper.xml

<resultMap id="deptAndEmpResultMapByStep" type="Dept">
    <id column="dept_id" property="deptId"/>
    <result column="dept_name" property="deptName"/>
    <collection property="emps"
                select="top.lukeewin.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                column="dept_id"/>
</resultMap>
<!-- Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId) -->
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
    select * from t_dept where dept_id = #{deptId}
</select>

EmpMapper.xml

<!-- List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId) -->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
    select * from t_emp where dept_id = #{deptId}
</select>

3、多对多

多对多

十一、动态SQL

1、if

通过 test 属性中的表达式判断标签中的内容是否有效(是否会拼接到 sql 中)

/**
 * 根据条件查询员工信息
 * @param emp
 * @return
 */
List<Emp> getEmpByCondition(Emp emp);
<!-- List<Emp> getEmpByCondition(Emp emp) -->
<select id="getEmpByCondition" resultType="Emp">
    select *
    from t_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>

2、where

where 1 = 1

<!-- List<Emp> getEmpByCondition(Emp emp) -->
<select id="getEmpByCondition" resultType="Emp">
    select *
    from t_emp
    where 1 = 1
    <if test="empName != null and empName != ''">
        and 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>

作用:可以自动去掉内容前多余的 and ,不会自动去掉内容后多余的 and

<!-- List<Emp> getEmpByCondition(Emp emp) -->
<select id="getEmpByCondition" resultType="Emp">
    select *
    from t_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:在后面添加内容

prefix:覆盖前面的内容

suffixOverrives:覆盖后面的内容

<!-- List<Emp> getEmpByCondition(Emp emp) -->
<select id="getEmpByCondition" resultType="Emp">
    select *
    from t_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>

4、choose、when、otherwise

类似 if-else if-else

多选一

<!-- List<Emp> getEmpByChoose(Emp emp) -->
<select id="getEmpByChoose" resultType="Emp">
    select * from t_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>

5、foreach

作用:批量添加或批量删除

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

item:要循环的每一个元素

separator:分隔符

open:以什么开头

close:以什么结束

index:索引

批量添加

<!-- void insertMoreEmp(@Param("emps") List<Emp> emps) -->
<insert id="insertMoreEmp">
    insert into t_emp values
    <foreach collection="emps" item="emp" separator=",">
        (null, #{emp.empName}, #{emp.age}, #{emp.gender}, null)
    </foreach>
</insert>

批量删除

方式一:

<!-- void deleteMoreEmp(@Param("empIds") Integer[] empIds) -->
<delete id="deleteMoreEmp">
    delete from t_emp where emp_id in
    <foreach collection="empIds" item="empId" separator="," open="(" close=")">
        #{empId}
    </foreach>
</delete>

方式二:

<!-- void deleteMoreEmp(@Param("empIds") Integer[] empIds) -->
<delete id="deleteMoreEmp">
    delete from t_emp where
    <foreach collection="empIds" item="empId" separator="or">
        emp_id = #{empId}
    </foreach>
</delete>

6、sql

作用:将重复的 SQL 代码抽取出来被多条语句重复使用

include 标签中的 refid 指定要使用 SQL 代码段的 id

<sql id="empColumns">
    emp_id,emp_name,age,gender,dept_id
</sql>
<!-- List<Emp> getEmpByCondition(Emp emp) -->
<select id="getEmpByCondition" resultType="Emp">
    select <include refid="empColumns"/>
    from t_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>

十二、MyBatis中的缓存

一共有两级缓存

默认开启一级缓存 SqlSession级别 通过同一个 SqlSession 执行的 SQL 语句会被缓存

1、一级缓存

通过同一个 sqlSession 对象查询同一个数据,会直接从缓存中获取

image-20221202014946135

image-20221202015406745

image-20221202015433182

1.1、一级缓存失效的情况

不同的 sqlSession 对象

相同的 sqlSession 对象,但是查询条件不同

相同的 sqlSession 对象,但是两次查询操作之间进行过增删改操作

相同的 sqlSession 对象,但是两次查询期间进行了手动清空缓存操作 sqlSession1.clearCache()

2、二级缓存

SqlSessionFactory 级别,多个 SqlSession 可以共享

先保存到一级缓存中,之后一级缓存关闭之后才会被保存到二级缓存中

通过同一个 sqlSessionFactory 所获取的 sqlSession 对象查询的数据会被缓存

在通过同一个 sqlSessionFactory 所获取的 sqlSession 查询相同的数据会从缓存中获取

条件:

  • 在 MyBatis 核心配置文件中设置 cacheEnabled = "true",默认为true
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在 sqlSession 关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现Serializable接口

2.1、二级缓存失效的情况

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

2.2、二级缓存的相关配置

image-20221203022747093

2.3、整合第三方缓存

2.3.1、引入相关依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

image-20221203024117559

2.3.2、添加logback.xml配置文件

注意:由于引入了logback依赖,所以log4j就失效了,我们必须要建立logback.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行
            -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
                [%msg]%n</pattern>
        </encoder>
    </appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是: DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="top.lukeewin.mybatis.mapper" level="DEBUG"/>
</configuration>

2.3.3、添加ehcache.xml配置文件

位置:src/resources

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\Works\javalianxi\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

2.3.4、修改映射文件

需要在映射文件中的cache标签中使用type属性指定使用第三方缓存,如果没有指定,则默认使用 MyBatis 自带的二级缓存

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

3、MyBatis缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存当中可能会有其他线程已经查询出来的数据。
  2. 二级缓存没有命中,则再查询一级缓存。
  3. 一级缓存也没有命中,则执行查询数据库。
  4. SqlSession 关闭之后,一级缓存当中的数据会写入到二级缓存。

十三、MyBatis逆向工程

本质:代码生成器

在pom.xml中引入插件

<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.16</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

1、简单版本

src/resources下创建generatorConfig.xml配置文件

targetRuntime="MyBatis3Simple"

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
    MyBatis3: 生成带条件的CRUD(奢华尊享版)
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"
                        userId="lukeewin"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="top.lukeewin.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="top.lukeewin.mybatis.mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="top.lukeewin.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>

双击maven中的plugins下的mybatis-generator下的mybatis-generator.generate即可生成代码。

2、复杂版本

targetRuntime="MyBatis3"

xxxByExample通过条件进行操作

xxxSelective选择性进行操作,例如insertSelective选择性进行添加,如果某个字段为null那么,不会为这个字段赋值

updateByExampleSelective根据条件选择性修改,如果某个字段为null,那么不会修改该字段的值

updateByExample根据条件进行修改,如果某个字段为null,那么会修改该字段的值为null

2.1、测试

根据主键 id 来查询

@Test
public void testMBG() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = mapper.selectByPrimaryKey(1);
    System.out.println(emp);
}

image-20221203230528937

查询所有数据

@Test
public void testMBG1() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    List<Emp> list = mapper.selectByExample(null);
    list.forEach(System.out::println);
}

image-20221203230832939

根据条件来查询特定的数据

通过XxxExample对象中的createCriteria()后的方法来指定条件

 @Test
 public void testMBG2() {
     SqlSession sqlSession = SqlSessionUtil.getSqlSession();
     EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
     EmpExample empExample = new EmpExample();
     empExample.createCriteria().andEmpNameEqualTo("高圆圆");
     List<Emp> list = mapper.selectByExample(empExample);
     list.forEach(System.out::println);
 }

要使条件之间用or连接,xxxExample调用or()方法之后,然后拼接其它条件即可

empExample.createCriteria().andEmpNameEqualTo("高圆圆").andAgeGreaterThanOrEqualTo(22);
empExample.or().andGenderEqualTo("女");

测试普通修改

@Test
public void testMBG3() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp(2, "小明", null, "男");
    mapper.updateByPrimaryKey(emp);
}

image-20221203233003065

测试选择性修改

@Test
public void testMBG4() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp emp = new Emp(2, "小明", null, "男");
    mapper.updateByPrimaryKeySelective(emp);
}

image-20221203233209987

十四、分页

添加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

配置分页插件

在 MyBatis 核心配置文件中配置

<plugins>
    <!--设置分页插件-->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

测试

@Test
public void test() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    // 要在查询之前开启分页功能
    PageHelper.startPage(1, 4);
    List<Emp> list = mapper.selectByExample(null);
    list.forEach(System.out::println);
}

分页信息保存在 page 对象中,更多的分页信息保存在 pageInfo 中

PageInfo<>(list, 5)第一个参数为查询出来的数据,第二个参数为导航分页

@Test
public void test() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    // 要在查询之前开启分页功能
    Page<Object> page = PageHelper.startPage(1, 4);
    List<Emp> list = mapper.selectByExample(null);
    // 查询功能之后可以获取分页相关的所有数据
    PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);
    list.forEach(System.out::println);
    System.out.println(pageInfo);

image-20221203234943141

Q.E.D.


热爱生活,热爱程序