java学习之Mybatis


参考资料
尚硅谷Mybatis2022版教程

1.什么是Mybatis

1.1Mybatis的特性

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java
Objects,普通的Java对象)映射成数据库中的记录
4) MyBatis 是一个 半自动的ORM(对象关系映射)框架
5)轻量级,性能出色
6)SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
7)开发效率稍逊于HIbernate,但是完全能够接受

1.2Mybatis的下载

下载地址:
mybatis下载
点开链接后往下拉
在这里插入图片描述
选择第一个下载
在这里插入图片描述
下载后解压即可,里面的pdf是官方文档,建议收藏,jar包可以通过maven下载,所以用不到。
在这里插入图片描述

2.搭建Mybatis

2.1创建maven工程

新建一个空的project,在Settings中找到maven,修改idea自带的maven为你的本地maven
在这里插入图片描述
a>打包方式:jar
b>引入依赖

<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>5.1.3</version>
        </dependency>
    </dependencies>

2.2创建mybatis的核心配置文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息 核心配置文件存放的位置是src/main/resources目录下

a)官方文档中配置信息的位置
在这里插入图片描述

<?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?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

2.3创建Mapper接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类。

a)在数据库中创建一张表
在这里插入图片描述
b)创建对应的实体类*

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String sex;
    private String email;
}

c)创建Mapper接口(即原来的Dao)

public interface UserMapper {
}

2.4创建Mybatis的映射文件

相关概念:ORM(Object Relationship Mapping)对象关系映射。
对象:Java的实体类对象
关系:关系型数据库
映射:二者之间的对应关系
在这里插入图片描述

1、映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml
例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
因此一个映射文件对应一个实体类,对应一张表的操作
MyBatis映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置是src/main/resources/mappers目录下
2、MyBatis中可以面向接口操作数据,要保证两个一致:
a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致
b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

下面假设有一个UserMapper(即UserDao),中有一个添加的方法,方法名为:insertUser
那么我们的映射文件(UserMapper.xml)中的对应关系为

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--mapper接口的全类名和映射文件的命名空间(namespace)保持一致-->
<mapper namespace="mapper.UserMapper">
    <!---->
    <insert id="insertUser">
        insert into t_user values (null,'admin','123456',23,'男','12345@qq.com')
    </insert>
</mapper>

在mybatis配置文件中引入映射文件

<mapper resource="mappers/UserMapper.xml"/>

2.5测试功能CRUD

a)添加功能
在上文,我们已经简历好了映射关系,接下来我们来测试添加功能

public class Test {
    @org.junit.Test
    public void testMybatis() throws IOException {
        //加载核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //获取mybatis操作数据库的会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口的实现类对象,这个方法底层用了代理模式,可以返回一个接口的实现类对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //测试功能
        int result = mapper.insertUser();
        System.out.println(result);
        //提交事务
        sqlSession.commit();
    }
}

SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
SqlSessionFactory:是“生产”SqlSession的“工厂”。
工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

我们也可以优化上面的代码,改为自动提交:
只需要修改一处,加上一个参数true

SqlSession sqlSession = sqlSessionFactory.openSession(true);

b)加入log4j日志功能
加入依赖

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

加入log4j的配置文件
log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下

<?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="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
        </layout>
    </appender>
    <!--日志范围-->
    <logger name="java.sql">
        <!--日志级别-->
        <!--FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细-->
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

下面是添加功能输出的日志信息
在这里插入图片描述
c)修改和删除功能
流程:先写Mapper接口中的方法,再去映射文件中写sql语句
接口对象中的

    /**
     * 修改用户信息
     */
    void updateUser();

    /**
     *删除用户信息
     */
    void deleteUser();

映射文件UserMapper.xml中的

    <!--void updateUser();-->
    <update id="updateUser">
        update t_user set username='张三' where id=3
    </update>

    <!--void deleteUser();-->
    <delete id="deleteUser">
        delete  from t_user where id=5
    </delete>

d)查询功能
接口对象中的

    /**
     * 根据id查询一条用户信息
     */
    User getUserById();

    /**
     * 查询所有用户信息
     */
    List<User> getAllUser();

映射文件UserMapper.xml中的

    <!--User getUserById();-->
    <!--注意,查询时,我们需要设置结果类型,即要转换成的实体类对象-->
    <!--所以查询必须要设置resultType或resultMap-->
    <!--
    resultType:设置默认的映射关系(自动通过字段名为属性名赋值)
    resultMap:设置自定义映射(字段名和属性名不一样时,需要用这个)
    -->
    <select id="getUserById" resultType="pojo.User">
        select * from t_user where id=3
    </select>

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

3.详解核心配置文件(了解)

核心配置文件中的标签必须按照固定的顺序:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
?,environments?,databaseIdProvider?,mappers?

a)environment

environments:配置多个连接数据库的环境
属性:
default:设置默认使用的环境的id

environment(在environments中):配置某个具体的环境
属性:
id:表示连接数据库的环境的唯一标识,不能重复
transactionManager:事务管理方式,type有JDBC(表示当前环境中,执行SQL时,使用的是jdbc中原生的事务管理方式),MANAGED(被管理,例如spring)
dataSource:配置数据源,type有POOLED(表示使用数据库连接池缓存数据库连接),UNPOLLED(表示不使用数据库连接池),JNDI(表示使用上下文中的数据源)

b)properties

properties配置文件,是以键值对的形式存储数据的,为了properties文件内容重复,我们可以在键值对前加上一个前缀来区分功能。

在mybatis核心配置文件中引入properties文件

<properties resource="jdbc.properties"/>

通过该文件中的键值对来配置数据库

                <property name="driver" value="${jdbc.driver}"/>
                <!--连接地址-->
                <property name="url" value="${jdbc.name}"/>
                <!--用户名密码-->
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>

c)typeAliases

类型别名,根据类型顺序,应该放在properties之后,注意别名不区分大小写

    <!--设置类型别名(不区分大小写)-->
    <typeAliases>
        <!--type为需要设置别名的全类目-->
        <!--设置后在查询时,我们就可以直接写User,alias可以不写,如果不写,默认类目-->
        <typeAlias type="pojo.User" alias="User"></typeAlias>
    </typeAliases>

另一种更简洁的方式

    <!--设置别名2-->
    <!--以包为单位,将报下的所有类型设置默认的别名,即类名,且类目不区分大小写-->
    <typeAliases>
        <package name="pojo"/>
    </typeAliases>

d)mappers

此标签用于引入映射文件
在前面的CRUD中已经介绍了单独引入映射文件的方法
下面介绍一种以包为单位的引入方法,引入mapper包下的所有映射文件

    <mappers>
        <!--<mapper resource="mapper/UserMapper.xml"/>-->
        <!--
        以包为单位引入映射文件
        要求:
        1.mapper接口所在的包要和映射文件所在的包的名称一致
        2.mapper接口要和映射文件的名字一致
        -->
        <package name="mapper"/>
    </mappers>

4.在idea中设置核心配置文件和映射文件的模板

我们可以自己设置一个文件模板,以防止重复劳动
核心配置文件
点击settiings,找到如图选项,点击加号
在这里插入图片描述
在这里插入图片描述
随后,右键new就可以创建一个我们配制好的文件的模板
在这里插入图片描述

映射文件

和配置文件一样的流程,这里就不再多介绍了

5.Mybatis获取参数值的两种方式(${}和#{})

在这里插入图片描述

5.1单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号

    <!--User GetUserByUsername(String username)-->
    <select id="GetUserByUsername" resultType="User">
        <!--用#{}-->
        select * from t_user where username = #{username}
        <!--用${}-->
        select * from t_user where username = '${username}'
    </select>

5.2多个字面量类型的参数

若mapper接口中的方法参数为多个时
此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;或以param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

    <!--User checkLogin(String username,String password)-->
    <select id="checkLogin" resultType="User">
        <!--用#{}-->
        select * from t_user where username=#{arg0} and password=#{arg1}
        <!--用${}-->
        select * from t_user where username='${arg0}' and password='${arg1}'
    </select>

5.3map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号,写法与5.2类似,只不过键值对变成了我们自己定义的。

5.4实体类类型的参数(掌握)

若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号,这里底部是用set和get方法实现的。

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

5.5使用@Param标识参数(掌握)

可以通过@Param注解标识mapper接口中的方法参数
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;或以param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
其实就是使用mybatis自定义map集合中的键,且5.1,5,2,5.3都可以用此方法代替

mapper接口中的代码

    /**
     * 验证登录,使用@Param
     */
    User checkLoginByParam(@Param("username") String username, @Param("password") String password);

映射文件中的sql语句

    <!--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>

6.Mybatis的各种查询功能

6.1查询一个实体类对象

若查询出的数据,只有一条,可以用实体类对象或集合接受,但若有多条,不能用实体类对象接受,不然会报异常TooManyResultsException

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

6.2查询一个list集合

若查询出的数据有多条,可以用list接受,一定不能用实体类接受

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

6.3查询单个数据

Mybatis中一共了一些基本数据类型的别名
详情见帮助文档

    <!--Integer getCount(),返回值是数值类型-->
    <select id="getCount" resultType="java.lang.Integer">
        select count(*) from t_user
    </select>

6.4查询一条数据为map集合

    <!--Map<String,Object> getUserByIdToMap(@Param("id") Integer id),map是mybatis给我们提供的Map的别名,查询结果以字段名为键,字段的值为值-->
    <select id="getUserByIdToMap" resultType="map">
        select * from t_user where id=#{id}
    </select>

输出结果实例:
在这里插入图片描述

6.5查询多条数据为map集合

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

注意,此时每一条数据都会变成一个类似于6.4中的map集合,所以我们方法的返回值不能为Map
a)用List实体类进行接收

List<Map<String,Object>> getAllUserToMap();

b)在mapper接口的方法上,使用MapKey注解

    @MapKey("id")//把查询出来的某个字段作为键,把对应的map作为值,这里就是以id作为键,放在同一个map集合中,注意,字段需要唯一
    Map<String,Object> getAllUserToMap();

7.特殊SQL的执行

7.1模糊查询

    <!--List<User> getUserByLike(@Param("username") String username)-->
    <select id="getUserByLike" resultType="User">
        <!--写法一,使用${}-->
        select * from t_user where username like '%${username}%'
        <!--写法二,使用SQL的字符串拼接函数-->
        select * from t_user where username like concat('%',#{username},'%')
        <!--写法三-->
        select * from t_user where username like "%"#{username}"%"
    </select>

7.2批量删除

    <!--int deleteMore(@Param("ids") String ids)-->
    <delete id="deleteMore">
        <!--不能使用#{},应为会自动加单引号,这样的sql语句是不正确的,不为字符串的不能加''-->
        delete from t_user where id in (${ids})
    </delete>

7.3动态设置表名

    <!--List<User> getUserByTableName(@Param("tableName") String tableName)-->
    <select id="getUserByTableName" resultType="User">
        <!--只能用${},因为表名不能加''-->
        select * from ${tableName}
    </select>

7.4添加功能获取自增的主键

t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
1、添加班级信息
2、获取新添加的班级的id
3、为班级分配学生,即将某学的班级id修改为新添加的班级的id

    <!--void insertUser(User user),获取自动递增的主键需要设置两个属性useGeneratedKeys(sql使用了自动递增的主键) keyProperty(将自动递增的主键赋值给参数中的某个属性中)-->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
    </insert>

测试代码

        User user = new User(null,"王五","123",23,"男","1234@qq.com");
        sqlMapper.insertUser(user);
        //此时自动递增的主键已经被放在了id属性中
        System.out.println(user.getId());

8.自定义映射resultMap

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

8.1resultMap处理字段和属性的映射关系

a)通过字段名别名

    <!--List<Emp> getAllEmp()-->
    <select id="getAllEmp" resultType="Emp">
        <!--若不设置别名,字段名与属性不一致幅查询结果会为null-->
        select eid,emp_name empName,age,sex,email from t_emp
    </select>

b)通过全局配置mapUnderscoreToCamelCase
在mybatis中的配置文件加入

    <!--设置mybatis的全局配置-->
    <settings>
        <!--将表中的下划线字段名自动映射为驼峰,emp_name->empName-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

c)resultMap

    <!--设置自定义映射关系-->
    <resultMap id="empResultMap" type="Emp">
        <!--id设置主键的映射关系(唯一标识),type设置映射关系中的实体类类型-->
        <!--property:属性名(必须是type设置的实体类的属性名)column:字段名(必须是sql语句查询出的字段名)-->
        <id property="eid" column="eid"></id>
        <!--result设置普通字段的映射关系-->
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
    </resultMap>
    <!--List<Emp> getAllEmp()-->
    <select id="getAllEmp" resultMap="empResultMap">
        select * from t_emp
    </select>

d)通过级联属性赋值解决多对一的映射关系(第一种方式)
假设有两个类,emp与dept,一个emp对应一个dept,一个dept对应多个emp
emp中:

private Dept dept;

现在假设有一个方法getEmpAndDept,我们希望查询出一个emp对应的dept信息

    <!--处理多对一映射方式一:级联赋值-->
    <resultMap id="empAndDeptResultMapOne" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
        <!--级联赋值给emp中的dept属性赋值-->
        <result property="dept.did" column="did"></result>
        <result property="dept.deptName" column="dept_name"></result>
    </resultMap>
    <!--Emp getEmpAndDept(@Param("eid") Integer eid)-->
    <select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
        select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid=#{eid}
    </select>

d)通过association解决多对一的映射关系(第二种方式)

    <!--处理多对一映射方式二:association-->
    <resultMap id="empAndDeptResultMapTwo" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
        <!--
        association:处理多对一的关系,相当于一个外部bean注入
        property:需要被处理多对一映射关系的属性名
        javaType:该属性类型的实体类
        -->
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="deptName" column="dept_name"></result>
        </association>
    </resultMap>
    <!--Emp getEmpAndDept(@Param("eid") Integer eid)-->
    <select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
        select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid=#{eid}
    </select>

e)通过分步查询解决多对一的映射关系(第三种方式,重点掌握)

    <!--
    assoication中:
    select:设置分步查询的sql的唯一标识
    column:设置分步查询的查询条件
    -->
    <!--先查出eid对应的员工信息,再通过did查询其所对应的部门-->
    <resultMap id="empAndDeptByStepResultMap" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
        <association property="dept"
                     select="com.javalearn.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="did">
        </association>
    </resultMap>
    <!--Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid)-->
    <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
        select * from t_emp where eid = #{eid}
    </select>

第二条sql语句:

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

分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载(只访问员工就只查询员工,不会再执行第二步的查询部门信息),在settings标签下
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个 属性会按需加载
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加载)|eager(立即加载)

延迟加载的效果如下
在这里插入图片描述
f)通过collection解决一对多映射关系
一个部门dept,对应多个员工emp,dept中如下

  private List<Emp> emps;

查询一个部门和其所有员工的信息:

    <!--处理一对多的第一种方式-->
    <resultMap id="DeptAndEmpResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <!--
        collection:处理一对多的映射关系
        ofType:表示该属性所对应集合中存储数据的类型
        -->
        <collection property="emps" ofType="Emp">
            <id property="eid" column="eid"></id>
            <result property="empName" column="emp_name"></result>
            <result property="age" column="age"></result>
            <result property="sex" column="sex"></result>
            <result property="email" column="email"></result>
        </collection>
    </resultMap>
    <!--Dept getDeptAndEmp(@Param("did") Integer did)-->
    <select id="getDeptAndEmp" resultMap="DeptAndEmpResultMap">
        select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did=#{did}
    </select>

f)通过分步查询解决一对多映射关系
也是一样的,我们先查询部门信息,在根据did查询员工信息

第一步查部门

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

第二步查员工

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

9.动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。(主要是拼接条件或关键字)

9.1if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行,1=1是为了SQL语句不出错

    <!--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="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
    </select>

假设我们的方法传入参数为:

List<Emp> list = mapper.getEmpByCondition(new Emp(null,null,23,"男","123@qq.com"));

经过拼接的sql语句为

在这里插入图片描述

9.2where

where和if一般结合使用:
a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
b>若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
注意:where标签不能去掉条件最后多余的and

    <!--List<Emp> getEmpByCondition(Emp emp)-->
    <!--方式二:where标签-->
    <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="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="email != null and email != ''">
                and email = #{email}
            </if>
        </where>
    </select>

9.3trim

trim用于去掉或添加标签中的内容
常用属性:
prefix:在trim标签中的内容的前面添加某些内容
suffix:在trim标签中的内容的后面添加某些内容
prefixOverrides:在trim标签中的内容的前面去掉某些内容
suffixOverrides:在trim标签中的内容的后面去掉某些内容

    <!--List<Emp> getEmpByCondition(Emp emp)-->
    <!--方式三:trim-->
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp
        <!--在前面加上where,去除后面多余的and或者or,所有标签均不成立则trim标签无效-->
        <trim prefix="where" suffixOverrides="and|or">
            <if test="empName != null and empName != ''">
                emp_name = #{empName} and
            </if>
            <if test="age != null and age != ''">
                age = #{age} or
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex} and
            </if>
            <if test="email != null and email != ''">
                email = #{email}
            </if>
        </trim>
    </select>

9.4choose、when、otherwise

choose、when、otherwise相当于if…else if…else

    <!--List<Emp> getEmpByChoose(Emp emp)-->
    <select id="getEmpByChoose" resultType="Emp">
        select * from t_emp
        <!--使用where自动生成where-->
        <where>
            <!--choose是一个副标签-->
            <choose>
                <!--有一个when成立后面的when就不会执行了-->
                <when test="empName != null and empName !=''">
                    emp_name = #{empName}
                </when>
                <when test="age != null and age !=''">
                    age = #{age}
                </when>
                <when test="sex != null and sex !=''">
                    sex = #{sex}
                </when>
                <when test="empName != null and empName !=''">
                    email = #{email}
                </when>
                <!--所有条件都不满足就会执行这个otherwise-->
                <otherwise>
                    did = 1
                </otherwise>
            </choose>
        </where>
    </select>

9.5foreach

通过传入数组参数的方式实现批量删除

    <!--int deleteMoreByArray(@Param("eids") Integer[] eids),通过数组实现批量删除-->
    <delete id="deleteMoreByArray">
        delete from t_emp where eid in
        <!--collection表示我们要访问的这个数组或集合,item是数组或集合里的每一个数据,separator:数据的分隔符 open:以什么开始 close:以什么结束-->
        <foreach collection="eids" item="eid" separator="," open="(" close=")">
            #{eid}
        </foreach>
    </delete>

通过传入集合参数实现批量添加

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

9.6SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

    <!--sql片段-->
    <sql id="empColumns">eid,emp_name,age,sex,email</sql>

引入sql片段

select <include refid="empColumns"></include> from t_emp

10.mybatis的缓存

10.1mybatis的一级缓存(SqlSession级别)

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问(一级缓存是默认开启的),mapper不是同一个也没有关系,只要是通过同一个SqlSession获取的就可以。

一个简单的例子:

    @Test
    public void test9(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        Emp emp = mapper.getEmpByEid(1);
        System.out.println(emp);
        Emp emp2 = mapper.getEmpByEid(1);
        System.out.println(emp2);
    }

输出结果
可以看到只执行了一条sql语句,第二条数据是从缓存中查询的
在这里插入图片描述
下面是一级缓存失效的四种情况

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同(查询了不同的数据)
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作(数据被改变了,缓存会被清空)
  4. 同一个SqlSession两次查询期间手动清空了缓存(sqlSession.clearCache();)

10.2mybatis的二级缓存(SqlSessionFactory级别)

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
a>在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
b>在映射文件中设置标签<cache />
在这里插入图片描述

c>二级缓存必须在SqlSession关闭或提交之后有效(没有关闭或提交时,数据会保存到一级缓存,关闭或提交后会保存到二级缓存)
d>查询的数据所转换的实体类类型必须实现序列化的接口(Serializable)

一个小小的例子:

    @Test
    public void test10(){
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
            SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
            CacheMapper mapper = sqlSession1.getMapper(CacheMapper.class);
            System.out.println(mapper.getEmpByEid(1));
            sqlSession1.close();
            CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
            System.out.println(mapper2.getEmpByEid(1));
            sqlSession2.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

输出结果:
在这里插入图片描述
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
在mapper配置文件中添加的cache标签可以设置一些属性:

10.3二级缓存的相关设置

在映射文件中的cache标签中设置

1.evtion属性:缓存回收策略
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
2.flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新(增删改)
3.size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
4.readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化,对原本的缓存对象不会有影响)。这会慢一些,但是安全,因此默认是false。

10.4mybatis缓存查询的顺序(从大到小)

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

10.5整合第三方缓存EHCache(代替二级缓存)

a>添加依赖

        <!-- 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>

b>各jar包功能
在这里插入图片描述
c>创建EHCache的配置文件ehcache.xml

<?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:\atguigu\ehcache"/> 
    
    <defaultCache 
            maxElementsInMemory="1000" 
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120" 
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache> 
</ehcache>

在这里插入图片描述
d>设置二级缓存的类型

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

e>加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件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>
    <!-- 根据特殊需求指定局部日志级别 ,换成自己的mapper路径-->
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

11.mybatis的逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
Java实体类
Mapper接口
Mapper映射文件

11.1创建逆向工程的步骤

a>添加依赖和插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javalearn.mybatis</groupId>
    <artifactId>Mybatis_MBG</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>18</maven.compiler.source>
        <maven.compiler.target>18</maven.compiler.target>
    </properties>

    <!-- 依赖MyBatis核心包 -->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>
    <!-- 控制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>
                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>
                    <!-- MySQL驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.8</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

b>创建MyBatis的核心配置文件
c>创建逆向工程的配置文件
文件名必须是:generatorConfig.xml

<?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.jdbc.Driver" 
                        connectionURL="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" 
                        userId="root" 
                        password=""> 
        </jdbcConnection> 
        <!-- javaBean的生成策略--> 
        <!--targetPackage:生成的包,targetProject:生成的位置-->
        <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java">
            <!--enableSubPackages:是否生成子包,true则每一个点都是一个子包,trimStrings:去掉字符串前后的空格-->
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" /> 
        </javaModelGenerator> 
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\resources"> 
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.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>

d>执行MBG插件的generate目标
在这里插入图片描述
自动生成的
在这里插入图片描述

11.2mybatis逆向工程奢华尊享版

修改逆向工程配置文件中context标签中的内容为:

    <context id="DB2Tables" targetRuntime="MyBatis3">

此时生成的目录
在这里插入图片描述
注意,映射文件中生成的带有Example的,都是带有条件的sql语句

下面是两个添加方法
在这里插入图片描述
在使用中直接调用就可以了,下面是几个简单的例子,非常的方便

@org.junit.Test
    public void testMBG(){
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
            //查询所有数据,没有条件查询的就是查询所有
            //List<Emp> emps = mapper.selectByExample(null);
            //emps.forEach(emp -> System.out.println(emp));

            //根据条件查询
            EmpExample empExample = new EmpExample();
            //所有的条件都是以and开头的,找到字段对应的属性名即可
/*          empExample.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
            empExample.or().andDidIsNull();
            List<Emp> emps = mapper.selectByExample(empExample);
            System.out.println(emps);*/

            //根据主键修改,这样修改后,sex就会变为null
            //mapper.updateByPrimaryKey(new Emp(1,"admin",22,"男","456",3));

            //选择性修改,这样修改,不会修改为null的字段
            mapper.updateByPrimaryKeySelective(new Emp(1,"admin",22,null,"456",3));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

12.mybatis的分页插件

12.1分页插件的配置

a>添加依赖

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

b>配置分页插件
在MyBatis的核心配置文件中配置插件

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

12.2分页插件的使用

分页功能,在查询语句后的limit index,pageSize
index:当前页的起始索引
pageSize:每页显示的条数
pageNum:当前页的页码
index=(pageNum-1)*pageSize

a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能

pageNum:当前页的页码
pageSize:每页显示的条数

b>在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, intnavigatePages)获取分页相关数据

list:分页之后的数据
navigatePages:导航分页的页码数

c>分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
常用数据:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值