MyBatis

1.MyBatis简介

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

Mybatis网站:https://github.com/mybatis/mybatis-3

image-20230612083756901

2.MyBatis特性

  1. MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

  2. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Ordinary Java Objects,普通的Java对象)映射成数据库中的记录

  4. MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

3.MyBatis和其他持久层技术的对比

  • JDBC

    • SQL夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和JPA

    • 操作简便,开发效率高
    • 程序中的长难复杂SQL需要绕过框架
    • 内部自动生产的SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • MyBatis

    • 轻量级,性能出色
    • SQL和Java编码分开,功能边界清晰。Java代码专注业务、SQL 语句专注数据
    • 开发效率稍逊于Hlbernate,但是完全能够接受

4.MyBatis环境搭建

  1. 创建Maven工程

  2. 选择SDK

image-20230611220836240

  1. **修改Maven设置 **

image-20230611220951216

  1. 配置pom.xml配置文件

设置打包方式为jar,并引入以下依赖

 <dependencies>
<!--        MyBatis核心-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
<!--        junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
<!--        MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
    </dependencies>
  1. 创建MyBatis的核心配置文件

一:在resources文件夹下创建

二:配置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>
<!--    配置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
<!--            事务管理器类型为JDBC-->
            <transactionManager type="JDBC"/>
<!--            数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
<!--    引入映射文件-->
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>
  1. 创建mapper接口

为什么要创建mapper接口?因为MyBatis中有面向接口编程的功能,当调用接口中的方法时就会自动匹配sql语句,并且执行。

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

  1. 创建MyBatis的映射文件,执行sql测试添加功能

相关概念:ORMObject Relationship Mapping)对象关系映射。

对象:Java的实体类对象

关系:关系型数据库

映射:二者之间的对应关系

1、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

因此一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置是src/main/resources/mappers目录下

mapper接口

public interface UserMapper {
    /*
    * Mybatis面向接口编程的两个一致
    * 1.映射文件的namespace要和mapper接口的全类名保持一致
    * 2.映射文件中SQL语句的id要和mapper接口的方法名保持一致
    * */
//    添加用户
    public int addUser();
}

映射文件

<?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="cn.pdsu.mybatis.mapper.UserMapper">
<!--    public int addUser();-->
    <insert id="addUser">
        insert into user value (3,"珊瑚宫心海","123456",18,'女')
   </insert>
</mapper>

然后在MyBatis核心配置文件中引入映射文件,resource路径为mapper映射文件的路径

创建测试类

   @Test
    public void test1() throws IOException {
//    加载MyBatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//    获取SqlSession,并设置自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//    获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int result = mapper.addUser();
        System.out.println("result:"+result);
    }

测试成功

  1. 加入log4j日志的配置文件

相应依赖

<!--log4j日志文件-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

配置文件

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

执行程序,打印日志信息

image-20230611221926090

5.MyBatis框架的对数据库的CRUD功能

5.1添加功能

  1. 在mapper接口中添加方法
//    添加用户
    public int addUser();
  1. 创建实体类的映射文件,并添加插入sql语句
<?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="cn.pdsu.mybatis.mapper.UserMapper">
<!--    public int addUser();-->
    <insert id="addUser">
        insert into user value (4,"申鹤","123456",18,'女')
   </insert>
</mapper>	
  1. 在Mybatis核心配置文件中引入实体类的映射文件
<!--    引入实体类的映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
  1. 编写测试类
    @Test
    public void test1() throws IOException {
//    加载MyBatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//    获取SqlSession,并设置自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//    获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//    调用mapper接口中的添加方法        
        int result = mapper.addUser();
        System.out.println("result:"+result);
    }

5.2修改功能

  1. 在mapper接口中添加相应的方法
//    修改用户
    public int updateUser();
  1. 在mapper映射文件中添加sql语句
<!--        public void updateUser();-->
    <update id="updateUser">
        update user set age = 19 where username = "枫原万叶"
    </update>
  1. 在Mybatis核心配置文件中引入实体类的映射文件(上一步已完成)

  2. 编写测试类

    @Test
    public void test12() throws IOException {
//    加载MyBatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//    获取SqlSession,并设置自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//    获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//    调用mapper接口中的修改方法   
        int result = mapper.updateUser();
        System.out.println("result:"+result);
    }

image-20230611222438226

修改前:

修改后:

image-20230611222511712

5.3删除功能

  1. 在mapper接口中添加相应的方法
//删除用户信息
public int delete;
  1. 在mapper映射文件中添加sql语句
<!--        public void deleteUser();-->
    <delete id="deleteUser">
        delete user from user where name = "珊瑚宫心海"
    </delete>
  1. 在Mybatis核心配置文件中引入实体类的映射文件(上一步已完成)

  2. 编写测试类

    @Test
    public void test12() throws IOException {
//    加载MyBatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//    获取SqlSession,并设置自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//    获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//    调用mapper接口中的修改方法   
        int result = mapper.updateUser();
        System.out.println("result:"+result);
    }

修改前:

image-20230611225140732

修改后:

image-20230611225107589

5.4查询功能

查询功能注意的点:

  1. 查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系

resultType:自动映射,用于属性名和表中字段名一致的情况

resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

映射文件:

<!--        public int findUser();
   查询功能标签必须设置resultType或则resultMap
   resultType设置默认的映射关系
   resultMap设置自定义的映射关系
-->
    <select id="findUser" resultType="cn.pdsu.mybatis.pojo.User">
        select * from user where id = 1
    </select>

image-20230611225452997

  1. 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

映射文件:

<!--        public List<User> findAllUser();-->
    <select id="findAllUser" resultType="cn.pdsu.mybatis.pojo.User">
        select * from user
    </select> 
    @Test
    public void test5() throws IOException {
//    加载MyBatis核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//    获取SqlSession,并设置自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//    获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//    调用mapper接口中的添加方法
        List<User> allUser = mapper.findAllUser();
        allUser.forEach(System.out::println);
    }

image-20230611225436465

6.MyBatis核心配置文件properties

对于之前的引入数据源的方式,也可以通过propertise配置文件来配置数据源

前面加jdbc前缀的目的是为了区分多个字段名相同的情况

MyBatis核心配置文件

<configuration>
<!--    配置propertise配置文件-->
    <properties resource="jdbc.properties"></properties>
<!--    配置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
<!--            事务管理器类型为JDBC-->
            <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>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

7.MyBatis核心配置文件typeAllases

对于之前的查询映射文件,标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系,对于全类名可以采用核心配置文件中的typeAliases标签来设置类型别名

<configuration>
    <!--
    mybatis核心配置文件中的标签顺序需要符合以下规范,否则将会报错
    The content of element type "configuration" must match
    "(properties?,settings?,typeAliases?,typeHandlers?,
    objectFactory?,objectWrapperFactory?,reflectorFactory?,
    plugins?,environments?,databaseIdProvider?,mappers?)".-->
<!--    配置propertise配置文件-->
    <properties resource="jdbc.properties"></properties>
<!--    设置类型别名-->
    <typeAliases>
        <typeAlias type="cn.pdsu.mybatis.pojo.User"></typeAlias>
    </typeAliases> 
    .........
</configuration>

在核心配置文件当中设置了类型别名后就可以在mapper映射文件当中将resultType的全类名替换为该类型别名

    <select id="findUser" resultType="User">
        select * from user where id = 1
    </select>
<!--        public List<User> findAllUser();-->
    <select id="findAllUser" resultType="User">
        select * from user
    </select>

但是通常情况下不会使用typeAlias 标签,而是会使用package标签

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

8.MyBatis核心配置文件mappers

对于之前的在MyBatis核心配置文件中引入映射文件是采用以下方式的

<!--    引入映射文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>

此种方式是每一个实体类对应一个mapper接口,每一个mapper接口对应一个映射文件,当实体类较多时就需要引入多个映射文件,此时就可以采用和以上设置全类名的方式相同的方式以包为单位引入映射文件

创建包的方式,以斜线分割,如果用点的话创建的将会是一个文件夹

image-20230611225930806

主要注意的是:

  • mapper接口所在的包和映射文件所在的包一致
  • mapper接口要和映射文件的名字一致

image-20230611225950958

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

测试成功

image-20230611230032097

9.MyBatis核心配置文件、映射文件模板配置

9.1MyBatis核心配置文件

生成配置文件

9.2映射文件

10.封装sqlSessionUtils工具类

  1. sqlSessionUtils工具类
public class sqlSessionUtils {
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = null;
        try {
//        加载mybatis核心配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//    获取SqlSessionFactoryBuilder对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//            自动提交事务
           sqlSession = sqlSessionFactory.openSession(true);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }
}
  1. 测试类
    @Test
    public void test6(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//        获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
//        通过mapper接口调用接口中方法
        List<User> userList = mapper.test1();
        userList.forEach(System.out::println);

    }

11.MyBatis获取参数值的两种方式

MyBatis获取参数值的两种方式:${}#{}

  • ${}的本质就是字符串拼接

  • ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;

  • #{}的本质就是占位符赋值

    • #{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号

11.1mybatis获取参数值得各种情况

11.1.1单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号

若不加单引号为下面情况

测试:

映射文件:值得注意的是${}或则#{}中的参数名可以为任何值,重要的不是参数名而是参数值

<!--        public User getUserByName();-->
    <select id="getUserByName" resultType="User">
        <!--       使用字符串拼接的方式拼接sql-->
--          select * from user where username = '${username}'
        <!--       使用占位符赋值的方式拼接sql-->
        select * from user where username = #{username}
    </select>

测试类:

    @Test
    public void test6(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
//        通过mapper接口调用接口中方法
        User user = mapper.getUserByName("枫原万叶");
        System.out.println(user);
    }

image-20230611232425878

11.1.2多个字面量类型的参数

若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中,以两种方式进行存储。

  • 以arg0,arg1…为键,以参数为值;
  • 以param1,param2…为键,以参数为值;

因此只需要通过${}或#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。

<!--        public User checkLogin(String username,String password);-->
    <select id="checkLogin" resultType="User">
        select * from user where username = #{arg0} and password = #{arg1}
    </select>

测试类:

    @Test
    public void test7(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
//        通过mapper接口调用接口中方法
        User user = mapper.checkLogin("枫原万叶","123456");
        System.out.println(user);
    }

image-20230611232628336

11.1.3Map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

mapper接口中的方法参数为map集合

//    验证登录(参数为Map集合)
    public User checkLoginByMap(Map<String,Object> map);

映射文件

<!--        public User checkLoginByMap(Map<String,Object> map);-->
    <select id="checkLoginByMap" resultType="User">
        select * from user where username = #{username} and password = #{password}
    </select>

测试方法:

    @Test
    public void test8(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("username","枫原万叶");
        map.put("password","123456");
        //        通过mapper接口调用接口中方法
        User user = mapper.checkLoginByMap(map);
        System.out.println(user);
    }

image-20230611233553188

11.1.4实体类类型的参数

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

映射文件

<!--    public void addUser(User user);-->
    <insert id="addUser">
        insert into user values(#{id},#{username},#{password},#{age},#{sex})
    </insert>

测试类

  @Test
    public void test9(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
        //        通过mapper接口调用接口中方法
       mapper.addUser(new User(3,"珊瑚宫心海","123456",18,'女'));
    }

image-20230611233832112

11.1.5使用@Param标识参数

可以通过@Param注解标识mapper接口中的方法参数

此时,MyBatis会将这些参数放在map集合中,

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

以param1,param2…为键,以参数为值;

只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

mapper接口中的方法

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

mapper接口的映射文件

<!--        public User checkLoginByParam(@Param("username") String username,@Param("password") String password);-->
    <select id="checkLoginByParam" resultType="User">
        select * from user where username = #{username} and password = #{password}
    </select>

测试类

   public void test10(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
        //        通过mapper接口调用接口中方法
        User user = mapper.checkLoginByParam("枫原万叶", "123456");
        System.out.println(user);
    }

image-20230611234126882

12.MyBatis的各种查询功能

12.1查询一个实体类对象

mapper接口中的方法

// 根据用户id查询用户信息 
public User getUserBuId(@Param("id") int id);

映射文件

<!--public User getUserBuId(@Param("id") int id);-->
  <select id="getUserBuId" resultType="User">
       select * from user where id = #{id}
  </select>

测试类

  @Test
    public void test11(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        User user = mapper.getUserBuId(1);
        System.out.println(user);
    }

image-20230611234847331

12.2查询一个List集合

mapper接口中的方法

// 查询所有用户信息 
public List\<User\> findAllUser();

映射文件

<!--    public List<User> findAllUser();-->
    <select id="findAllUser" resultType="User">
        select * from user
    </select>

测试类

    @Test
    public void test12(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        List<User> user = mapper.findAllUser();
        user.forEach(System.out::println);
    }

12.3查询单个数据

mapper接口中的方法

//    查询用户个数
    public int findUserCount();

映射文件

<!--        public int findUserCount();
   resultType定义返回值类型为int-->
    <select id="findUserCount" resultType="int">
        select count(*) from user
    </select>

测试类

  @Test
    public void test13(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        int userCount = mapper.findUserCount();
        System.out.println(userCount);
    }

image-20230611235053220

12.4查询一条数据为map集合

mapper接口中的方法

//    根据id查询用户信息为map集合
    public Map<String,Object> getUserByIdToMap(@Param("id") int id );

映射文件

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

测试类

    @Test
    public void test14(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        Map<String, Object> map = mapper.getUserByIdToMap(1);
        System.out.println(map);
    }

image-20230611235309909

12.5查询多条数据为map集合

第一种方法:

mapper接口中的方法

//    查询所有用户信息为map类型
    public List<Map<String,Object>> getAllUserByIdToMap();

映射文件

<!--        public Map<String,Object> getAllUserByIdToMap();-->
    <select id="getAllUserByIdToMap" resultType="map">
        select * from user
    </select>

测试类

    @Test
    public void test15(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        List<Map<String, Object>> list = mapper.getAllUserByIdToMap();
        list.forEach(System.out::println);
    }

image-20230611235331866

第二种方法:

将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的map集合

mapper接口中的方法

//    查询所有用户信息为map类型
@MapKey("id")
public Map<String, Object> getAllUserByIdToMap();

映射文件

<!--        public Map<String,Object> getAllUserByIdToMap();-->
    <select id="getAllUserByIdToMap" resultType="map">
        select * from user
    </select>

测试类

    @Test
    public void test15(){
//        根据SqlSessionUtils工具类获取到SqlSession对象
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
//    获取mapper接口对象
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        //        通过mapper接口调用接口中方法
        Map<String, Object> map = mapper.getAllUserByIdToMap();
        System.out.println(map);
    }

image-20230611235418568

13.特殊SQL执行(只能使用${}的情况)

13.1模糊查询

对于模糊查询,如下面语句所示

select * from user where username like "%#{username}%"

当使用模糊查询时,如果采用#{}的方式,那么此sql语句中的参数就会当成一整个字符串,就会出现异常的情况

image-20230611235512377

对于以上问题,就可以采用${}的方式进行解决

select * from user where username like "%${username}%"

image-20230611235551435

还有一种最常用的方式

select \* from user where username like "%"#{username}"%"

13.2批量删除

delete from user where id in (\#{ids})

对于以上删除的SQL语句,采用#{}的方式来设置参数,由于#{}的方式会自动添加引号,这样就会导致SQL语句变成下面

//错误的SQL语句 delete from user where id in (“1,2”)

//正确的SQL语句 delete from user where id in (1,2)

对于以上问,就必须要使用${}的方式来设置参数

delete from user where id in (\${ids})

image-20230611235723994

13.3动态获取表名

mapper接口中的方法

//    查询指定表中的数据
    public List<User> findUserByTable(@Param("tableName") String tableName);

映射文件

<!--        public List<User> findUserByTable(@Param("tableName") String tableName);-->
    <select id="findUserByTable" resultType="User">
        select * from ${tableName}
    </select>

image-20230611235742684

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

映射文件

<!--        public void addUser(User user);
    useGeneratedKeys设置当前主键使用了自动递增的功能
    keyProperty将自增的主键的值赋值给传输到映射文件中参数的某个属性
    此sql语句中就是将id赋值给null
-->
    <insert id="addUser" useGeneratedKeys="true" keyProperty="id">
        insert into user values(null,#{username},#{password},#{age},#{sex})
    </insert>

测试类

@Test
public void test4(){
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    nuClearMapper mapper = sqlSession.getMapper(nuClearMapper.class);
    mapper.addUser(new User(null, "可莉", "123456", 18, '女'));
}

image-20230611235833669

14.处理属性名和字段名不一样的问题

对于实体类和数据库表中的属性名不一致的情况,实体类中属性的命名方式是驼峰原则,而数据库表中的属性时使用的字段命名规则。

如果实体类中和数据库表中的字段属性名不同的话,就会出现映射不到的问题,那么返回的值就会为空

如下面的查询功能:

image-20230611235944766

image-20230611235959683

对于属性名和字段名不一样的情况,其映射关系该如何处理呢

14.1为sql语句中字段设置别名

<!--    public List<Employee> findAll();-->
    <select id="findAll" resultType="Employee">
    //修改前
        select * from t_employee
    //修改后
        select eid,e_name eName,did from t_employee
    </select>

14.2在Mybatis核心配置文件中设置全局配置,将“_”自动映射为驼峰

<!--设置Mybatis的全局配置-->
    <settings>
<!--"_"映射为驼峰,e_name->eName-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

14.3通过resultMap设置自定义映射

resultMap:设置自定义映射

属性:

id:表示自定义映射的唯一标识

type:查询的数据要映射的实体类的类型

  • 子标签:
  • id:设置主键的映射关系
  • result:设置普通字段的映射关系
  • association:设置多对一的映射关系
  • collection:设置一对多的映射关系
  • 属性:
    • property:设置映射关系中实体类中的属性名
    • column:设置映射关系中数据库表中的字段名

如下:

<!--    public List<Employee> findAll();-->
<!--    设置属性和字段的映射关系-->
    <resultMap id="EmployeeMap" type="Employee">
<!--        id是设置主键的映射关系-->
        <id property="eid" column="eid"></id>
<!--        result是设置普通字段的映射关系-->
        <result property="eName" column="e_name"></result>
        <result property="did" column="did"></result>
    </resultMap>
    <select id="findAll" resultMap="EmployeeMap">
        select * from t_employee
    </select>

15.多对一映射处理(多个员工对应一个部门)

在员工表中添加所对应部门的属性

(一):级联方式处理多对一映射关系

查询员工信息以及员工所对应的部门信息

   <!--    设置属性和字段的映射关系-->
     <resultMap id="empAndDeptOne" type="Employee">
        <id property="eid" column="eid"></id>
        <result property="eName" column="e_name"></result>
        <result property="did" column="did"></result>
        <result property="dept.did" column="did"></result>
        <result property="dept.dName" column="d_name"></result>
    </resultMap>
<!--        Employee getEmpAndDept(@Param("eid") int eid);-->
    <select id="getEmpAndDept" resultMap="empAndDeptOne">
    //SQL语句采用左连接的方式进行多表查询
        select * from t_employee left join t_dept
            on t_employee.did = t_dept.did
                where t_employee.did = #{eid}
    </select>

image-20230612000255304

(二):使用association处理多对一映射关系

 <resultMap id="empAndDeptTwo" type="Employee">
        <id property="eid" column="eid"></id>
        <result property="eName" column="e_name"></result>
        <result property="did" column="did"></result>
//     property需要处理多对的映射关系的属性名
//     javaType该类型的属性
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="dName" column="d_name"></result>
        </association>
    </resultMap>
<!--        Employee getEmpAndDept(@Param("eid") int eid);-->
    <select id="getEmpAndDept" resultMap="empAndDeptTwo">
        select * from t_employee left join t_dept
            on t_employee.did = t_dept.did
                where t_employee.eid = #{eid}
    </select>

image-20230612000313591

(三):使用分步查询处理多对一映射关系

EmployeeMapper映射文件:

<!--     public Employee getEmployeeByStepOne(@Param("eid") int eid);-->
    <resultMap id="getEmployeeByStepOneMap" type="Employee">
        <id property="eid" column="eid"></id>
        <result property="eName" column="e_name"></result>
        <result property="did" column="did"></result>
        <result property="dept.did" column="did"></result>
        <result property="dept.dName" column="d_name"></result>
<!--        select:设置分步查询SQL的唯一标识(当前mapper接口的全类名.方法名)
            column:设置分步查询的条件(即根据did进行查询部门信息)
-->
        <association property="dept" select="cn.pdsu.mybatis.mapper.DeptMapper.getDeptByStepOne" column="did">
        </association>
    </resultMap>
    <select id="getEmployeeByStepOne" resultMap="getEmployeeByStepOneMap">
        select * from t_employee where eid = #{eid}
    </select>

DeptMapper映射文件

<!--    public Dept getDeptByStepOne(@Param("did") int did);-->
<!--    根据核心配置文件设置属性和字段之间的关系-->
    <select id="getDeptByStepOne" resultType="Dept">
        select * from t_dept where did = #{did}
    </select>

image-20230612000502266

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

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

此时可通过association中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=”lazy(延迟加载)|eager(立即加载)”

测试:

对员工姓名进行查询:

@Test
public void test3(){
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Employee empAndDept = mapper.getEmployeeByStepOne(1);
    System.out.println(empAndDept.geteName());
}

核心配置文件配置:

<!--设置Mybatis的全局配置-->
    <settings>
<!--"_"映射为驼峰,e_name->eName-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
<!--        对所有的分布查询开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
<!--     public Employee getEmployeeByStepOne(@Param("eid") int eid);-->
    <resultMap id="getEmployeeByStepOneMap" type="Employee">
        <id property="eid" column="eid"></id>
        <result property="eName" column="e_name"></result>
        <result property="did" column="did"></result>
        <result property="dept.did" column="did"></result>
        <result property="dept.dName" column="d_name"></result>
<!--        select:设置分步查询SQL的唯一标识(当前mapper接口的全类名.方法名)
            column:设置分步查询的条件(即根据did进行查询部门信息)
            fetchType:开启全局延迟加载之后,可以通过此属性手动可控制延迟加载的效果
-->
        <association property="dept"
                     select="cn.pdsu.mybatis.mapper.DeptMapper.getDeptByStepOne"
                     column="did"
                     fetchType="lazy"></association>
    </resultMap>
    <select id="getEmployeeByStepOne" resultMap="getEmployeeByStepOneMap">
        select * from t_employee where eid = #{eid}
    </select>

当fetchType属性的值为lazy时,开启延时加载

image-20230612000616457

当fetchType属性的值为eager时,立即加载

image-20230612000627579

16.一对多映射处理(一个部门含有多个员工)

image-20230612000754098

一:使用collection处理一对多映射关系

collection标签中

property:处理一对多关系的属性(对应的时Dept类中的emps属性)

ofType:该属性所对应的集合中储存数据的类型(对应属性emps的数据类型为Employee)

<!--        public Dept getDeptAndEmps(@Param("did") int did);-->
    <resultMap id="DeptAndEmpsMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="dName" column="d_name"></result>
        <collection property="emps" ofType="Employee">
            <id property="eid" column="eid"></id>
            <result property="eName" column="e_name"></result>
        </collection>
    </resultMap>
    <select id="getDeptAndEmps" resultMap="DeptAndEmpsMap">
        select * from t_dept left join
            t_employee on t_dept.did = t_employee.did
             where t_dept.did = #{did}
    </select>

image-20230612000826815

二:使用分步查询处理一对多映射关系

DeptMapper

<!--     public Dept getDeptAndEmpsStepOne(@Param("did") int did);-->
    <resultMap id="DeptAndEmpsStepOneMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="dName" column="d_name"></result>
        <collection property="emps"
<!--        select:设置分步查询SQL的唯一标识(当前mapper接口的全类名.方法名)
            column:设置分步查询的条件(即根据did进行查询员工信息)
-->
                    select="cn.pdsu.mybatis.mapper.EmployeeMapper.getDeptAndEmpsStepTwo"
                    column="did">
        </collection>
    </resultMap>
    
    <select id="getDeptAndEmpsStepOne" resultMap="DeptAndEmpsStepOneMap">
        select * from t_dept where did = #{did}
    </select>

其中collection 标签中的select的属性值为对应实体类接口中的方法

image-20230612000945274

EmployeeMapper

<!--     public List<Employee> getDeptAndEmpsStepTwo(@Param("did") int did); -->
    <select id="getDeptAndEmpsStepTwo" resultType="Employee">
        select * from t_employee where did = #{did}
    </select>

image-20230612001000437

对于一对多的情况,同样可以实现延迟加载

image-20230612001018357

image-20230612001027339

image-20230612001038348

17.动态SQL

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

17.1if标签

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会添加到SQL语句中

为sql语句添加where 1 = 1的条件设置为sql语句恒成立

<!--       public List<Employee> getEmpByCondition(Employee employee)-->
    <select id="getEmpByCondition" resultType="Employee">
        select * from t_employee where 1 = 1
        <if test="eid != null and eid != ''">
            and eid = #{eid}
        </if>
        <if test="eName != null and eName != ''">
            and e_name = #{eName}
        </if>
        <if test="did != null and did != ''">
            and did = #{did}
        </if>
    </select>

image-20230612001136152

17.2where标签

where和if一般结合使用:where标签可以动态生成where关键字,并且将内容前多余的and或or去掉。

<!--       public List<Employee> getEmpByCondition(Employee employee)-->
    <select id="getEmpByCondition" resultType="Employee">
        select * from t_employee
        <where>
            <if test="eid != null and eid != ''">
                and eid = #{eid}
            </if>
            <if test="eName != null and eName != ''">
                and e_name = #{eName}
            </if>
            <if test="did != null and did != ''">
                and did = #{did}
            </if>
        </where>
    </select>

image-20230612001204741

17.3trim标签

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

常用属性:

  • prefix:在trim标签中的内容的前面添加某些内容
  • prefixOverrides:在trim标签中的内容的前面去掉某些内容
  • suffix:在trim标签中的内容的后面添加某些内容
  • suffixOverrides:在trim标签中的内容的后面去掉某些内容

下面的例子就是在sql语句中前面添加where关键字,并且去掉后面的and关键字

<!--       public List<Employee> getEmpByCondition(Employee employee)-->
    <select id="getEmpByCondition" resultType="Employee">
        select * from t_employee
        <trim prefix="where" suffixOverrides="and|or">
            <if test="eid != null and eid != ''">
                 eid = #{eid} and
            </if>
            <if test="eName != null and eName != ''">
                 e_name = #{eName} and
            </if>
            <if test="did != null and did != ''">
                 did = #{did} and
            </if>
        </trim>
    </select>

image-20230612001243070

17.4choose、when、otherwise

choose、when、otherwise相当于if…else if..else,其中choose是父标签

when标签至少有一个,otherwise标签最多有一个

按照when标签的顺序,当有一个when标签满足条件时,就结束循环,

当when标签都不满足时,就会执行otherwise标签中的内容,有点类似于switch语句

<!--    public List<Employee> getEmpByChoose(Employee employee);-->
    <select id="getEmpByChoose" resultType="Employee">
        select * from t_employee
        <where>
            <choose>
                <when test="eid != null and eid != ''">
                    eid = #{eid}
                </when>
                <when test="eName != null and eName != ''">
                    e_name = #{eName}
                </when>
                <when test="did != null and did != ''">
                    did = #{did}
                </when>
                <otherwise>
                    eid = 3
                </otherwise>
            </choose>
        </where>
    </select>

image-20230612001310485

17.5foreach循环标签

foreach标签经常用于遍历集合,构建in条件语句或者批量操作语句。

相关属性

  • collection:设置要循环的数组或集合
  • item:表示集合或数组中的每一个数据
  • separator:设置循环体之间的分隔符
  • open:设置foreach标签中的内容的开始符
  • close:设置foreach标签中的内容的结束符

例子:

一:根据eid批量删除,相关的SQL语句是

delete from t_employee where eid in (1,2)

mapper接口

//通过数组实现批量删除
    public int deleteByArray(@Param("eids") Integer[] eid);

映射文件

表示对eids数据进行循环,eid作为数组中的每一个数据,每一个数据之间的分隔符为“,”,并且以“(”开始,以“)”结束。

<!--        public int deleteByArray(Integer eid);-->
    <delete id="deleteByArray">
        delete from t_employee where eid in
          <foreach collection="eids" item="eid" separator="," open="(" close=")">
              #{eid}
          </foreach>
    </delete>

image-20230612001440996

二:实现批量添加,相关的SQL语句是

insert into t_employee values (?,?,?) , 
                              (?,?,?) , 
                              (?,?,?) , (?,?,?)

mapper接口

//  通过List集合实现批量添加
    public int insertByArray(@Param("emps") List<Employee> employees);

映射文件

foreach标签中意思是对emps集合进行循环,eid为集合中的每一个数据,以“,”为分隔符

<!--        public int insertByArray(List<Employee> employees); -->
    <insert id="insertByArray">
        insert into t_employee values
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.eid},#{emp.eName},#{emp.did})
        </foreach>
    </insert>

测试类

public void test4(){
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
    Employee employee1 = new Employee(5,"班尼特",1);
    Employee employee2 = new Employee(6,"行秋",1);
    Employee employee3 = new Employee(7,"申鹤",2);
    Employee employee4 = new Employee(8,"钟离",2);
    List<Employee> employees = Arrays.asList(employee1, employee2, employee3, employee4);
    int i = mapper.insertByArray(employees);
    System.out.println(i);
}

17.6sql标签(SQL片段)

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

例子:当查询职工信息时,使用sql片段实现

image-20230612001543068

<sql id="selectSql">eid,e_name,did</sql>
<select id="getEmpByCondition" resultType="Employee">
    select <include refid="selectSql"></include> from t_employee
    <trim prefix="where" suffixOverrides="and|or">
        <if test="eid != null and eid != ''">
             eid = #{eid} and
        </if>
        <if test="eName != null and eName != ''">
             e_name = #{eName} and
        </if>
        <if test="did != null and did != ''">
             did = #{did} and
        </if>
    </trim>
</select>

image-20230612001559239

18.MyBatis的缓存

18.1MyBatis的缓存机制

Mybatis 缓存机制原理是将第一次从数据库 SQL 查询的结果数据保存到缓存(内存中),当下一次 SQL 查询和第一次相同,如果缓存中有数据则直接获取,而不用再从数据库获取,从而减少数据库访问频率,大大提升数据库性能。

18.2缓存机制分类

MyBatis 提供了两种缓存机制:一级缓存和二级缓存

  • 一级缓存是 SqlSession 级别的缓存,它是默认开启的。在操作数据库时需要构造 SqlSession 对象,它是应用程序与持久存储层之间执行交互操作的一个单线程对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 SqlSession 之间的缓存数据区(HashMap)是互相不影响的。
  • 二级缓存是Mapper级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。但是,二级缓存默认是关闭的,需要手动开启。

18.3Mybatis一级缓存

18.3.1一级缓存说明

在操作数据库时需要构造 SqlSession 对象,在对象中存在一个HashMap用于存储缓存数据。

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

image-20230822125509608

一级缓存满足条件如下:

  • 同一个 SqlSession 对象
  • 同一个SqlSession两次查询期间没有进行增删改操作
  • 相同的 SQL 语句和参数

注:使用 SqlSession.clearCache( ) 方法可以强制清除一级缓存

18.3.2一级缓存测试

18.3.2.1使用同一个SqlSession

既然每个 SqlSession 都会有自己的一个缓存,那么我们用同一个 SqlSession 是不是就能感受到一级缓存的存在呢?调用多次 getMapper 方法,生成对应的SQL语句,判断每次SQL语句是从缓存中取还是对数据库进行操作,下面的例子来证明一下

@Test
   public void cacheTest() {
       List<UserEntity> userEntities = userMapper.selectUserByAge(20);
       System.out.println(userEntities);
     
       List<UserEntity> userEntities2 = userMapper.selectUserByAge(20);
       System.out.println(userEntities2);

       List<UserEntity> userEntities3 = userMapper.selectUserByAge(20);
       System.out.println(userEntities3);
   }

执行测试,输出结果如下:

可以看到只执行了一次查询操作

2020-08-10 16:14:44,790 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==>  Preparing: select * from tb_user where age > ? 
2020-08-10 16:14:44,837 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==> Parameters: 20(Integer)
2020-08-10 16:14:44,884 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] <==      Total: 7
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}

18.3.2.2两次查询期间进行增删改操作

接着我在第一条和第二条 SQL语句 之间插入更新的 SQL 语句,代码如下:

@Test
public void cacheTest() {
    List<UserEntity> userEntities = userMapper.selectUserByAge(20);
    System.out.println(userEntities);

    int result = userMapper.updateUser(1,"张三");
    sqlSession.commit();

    List<UserEntity> userEntities2 = userMapper.selectUserByAge(20);
    System.out.println(userEntities2);
}

执行测试,结果如下:

020-08-10 16:20:47,384 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==>  Preparing: select * from tb_user where age > ? 
2020-08-10 16:20:47,431 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==> Parameters: 20(Integer)
2020-08-10 16:20:47,478 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] <==      Total: 7
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}
2020-08-10 16:20:47,478 [main] [mapper.UserMapper.updateUser]-[DEBUG] ==>  Preparing: update tb_user set name=? where id=? 
2020-08-10 16:20:47,478 [main] [mapper.UserMapper.updateUser]-[DEBUG] ==> Parameters: 张三(String), 1(Integer)
2020-08-10 16:20:47,478 [main] [mapper.UserMapper.updateUser]-[DEBUG] <==    Updates: 1
2020-08-10 16:20:47,493 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==>  Preparing: select * from tb_user where age > ? 
2020-08-10 16:20:47,493 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==> Parameters: 20(Integer)
2020-08-10 16:20:47,509 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] <==      Total: 7
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null},
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null},

可以看到,在两次查询 SQL 语句中使用插入 SQL 语句,会对一级缓存进行刷新,会导致一级缓存失效。

18.3.2.3不同的SqlSession的

我们知道一级缓存就是 SqlSession 级别的缓存,而同一个 SqlSession 会有相同的一级缓存,那么使用不同的 SqlSession 是不是会对一级缓存产生影响呢?

@Test
  public void cacheTest() {
      List<UserEntity> userEntities = userMapper.selectUserByAge(20);
      System.out.println(userEntities);
      //使用sqlSession创建新的SqlSession
      UserMapper userMapper2
              = sqlSessionFactory.openSession().getMapper(UserMapper.class);
      List<UserEntity> userEntities2 = userMapper2.selectUserByAge(20);
      System.out.println(userEntities2);

执行测试,结果如下:

2020-08-10 16:26:36,243 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==>  Preparing: select * from tb_user where age > ? 
2020-08-10 16:26:36,290 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==> Parameters: 20(Integer)
2020-08-10 16:26:36,322 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] <==      Total: 7
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}
2020-08-10 16:26:36,337 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==>  Preparing: select * from tb_user where age > ? 
2020-08-10 16:26:36,337 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] ==> Parameters: 20(Integer)
2020-08-10 16:26:36,353 [main] [mapper.UserMapper.selectUserByAge]-[DEBUG] <==      Total: 7
[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}

上面代码使用了不同的 SqlSession 对同一个 SQL 语句执行了相同的查询操作,却对数据库执行了两次相同的查询操作,生成了不同的 UserEnity 对象,由此可见,不同的 SqlSession 是肯定会对一级缓存产生影响的。

18.3.2一级缓存的问题

当有两个 SqlSession 对象存在,一个用于查询数据,一个用于更新数据,如果查询和更新是同一张表的相同数据,这时可能会出现数据脏读。

例如,假设有两个 SqlSession 对象 A 和 B。A 执行了一个查询语句并缓存了查询结果。然后 B 执行了一个更新语句并提交了更改。此时,如果 A 再次执行相同的查询语句,它将直接从缓存中获取结果,而不是再次查询数据库。这意味着 A 获取到的数据可能是过时的,因为它没有感知到 B 所做的更改。

而解决办法是清空缓存。

mybatis:
  configuration:
    cache-enabled: false #禁用二级缓存
    local-cache-scope: statement #一级缓存指定为statement级别

18.2Mybatis二级缓存

18.2.1二级缓存概述

  • 二级缓存是 Mapper 级别的缓存。

    多个 SqlSession 对象 SQL 语句查询数据库结果会存放二级缓存区域,而多个SqlSession对象可以共用二级缓存。

  • 二级缓存是多个 SqlSesion 对象共用的。

    其作用范围是Mapper的同一个 namespace ,不同的 SqlSession 对象再次执行相同 namepace 下的 SQL 语句,第一次执行会将数据库中查询结果数据存储到二级缓存中,第二次会从二级缓存中获取数据,而不再从数据库中获取,从而提高查询效率。

  • MyBatis 二级缓存默认关闭,需要手动开启二级缓存。

    MyBatis 的二级缓存是 Mapper 范围级别,除了在 MyBatis 环境配置mybatis-config.xml设置二级缓存总开关,还要在具体的mapper.xml中加入 标签。

18.2.2二级缓存配置

  1. 在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置
<settings>
        <!--缓存,默认也是开启-->
        <!--有些二级缓存可能用到序列化技术,所以entity类要Serializable接口-->
        <setting name="cacheEnabled" value="true"/>
</settings>
  1. 配置完毕之后,找到相对应想要做二级缓存的XXMapper.xml文件,在中添加<cache/>标签:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

配置中参数信息含义如下:

  • eviction : 缓存的回收策略
  • flushInterval : 时间间隔(毫秒)
  • size : 引用数目,缓存对象数目和运行环境数目,默认1024
  • readOnly : 是否只读

其中,对于eviction的回收策略,有其四种:

  1. LRU - 最近最少使用:移出最长时间不被使用的对象 (默认);

  2. FIFO - 先进先出(队列):按对象进入缓存的顺序移除 ;

  3. SOFT - 软引用: 移除基于垃圾回收器状态和软引用规则的对象 ;

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

18.2.3如何禁用二级缓存

  1. 给mybatis-config.xml添加如下属性时,将全局关闭缓存;
<settings>
        <setting name="cacheEnabled" value="false"/>
</settings>
  1. 不在不需要进行缓存的Mapper.xml添加

  2. 给对应SQL语句进行设置局部禁用缓存(userCache) 例

<select id="getSomeThing" parameterType="Integer" resultType="Integer" useCache="false">
        SELECT * FROM `table`
</select>

18.3缓存说明

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

19.MyBatis的逆向工程

  1. 什么是逆向工程

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

的。

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

  • Java实体类
  • Mapper接口
  • Mapper映射文件
  1. 创建逆向工程的步骤

一:清新简洁版

  1. 添加相关依赖和插件
    <dependencies>
<!--        单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
<!--        依赖mybatis核心包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
<!--        mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
<!--        log4j日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</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.19</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
  1. 创建逆向工程的配置文件
<?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?serverTimezone=Asia/Shanghai"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.pdsu.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.pdsu.mybatis.mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.pdsu.mybatis.mapper"
                             targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 逆向分析的表 --> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_employee" domainObjectName="Employee"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>
  1. 执行项目插件的generate目标
image-20230612001951438
  1. 执行结果

可以看出已经自动生成mapper接口、实体类和实体类映射文件

image-20230612002029776

二:奢华尊享版

  1. 和清新简洁版不同的是,奢华尊享版需要将generatorConfig配置文件中的

image-20230612002044042

targetRuntime属性值更换为MyBatis3

image-20230612002111990

  1. 执行项目插件的generate目标
image-20230612002147421

其中mapper接口中有很多方法

二:QBC查询

什么是QBC查询:QBC即Quary By Criteria,Criteria是Criterion的复数,译为规则,准则,在sql语句中相当于查询条件。QBC查询是将查询条件通过Java对象进行模块化封装。

测试:

    @Test
    public void test1() {
        try {
//            加载mybatis核心配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//            获取SqlSession,并设置自动提交事务
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
//            获取到mapper接口
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//            创建实体类的Example对象
            EmployeeExample example = new EmployeeExample();
//            实体类的Example对象调用createCriteria方法,然后再调用响应的SQL方法
//            下面的例子为查询eName为“枫原万叶”的employee
            example.createCriteria().andENameEqualTo("枫原万叶");
            List<Employee> employees = mapper.selectByExample(example);
            employees.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

20.分页插件

  1. 配置分页插件,加入相应依赖和插件配置
<!--        分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
  1. 分页插件的使用

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

pageNum:当前页的页码

pageSize:每页显示的条数

    @Test
    public void test2() {
        try {
//            加载mybatis核心配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//            获取SqlSession,并设置自动提交事务
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
//            获取到mapper接口
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//           开启分页功能
            PageHelper.startPage(1, 3);
            List<Employee> employees = mapper.selectByExample(null);
            employees.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

即打印第一页,并且显示3条数据

image-20230612002348128

打印第二页,并且显示3条数据

image-20230612002355272

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

list:分页之后的数据

navigatePages:导航分页的页码数

    @Test
    public void test2() {
        try {
//            加载mybatis核心配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//            获取SqlSession,并设置自动提交事务
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
//            获取到mapper接口
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//           开启分页功能
            PageHelper.startPage(2, 3);
            List<Employee> employees = mapper.selectByExample(null);
            PageInfo<Employee> pageInfo = new PageInfo<>(employees,3);
            System.out.println(pageInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

PageInfo对象

PageInfo{pageNum=2, pageSize=3, size=3, 
            startRow=4, endRow=6, total=8, pages=3, 
            list=Page{count=true, pageNum=2, pageSize=3, 
            startRow=3, endRow=6, total=8, pages=3, 
            reasonable=false, pageSizeZero=false}
            [Employee{eid=4, eName='雷电将军', did=2}, 
            Employee{eid=5, eName='班尼特', did=1}, 
            Employee{eid=6, eName='行秋', did=1}], 
            prePage=1, nextPage=3, isFirstPage=false, 
            isLastPage=false, hasPreviousPage=true, 
            hasNextPage=true, navigatePages=3, 
            navigateFirstPage=1, navigateLastPage=3, 
            navigatepageNums=[1, 2, 3]}

常用数据:

pageNum:当前页的页码

pageSize:每页显示的条数

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

total:总记录数

pages:总页数

prePage:上一页的页码

nextPage:下一页的页码

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

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

navigatePages:导航分页的页码数

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

21.MyBatis的原理

21.1JDBC概述

JDBC(Java Database Connectivity)是Java中用于执行SQL语句的API,它提供了一种访问多种关系数据库的方法。

Java JDBC API提供以下接口和类 。

接口:

  • Connection:特定数据库的连接(会话)。在连接上下文中执行SQL语句并返回结果。
  • PreparedStatement:表示预编译的 SQL 语句的对象。
  • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
  • ResultSet :表示数据库结果集的数据表,通常通过执行查询数据库的语句生成 。
  • CallableStatement :用于执行 SQL 存储过程的接口 。

类:

  • DriverManager:负责管理JDBC驱动程序。使用JDBC驱动程序之前,必须先将驱动程序加载并注册后才可以使用,同时提供方法来建立与数据库的连接。(加载到内存中才能使用)
  • SQLException:有关数据库操作的异常。

21.2JDBC的执行流程

21.2.1说明

  1. 载入JDBC驱动程序
  2. 定义连接URL
  3. 建立连接
  4. 创建Statement对象
  5. 执行查询或更新
  6. 结果处理
  7. 关闭连接

image.png

21.2.2Connection

  • 一个Connection对象表示通过JDBC驱动与数据源建立的连接

  • Connection对象表示客户端会话,因此它需要一些相关的状态信息,例如用户Id、一组SQL语句和会话中使用的结果集以及事务隔离级别等信息。

  • connection的获取可以从两个方面获取

    • JDBC API中提供的DriverManager类获取
Connection connection = DriverManager.getConnection(
  "jdbc:mysql://127.0.0.1:3306/data?user=root&password=123456");
    • DataSource接口的实现类获取
DataSource dataSource = new UnpooledDataSource(
        "com.mysql.cj.jdbc.Driver",
        "jdbc:mysql://127.0.0.1:3306/data?user=root&password=123456&AllowPublicKeyRetrieval=true",
        "root","qwer1234");

21.2.3Statement

  • Statement 不支持输入参数,有sql注入的风险
  • PreparedStatement:增加了设置SQL参数的方法
  • CallableStatement:增加了调用存储过程以及检索存储过程调用结果的方法
//PreparedStatement
Connection connection = DriverManager.getConnection("");
String sql = "SELECT * FROM admin WHERE username = ? AND password = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"username");
preparedStatement.setString(2,"password");
ResultSet resultSet = preparedStatement.executeQuery();
       
//Statement 
String sql2 = "SELECT * FROM admin WHERE username = 'username' AND password = 'password'";
Statement statement2 = connection.createStatement();
ResultSet resultSet2 = statement.executeQuery(sql);

21.3MyBatis的原理

MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

MyBatis的工作流程包括以下几个步骤:

image-20230822162610960

  1. 加载Mybatis的配置文件。
  2. 根据配置文件信息,使用SqlSessionFactoryBuilder()来创建一个sqlSessionFactory对象。
  3. 使用sqlSessionFactory会话工厂的openSqlSession来创建一个SqlSession对象。该对象中包含了执行SQL语句的所有方法。
  4. sqlSession对象内部调用Executor执行器,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句。首先会根据此sql语句去缓存中查找,如果缓存中没有值,就调用doQuery方法从数据库中查询数据。
  5. doQuery()方法中创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来创建Statement对象,将SQL语句发送到数据库中,完成对数据库的查询操作,最终返回List结果集。
  6. 最后使用resultSetHandler对结果集进行处理,完成输出结果映射。

22.MyBatis的插件的原理

22.1MyBatis的插件概述

MyBatis的插件本身就是一个拦截器。

22.2MyBatis插件实现

  1. 实现MyBatis提供的Interceptor接口,重写其中的interceptor方法、plugin方法和setProperties方法。
  2. 然后在实现插件接口的类上添加@Intercepts注解指定要拦截哪一个接口的哪些方法,最后在配置文件中配置自定义插件。

image-20230823000502974

22.3MyBatis插件的实现原理

MyBatis插件的本质就是拦截器。MyBatis 仅可以编写针对 ParameterHandlerExecutorStatementHandlerResultSetHandler这 4 种接口的插件,MyBatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandlerinvoke() 方法来执行拦截方法的自身逻辑。

  1. ParameterHandler(参数处理器):首先执行,用于处理参数。
    - 适合拦截 SQL 语句执行前的操作,例如修改或验证参数。
    - 如果你需要在 SQL 语句执行之前对参数进行操作或验证,可以选择拦截 ParameterHandler。
  2. StatementHandler(语句处理器):在参数处理器之后执行,用于处理 SQL 语句的生成和处理。
    - 适合拦截 SQL 语句的生成和处理。
    - 如果你需要动态修改生成的 SQL 语句,或者修改 Statement 对象的行为,可以选择拦截 StatementHandler。
  3. Executor(执行器):在语句处理器之后执行,用于执行 SQL 语句。
    - 适合拦截 SQL 语句的执行,包括查询、更新、删除等操作。
    - 如果你需要在 SQL 语句执行前后执行一些额外的逻辑,或者记录执行日志、计算执行时间等,可以选择拦截 Executor。
  4. ResultSetHandler(结果集处理器):最后执行,用于处理查询结果集。
    - 适合拦截结果集的处理,通常用于自定义结果集的映射或处理。
    - 如果你需要自定义结果集的处理逻辑,可以选择拦截 ResultSetHandler。

MyBatis
https://xhablog.online/2022/06/25/MyBatis/
作者
Xu huaiang
发布于
2022年6月25日
许可协议