MyBatis框架使用指南(快速上手)

目录

介绍

mybatis核心配置文件: 

映射文件:

mybatis执行原理

mybatis获取参数的两种方式#{}和${}(重点) 

MyBatis获取Mapper方法的参数值的各种情况 

MyBatis针对表的各种查询 

MyBatis处理模糊查询

MyBtis处理批量删除

MyBatis根据动态的表名查询

MyBatis获取自增的主键值

数据库表字段名和实体类属性名的映射

一对一映射

多对一映射

解决方式一:使用自定义结果集映射resultMap解决上述问题(级联属性):

 解决方式二:在resulMap中使用association标签定义多对一映射关系

解决方式三:使用分步查询

​编辑

延迟加载

一对多映射

使用分步查询 

用一条sql语句完成查询

MyBatis实现动态SQL

if标签

where标签

trim标签 

 choose、when、otherwise一套标签

 foreach循环遍历

 foreach实现批量删除

foreach实现批量添加

SQL标签

MyBatis缓存

MyBatis的一级缓存

MyBatis的二级缓存

MyBatis缓存查询的顺序

整合第三方缓存EHCache(了解)

MyBatis的逆向工程 

创建逆向工程的步骤

奢华尊享版的逆向工程功能测试 

 MyBatis使用分页插件

分页插件使用步骤


介绍

最近学习了MyBaits框架,来记录一下我的学习体会。

首先MyBatis是持久层的一个框架,他封装了JDBC,几乎避免了手写JDBC的绝大部分代码,可以避免那些重复而繁多的JDBC代码。

MyBatis框架=mybatis核心jar包+配置文件,两个结合一起工作。

配置文件分为 核心配置文件 和 映射文件。

        核心配置文件(mybatis-config.xml):配置连接数据库的信息以及MyBatis的全局配置信息

        映射文件(XXXMaper.xml):主要写sql语句,一个操作数据库表的Mapper接口,就对应一个映射文件。

mybatis核心配置文件: 

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

    <!--Mybatis的核心配置文件中的标签的顺序
    (properties?,settings?,typeAliases?,typeHandlers?,
    objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,
    environments?,databaseIdProvider?,mappers?)".-->


    <!--引入properties文件,
    可能会引入多个,所以配置文件中的如username键可能会一样,所以在前面加上jdbc.username用来标识-->
    <properties resource="jdbc.properties"/>

    <!--typeAliases:可以定义类型的别名-->
    <typeAliases>
        <!--类型的别名,这样在其他地方用用到com.lin.mybatis.bean.User就可以用User代替,而不用写那么长的全类名,
        可以理解为为User是一个变量,而com.lin.mybatis.bean.User是变量值.
        alias不写,默认就是以类名且忽略大小写为别名-->
<!--        <typeAlias type="com.lin.mybatis.bean.User" alias="User"/>-->

        <!--用的比较多,package:可以为这个包下的全部类起别名,且就是以类名且忽略大小写为别名-->
        <package name="com.lin.mybatis.bean"/>
    </typeAliases>


    <!--
        environments:设置多个连接数据库的环境
            属性:default指明默认使用的连接数据库的环境
    -->
    <environments default="development">
        <!--
            environment:设置具体某个连接数据库的环境
                id属性:唯一标识连接数据库的环境
        -->
        <environment id="development">

            <!--
             transactionManager:设置事务管理方式
             属性:
                 type:设置事务管理方式,type="JDBC|MANAGED"
                 type="JDBC":设置当前环境的事务管理方式为原生的JDBC,事务的提交或回滚都必须手动处理
                 type="MANAGED":设置事务被管理,例如spring中的AOP
             -->
            <transactionManager type="JDBC"/>

            <!--
            dataSource:设置数据源
            属性:
	            type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
	            type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
	            type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建
	            type="JNDI":调用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <!--设置连接数据库的四要素, ${key值}可以读取引入的properties文件中key对应的value值-->
                <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.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/MyBatis"/>
                <property name="username" value="root"/>
                <property name="password" value="001219"/>
            </dataSource>
        </environment>
    </environments>

    <!--
        引入映射文件
        一个操作表的Mapper就对应一个映射文件,平时开发中会有很多映射文件,这样就得写很多Mapper标签.
        我们可以像上面的以包的形式引入映射文件
    -->
    <mappers>
<!--        <mapper resource="./mappers/UserMapper.xml"/>-->

        <!--这样就可以一次性将com.lin.mybatis.mapper包下的所有映射文件都引入进来了.
            两个要求:
                Mapper接口所在的包要和映射文件所在的包一致(包名一样)
                Mapper接口名要和映射文件名一致,
                    届时可以通过Mapper接口的全类名找到对应的映射文件。
        -->
        <package name="com.lin.mybatis.mapper"/>
    </mappers>
</configuration>

映射文件:

<?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接口的全类名">
    
     <select id="Mapper接口中的方法名">
        select * from t_user
    </select>
</mapper>

mybatis执行原理

置文当我们调用Mapper接口中的方法时,Mybatis会找到核心配置文件,读取映射文件,然后找到当前Mapper接口对应的映射文件,然后根据方法名在映射文件中定位到唯一的sql语句进行执行。

1.我们在Mapper接口中定义好操作数据库表的方法,如:getUserByUserName(String username)

/**
 *
 * 操作数据库中User表的Mapper,相当于之前我们学的Dao接口。
 * 现在不用写实现类,MyBatis底层会使用代理模式创建Mapper接口的实现类对象然后返回,
 * 我们直接使用这个实现类对象调用接口中的方法即可,他会自动去找映射文件中对应的sql语句执行。
 *
 * @author shkstart
 * @create 2022-05-25 16:26
 */
public interface UserMapper {


    /**
     * 根据用户名查找用户信息
     * @return
     */
    User getUserByUserName(String username);

}

2.Mapper对应的映射文件UserMapper.xml

<mapper namespace="com.lin.mybatis.mapper.UserMapper">

    <!--User getUserByUserName(String username);-->
    <select id="getUserByUserName" resultType="User">
        select * from t_user where name = #{username}
    </select>
</mapper>

3.调用Mapper接口中的方法

    @Test
    public void testGetUserByUserName() throws IOException {

        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        UserMapper userMapper1 = new SqlSessionFactoryBuilder().build(is)
        .openSession(true).getMapper(UserMapper.class);

        // "张三"就是sql语句的参数
        User user =  userMapper.getUserByUserName("张三");
        System.out.println(user);
}

mybatis获取参数的两种方式#{}和${}(重点) 

  • ${}的本质就是字符串拼接,#{}的本质就是占位符赋值

  • #{} 对应的变量会自动加上单引号 ,${} 对应的变量不会加上单引号 
    • 字符串类型数据、日期类型数据、字段名等可以加单引号

我们底层的东西就先不深挖了,先学会用。

调用了Mapper接口中的 User getUserByUserName(String username); 方法后:

1、MyBatis会根据这个Mapper的全类名com.lin.mybatis.mapper.UserMapper找到它所对应的映射文件UserMapper.xml,

2、然后根据方法名getUserByUserName在映射文件中找到对应id为getUserByUserName的sql语句:

<select id="getUserByUserName" resultType="User">

        <!-- a> 使用#{}获取参数-->
        select * from t_user where name = #{username}

        <!-- b> 使用${}获取参数-->
        select * from t_user where name = ${username}

</select>

3、接着就是MyBatis会预编译上述找到的sql语句:

        a> "select * from t_user where name = ?"  (体现占位符)

        b> "select * from t_user where name =" + ${username} + "   (体现字符串拼接)

4、接着应该是MyBatis获取参数(这个参数就是调用Mapper中的方法的实参)填充占位符了

        a>select * from t_user where name = '张三'

        b>select * from t_user where name = '张三'

5、最后执行sql语句返回结果集

MyBatis获取Mapper方法的参数值的各种情况 

1.当Mapper方法中的参数为一个时,此时可以使用 ${}和#{} 以任意的名称(最好见名识意)获取参数的值,注意${}需要手动加单引号
可以用@param代替

 2.当Mapper接口中的方法的参数为多个时,调用方法时,MyBatis会将参数值放到一个Map集合中,可以通过 ${键}和#{键} 的方式来获取参数值。
  键有两种形式:
      a:arg0,arg1,.....
      b:param1,param2,...
  值(就是用户传过来的参数值):
     按照键的顺序一一对应: param1对应参数值1,param2对应参数值2,......

3.当Mapper接口方法的参数有多个时,可以将方法的参数设置为一个Map集合,调用方法时,这样就和第2种情况一样
可以通过 ${键}和#{键} 的方式来访问值。
 select * from t_user where name = #{username} and password = #{password}

4.当Mapper接口方法的参数是一个实体类类型时,调用此方法时,
 映射文件中的sql语句可以通过可以通过 ${属性值}和#{属性名} 的方式来访问属性值。

5.使用@Param注解来命名注解
 MyBatis会将@Param注解的值当成Map集合的key,参数值当成Map集合的value
 也会把param1,param2,....当成键
 两种.可以通过 ${键}和#{键} 的方式来获取参数值。

总结:可以只分为下面两种情况来处理
        实体类型参数和
        @param情况.

代码理解:

    @Test
    public void testGetUserByNameAndPwd(){
        UserMapper userMapper = SqlSessionUtil.getSqlSession().getMapper(UserMapper.class);

        User user =  userMapper.getUserByNameAndPwd("root","ni");
        System.out.println(user);
    }

    @Test
    public void testInsertUser(){
        UserMapper userMapper = SqlSessionUtil.getSqlSession().getMapper(UserMapper.class);

        User user = new User(23, "梨花", "lll", "女", 12);

        int count = userMapper.insertUser(user);
        System.out.println(count);
    }
public interface UserMapper {

    /**
     * 根据用户名和密码查询用户信息
        key        value
        uName      root
        password    ni
        
     * @return
     */
    User getUserByNameAndPwd(@Param("uName") String username,@Param("password") String pwd);

     /**
     * 增加用户

        key        value
        id          23
        name        梨花
        password    111
        sex         女
        age         12

     * @param user
     * @return
     */
    int insertUser(User user);

}
<mapper namespace="com.lin.mybatis.mapper.UserMapper">
    <!--User getUserByNameAndPwd(String username,String pwd);-->
    <select id="getUserByNameAndPwd" resultType="User">
        select * from t_user where name = #{uName} and password = #{password}
    </select>

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

MyBatis针对表的各种查询 

* 针对表的各种查询
* 1.若查询出来的结果集有多条记录:
*      a: 可以通过List集合来接收(List集合中每个元素是一个实体类类型)
*      b: 可以通过List集合来接收(List集合中每个元素是一个Map类型,Map中是每条记录的键值对)
*      [{password=ni, sex=男, name=root, id=2, age=12}, {password=root, sex=男, name=dfd, id=3, age=12}]
*      c: 使用@MapKey注解, @MapKey("id")以查询的记录中的某个唯一的字段为键,每条记录构成的Map为值。
*      {2={password=ni, sex=男, name=root, id=2, age=12}, 3={password=root, sex=男, name=dfd, id=3, age=12}}
*
* 注意:不能用实体类对象来接收,会报TooManyResultsException异常(多对一)
*
* 2.若查询出来的结果集只有一条记录
*      a: 可以通过实体类对象来接收
*      b: 也可以通过List集合来接收
*      c: 也可以通过Map集合来接收(字段名为键,字段值为值)

代码理解:

public interface SelectMapper {


    /**
     * 查询所有的用户信息,并将所有的用户封装进一个集合中(多条记录)
     * a: 可以通过List集合来接收(List集合中每个元素是一个实体类类型)
     * @return
     */
//    User getAllUser();错的,报TooManyResultsException异常
    List<User> getAllUser();


     /**
     * 查询所有的用户信息,(多条记录)
     * b:可以通过List集合来接收(List集合中每个元素是一个Map类型,每个Map中是每条记录的键值对)
     * @return
     */
    List<Map<String,Object>> getAllUserToListMap();

     /**
     * 查询所有的用户信息,结果转换为一个Map(多条记录)
     *  c: 使用@MapKey注解, 
        @MapKey("id")以查询的记录中的某个唯一的字段为Map的键,每条记录构成Map的值。
     * @return
     */
    @MapKey("id")
    Map<String,Object> getAllUserToMap();


    /**
     * 查询结果只有一条
     * 通过用户名查询用户信息,
     * a: 可以通过实体类对象来接收
     *
     * @return
     */
    User getUserByName(@Param("username") String username);

    /**
     * 查询结果只有一条
     * 通过Id查询用户信息,并将用户信息封装进一个List集合中返回
     * b: 也可以通过List集合来接收(List中只有一个实体类对象)
     *
     * @param id
     * @return
     */
    List<User> getUserByIdToList(@Param("id")Integer id);


    /**
     * 查询结果只有一条
     * 通过Id查询用户信息,并将用户信息封装进一个map集合中返回
     * 
     * c: 也可以通过Map集合来接收(查询结果集的字段名为键,字段值为值)
     * @return
     */
    Map<String,Object> getUserByIdToMap(@Param("id")Integer id);

    /**
     * 查询用户数量
     * 查询结果集只有单行单列的情况
     * 
     * @return
     */
    int getNumOfUser();

}
<mapper namespace="com.lin.mybatis.mapper.SelectMapper">

    <!--
        List<User> getAllUser();
        resultType填最小封装对象,这里用List集合装用户,List中又是由User对象存
    -->
    <select id="getAllUser" resultType="User">
        select * from t_user
    </select>

    <!--List<Map<String,Object>> getAllUserToListMap();
        List中有多个Map来存储查询结果的记录
    -->
    <select id="getAllUserToListMap" resultType="Map">
        select * from t_user
    </select>

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

     <!--User getUserByName(String username);-->
    <select id="getUserByName" resultType="User">
        select * from t_user where name = #{username}
    </select>

    <!--List<User> getUserByIdToList(@Param("id")Integer id);-->
    <!--查询结果是一条用一个List接收,resultType填最小封装单位是User,
    因为List中最小单位是User-->
<!--  错误的:  <select id="getUserByIdToList" resultType="java.util.List">-->
    <select id="getUserByIdToList" resultType="User">
        select * from t_user where id = #{id}
    </select>

        <!--Map<String,Object> getUserByIdToMap(@Param("id")Integer id);-->
    <!--查询结果是一条用一个Map接收,这个最小封装单位是Map,
    因为一条记录是以键值对封装到map中-->
    <select id="getUserByIdToMap" resultType="map">
        select * from t_user where id = #{id}
    </select>

        <!--int getNumOfUser();-->
    <!--MyBatis中有一些默认的类型别名:java.lang.Integer(Integer,Int)
                                    int(_int,_integer)
                                    Map(map)\String(string)
    -->
    <select id="getNumOfUser" resultType="Integer">
        <!-- 以下这个sql语句查询的结果是一个单行单列的结果,是一个数字,是Intege类型 -->
        select count(*) from t_user
    </select>


</mapper>

MyBatis处理模糊查询

public interface LikeQueryMapper {

    /**
     * 使用模糊的用户名查询用户信息
     *
     * @param likeName
     * @return
     */
    List<User> getUserByLikeName(@Param("likeName")String likeName);
}
<mapper namespace="com.lin.mybatis.mapper.LikeQueryMapper">

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

</mapper>

其中select * from t_user where name like "%"#{likeName}"%" 最常用

MyBtis处理批量删除

    /**
     * 根据用户的ids批量删除多个用户
     *
     * @param ids
     * @return
     */
    int deleteUserByIds(@Param("ids")String ids);
    <!--int deleteUserByIds(@Param("ids")String ids);-->
    <delete id="deleteUserByIds">
        delete from t_user where id in(#{ids})
    </delete>
    @Test
    public void deleteUserByIdsTest(){
        SQLMapper mapper = SqlSessionUtil.getSqlSession().getMapper(SQLMapper.class);
        int count = mapper.deleteUserByIds("5,6,7");
        System.out.println(count);
    }

 运行截图:

 

从以上现象可以看出,实现批量删除不能使用#{}的方式获取参数值,因为它会自动将参数值加上单引号,即会将delete from t_user where id in(#{ids}) 解析成delete from t_user where id in('5,6,7');  只有id为5,6,7的数据会被删除

而批量删除正确的sql语句应该是:delete from t_user where id in(5,6,7);

所以批量删除只能用${}的方式获取参数值,它不会自动加单引号。

    <!--int deleteUserByIds(@Param("ids")String ids);-->
    <delete id="deleteUserByIds">
        <!--错误:delete from t_user where id in(#{ids})-->
        delete from t_user where id in(${ids})
    </delete>

执行结果:

MyBatis根据动态的表名查询

    /**
     * 自定义要查询的表,根据传来的表名进行查询
     *
     * @param tableName
     * @return
     */
    List<User> getUserByTable(String tableName);
    <!--List<User> getUserByTable(String tableName);-->
    <select id="getUserByTable" resultType="User">
        select * from #{tableName}
    </select>
    @Test
    public void getUserByTableTest(){
        SQLMapper mapper = SqlSessionUtil.getSqlSession().getMapper(SQLMapper.class);

        List<User> users = mapper.getUserByTable("t_user");
        System.out.println(users);
    }

运行结果:

可以看出来使用#{}的方式出错了,因为他会给要查询的表名加上单引号,

 select * from 't_user';

而我们的表名是不能加单引号,所以这个也只能用${}的方式获取参数值。

 <!--List<User> getUserByTable(String tableName);-->
    <select id="getUserByTable" resultType="User">
        <!--select * from #{tableName}-->
        select * from ${tableName}
    </select>

 运行结果:

MyBatis获取自增的主键值

这个是干嘛的? 

业务场景:

有一个班级信息表t_class(class_id,class_name);

有一个学生信息表t_student(student_id,stu_name,class_id);

1、要添加一个班级信息

2、获取新添加班级的主键值(MyBatis获取自增的主键值)

3、然后添加一个学生信息,同时指定学生所在的班级为刚刚添加的班级。(所以就需要获取刚刚添加班级的主键class_id)

使用:

    /**
     * 插入用户
     *
     * @param user
     * @return
     */
    int insertUser(User user);
    <!--
        int insertUser(User user);

        useGeneratedKeys属性:表明此sql语句是否使用了自动递增的主键
        keyProperty属性:指定此sql语句执行完后产生的自动递增的主键值
        将赋给传到此sql语句中参数的某个属性值
            keyProperty="id",这里会赋值给传过来的参数User对象
    -->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values(null,#{username},#{password},#{sex},#{age})
    </insert>
    @Test
    public void insertUserTest(){
        SQLMapper mapper = SqlSessionUtil.getSqlSession().getMapper(SQLMapper.class);

        User user = new User(null, "测试名", "234", "男", 23);

        // 插入用户信息,id为空,但是表里面id字段是自增的,会将自增的id赋给user
        int count = mapper.insertUser(user);
        System.out.println(user);
    }

运行结果:

 

数据库表字段名和实体类属性名的映射

一对一映射

表:

 对应实体类:

 以下的查询sql,执行完毕后,会根据结果集中的字段名调对应的名字的属性名然后调该属性的set方法,将字段对应的值设置给属性。

<mapper namespace="com.lin.mybatis.mapper.EmpMapper">

    <!--List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultType="Emp">
        select * from t_emp
    </select>
</mapper>

 问题引出,但是由于属性和字段的命名规则不一样,所以可能会出现以下情况:

问题解决,倘若字段名和属性名不一致,可以通过以下两种方式处理字段和属性之间的映射关系:

1、给查询的字段名其别名,这个别名要和属性名一样

<mapper namespace="com.lin.mybatis.mapper.EmpMapper">

    <!--List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultType="Emp">
        select eid,emp_name empName,sex,age,email,did from t_emp
    </select>
</mapper>

 2、在MyBatis核心配置文件中定义全局设置,

    <settings>
        <!--
            mapUnderscoreToCamelCase:将下划线_自动映射为驼峰,默认值为false
      
            如:emp_name==empName
        -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
<mapper namespace="com.lin.mybatis.mapper.EmpMapper">

    <!--List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultType="Emp">
        <!--select eid,emp_name empName,sex,age,email,did from t_emp-->
        select * from t_emp
    </select>
</mapper>

 3、使用resultMap自定义字段名和属性名的映射关系

<mapper namespace="com.lin.mybatis.mapper.EmpMapper">

    <!--
        resultMap:自定义结果集映射
            id:映射id作为唯一标识
            type:查询出来的结果集要映射的实体类类型
            
            id:设置主键的映射关系
            result:设置普通字段的映射关系

                property:要映射的实体类中的属性名
                column:要映射的结果集中的字段名,填查询出来的字段名


            自我理解:
            1.设置自定义映射后,MyBatis查询出结果集,会根据自定义映射关系,
            找到 emp_name 对应的属性名empName将字段值赋给对应属性。
            
            2.这种解决字段名和属性名不一致的方式一般比较少用的。
            
            3.一般用来处理一对多和多对一。
    -->
    <resultMap id="empResultMap" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
    </resultMap>

    <!--List<Emp> getAllEmp();-->
    <select id="getAllEmp" resultMap="empResultMap">
        select * from t_emp
    </select>

多对一映射

多个员工对应一个部门

业务背景:

想要查询员工的信息,

// 员工表t_emp对应的实体类
public class Emp {
    private Integer eid;
    private String empName;
    private String sex;
    private Integer age;
    private String email;
    private Dept dept;// 员工所在的部门
}
    /**
     * 根据员工编号,查询所有的员工信息与其所在的部门信息
     *
     * @return
     */
    Emp getEmpAndDeptById(@Param("eid")Integer eid);
    <!--Emp getEmpAndDeptById(@Param("eid")Integer eid);-->
    <!--此时不能再用resultType了,因为查询出来的结果集的字段和实体类型的属性映射失败-->
    <select id="getEmpAndDeptById" resultType="Emp">
        select * from t_emp e left join t_dept d on e.did = d.did where eid = #{eid}
    </select>

查询结果集:

 运行结果:

结果集的字段和实体类的属性映射失败,导致某些属性没有赋值。

解决方式一:使用自定义结果集映射resultMap解决上述问题(级联属性):

    <!--应该使用自定义结果集映射resultMap-->
<!--    <select id="getEmpAndDeptById" resultType="Emp">-->
    <select id="getEmpAndDeptById" resultMap="empAndDeptResultMap">
        select * from t_emp e left join t_dept d on e.did = d.did where eid = #{eid}
    </select>

    <resultMap id="empAndDeptResultMap" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="sex" column="sex"></result>
        <result property="age" column="age"></result>
        <result property="email" column="email"></result>
        <result property="dept.did" column="did"></result>
        <result property="dept.deptName" column="dept_name"></result>
    </resultMap>

 MyBatis会根据自定义结果集映射将查询出来的结果集按照此套映射,将字段值设置给对应属性,比如:emp_name查询出来的值为 周杰伦,对应Emp的empName属性设置为 周杰伦;dept_name为B,对应Emp的dept的deptName设置为B。

 解决方式二:在resulMap中使用association标签定义多对一映射关系

<!--
        association:用于处理多对一的映射关系。如:多个字段映射一个dept属性
        property:多对一的那个一,就是属性名
        javaType:该属性的类型,可以使用别名
            id:该类型的主键
            result:该类型的其他属性
            
        个人理解:MyBatis会根据javaType通过反射创建对象,然后通过反射给对应属性赋值,
        最后将对象赋值给dept属性。
    -->
    <resultMap id="empAndDeptResultMap2" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="sex" column="sex"></result>
        <result property="age" column="age"></result>
        <result property="email" column="email"></result>
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="deptName" column="dept_name"></result>
        </association>
    </resultMap>

        <select id="getEmpAndDeptById" resultMap="empAndDeptResultMap2">
        select * from t_emp e left join t_dept d on e.did = d.did where eid = #{eid}
    </select>

    @Test
    public void getEmpAndDeptByIdTest(){
        EmpMapper mapper = SqlSessionUtil.getSqlSession().getMapper(EmpMapper.class);

        Emp emp = mapper.getEmpAndDeptById(2);
        System.out.println(emp);
    }

调用Mapper接口中的方法,会执行getEmpAndDeptById这个sql语句,然后根据empAndDeptResultMap2映射关系,处理结果集和实体类型Emp之间的对应关系。

解决方式三:使用分步查询

某个字段使用其他的查询结果赋值。

    /**
     * 根据员工id查询员工信息
     *
     * @param eid
     * @return
     */
    Emp getEmpById(Integer eid);
    <!--Emp getEmpById(Integer eid);-->
    <select id="getEmpById" resultMap="empResultMap2">
        select * from t_emp where eid = #{eid}
    </select>
    <resultMap id="empResultMap2" type="Emp">
        <result property="empName" column="emp_name"></result>
        <!--
            property="dept":要映射的对象是dept属性
            select:查询语句,值就写sql语句的唯一表示(mapper.xml中的命名空间.sql语句id or Mapper接口全类名.方法名)
                指明dept的属性值就是这个查询语句的结果
            column:字段名,其对应的值作为select查询语句的条件。
        -->
        <association property="dept"
                     select="com.lin.mybatis.mapper.DeptMapper.getDeptById"
                     column="did">
        </association>
    </resultMap>

查询部门:

<mapper namespace="com.lin.mybatis.mapper.DeptMapper">

    <!--Dept getDeptById(Integer did);-->
    <select id="getDeptById" resultMap="deptResultMap">
        <!--此查询语句的条件就是需要一个部门编号id-->
        select * from t_dept where did = #{did}
    </select>

    <resultMap id="deptResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
    </resultMap>
</mapper>

延迟加载

  • 延迟加载:只根据所需的数据执行对应的sql语句,不会执行不相关的sql语句。倘若没有开启延迟加载的话,某些sql语句之间产生关联了,然后所需的数据只需要执行其中的一条sql语句即可,但是由于没有开启延迟加载,就得把这些关联的sql语句全部都执行,然后获取所需的数据。

  • 分步查询的优点:可以实现延迟加载,但是必须在MyBatis核心配置文件中设置全局配置信息:

  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。(默认为false)

    • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载。(默认为false)

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

 以下就是分步查询的配置,查询员工信息dept,员工所对应的部门信息需要由另一条查询语句来查询部门信息。

    <!--Emp getEmpById(Integer eid);-->
    <select id="getEmpById" resultMap="empResultMap2">
        select * from t_emp where eid = #{eid}
    </select>
    <resultMap id="empResultMap2" type="Emp">
        <result property="empName" column="emp_name"></result>
        <!--
            分步查询:
                dept属性的值由另一个查询语句的结果赋值。

            property="dept":要映射的对象是dept属性
            select:查询语句,值就写sql语句的唯一表示(mapper.xml中的命名空间.sql语句id or Mapper接口全类名.方法名)
                指明dept的属性值就是这个查询语句的结果
            column:字段名,其对应的值作为select查询语句的条件。
        -->
        <association property="dept"
                     select="com.lin.mybatis.mapper.DeptMapper.getDeptById"
                     column="did">
        </association>
    </resultMap>

要员工的全部信息(包括部门信息):

    @Test
    public void getEmpByIdTest(){
        EmpMapper mapper = SqlSessionUtil.getSqlSession().getMapper(EmpMapper.class);

        Emp emp = mapper.getEmpById(4);
        System.out.println(emp);
    }

查询员工的全部信息,所以需要查询员工对应的部门信息,所以将关联的查询部门信息的sql语句一起执行了。 

只要员工的姓名信息: 

    @Test
    public void getEmpByIdTest(){
        EmpMapper mapper = SqlSessionUtil.getSqlSession().getMapper(EmpMapper.class);

        Emp emp = mapper.getEmpById(4);
        System.out.println(emp.getEmpName());
    }

从运行结果可以看出,只执行了查询员工信息的sql语句select * from t_emp where eid = ?

因为我们只需要员工的名字,所以不需要查询部门信息,因此延迟加载了互相关联的查询部门信息的sql语句。

只要员工的所在的部门信息

    @Test
    public void getEmpByIdTest(){
        EmpMapper mapper = SqlSessionUtil.getSqlSession().getMapper(EmpMapper.class);

        Emp emp = mapper.getEmpById(4);
        System.out.println(emp.getEmpName());
        System.out.println("-----------------");
        System.out.println(emp.getDept());
    }

fetchType:指定当前的查询是否使用延迟加载。(eager表示使用,lazy表示使用)
    <!--Emp getEmpById(Integer eid);-->
    <select id="getEmpById" resultMap="empResultMap2">
        select * from t_emp where eid = #{eid}
    </select>
    <resultMap id="empResultMap2" type="Emp">
        <result property="empName" column="emp_name"></result>
        <!--
            分步查询:
                dept属性的值由另一个查询语句的结果赋值。

            property="dept":要映射的对象是dept属性
            select:查询语句,值就写sql语句的唯一表示(mapper.xml中的命名空间.sql语句id or Mapper接口全类名.方法名)
                指明dept的属性值就是这个查询语句的结果
            column:字段名,其对应的值作为select查询语句的条件。
            fetchType:指定当前的查询是否使用延迟加载。(eager表示使用,lazy表示使用)
        -->
        <association property="dept"
                     select="com.lin.mybatis.mapper.DeptMapper.getDeptById"
                     column="did"
                     fetchType="eager">
        </association>
    </resultMap>
    @Test
    public void getEmpByIdTest(){
        EmpMapper mapper = SqlSessionUtil.getSqlSession().getMapper(EmpMapper.class);

        Emp emp = mapper.getEmpById(4);
        System.out.println(emp.getEmpName());
/*        System.out.println("-----------------");
        System.out.println(emp.getDept());*/
    }

可以看出没有延迟加载。 

一对多映射

一个部门有多个员工。

public interface DeptMapper {

    /**
     * 根据部门id查询部门信息
     *
     * @param did
     * @return
     */
    Dept getDeptById(Integer did);


}

使用分步查询 

    <!--Dept getDeptById(Integer did);-->
    <select id="getDeptById" resultMap="deptResultMap">
        select * from t_dept where did = #{did}
    </select>

    <resultMap id="deptResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <!--emps属性,需要使用多对一映射,需要使用另一条分步查询语句来赋值。
            column是sql语句的条件所需的值-->
        <association property="emps"
                     select="com.lin.mybatis.mapper.EmpMapper.getEmpsByDid"
                     column="did">
        </association>
    </resultMap>

emps属性需要由以下的sql语句来查询并赋值: 

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

用一条sql语句完成查询

<!--Dept getDeptById2(Integer did);-->
    <select id="getDeptById2" resultMap="deptResultMap2">
        select * from t_dept d left join t_emp e 
        on d.did = e.did where d.did = #{did}
    </select>

    <!--
        collection:用来处理属性是集合的映射关系,处理一对多的映射关系(一个Emp对应几个字段)
            ofType:指明集合中元素的类型,好使用反射调用属性设置,
    -->
    <resultMap id="deptResultMap2" type="Dept">
        <result property="deptName" column="dept_name"></result>

        <collection property="emps" ofType="Emp">
            <id property="eid" column="eid"></id>
            <result property="empName" column="emp_name"></result>
            <result property="sex" column="sex"></result>
            <result property="age" column="age"></result>
            <result property="email" column="email"></result>
        </collection>
    </resultMap>

MyBatis实现动态SQL

if标签

在我们的平时业务中,常常要用到多条件查询。

比如查询一个员工的信息,要根据用户填写的信息来查询,要根据员工的姓名、性别、年龄、邮箱等信息来查询员工信息。

public interface DynamicSqlMapper {

    /**
     * 根据员工的姓名、性别、年龄、邮箱等信息来查询员工信息。
     *
     * 将员工的姓名、性别、年龄、邮箱等信息封装成一个员工对象作为参数
     *
     * @param emp
     * @return
     */
    List<Emp> getEmpByConditions(Emp emp);
}
<mapper namespace="com.lin.mybatis.mapper.DynamicSqlMapper">

    <!--List<Emp> getEmpByConditions(Emp emp);-->
    <!--
        if标签:是mybatis中提供的,其中test属性可以填传递过来的数据
        只有test属性为真,
        if标签体中的内容才会被拼接到上面的sql语句(select * from t_emp where)中
    -->
    <select id="getEmpByConditions" resultType="Emp">
        select * from t_emp where
        <if test="empName != null and empName != ''">
             emp_name = #{empName}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
    </select>
</mapper>
    @Test
    public void getEmpByConditionsTest(){
        DynamicSqlMapper mapper = SqlSessionUtil.getSqlSession().getMapper(DynamicSqlMapper.class);

        // 用户可能在前端输入了要查询的员工信息
        
        // 然后后端,获取请求参数,获取要查询的员工信息
        String empName = "易烊千玺";
        int age = 21;
        String sex = "男";
        Emp emp = new Emp(null, empName, sex, age, null);

        List<Emp> empList = mapper.getEmpByConditions(emp);
        System.out.println(empList);
    }

 也有可能只输入了性别和年龄:

    @Test
    public void getEmpByConditionsTest(){
        DynamicSqlMapper mapper = SqlSessionUtil.getSqlSession().getMapper(DynamicSqlMapper.class);

        // 用户可能在前端输入了要查询的员工信息

        // 然后后端,获取请求参数,获取要查询的员工信息


        // 也有可能只输入了年龄和性别
        Emp emp2 = new Emp(null, null, "男", 21, null);

        List<Emp> empList = mapper.getEmpByConditions(emp2);
        System.out.println(empList);
    }

修改映射文件:

<mapper namespace="com.lin.mybatis.mapper.DynamicSqlMapper">

    <!--List<Emp> getEmpByConditions(Emp emp);-->
    <!--
        if标签:是mybatis中提供的,其中test属性可以填传递过来的数据
        只有test属性为真,if标签体中的内容才会被拼接到上面的sql语句(select * from t_emp where)中

        1=1是恒成立条件,不影响查询结果,又可以防止empName为null,可以和sql语句一起拼接
    -->
    <select id="getEmpByConditions" resultType="Emp">
        select * from t_emp where 1=1
        <if test="empName != null and empName != ''">
             emp_name = #{empName}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
    </select>
</mapper>

where标签

上面的if标签,我们可以看到where关键字是写死的,那么如果下面的if标签都不成立,那么where关键字就很多余。

基于以上问题引出where标签:

1、where标签一般和if标签一起使用

2、如果where标签中有内容(就是if标签成立)则会自动生成WHERE关键字和上面的sql语句拼接,而且会将最前面的and或or关键字去除掉。

3、如果where标签中没有内容(if标签都不成立),则不会生成WHERE关键字,体现动态性。

<select id="getEmpByConditions" resultType="Emp">
        select * from t_emp
        <where>
            <if test="empName != null and empName != ''">
                emp_name = #{empName}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="email != null and email != ''">
                and email = #{email}
            </if>
        </where>
    </select>

trim标签 

  • trim用于去掉或添加标签中的内容

  • 常用属性

    • prefix:在trim标签中的内容的前面添加某些内容

    • suffix:在trim标签中的内容的后面添加某些内容
    • prefixOverrides:在trim标签中的内容的前面去掉某些内容
    • suffixOverrides:在trim标签中的内容的后面去掉某些内容
  • 若trim中的标签都不满足条件,则trim标签没有任何效果,也就是只剩下select * from t_emp

<select id="getEmpByConditions" resultType="Emp">
        select * from t_emp
        <trim prefix="where" suffixOverrides="and">
            <if test="empName != null and empName != ''">
                emp_name = #{empName} and
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex} and
            </if>
            <if test="age != null and age != ''">
                 age = #{age} and
            </if>
            <if test="email != null and email != ''">
                 email = #{email}
            </if>
        </trim>
    </select>

 choose、when、otherwise一套标签

  • choose、when、otherwise相当于if...else if..else或者switch case default

  • 1.when至少要有一个,otherwise至多只有一个  

  • 2.when只会执行一个,如果有多个when都成立,那么谁在前面执行谁的

  • 3.when都不成立的话,则执行otherwise中的

    @Test
    public void getEmpByConditionsTest(){
        DynamicSqlMapper mapper = SqlSessionUtil.getSqlSession().getMapper(DynamicSqlMapper.class);

        // 用户可能在前端输入了要查询的员工信息

        // 然后后端,获取请求参数,获取要查询的员工信息

        // 也有可能只输入了年龄和性别
        Emp emp2 = new Emp(null, null, "男", 21, null);

        List<Emp> empList = mapper.getEmpByConditions(emp2);
        System.out.println(empList);
    }
<!--
        choose、when、otherwise相当于if...else if..else或者switch case default

        1.when至少要有一个,otherwise至多只有一个
        2.when只会执行一个,如果有多个when都成立,那么谁在前面执行谁的
    -->
    <select id="getEmpByConditions" resultType="Emp">
        select * from t_emp
        <where>
            <choose>
                <when test="empName != null and empName != ''">
                    emp_name = #{empName}
                </when>
                <when test="sex != null and sex != ''">
                    sex = #{sex}
                </when>
                <when test="age != null and age != ''">
                    age = #{age}
                </when>
                <when test="email != null and email != ''">
                    email = #{email}
                </when>
                <otherwise>
                    did = 1
                </otherwise>
            </choose>
        </where>
    </select>

 foreach循环遍历

 foreach循环遍历输出,主要帮我们实现一些批量操作。

  • collection:要遍历的集合或数组
  • item:给集合或数组中的每一项元素起个变量名,代表集合或数组中的每一项元素
  • separator:指明遍历输出的每一项数据之间的分隔符
  • open:指明以什么开始遍历输出,设置foreach标签中的内容的开始符
  • close:指明以什么结束遍历输出,设置foreach标签中的内容的结束符

 foreach实现批量删除

    /**
     * 根据员工编号id构成的数组来删除员工
     *
     * @Param("eids") 到时候,mybatis会以@param注释的值为键,然后参数数组为值.
     *
     * @param eids
     * @return
     */
    int deleteEmpByEids(@Param("eids") Integer[] eids);
    <!--int deleteEmpByEids(Integer[] eids);-->
    <delete id="deleteEmpByEids">
        delete from t_emp where eid in
        (
            <foreach collection="eids" item="eid" separator=",">
                #{eid}
            </foreach>
        )
    </delete>
    <!--int deleteEmpByEids(Integer[] eids);-->
    <delete id="deleteEmpByEids">
        delete from t_emp where eid in
            <foreach collection="eids" item="eid" separator="," open="(" close=")">
                #{eid}
            </foreach>
    </delete>
    @Test
    public void deleteEmpByEidsTest(){
        DynamicSqlMapper mapper = SqlSessionUtil.getSqlSession().getMapper(DynamicSqlMapper.class);

        int count = mapper.deleteEmpByEids(new Integer[]{6, 7, 8});
        System.out.println(count);
    }
<foreach collection="eids" item="eid" separator="," open="(" close=")">
    #{eid}
</foreach>

这一整个foreach标签,最后遍历输出的就是:(6,7,8)

 

foreach实现批量添加

    /**
     * 批量添加员工
     *@Param("empList") 到时候, mybatis会会将empList集合添加到Map集合中,
     *      以@param注释的值为键,然后参数数组为值.
     *
     * @param empList
     * @return
     */
    int addEmpsByList(@Param("empList") List<Emp> empList);
    <!--int addEmpsByList(List<Emp> empList);-->
<!--insert into t_emp values(null,ename,sex,age,email,null),(null,ename,sex,age,email,null);-->
    <insert id="addEmpsByList">
        insert into t_emp values
        <foreach collection="empList" item="emp" separator=",">
            (null,#{emp.empName},#{emp.sex},#{emp.age},#{emp.email},null)
        </foreach>
    </insert>
    @Test
    public void addEmpsByListTest(){
        DynamicSqlMapper mapper = SqlSessionUtil.getSqlSession().getMapper(DynamicSqlMapper.class);

        // 要添加的员工集合
        List<Emp> empList = new ArrayList<>();
        empList.add(new Emp(null,"a","男",12,"234@qq.com"));
        empList.add(new Emp(null,"b","男",23,"234@qq.com"));
        empList.add(new Emp(null,"c","男",25,"234@qq.com"));

        int count = mapper.addEmpsByList(empList);
        System.out.println(count);
    }

SQL标签

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

    • sql片段:<sql id="empColumns">emp_name,sex,age,email</sql>
      引用:<include refid="empColumns"></include>
<sql id="empColumns">emp_name,sex,age,email</sql>
    <select id="getEmpByConditions" resultType="Emp">
        <!--select emp_name,sex,age,email from t_emp-->
        select <include refid="empColumns"></include> from t_emp

MyBatis缓存

  • 缓存只针对我们的查询功能
  • MyBatis会将查询的数据缓存起来,如果下次查询的数据有在缓存中,则直接从缓存中获取,没有的话才去数据库查询,减少了JDBC的操作。

MyBatis的一级缓存

  • 一级缓存是默认开启的。

  • 一级缓存是SqlSession级别的,通过一个SqlSession查询的数据会被缓存,下次在同一个SqlSession中查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

  • 一级缓存的范围就是要保证在同一个SqlSession中才行。

  •  使一级缓存失效的四种情况:

    • 1. 不同的SqlSession对应不同的一级缓存  
      2. 同一个SqlSession但是查询条件不同
      3. 同一个SqlSession两次查询期间执行了任何一次增删改操作,不管有没有影响到查询的数据,任何一次增删改操作MyBatis都会去清空一级缓存。
      4. 同一个SqlSession两次查询期间手动清空了缓存,代码:SqlSession.clearCache();

public interface CacheMapper {

    /**
     *
     * @param eid
     * @return
     */
    Emp getEmpByEid(Integer eid);
}
<mapper namespace="com.lin.mybatis.mapper.CacheMapper">

    <!--Emp getEmpByEid(Integer eid);-->
    <select id="getEmpByEid" resultType="Emp">
        select * from t_emp where eid = #{eid}
    </select>
</mapper>

同一个sqlSession: 

    @Test
    public void testCache1(){
        // 一个sqlSession
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();

        CacheMapper mapper1 = sqlSession.getMapper(CacheMapper.class);
        Emp emp1 = mapper1.getEmpByEid(1);
        System.out.println(emp1);

        CacheMapper mapper2 = sqlSession.getMapper(CacheMapper.class);
        Emp emp2 = mapper2.getEmpByEid(1);
        System.out.println(emp2);
    }

不同一个sqlSession:  

    @Test
    public void testCache2(){
        // 两个sqlSession
        SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
        SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();

        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        Emp emp1 = mapper1.getEmpByEid(1);
        System.out.println(emp1);

        CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
        Emp emp2 = mapper2.getEmpByEid(1);
        System.out.println(emp2);
    }

MyBatis的二级缓存

  • 二级缓存是SqlSessionFactory(可以理解为一个数据库连接池)级别,通过一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;后面再通过同一个SqlSessionFactory创建的SqlSession进行查询,会从二级缓存中找,如果有相同的查询,则直接从缓存中取,没有则访问数据库;此后若再次执行相同的查询语句,结果就会从缓存中获取

  • 二级缓存开启的条件

    • 1. 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
      2. 在映射文件中设置标签<cache />
      3. 二级缓存必须在SqlSession关闭或提交之后有效,只有Sqlsession关闭或提交之后,查询的数据才会被保存到二级缓存中,否则保存到一级缓存中。
      4. 查询的数据所转换的实体类类型必须实现序列化的接口。比如查询员工共信息,那么员工类就必须implement Serializable

    • 只有满足以上四个条件,二级缓存才能生效。

  • 二级缓存的相关配置

    • 在mapper配置文件中添加的cache标签可以设置一些属性

    • eviction属性:缓存回收策略

      • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。

      • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除他们。

      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

      • 默认的是 LRU

    • flushInterval属性:刷新间隔,就是清空缓存的时间间隔,单位毫秒

      • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(增删改)时刷新清空缓存。

    • size属性:引用数目,正整数

      • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出

    • readOnly属性:只读,true/false

      • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。速度快。

      • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false

MyBatis缓存查询的顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用

  • 如果二级缓存没有命中,再查询一级缓存

  • 如果一级缓存也没有命中,则查询数据库

  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存

整合第三方缓存EHCache(了解)

由于我们的MyBatis是一个持久层框架,所以它在做缓存功能可能没有那么专业,所以我们可以用第三方的缓存来替换MyBatis的二级缓存,但是一级缓存是替换不了的。 

 第一步:添加依赖

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

各个jar包的功能:

jar包名称作用
mybatis-ehcacheMybatis和EHCache的整合包
ehcacheEHCache核心包
slf4j-apiSLF4J日志门面包
logback-classic支持SLF4J门面接口的一个具体实现

第二步:创建EHCache的配置文件ehcache.xml

 名字必须叫`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>

第三步:设置二级缓存的类型

  • 在xxxMapper.xml文件中设置二级缓存类型

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

第四步:加入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>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

EHCache配置文件说明

属性名是否必须作用
maxElementsInMemory在内存中缓存的element的最大数目
maxElementsOnDisk在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMBDiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false
diskExpiryThreadIntervalSeconds磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出

MyBatis的逆向工程 

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的

  • 逆向工程:先创建数据库表,由框架负责根据数据库中的表,反向生成如下资源:

    • Java实体类

    • Mapper接口

    • Mapper映射文件

创建逆向工程的步骤

第一步:在项目下创建一个Maven模块

第二步:在Maven模块的pom.xml文件中添加依赖和插件

<!--打包方式--> 
<packaging>jar</packaging>
<dependencies>
	<!-- MyBatis核心依赖包 -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.9</version>
	</dependency>
	<!-- junit测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.13.2</version>
		<scope>test</scope>
	</dependency>
	<!-- MySQL驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.27</version>
	</dependency>
	<!-- log4j日志 -->
	<dependency>
		<groupId>log4j</groupId>
		<artifactId>log4j</artifactId>
		<version>1.2.17</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>8.0.27</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</build>

第三步:创建MyBatis的核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"/>
    <typeAliases>
        <package name=""/>
    </typeAliases>
    <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>
    </environments>
    <mappers>
        <package name=""/>
    </mappers>
</configuration>

第四步:创建逆向工程所需的配置文件

  • 文件名必须是: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.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        
        <!-- javaBean的生成策略-->
        <!--
            targetPackage:javabean生成的包名
            targetProject:javabean生成的位置
                .\src\main\java当前模块下的src。。。
            enableSubPackages:能否生成子包,true表示可以,false则com.lin.mybatis.bean表示一个目录
            trimStrings:是否去除字段中的前后空格,要根据字段名生成JavaBean的属性名
                
        -->
        <javaModelGenerator targetPackage="com.lin.mybatis.bean" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.lin.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.lin.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>

第五步:执行插件的generate目标进行逆向工程生成

 看到以下说明成功了:

如果出现报错:Exception getting JDBC Driver,可能是pom.xml中,数据库驱动配置错误

那你就要检查pom.xml文件中的dependencies标签中的mysql驱动要和mybatis-generator-maven-plugin插件中的mysql驱动的版本是否一致  

奢华尊享版的逆向工程功能测试 

  • selectByExample:按条件查询,需要传入一个example对象或者null;如果传入一个null,则表示没有条件,也就是查询所有数据

  • example.createCriteria().andXXX:创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系

  • example.or().andXXX:将之前添加的条件通过or拼接其他条件

测试查询: 

    // 测试查询功能
    @Test
    public void testMBGOfQuery1() throws IOException {
        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> empList = mapper.selectByExample(null);// 传入null表示条件为null即没有条件,就是查询所有的员工
        empList.forEach(emp -> System.out.println(emp));

    }

 代码:

    // 测试查询功能
    @Test
    public void testMBGOfQuery2() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // 根据条件查询员工信息
        EmpExample example = new EmpExample();// 用于条件查询
        // 创建要查询的条件
        // 1.这是添加与and条件
        example.createCriteria().andEmpNameEqualTo("易烊千玺").andAgeGreaterThanOrEqualTo(10);
        // 2.这是添加或or条件
        example.or().andEmpNameEqualTo("周杰伦");

        List<Emp> empList = mapper.selectByExample(example);
        for (Emp emp : empList) {
            System.out.println(emp);
        }
    }

测试修改:

普通修改: 

    // 测试修改功能
    @Test
    public void testMBGOfUpdate1() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // updateByPrimaryKey()方法是普通的根据主键修改记录信息,如果其中有空的字段的话,还是会修改成null
        int count = mapper.updateByPrimaryKey(new Emp(1, "周杰伦", "男", null, "jay@qq.com", 5));
        System.out.println(count);
    }

选择性修改: 

    // 测试修改功能
    @Test
    public void testMBGOfUpdate2() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // updateByPrimaryKeySelective()方法是选择性的根据主键修改记录信息,如果其中有空的字段的话,则这个字段会被去除掉,不会修改为null
        int count = mapper.updateByPrimaryKeySelective(new Emp(1, "周杰伦", "男", null, "zhou@qq.com", 5));
        System.out.println(count);
    }

 MyBatis使用分页插件

分页插件使用步骤

第一步:在pom.xml文件中添加依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.2.0</version>
</dependency>

第二步:在mybatis核心配置文件中配置分页插件

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

第三步:在查询之前开启分页功能

开启分页功能:PageHelper.startPage(int pageNum, int pageSize);

pageNum是页码,pageSize是每页显示的记录数。

    // 测试分页插件功能
    @Test
    public void testPage1() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // 开启分页功能
        PageHelper.startPage(1,4);

        // 查询所有的员工信息
        List<Emp> empList = mapper.selectByExample(null);// 传入null表示条件为null即没有条件,就是查询所有的员工
        empList.forEach(emp -> System.out.println(emp));

    }

 

 第四步:在查询完分页所需的数据之后,获取和分页相关的信息

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

  • PageInfo对象包含了做分页功能相关的很多信息。

    • list:分页的数据

    • navigatePages:导航分页的页码数,就是分页导航条一次显示多少个页码

    // 测试分页插件功能
    @Test
    public void testPage1() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // 开启分页功能
        PageHelper.startPage(3, 4);

        // 查询所有的员工信息,这里会获取第三页然后每页4条的数据
        List<Emp> empList = mapper.selectByExample(null);// 传入null表示条件为null即没有条件,就是查询所有的员工
        empList.forEach(emp -> System.out.println(emp));

        // 在查询完分页所需的数据后,获取和分页相关的信息(PageInfo包含了和分页功能相关的所有数据)
        PageInfo<Emp> page = new PageInfo<>(empList,5);


        System.out.println("-------page相关---------");
        System.out.println(page);
    }

pageInfo:

常用数据:

  • pageNum:当前页的页码

  • pageSize:每页显示的条数

  • size:当前页显示的真实条数

  • total:总记录数

  • pages:总页数

  • prePage:上一页的页码

  • nextPage:下一页的页码

  • isFirstPage/isLastPage:是否为第一页/最后一页

  • hasPreviousPage/hasNextPage:是否存在上一页/下一页

  • navigatePages:导航分页的页码数

  • navigatepageNums:导航分页的页码,[1,2,3,4,5]

PageInfo{pageNum=3, pageSize=4, size=4, startRow=9, endRow=12, total=23, pages=6,

list=Page{count=true, pageNum=3, pageSize=4, startRow=8, endRow=12, total=23, pages=6, reasonable=false, pageSizeZero=false}[Emp{eid=12, empName='b', sex='男', age=23, email='234@qq.com', did=null}, Emp{eid=13, empName='c', sex='男', age=25, email='234@qq.com', did=null}, Emp{eid=14, empName='aa', sex='null', age=null, email='null', did=null}, Emp{eid=15, empName='aa', sex='null', age=null, email='null', did=null}],

prePage=2, nextPage=4, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}

Mybatis日志输出

mybatis提供了很多种日志功能:

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2【Spring5要求掌握的】
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING【掌握】标准日志
  • NO_LOGGING

mybatis中具体使用哪个日志,在mybatis的核心配置文件中设定。

依赖:

log4j.properties配置:

#log4j.rootLogger = level, appenderName1, appenderName2, …
#appenderName:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。console是输出到控制台
#level:DEBUG < INFO < WARN < ERROR < FATAL,只会输出大于等于该级别的日志信息
log4j.rootLogger=debug,console


#log4j.appender.appenderName1=className. console上面设置日志输出到哪,org.apache.log4j.ConsoleAppender表示控制台、org.apache.log4j.FileAppender表示文件
log4j.appender.console=org.apache.log4j.ConsoleAppender
#设置日志输出的格式。org.apache.log4j.PatternLayout表示可定制化、org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#ConversionPattern设置以怎样的格式输出日志信息。百度很多。
log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %p %C{1} : %m%n


#log4j.logger.全类名=level 自定义指定某个类中的日志输出级别
log4j.logger.org.apache.zookeeper=WARN
log4j.logger.org.apache.dubbo.config.bootstrap.DubboBootstrap=WARN
log4j.logger.com.lin.module1.Module1Application=INFO

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺着听Jay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值