The most complete tutorial of Mybatis (one from entry to mastery)

1. Concept

 

 

2. Build the development environment

2.1 Development environment

 2.2 Create a Maven project

<!--junit测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<!--Mybatis核心-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!--Mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

 2.3 Create the core configuration file of Mybatis

It is customary to name it mybatis-configxml. This file name is just a suggestion, not a mandatory requirement. After Spring is integrated in the future, this configuration file can be omitted, so you can copy and paste directly when operating.

The core configuration file is mainly used to configure the environment for connecting to the database and the global configuration information of MyBatis

The location where the core configuration file is stored is in the src/main/resources directory

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

2.4 Create mapper interface

 2.5 Create Mybatis mapping file

 

 The mapper interface and the mapping file must be consistent:

1. The full class name of the mapper interface is consistent with the namespace of the mapping file

2. The method name of the method in the mapper interface should be consistent with the id of the SQL in the mapping file

<?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="com.fd.mybatis.mapper.UserMapper">

    <!--
    mapper接口和映射文件要保证两个一致:
        1,mapper接口的全类名和映射文件的namespace一致
        2、mapper接口中的方法的方法名要和映射文件中的sqL的id保持一致
    -->
    <insert id="insertUser">
        insert into t_user values('4','admin','123456',23,'男','[email protected]')
    </insert>
</mapper>

2.6 Create a test method

@Test
public void mybatisTest() {
    try {
        // 获取核心配置文件的输入流
        InputStream stream = Resources.getResourceAsStream("mybatis_config.xml");
        // 获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream);
        // 获取sql的会话对象SqlSession(不会自动提交事务),是Mybatis提供的操作数据的对象
        // SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取sql的会话对象SqlSession(会自动提交事务)
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 获取UserMapper的代理实现类,
        // 通过getMapper方法,重写接口方法:通过UserMapper的全类名来找到当前对象的映射文件,再通过要调用的方法找到要调用的sql语句

        /**
         * mapper接口和映射文件要保证两个一致:
         *         1,mapper接口的全类名和映射文件的namespace一致
         *         2、mapper接口中的方法的方法名要和映射文件中的sqL的id保持一致
         * */

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 执行sql方法
        int result = mapper.insertUser();

        // 接口重写的底层实现: 通过唯一标识找到sql并执行,唯一标识是namespace.sqlId
        // int result = sqlSession.insert("com.fd.mybatis.mapper.UserMapper.insertUser");

        System.out.println("结果:" + result);
        // 需要手动提交事务
        // sqlSession.commit();
        // 关闭会话
        sqlSession.close();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

2.7 Add log4j logging function

1. Add dependencies

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

 2. Add log4j configuration file

The log4j configuration file is named log4j.xml, and is stored in the src/main/resources directory

<?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">
        <level value="debug"/>
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info"/>
    </logger>
    <root>
        <level value="debug"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

The level of the log: the content printed from left to right is more and more detailed

FATAL (fatal)>ERROR (error)>WARN (warning)>INFO (information)>DEBUG (debugging)

Realize the effect:

2.8 Summary:

  1. Create the core configuration file of Mybatis
  2. Create mapper interface object
  3. Create a mapper interface mapping file (the full class name of the mapper interface is consistent with the namespace of the mapping file, and the method name of the method in the mapper interface must be consistent with the sql id in the mapping file)
  4. create test method

3. Mybatis core configuration file explanation

3.1 Introducing the properties file

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
jdbc.username=root
jdbc.password=mysql123.

3.2 Core configuration file

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

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

    <!--引入properties文件,此后就可以在当前文件中使用${key}的方式访问value-->
    <properties resource="jdbc.properties"/>

    <!--
        typeAliases:设置类型别名,即为某个具体的类型设置一个别名
        在Mybatis的范围中,就可以使用别名标识一个具体的类型
    -->
    <typeAliases>
        <!--
            type: 设置需要起别名的类型
            alias: 设置某个类型的别名
        -->
        <typeAlias type="com.fd.mybatis.pojo.User" alias="abc"></typeAlias>
        <!--若不设置alias,当前的类型拥有默认的别名,即类名且不区分大小写-->
        <typeAlias type="com.fd.mybatis.pojo.User"></typeAlias>
        <!--通过包设置类型别名,指定包下所有的类型将全部拥有默认的别名,即类名且不区分大小写-->
        <package name="com.fd.mybatis.pojo"/>
    </typeAliases>
    <!--
        environments: 配置连接数据库的环境
            属性:
            default: 设置默认使用的环境的id
    -->
    <environments default="development">
        <!--
            environment: 设置一个具体的连接数据库的环境
            属性:
                id: 设置环境的唯一标识,不能重复
        -->
        <environment id="development">
            <!--
                transactionManager:设置事务管理器
                属性:
                    type: 设置事务管理的方式
                    type="JDBC | MANAGED" 两种方式
                    JDBC:表示使用JDBC中原生的事务管理方式
                    MANAGED:被管理,例如Spring
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:设置数据源
                属性:
                    type:设置数据源的类型
                    type="POOLED | UNPOOLED | JNDI"
                    POOLED:表示使用数据库连接池,创建好连接后交给连接池管理
                    UNPOOLED:表示不使用数据库连接池,连接数据库时每次新创建连接
                    JNDI:表示使用上下文中的数据源
            -->
            <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>
    <!--引入mybatis的映射文件-->
    <mappers>
        <!--一个mapper接口就要创建一个映射文件,实际开发中接口很多一般不推荐使用这种方式,推荐使用包引入的方式-->
        <!--<mapper resource="mappers/UserMapper.xml"/>-->
        <!--
            以包的方式引入映射文件,但是必须满足两个条件:
            1、mapper接口和映射文件所在的包必须一致
            2、mapper接口的名宇和映射文件的名字必须一致
        -->
        <package name="com.fd.mybatis.mapper"/>
    </mappers>
</configuration>

3.3 Code framework structure

4. User object database: add, delete, modify and check

4.1 mapper interface object

package com.fd.mybatis.mapper;

import com.fd.mybatis.pojo.User;

import java.util.List;

/**
 * @author 付东
 */
public interface UserMapper {

    /**
     * 新增用户
     *
     * @author lucky_fd
     * @return int
     **/
    int insertUser();

    /**
     * 修改用户
     *
     * @author lucky_fd
     * @return int
     **/
    int updateUser();

    /**
     * 删除用户
     *
     * @author lucky_fd
     * @return int
     **/
    int deleteUser();

    /**
     * 查询用户
     *
     * @author lucky_fd
     * @return com.fd.mybatis.pojo.User
     **/
    User getUserById();

    /**
     * 查询用户
     *
     * @author lucky_fd
     * @return java.util.List<com.fd.mybatis.pojo.User>
     **/
    List<User> selectUser();
}

4.2 mapper mapping file

<?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="com.fd.mybatis.mapper.UserMapper">

    <!--
    mapper接口和映射文件要保证两个一致:
        1,mapper接口的全类名和映射文件的namespace一致
        2、mapper接口中的方法的方法名要和映射文件中的sqL的d保持一致
    -->
    <insert id="insertUser">
        insert into t_user values('2','root','123456',23,'男','[email protected]')
    </insert>

    <update id="updateUser">
        update t_user set password = 'qwer' where id = '1';
    </update>

    <delete id="deleteUser">
        delete from t_user where id = '1'
    </delete>

    <!--
        resultType: 设置结果类型,及查询的数据要转换的java类型
        resultMap: 自定义映射,处理多对一或一对多的映射关系
    -->
    <select id="getUserById" resultType="com.fd.mybatis.pojo.User">
        select * from t_user where id = '1'
    </select>

    <select id="selectUser" resultType="User">
        select * from t_user
    </select>
</mapper>

4.3 Test class

package com.fd.mybatis;

import static org.junit.Assert.assertTrue;

import com.fd.mybatis.mapper.UserMapper;
import com.fd.mybatis.pojo.User;
import com.fd.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

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

/**
 * Unit test for simple App.
 */
public class AppTest
{
    /**
     * Rigorous Test :-)
     */
    @Test
    public void shouldAnswerWithTrue()
    {
        assertTrue( true );
    }

    @Test
    public void mybatisAddTest() {
        try {
            // 获取核心配置文件的输入流
            InputStream stream = Resources.getResourceAsStream("mybatis_config.xml");
            // 获取SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 获取SqlSessionFactory对象
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream);
            // 获取sql的会话对象SqlSession(不会自动提交事务),是Mybatis提供的操作数据的对象
            // SqlSession sqlSession = sqlSessionFactory.openSession();
            // 获取sql的会话对象SqlSession(会自动提交事务)
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            // 获取UserMapper的代理实现类,
            // 通过getMapper方法,重写接口方法:通过UserMapper的全类名来找到当前对象的映射文件,再通过要调用的方法找到要调用的sql语句

            /**
             * mapper接口和映射文件要保证两个一致:
             *         1,mapper接口的全类名和映射文件的namespace一致
             *         2、mapper接口中的方法的方法名要和映射文件中的sqL的d保持一致
             * */

            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            // 执行sql方法
            int result = mapper.insertUser();

            // 接口重写的底层实现: 通过唯一标识找到sql并执行,唯一标识是namespace.sqlId
            // int result = sqlSession.insert("com.fd.mybatis.mapper.UserMapper.insertUser");

            System.out.println("结果:" + result);
            // 需要手动提交事务
            // sqlSession.commit();
            // 关闭会话
            sqlSession.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void updateTest() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int result = mapper.updateUser();
        System.out.println("结果:" + result);
    }

    @Test
    public void deleteTest() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int result = mapper.deleteUser();
        System.out.println("结果:" + result);
    }

    @Test
    public void selectTest() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById();
        System.out.println(user.toString());
    }

    @Test
    public void SelectAllUserTest() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.selectUser();
        userList.forEach(user -> System.out.println(user.toString()));
    }
}

4.4 Code structure:

5. How Mybatis obtains parameter values

5.1 If the parameter of the mapper interface method is a single literal type ( recommended )

At this point, you can use #{} and ${} to get the parameter value with any content. Be sure to pay attention to the single quotes in ${}

5.2 If the parameters of the mapper interface method are multiple literal types

At this point MyBatis will put the parameters in the map collection and store data in two ways

a> with arg0, arg1... as the key, with parameters as the value

b> take param1, param2... as the key, and take the parameter as the value

Therefore, you only need to access the keys of the map collection through #{} and ${} to get the corresponding value. Be sure to pay attention to the single quotes in ${}

 5.3 If the parameter of the mapper interface method is a parameter of the map collection type

You only need to access the keys of the map collection through #{} and ${} to get the corresponding value. Be sure to pay attention to the single quotes in ${}

5.4 If the parameter of the mapper interface method is a parameter of the entity class type ( recommended )

You only need to access the attributes in the entity class through #{} and ${} to get the corresponding attribute value. Be sure to pay attention to the single quotes in ${}

5.5 You can set the @Param annotation on the parameters of the mapper interface method ( recommended )

At this point MyBatis will put these parameters in the map and store them in two ways

a> The value attribute of the @Param annotation is the key, and the parameter is the value

b> take param1, param2... as the key, and take the parameter as the value

You only need to access the keys of the map collection through #{} and ${} to get the corresponding value. Be sure to pay attention to the single quotes in ${}

6. Mybatis query function

6.1 mapper interface object

package com.fd.mybatis.mapper;

import com.fd.mybatis.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface SelectMapper {

    /*
    * 若sql语句查询的结果为多条时,一定不能以实体类类型作为方法的返回值,
    *       否则会抛出异常TooManyResultsException
    * 若sql语句查询的结果为1条时,此时可以使用实体类类型List集合类型作为方法的返回值
    *
    * */

    User getUserById(@Param("id") String id);

    List<User> getAllUser();

    Integer getCount();

    Map<String, Object> getUserByIdToMap(String id);

    /**
     * 查询所有的用户为map集合
     *
     * 若查询的数据有多条时,并且要将每条数据转换为map集台
     * 此时有两种解决方案:
     *  1、将mapper接口方法的返回值设置为泛型是map的List集合
     *     List<Map<String, Object>> getAllUserToMap();
     *  2、可以将每条数据转换的map集合放在一个大的map中,但是必须要通过@MapKey注解
     *      将查询的某个字段的值作为大的map的键
     *      @MapKey("id")
     *     Map<String, Object> getAllUserToMap();
     *
     * @author lucky_fd
     * @return java.util.Map<java.lang.String,java.lang.Object>
     **/
    @MapKey("id")
    Map<String, Object> getAllUserToMap(); // List<Map<String, Object>> getAllUserToMap();

    /**
     * 通过用户名模糊查询
     *
     * @author lucky_fd
     * @param name 用户名
     * @return java.util.List<com.fd.mybatis.pojo.User>
     **/
    List<User> selectUserByLike(String name);
}

6.2 mapper mapping file

<?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="com.fd.mybatis.mapper.SelectMapper">

    <select id="getUserById" resultType="User">
        select * from t_user where id = #{id}
    </select>

    <select id="getAllUser" resultType="User">
        select * from t_user
    </select>

    <!--
        MyBatis中为Java中常用的类型设置了类型别名
        Integer: Integer,int
        int: _int,_integer
        Map: map
        String: string
    -->
    <select id="getCount" resultType="int">
        select count(*) from t_user
    </select>

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

    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>

    <!--
        模糊查询方法:(三种方式)
    -->
    <select id="selectUserByLike" resultType="User">
        <!--select * from t_user where name like '%${name}%'-->
        <!--select * from t_user where name like concat('%',#{name}, '%')-->
        select * from t_user where name like "%"#{name}"%"
    </select>
</mapper>

7. Mybatis gets the auto-increment primary key

7.1 Interface Object Methods

/**
 * 新增用户
 *
 * */
int insertUser(User user);

7.2 mapper mapping file

<!--
    useGeneratedkeys:表示当前添加功能使用自增的主健
    keyProperty:将添加的数据的自增主健为实体类类型的参数的属性赋值
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values (null, #{name}, #{password}, #{age}, #{gender}, #{email})
</insert>

7.3 Test method

/**
 * 新增用户
 * 并返回自增主键
 * */
@Test
public void insertUserTest() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
    User user = new User(null, "张三", "qesdf", 25, "男", "[email protected]");
    mapper.insertUser(user);
    // 返回自增ID
    System.out.println(user);
}

 Test Results:

8.resultMap custom mapping function

8.1 Entity class:

package com.fd.mybatis.pojo;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-05-22
 */

public class Dept {
    private String deptId;

    private String deptName;

    public Dept(String deptId, String deptName) {
        this.deptId = deptId;
        this.deptName = deptName;
    }

    public Dept() {
    }

    public String getDeptId() {
        return deptId;
    }

    public void setDeptId(String deptId) {
        this.deptId = deptId;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptId='" + deptId + '\'' +
                ", deptName='" + deptName + '\'' +
                '}';
    }
}


package com.fd.mybatis.pojo;

/**
 * SSM
 *
 * @author lucky_fd
 * @since 2023-05-22
 */

public class Emp {
    private String empId;

    private String name;

    private Integer age;

    private String gender;

    private Integer deptId;

    private Dept dept;

    public Emp() {
    }

    public Emp(String empId, String name, Integer age, String gender, Integer deptId) {
        this.empId = empId;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.deptId = deptId;
    }

    public String getEmpId() {
        return empId;
    }

    public void setEmpId(String empId) {
        this.empId = empId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getDeptId() {
        return deptId;
    }

    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empId='" + empId + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", deptId=" + deptId +
                ", dept=" + dept +
                '}';
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }
}

8.2 mapper interface object

package com.fd.mybatis.mapper;

import com.fd.mybatis.pojo.Emp;

public interface EmpMapper {

    Emp getEmpById(String id);

    Emp getEmpAndDeptById(String id);
}



package com.fd.mybatis.mapper;

import com.fd.mybatis.pojo.Dept;

public interface DeptMapper {

    Dept getDeptById(String id);
}

 8.3 mapper configuration file

<?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>
    <!--
        Mybatis核心配置文件中的标签必须按照指定的顺序配置
        (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)
    -->

    <properties resource="jdbc.properties"/>

    <settings>
        <!--将下划线映射为驼峰-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--
            按需加载 默认值为false
            aggressiveLazyLoading: 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
            此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sl。
        -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    <typeAliases>
        <package name="com.fd.mybatis.pojo"/>
    </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="com.fd.mybatis.mapper"/>
    </mappers>
</configuration>

 8.4 mapper mapping file

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

    <!--
        字段名和属性名不一致的情况,如何处理映射关系:
        1、为查询的字段设置别名,和属性名保持一致
        2、当字段符合MySOL的要求使用,而属性符合java的要求使用驼峰
            此时可以在MyBatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰
            emp_id:empId, emp_name : empName
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        3、使用resultMap设置自定义映射关系
            处理多对一得映射关系:
            1、级联方式处理
            2、association
            3、分步查询
    -->

    <!--
        resultMap:设置自定义的映射关系
            id:唯一标识
            type:处理映射关系的实体类的类型
        常用的标签:
            id:处理主键和实体类中属性的映射关系
            result: 处理普通字段和实体类中属性的映射关系
            column: 设置映射关系中的字段名,必须是sqL查询出的某个字段
            property: 设通映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
            association:
    -->
    <resultMap id="empResultMap" type="com.fd.mybatis.pojo.Emp">
        <id property="empId" column="emp_id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="gender" column="gender"/>
        <result property="deptId" column="dept_id"/>
    </resultMap>

    <!--resultType="com.fd.mybatis.pojo.Emp"-->
    <select id="getEmpById"  resultMap="empResultMap">
        select * from t_emp where emp_id = #{id}
    </select>

    <!--
        级联处理多对一映射关系
    -->
    <resultMap id="empAndDeptResultMapOne" type="Emp">
        <id property="empId" column="emp_id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="gender" column="gender"/>
        <result property="dept.deptId" column="dept_id"/>
        <result property="dept.deptName" column="dept_name"/>
    </resultMap>

    <!--
        association: 处理多对一得映射关系
        property:设置需要处理映射关系的属性的属性名
        javaType: 设置要处理的属性的类型I
    -->
    <resultMap id="empAndDeptResultMapTwo" type="Emp">
        <id property="empId" column="emp_id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="gender" column="gender"/>
        <association property="dept" javaType="Dept">
            <id property="deptId" column="dept_id"/>
            <result property="deptName" column="dept_name"/>
        </association>
    </resultMap>

    <!--
        分步查询:
        property:设置需要处理映射关系的属性的属性名
        select:设置分步查询的sql的唯一标识
        column:将查询出的某个宇段作为分步查询的sqL的条你
    -->
    <resultMap id="empAndDeptResultMapThree" type="Emp">
        <id property="empId" column="emp_id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="gender" column="gender"/>
        <association property="dept" column="dept_id" select="com.fd.mybatis.mapper.DeptMapper.getDeptById"/>
    </resultMap>

    <!--
        resultMap:设置自定义的映射关系
    -->
    <select id="getEmpAndDeptById" resultMap="empAndDeptResultMapThree">
        select emp.*, d.* from t_emp emp left join t_dept d on emp.dept_id = d.dept_id where emp.emp_id = #{id}
    </select>

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

    <!--
         ofType: 设置集合类型的属性中存储的数据的类型
    -->
    <resultMap id="resultMap" type="com.fd.mybatis.pojo.Dept">
        <result property="deptId" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
        <collection property="empList" ofType="Emp">
            <id property="empId" column="emp_id"/>
            <result property="age" column="age"/>
            <result property="name" column="name"/>
            <result property="gender" column="gender"/>
        </collection>
    </resultMap>

    <resultMap id="resultMapTwo" type="com.fd.mybatis.pojo.Dept">
        <result property="deptId" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
        <collection property="empList" column="dept_id" select="selectEmpByDeptId" fetchType="lazy"/>
    </resultMap>

    <select id="getDeptById" resultType="com.fd.mybatis.pojo.Dept">
        select * from t_dept where dept_id = #{id}
    </select>

    <select id="getDeptAndEmpById" resultMap="resultMap">
        select emp.*, d.* from t_emp emp right join t_dept d on emp.dept_id = d.dept_id where d.dept_id = #{id}
    </select>
</mapper>

8.5 resultMap tag custom mapping

A resultMap tag corresponds to an entity class. id is his name, type is their body. There is a one-to-one correspondence between the entity class and the corresponding table, and each attribute in the entity class corresponds to a field in a table. Among them, the annotation @TableField(exist = false) is marked on some attributes, which means that the attribute does not exist in the table, and most of them are referenced attributes, referenced collections, or some kind of signs declared to meet certain business requirements (For example, an alias created by assigning a certain field data in a certain table to a certain attribute in another entity during SQL multi-table query) and so on.

<resultMap id="唯一标识" type="映射的entity对象的绝对路径">
    <id column="表主键字段" jdbcType="字段类型" property="映射entity对象的主键属性" />
 
    <result column="表某个字段" jdbcType="字段类型" property="映射entity对象的某个属性"/>
 
    <!-- 指的是entity对象中的对象属性 -->
    <association property="entity中某个对象属性" javaType="这个对象的绝对路径">
        <id column="这个对象属性对应的表的主键字段" jdbcType="字段类型" property="这个对象属性内的主键属性"/>
        <result column="表某个字段" jdbcType="字段类型" property="这个对象属性内的某个属性"/>
    </association>
 
    <!-- 指的是entity对象中的集合属性 -->
    <collection property="entity中的某个集合属性" ofType="这个集合泛型所存实体类的绝对路径">
        <id column="这个集合属性中泛型所存实体类对象对应表的主键字段" jdbcType="字段类型"
            property="这个集合属性中泛型所存实体类对象的主键属性"
        />
        <result column="表某个字段" jdbcType="字段类型" 
                property="这个集合属性泛型所存实体类对象的属性"
        />  
    </collection>
 
    <!-- 引用另一个resultMap (套娃) -->
    <collection property="entity中的某个集合属性" 
                resultMap="这个引用的resultMap的type,就是这个集合属性泛型所存实体类的绝对路径"
    />
</resultMap>

8.6 Many-to-one mapping processing

1. Cascade processing mapping

<!--
    级联处理多对一映射关系
-->
<resultMap id="empAndDeptResultMapOne" type="Emp">
    <id property="empId" column="emp_id"/>
    <result property="age" column="age"/>
    <result property="name" column="name"/>
    <result property="gender" column="gender"/>
    <result property="dept.deptId" column="dept_id"/>
    <result property="dept.deptName" column="dept_name"/>
</resultMap>

2.association: handle many-to-one mapping relationship

<!--
    association: 处理多对一得映射关系
    property:设置需要处理映射关系的属性的属性名
    javaType: 设置要处理的属性的类型I
-->
<resultMap id="empAndDeptResultMapTwo" type="Emp">
    <id property="empId" column="emp_id"/>
    <result property="age" column="age"/>
    <result property="name" column="name"/>
    <result property="gender" column="gender"/>
    <association property="dept" javaType="Dept">
        <id property="deptId" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
    </association>
</resultMap>

3. Step by step query

<!--
    分步查询:
    property:设置需要处理映射关系的属性的属性名
    select:设置分步查询的sql的唯一标识
    column:将查询出的某个宇段作为分步查询的sqL的条件
    fetchType:在开启了延迟加载的环境中,通过该属性设置当前的分步查询是否使用延迟加载
    fetchType="eager(立即加载) || lazy(延迟加载)”
-->
<resultMap id="empAndDeptResultMapThree" type="Emp">
    <id property="empId" column="emp_id"/>
    <result property="age" column="age"/>
    <result property="name" column="name"/>
    <result property="gender" column="gender"/>
    <association property="dept" column="dept_id" select="com.fd.mybatis.mapper.DeptMapper.getDeptById"/>
</resultMap>

Advantages of step-by-step queries: lazy loading can be achieved

But global configuration information must be set in the core configuration file:

lazyLoadingEnabled: Global switch for lazy loading. When enabled, all associated objects are lazy loaded

aggressiveLazyLoading: When enabled, any method call will load all properties of the object. Otherwise, each attribute is loaded on demand

At this point, on-demand loading can be realized, and only the corresponding sl will be executed for whatever data is obtained. At this time, you can set whether the current step-by-step query uses lazy loading through the fetchType attribute in assoiation and collection, fetchType="lazy (lazy loading) || eager (immediate loading)'

8.7 One-to-many mapping processing method

1. Handle one-to-many mapping through collection

<!--
     ofType: 设置集合类型的属性中存储的数据的类型
-->
<resultMap id="resultMap" type="com.fd.mybatis.pojo.Dept">
    <result property="deptId" column="dept_id"/>
    <result property="deptName" column="dept_name"/>
    <collection property="empList" ofType="Emp">
        <id property="empId" column="emp_id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="gender" column="gender"/>
    </collection>
</resultMap>

<select id="getDeptAndEmpById" resultMap="resultMap">
    select emp.*, d.* from t_emp emp right join t_dept d on emp.dept_id = d.dept_id where d.dept_id = #{id}
</select>

2. Handling one-to-many mappings with step-by-step queries

<resultMap id="resultMapTwo" type="com.fd.mybatis.pojo.Dept">
    <result property="deptId" column="dept_id"/>
    <result property="deptName" column="dept_name"/>
    <collection property="empList" column="dept_id" select="selectEmpByDeptId" fetchType="lazy"/>
</resultMap>

9. Mybatis dynamic SQL function

The dynamic SQL technology of the Mybatis framework is a function of dynamically assembling SQL statements according to specific conditions. Its purpose is to solve the pain point problem when splicing SQL statement strings.

9.1 if tag

Use the expression in the test attribute to determine whether the content in the label is valid (whether it will be spliced ​​into sql)

<if test="empName != null and empName != ''">
    and name = #{empName}
</if>

9.2 where tag

a. Where tag is conditionally established, it will automatically generate where keyword

b. The redundant and before the content in the where tag will be automatically removed, but the redundant and after the content cannot be removed

If none of the conditions in the c.where tag is true, where has no function

select * from t_emp
<where>
    <if test="empName != null and empName != ''">
        and name = #{empName}
    </if>
    <if test="age != null">
        and age = #{age}
    </if>
    <if test="gender != null and gender != ''">
        and gender = #{gender}
    </if>
</where>

9.3 trim label

prefix, suffix: add the specified content before or after the content in the tag

prefixOverrides, suffixOverrides: Remove the specified content before or after the content in the tag

select * from t_emp
<trim prefix="where" suffixOverrides="and">
    <if test="empName != null and empName != ''">
        name = #{empName} and
    </if>
    <if test="age != null">
        age = #{age} and
    </if>
    <if test="gender != null and gender != ''">
        gender = #{gender} and
    </if>
</trim>

9.4 choose when otherwise label

Equivalent to if...else if...else in java, set at least one when, and set at most one otherwise

select * from t_emp
<where>
    <choose>
        <when test="empName != null and empName != ''">
            name = #{empName}
        </when>
        <when test="age != null">
            age = #{age}
        </when>
    </choose>
</where>

9.5 foreach tag

In Mybatis, the list is also stored in the Map, the default "list" is the key name, and the list data is the value; the array in Mybatis is also stored in the Map, the default "array" is the key name, and the array array data is the value.

collection: set the array or collection to be looped

item: Use a string to represent each data in the array or collection

separator: Set the separator between the data of each cycle

open: what to start the loop with

close: everything in the loop ends with what

<insert id="insertMoreEmp">
    insert into t_emp values
    <foreach collection="emps” item="emp” separator=",">
        (null, #{emp.empName}, #{emp.age}, #(emp.gender}, null)
    </foreach>
</insert>

<delete id="delete">
    delete from t emp where
    <foreach collection="empids" item="empid" separator="or">
        emp_id = #{empId]
    </foreach>
</delete>

9.6 sql fragment tags

You can record a piece of sql, and use the include tag to quote where needed

<sql id="empColumns">
    emp_id, emp_name, age, gender, dept_id
</sql>

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

10. Mybatis cache

10.1 Level 1 cache of MyBatis

The first-level cache is at the SqlSession level. The data queried through the same SqlSession will be cached. The next time the same data is queried, it will be directly obtained from the cache and will not be re-accessed from the database. Level 1 cache is enabled by default

Four situations that invalidate the first-level cache:

1) Different SqlSessions correspond to different first-level caches

2) The same SqlSession but different query conditions

3) Any addition, deletion or modification operation is performed during two queries of the same SqlSession

4) Manually clear the cache sqlSession.clearCache() during two queries of the same SqlSession;

Case demonstration:

@Test
public void getEmpByIdTest() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    Emp emp = mapper.getEmpById("1");
    System.out.println(emp);
    Emp emp2 = mapper.getEmpById("1");
    System.out.println(emp2);
}

Case results:

10.2 Second level cache of MyBatis

The second-level cache is at the SqlSessionFactory level. The results of SqlSession queries created through the same SqlSessionFactory will be cached; if the same query statement is executed again later, the results will be obtained from the cache

Conditions for enabling the secondary cache:

a> In the core configuration file, set the global configuration attribute cacheEnabled="true", the default is true, no need to set

b> set the label in the mapping file

c> The second-level cache must be valid after the SqlSession is closed or submitted

d> The entity class type converted by the queried data must implement the serialized interface

Circumstances that invalidate the second-level cache:

Any addition, deletion, or modification between two queries will invalidate the first-level and second-level caches at the same time

Case demonstration:

@Test
public void cacheTest() throws IOException {
    InputStream io = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = builder.build(io);
    SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
    CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
    Emp emp = mapper1.getEmpById("1");
    System.out.println(emp);
    sqlSession1.close();
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
    Emp emp1 = mapper2.getEmpById("1");
    System.out.println(emp1);
    sqlSession2.close();
}

Case results:

10.3 Related configuration of the second level cache

The cache tag added in the mapper configuration file can set some properties

1. eviction attribute: cache recycling strategy, the default is LRU.

  • LRU (Least Recently Used) - the least recently used: remove the object that has not been used for the longest time
  • FIFO (First in First out) - first in first out: objects are removed in the order they entered the cache.
  • SOFT - soft references: remove objects based on garbage collector state and soft reference rules
  • WEAK - Weak References: More aggressively remove objects based on garbage collector state and weak reference rules.

2.flushInterval attribute: refresh interval in milliseconds

The default is not set, that is, there is no refresh interval, and the cache is only refreshed when the statement is called

3.size attribute: number of references, positive integer

Represents how many objects the cache can store at most, too large will easily lead to memory overflow

4.readOnly attribute: read-only, true/false

  • true: read-only cache, will return the same instance of the cache object to all callers. Therefore these objects cannot be modified. This provides a significant performance advantage.
  • false: Read-write cache; will return a copy of the cached object (via serialization). This will be slower, but safe, so the default is false.

10.4 The order of MyBatis cache queries

Query the second-level cache first, because there may be data that other programs have checked out in the second-level cache, which can be used directly.

If the second-level cache does not hit, then query the first-level cache

If there is no hit in the first level cache, query the database

After the SqlSession is closed, the data in the first level cache will be written to the second level cache

10.5 Integrate third-party cache EHCache

1. Add dependencies

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

 2. Functions of each jar package

 3. Create the configuration file ehcache.xml of EHCache

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="F:\java\ehcache" />

    <defaultCache
            maxElementsInMemory="10000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

        <!--
        <?xml version="1.0" encoding="UTF-8"?>
        <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
                 updateCheck="false">
            &lt;!&ndash;
               diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
               user.home – 用户主目录
               user.dir  – 用户当前工作目录
               java.io.tmpdir – 默认临时文件路径
             &ndash;&gt;
            <diskStore path="E:\java\ehcache"/>
            &lt;!&ndash;
               defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
             &ndash;&gt;
            &lt;!&ndash;
              name:缓存名称。
              maxElementsInMemory:缓存最大数目
              maxElementsOnDisk:硬盘最大缓存个数。
              eternal:对象是否永久有效,一但设置了,timeout将不起作用。
              overflowToDisk:是否保存到磁盘,当系统当机时
              timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
              timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
              diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
              diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
              diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
              memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
              clearOnFlush:内存数量最大时是否清除。
              memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
              FIFO,first in first out,这个是大家最熟的,先进先出。
              LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
              LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
           &ndash;&gt;
            <defaultCache
                    eternal="false"
                    maxElementsInMemory="10000"
                    overflowToDisk="false"
                    diskPersistent="false"
                    timeToIdleSeconds="1800"
                    timeToLiveSeconds="259200"
                    memoryStoreEvictionPolicy="LRU"/>

            <cache
                    name="cloud_user"
                    eternal="false"
                    maxElementsInMemory="5000"
                    overflowToDisk="false"
                    diskPersistent="false"
                    timeToIdleSeconds="1800"
                    timeToLiveSeconds="1800"
                    memoryStoreEvictionPolicy="LRU"/>

        </ehcache>
        -->

4. Mapper configuration file sets the type of secondary cache

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

5. Add logback log

When SLF4j exists, log4j as a simple log will be invalid. At this time, we need to use the specific implementation of SLF4j logback to print the log. Create the logback configuration file logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
   debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 value表示的是打印到哪里的-->
    <property name="LOG_HOME" value="${catalina.base}/logs/" />
    <!-- 定义日志文件名称  value表示的是log的名称-->
    <property name="appName" value="u-plan"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <Encoding>UTF-8</Encoding>
        <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
            %msg:日志消息,%n是换行符 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="appLogAppender"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <Encoding>UTF-8</Encoding>
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!-- 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 TimeBasedRollingPolicy:
            最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 %i:当文件大小超过maxFileSize时,按照i进行文件滚动 -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log
            </fileNamePattern>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动, 且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
                那些为了归档而创建的目录也会被删除。 -->
            <MaxHistory>30</MaxHistory>
            <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy -->
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>512MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
            %msg:日志消息,%n是换行符 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [%logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- logger主要用于存放日志对象,也可以定义日志类型、级别 name:表示匹配的logger类型前缀,也就是包的前半部分 level:要记录的日志级别,包括
        TRACE < DEBUG < INFO < WARN < ERROR additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,false:表示只用当前logger的appender-ref,true:表示当前logger的appender-ref和rootLogger的appender-ref都有效 -->
    <!-- hibernate logger -->
    <logger name="com.fd.mybatis.mapper" level="debug" />
    <!-- Spring framework logger -->
<!--    <logger name="org.springframework" level="error" additivity="false"></logger>
    <logger name="com.fairyland" level="info" additivity="true">
        <appender-ref ref="appLogAppender" />
    </logger>-->

    <!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应, 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration>

 6. Test method

@Test
public void cacheTest() throws IOException {
    InputStream io = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = builder.build(io);
    SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
    CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
    Emp emp = mapper1.getEmpById("1");
    System.out.println(emp);
    sqlSession1.close();
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
    Emp emp1 = mapper2.getEmpById("1");
    System.out.println(emp1);
    sqlSession2.close();
}

11. Mybatis paging function realization

11.1 How to implement paging

(1) MyBatis uses the RowBounds object for paging, which is memory paging performed on the ResultSet result set, not physical paging;

(2) You can directly write parameters with physical paging in sql to complete the physical paging function,

(3) You can also use the paging plug-in to complete physical paging.

11.2  RowBounds pagination

@Test
public void pageByRowsBoundsTest() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();

    RowBounds rowBounds = new RowBounds(1, 2);
    List<EMP> emps = sqlSession.selectList("com.fd.mybatis.mapper.EMPMapper.selectByExample", null, rowBounds);
    emps.forEach(System.out::println);
    sqlSession.close();

}

11.3  Using Limit Pagination

语法: select * from user limit startIndex,pageSize
select * from user limit 0,2;

 Implementation:

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from t_user limit #{startIndex},#{pageSize};
</select>

11.4 Pagination plug-in realizes paging

1. Add dependencies

<!--分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
</dependency>

 2. Configure the pagination plugin

The mybatis-config.xml configuration file adds a pagination plugin

<!--类型别名-->
<typeAliases>
    <package name="com.fd.mybatis.pojo"/>
</typeAliases>

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3. Use of pagination plug-ins

a. Use PageHelper.startPage(int pageNum, int pageSize) to enable the paging function before the query function

pageNum: the page number of the current page

pageSize: the number of items displayed on each page

b. After querying to obtain the list collection, use Pagelnfo pagelnfo = new Pagelnfo<>(List list, int navigatePages) to obtain paging related data.

list: data after pagination

navigatePages: the number of pages in the navigation page

c. Paging related data

Pagelnfo{

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: the page number of the current page

pageSize: the number of items displayed on each page

size: the actual number of items displayed on the current page

total: the total number of records

pages: total number of pages

prePage: the page number of the previous page

nextPage: the page number of the next page

isFirstPage/isLastPage: Whether it is the first page/last page

hasPreviousPage/hasNextPage: Whether there is a previous page/next page

navigatePages: the number of pages in the navigation page

navigatepageNums: the page number of the navigation page, [1,2,3,4,5]

4. Test method

@Test
public void pageTest() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    EMPMapper mapper = sqlSession.getMapper(EMPMapper.class);
    // 开启分页
    Page<Object> objectPage = PageHelper.startPage(1, 4);
    System.out.println(objectPage);

    // 查询所有数据
    List<EMP> emps = mapper.selectByExample(null);
    emps.forEach(System.out::println);
    System.out.println("--------------------------------");

   // 查询功能之后获取分页相关的所有数据
    PageInfo<EMP> pageInfo = new PageInfo<>(emps, 5);
    System.out.println(pageInfo);
    /*
    * PageInfo{
    * pageNum=1, pageSize=4, size=4, 
    * startRow=1, endRow=4, total=7, 
    * pages=2, 
    * list=Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=7, pages=2, reasonable=false, pageSizeZero=false}
    * [EMP [Hash = -1617114975, empId=1, name=张三弟弟, age=null, gender=男, deptId=1, serialVersionUID=1], EMP [Hash = -1616153014, empId=2, name=张三弟弟, age=40, gender=男, deptId=1, serialVersionUID=1], EMP [Hash = -2116313700, empId=3, name=王五, age=25, gender=男, deptId=1, serialVersionUID=1], EMP [Hash = 1482850467, empId=4, name=123, age=2, gender=1, deptId=1, serialVersionUID=1]], prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
     * 
    * */
}

12. Mybatis classic interview questions

1. What is the working principle of the Dao (Mapper) interface? Can the methods in the Dao(Mapper) interface be overloaded when the parameters are different?

Wrong answer: The method in the Dao interface cannot be overloaded, because it is a storage and search strategy of the full name + method name.

Correct answer:

Usually an xml mapping file will write a Dao interface corresponding to it. Dao interface is the interface that people often say Mapper. The fully qualified name of the interface is the namespace value in the mapping file, the method name of the interface is the MappedStatementid value in the mapping file, and the parameters in the interface method are the parameters passed to sql. MapperThe interface does not have an implementation class. When calling the interface method, the full name of the interface + the concatenated string of the method name is used as the key value, which can uniquely locate aMappedStatement。

The Dao interface of Mybatis can have multiple overloaded methods, but there must be only one mapping corresponding to multiple interfaces, otherwise an error will be reported when starting.

The working principle of the Dao interface is the JDK dynamic proxy. MyBatis will use the JDK dynamic proxy to generate a proxy proxy object for the Dao interface when running. The proxy object proxy will intercept the interface method, execute the sql represented by , and then return the sql execution result MappedStatement.

Supplement :

Dao interface methods can be overloaded, but the following conditions must be met:

  1. There is only one no-argument method and one argument-argument method
  2. When multiple methods have parameters, the number of parameters must be consistent. and use the same  @Param , or use  param1 this

 2. Does MyBatis support lazy loading? If supported, how is it implemented?

MyBatis only supports lazy loading of association objects and collection objects. Association refers to one-to-one queries, and collection refers to one-to-many queries. In the MyBatis configuration file, you can configure whether to enable lazy loadinglazyLoadingEnabled=true|false。

Its principle is to use CGLIBthe proxy object to create the target object. When the target method is called, enter the interceptor method, such as calling, if the a.getB().getName()interceptor invoke()method finds a.getB()that it is a null value, then it will send the pre-saved sql query associated with the B object separately. , query B, and then call a.setB(b), so the object b property of a has a value, and then complete the a.getB().getName()method call. This is the basic principle of lazy loading.

Of course, not only MyBatis, but almost all of them, including Hibernate, support the same principle of lazy loading.

3. Can MyBatis perform one-to-one and one-to-many association queries? What implementations are there, and what's the difference between them?

Yes, MyBatis can not only perform one-to-one, one-to-many associated queries, but also many-to-one, many-to-many associated queries, and many-to-one queries, which are actually one-to-one queries, just modify selectOne()to selectList();Many-to-many query is actually a one-to-many query, just selectOne()change to selectList().

There are two implementation methods for related object query, one is to send a single sql to query related objects, assign it to the main object, and then return the main object. The other is to use nested query. The meaning of nested query is to use join query. Part of the columns are the attribute values ​​of object A, and the other part of the columns are the attribute values ​​of associated object B. The advantage is that only one sql query can be sent. The main object and its related objects are checked out.

So here comes the question, join query results in 100 records, how to determine that there are 5 main objects instead of 100? The principle of deduplication is that the subtag <resultMap>in the tag <id>specifies the id column that uniquely identifies a record. MyBatis <id>completes the deduplication function of 100 records according to the column value. <id>There can be more than one, which represents the semantics of the joint primary key.

Similarly, the associated objects of the main object are also repeated according to this principle, although in general, only the main object will have duplicate records, and the associated objects will generally not be repeated.

Example: The following join queries 6 records, the first and second columns are the Teacher object column, and the third column is the Student object column. After MyBatis deduplicates, the result is 1 teacher and 6 students, not 6 teachers and 6 students .

4. Can MyBatis map Enum enumeration class?

MyBatis can map enumeration classes, not only enumeration classes, MyBatis can map any object to a column of a table. The mapping method is to customize one TypeHandlerand implement TypeHandlerthe setParameter()and getResult()interface methods. TypeHandlerHas two effects:

  • One is to complete the conversion from javaType to jdbcType;
  • The second is to complete the conversion from jdbcType to javaType, which is reflected in two methods setParameter()and getResult(), respectively representing setting the sql question mark placeholder parameter and obtaining the column query result.

Guess you like

Origin blog.csdn.net/weixin_44863237/article/details/131122988