尚硅谷SSM整合之mybatis学习打卡

MyBatis⭐️

1. MyBatis 简介

1.1 MyBatis 历史

MyBatis 前身为ibatis,提供包括SQL Maps Data Access Objects (DAO)

1.2 MyBatis 特性
  1. MyBatis 是支持定制化SQL,存储过程以及高级映射的优秀的持久层框架
  2. MyBatis避免了几乎所有的JDBC 代码和手动设置参数以及获取结果集
  3. MyBatis可以使用简单的xml或注解用于配置和原始映射,将接口和java的POJO(Plain Old java Objects, 普通的java对象)映射成数据库中的记录
  4. MyBatis 是一个半自动的ORM(Object Relation Mapping)框架
1.3 MyBatis 和其他持久化层技术的对比
  • JDBC

    • SQL夹杂在java代码中,造成耦合度太高,导致硬编码内伤

    • 维护不易且实际开发需求中SQL有变化,频繁修改的情况多见

    • 代码冗长,开发效率低

  • Hibernate和JPA(没用过)

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

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

2. 搭建mybatis

2.1 创建Maven工程
  1. 设置打包方式为jar
  2. 引入依赖
2.2 创建mybatis核心配置文件

mybatis.pdf 官方文档中直接可以找到

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入properties文件 以后就能使用el表达式来配置数据源信息-->
    <properties resource="jdbc.properties"/>
    <!--
        typeAliases: 为一个具体的类型设置一个别名,在mybatis的范围中就可以使用这个别名
    -->
    <typeAliases>
        <!--
            type: 别名类型
            alias: 设置某个类型的别名
            如果不设置alias,那么就会有一个默认的别名,就是类名,并且该别名不区分大小写
        -->
        <!-- <typeAlias type="com.cczj.mybatis.pojo.User" alias="User"/>-->
        <!-- <typeAlias type="com.cczj.mybatis.pojo.User"/> -->
        <!-- 以包名设置别名,该包下的所有类都有他们默认的别名-->
        <package name="com.cczj.mybatis.pojo"/>
    </typeAliases>
    <!-- 配置连接数据库的环境 default: 默认环境选择-->
    <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>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 引入mappers配置文件 -->
    <mappers>
        <!-- <mapper resource="mappers/UserMapper.xml"/>-->
        <!--
            以包的形式引入mapper映射文件,但必须满足两个条件
            1. mapper接口和映射文件所在的包名必须一致
            2. mapper接口的名字和文件的名字必须一致
        -->
        <package name="com.cczj.mybatis.mapper"/>
    </mappers>
</configuration>

注意:

The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.

配置tag要按照要求的顺序

2.3 创建mapper接口

该mapper接口类似于DAO (Data Access Object) 数据访问对象

2.4 创建mapper接口的映射文件

相关概念ORM (Object Relationship Mapping)对象映射关系

  • 对象:java的实体类对象
  • 关系:关系型数据库
  • 映射:二者之间对应的关系
java概念数据库概念
属性字段/列
对象记录/行
<?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.cczj.mybatis.mapper.UserMapper">
    <!-- int insertUser(); -->
    <insert id="insertUser">
        insert into ssm.t_user
        values (1, 'admin', '123456', 18, '男', '123456@qq.com');
    </insert>
</mapper>
2.5 log4j.xml配置文件

日志的级别

FATAL(致命) -> ERROR(错误) -> WARN(警告) -> INFO(信息) -> DEBUG(调试)

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

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

3.1 Mybatis 中获取参数方式的介绍
  1. ${} 本质是字符串拼接、#{} 本质是占位符赋值
  2. 使用${} 拼接时,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号
  3. 使用#{} 赋值时,若为字符串类型或日期类型的字段进行赋值时,可以自动加单引号
3.2 传入单个参数的情况
  1. 在接口中定义的方法,如果是单个参数,最好加入@param("指定的变量名") 作为指定名
  2. ${} 中可以使用任意变量名,但是需要加上'' 作为字符串拼接来使用。
  3. #{}中可以使用任意的变量名,因为在底层实现过程中,此标记仅仅作为 ? 这个占位符来使用,而传进来的具体参数值作为一个字面量是不会改变的。
3.3 传入多个参数的情况
  1. 在Mybatis内部,会将多个参数封装进一个集合中,这时的自变量名只能使用系统定义的参数名
  2. 集合中的参数调用名有两种:arg0、arg1、arg2...param1、param2、...
  3. 自定义Map集合:将参数值手动放入Map中,并将map作为参数传入接口中。
  4. 使用自定义Map集合时、参数调用名就是自己设定的Key

Mybatis 映射中传入参数时,为以下结果自定义了别名

右侧为Map类型,左侧为类型别名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMWtEsQC-1660203247212)(后端框架.assets/image-20220808192144372.png)]

3.4 解决模糊查询的三个方法

由于普通的#{username} 在引号'' 中会失去占位符的效果

  1. select * from table where username like '%${usename}%'
  2. select * from table where username like "%"#{username}"%"
  3. select * from table where username like concat('%', #{username}, '%')
3.5 获取自增的主键

增删改的操作下返回值都是一个int的该变量,所以我们要手动设置一个主键KEY

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"></insert>

useGeneratedKeys : 表示当前添加功能使用了自增的主键

keyProperty : 将添加的数据的自增主键作为实体类对象参数的主键名

4. ResultMap

4.1 如果pojo包中属性名和数据库表中字段名不同的解决办法
  1. 由JDBC可知,查询结果会通过getter、setter的方法呈现。如果名称不一致,那么无法进行存储值。因此解决办法就是为SQL语句结果设置别名。将查询语句结果别名设置为成员属性名。

  2. 在Mybatis的核心配置文件中设置一个全局变量,可以自动将下划线映射为驼峰

    1. <setting>
           mapUnderscoreToCamelCase 将 蛇形命名法 映射为 小驼峰命名法
          <setting name="mapUnderscoreToCamelCase" value="true"/>
      </setting>
      
4.2 自定义映射关系
<!-- 
	resultMap: 自定义映射关系 id: 自定义映射关系名称 type: pojo包中的类名
	id: 关于主键的唯一标识
	result: 处理普通字段和实体类属性的映射关系
	column: 映射关系中的字段名称  (数据库中字段名称)
	property: 设置映射关系查询结果的属性名 (实体类中成员属性名称)
	association: 处理多对一的映射关系 (处理实体类类型的属性)
-->
<resultMap id="empResultMap" type="Emp">
	<id column="emp_id" property="empId"/>
    <result column="emp_name" property="empName"/>
    <result column="emp_gender" property="empGender"/>
    <result column="emp_age" property="empAge"/>
</resultMap>
4.3 多对一的处理方式

因为成员属性的类型可能是一个类,那么要将多张表查询到一张表中,就需要用到以下处理方式

  1. 级联处理方式:

    1. <resultMap id="empResultMap" type="Emp">
      	<id column="emp_id" property="empId"/>
          <result column="emp_name" property="empName"/>
          <result column="emp_gender" property="empGender"/>
          <result column="emp_age" property="empAge"/>
          <!--注意以下两个result-->
          <result column="dept_id" property="dept.deptId"/>
          <result column="dept_name" property="dept.deptName"/>
      </resultMap>
      
      <select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
          select 
          t_dept.*, t_emp.* 
          from t_emp 
          left join t_dept 
          on t_emp.dept_id = t_dept.dept_id 
          where t_emp.emp_id = #{empId};
      </select>
      
  2. association:处理多对一的映射关系 (处理实体类类型的属性)

    1. <resultMap id="empResultMap" type="Emp">
      	<id column="emp_id" property="empId"/>
          <result column="emp_name" property="empName"/>
          <result column="emp_gender" property="empGender"/>
          <result column="emp_age" property="empAge"/>
          <!--
      		assciation: 处理多对一的映射关系 (处理实体类类型的属性)
      		property: 设置需要处理映射关系的属性的属性名
      		javaType: 设置需要处理映射关系的属性的类型
      	-->
          <association property="dept" javaType="Dept">
          	<id column="dept_id" property="deptId"/>
          	<result column="dept_name" property="deptName"/>
          </association>
      </resultMap>
      
      <select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
          select 
          t_dept.*, t_emp.* 
          from t_emp 
          left join t_dept 
          on t_emp.dept_id = t_dept.dept_id 
          where t_emp.emp_id = #{empId};
      </select>
      
  3. 分步查询方式:

    1. 通过两个不同的接口,调用不同的方法。即用子表的结果查询父表。

    2. <resultMap id="empResultMap" type="Emp">
      	<id column="emp_id" property="empId"/>
          <result column="emp_name" property="empName"/>
          <result column="emp_gender" property="empGender"/>
          <result column="emp_age" property="empAge"/>
          <!--
              property: 查询哪个成员属性
              select: 下个查询方法名
              column: 将子表查询结果中的哪个字段作为下一个表进行查询的条件
          -->
          <association property="dept"
                       select="com.cczj.mybatis.mapper.DeptMapper.方法名"
                       column="dept_id">
          </association>
      </resultMap>
      
      <select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
          select * from t_emp where t_emp.emp_id = #{empId}
      </select>
      
    3. <resultMap id="empResultMap" type="Emp">
      	<id column="dept_id" property="deptId"/>
          <result column="dept_name" property="deptName"/>
      </resultMap>
      
      <select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
          select * from t_dept where t_dept.dept_id = #{deptId}
      </select>
      
  4. 分布查询有什么优势呢?

    1. 可以实现延迟加载,但是必须在核心配置文件中进行全局配置

    2. <settings>
          <!-- 
      		lazyLoadingEnabled: 是否开启延迟加载 默认为false
      		aggressiveLazyLoading: 
      			是否开启按需加载 默认为false
      			按需加载开启后,只会进行分步执行的第一步结果
      	-->
          <settins name="lazyLoadingEnabled" value="true"/>
          <settins name="aggressiveLazyLoading" value="false"/>
      </settings>
      
4.4. 一对多的处理方式
  1. collection: 处理一对多和多对多的方式

    1. <resultMap id="empResultMap" type="Emp">
          <id column="dept_id" property="deptId"/>
          <result column="dept_name" property="deptName"/>
          <!--
              property: 设置集合的名称
      		ofType: 该集合类的类型
          -->
          <collection property="emps" ofType="Emp">
              <id column="emp_id" property="empId"/>
              <result column="emp_name" property="empName"/>
          	<result column="emp_gender" property="empGender"/>
          	<result column="emp_age" property="empAge"/>
          </collection>
      </resultMap>
      
      <select id="getEmpAndDeptByEmpId" resultMap="empResultMap">
          select 
          t_emp.* , t_dept.*
          from t_dept
          left join t_emp 
          on t_emp.dept_id = t_dept.dept_id 
          where t_emp.emp_id = #{empId};
      </select>
      
  2. 分布查询方法和多对一基本一致

5. 动态SQL

5.1 If where
  1. 动态拼接SQL语句的第一种写法:
<select id="方法名" resultType="结果类型">
	select * from t_emp where 1=1
    <if test="empName != null and empName != ''">
    	and empName = #{empName}
    </if>
    <if test="empAge != null and empAge != ''">
    	and empAge = #{empAge}
    </if>
</select>
  1. 动态拼接SQL语句的第二种写法:
<select id="方法名" resultType="结果类型">
	select * from t_emp
    <where>
        <if test="empName != null and empName != ''">
            empName = #{empName} and
        </if>
        <if test="empAge != null and empAge != ''">
            empAge = #{empAge}
        </if>
    </where>
</select>
5.2 trim

trim:

prefix、suffix: 在标签中内容前面或者后面添加指定内容

prefixOverrides、suffixOverrides: 在标签内容前面或者后面去掉指定内容

<select id="方法名" resultType="结果类型">
	select * from t_emp
    <trim prefix="where" suffixOverrides="and">
        <if test="empName != null and empName != ''">
            empName = #{empName}
        </if>
        <if test="empAge != null and empAge != ''">
            empAge = #{empAge}
        </if>
    </trim>
</select>
5.3 choose、when、otherwise

if、if else 和 else

<select id="方法名" resultType="结果类型">
	select * from t_emp
    <where>
    	<choose>
        	<when test="empName != null and empName != ''">
            	empName = #{empName}
            </when>
            <when test="empAge != null and empAge != ''">
            	empAge = #{empAge}
            </when>
            <otherwise>
            	类似于else ... 也可以看作default
            </otherwise>
        </choose>
    </where>
</select>
5.4 foreach

collection: 表示当前传入集合的参数名 (建议通过@param进行手动设置)

item: 表示当前循环的参数名 (类似于foreach中的每一次循环的结果)

separate: 循环分隔符

open: 当前循环所有的内容以什么开始 close:循环的内容以什么结束

index: 当前索引的下标是多少

<insert id="insertMoreEmp">
	insert into t_emp values
    <foreach collection="emps" item="emp" separator=",">
        	(null, #{emp.empName}, #{emp.empAge})
    </foreach>
</insert>
<delete id="deleteMoreEmp">
	delete from t_emp where emp_id in
    (
    	<foreach collection="empIds" item="empId" separator=",">
    		#{empId}
    	</foreach>
    )
    delete from t_emp where emp_id in
    <foreach collection="empIds" item="empId" separator="or">
        emp_id = #{empId}
    </foreach>
</delete>
5.5 SQL片段

将部分sql语句通过sql片段存起来,用到的时候通过include标签进行调用

<sql id="empColumns">
	emp_id,emp_name,emp_age,emp_gender
</sql>
<select>
	select <include refid="empColumns"></include> from t_emp
</select>

6. Mybatis Cache

6.1 Mybatis 的一级缓存

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

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

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作(即使更新的不是缓存数据)
  4. 同一个SqlSession两次查询期间手动清空了缓存
    1. sqlSession.clearCache(); 该方法用于清除一级缓存
6.2 Mybatis 的二级缓存

二级缓存是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后如果再次执行相同的查询语句,结果就会从缓存中获取

二次缓存的开启条件:

  • 在核心配置文件中,设置全局配置属性cacheEnabled="true" 默认为true,不需要设置
  • 在映射文件中设置标签cache (仅仅添加一个<cache/>即可)
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

使二级缓存失效的情况:

两次查询过程中执行了任意增删改的操作,会使一级和二级缓存同时失效

6.3 Mybatis 二级缓存中的配置信息

eviction: 缓存回收策略,有这几种回收策略

  • LRU - 最近最少回收,移除最长时间不被使用的对象
  • FIFO - 先进先出,按照缓存进入的顺序来移除它们
  • SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
  • WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象

默认是 LRU 最近最少回收策略

  • flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
  • readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
  • size : 缓存存放多少个元素
  • type: 指定自定义缓存的全类名(实现Cache 接口即可)
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKQwPEJ0-1660203247214)(后端框架.assets/33b6b5887c9a4a198964ca58ae86f04e.png)]

6.4 Mybatis 缓存查询的顺序

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

如果二级缓存也没有命中,那么再查询一级缓存 (当一级缓存没有结束时的情况下会发生该情况)

如果一级缓存也没有命中,那么再查询数据库

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

捶捶自己

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

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

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

打赏作者

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

抵扣说明:

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

余额充值