mybatis总结:

  1. mybatis最基础的版本是一个基于配置文件的dao层框架,通过在配置文件中配置实现对数据库的操作

    1. 配置mybatis-config.xml:使用的日志类。
    2. 然后配置环境,需要配置事务管理器,数据源。
    3. 最后就是配置映射文件的扫描,最基础的资源扫描,后面是类扫描,最后是包扫描
    4. 因为映射文件中涉及到很多的类需要封装,而所有的类在分装的时候写全类名比较麻烦,所以mybatis支持给类型区别名
    5. 对于写资源文件,最重要的就有4样东西
      1. namespace: 没有配置对应的接口的时候可以随意,用来给不同的操作数据库的函数不同的命名空间。
      2. 方法的名字id
      3. 参数的拆分
      4. 结果的封装ResultMapper
    6. 数据库的调用:
      1. 读入总的配置文件
      2. 根据配置文件,创建sql的对话工厂
      3. 有了工厂创建一个个sqlSession
      4. 然后根据sqlSession调用方法,通过selectXXX和updataXXX()实现查询和修改,参数是资源文件中定义的id坐标,包含命名看见,和相应的传入的参数。
      5. 提交修改。

    根据上面的基础操作衍生处了一些列的问题:

    1. 每次传入的方法,或者返回的方法名称复杂,不友好

      解决方法:别名TypeAlias

      • 最低级的在mybatis-config.xml中配置所有的别名,注意:扫描包,大小写不区分
      • 可以通过在类对象上@Alias注解取别名,可以覆盖上一种方式
      • 适用范围所有的配置文件
    2. 传递参数复杂:

      • 原始的传递参数的方式有:(参数可以不写,自动判定)
        • 基础的数据类型
        • 传递一个对现象,在sql语句中直接使用它的属性进行拆包
        • 传递多个参数,将参数封装成一个HashMap的形式。Map<string,object>
      • 现在的方式:使用接口类来代替原本的xml配置
        • 核心思想: 设置接口,然后让mybatis自动注入查询方法。xml文件用来注入的方法,接口提供方法调用
        • 前提:
          • xml的命名空间和包名相似。
          • 方法名和id相似
          • 返回值的基础类型相同。如果是列表不影响。
          • ResultMapper需要进行重新封装数据,如果属性名完全形同,可以使用默认的封装器
        • 参数:
          • 除了基本的,不需要封装多参数,使用paramk(k>=1)argx(x>=0)开始
          • @Param("name") 给参数重命名,方便后米娜的引用
    3. 如何避免mapper文件的方法注入:

      解决方法:注解注入方法

      • @Select
      • @Update
      • @Delete
      • @Insert
    4. 插入的值为空值

<insert id="save" parameterType="User">
	insert into USER(USER_GUID,USER_NAME,USER_DESC)
		values (#{userGuid, jdbcType=VARCHAR}, #{userName, jdbcType=VARCHAR},#{userDesc, jdbcType=VARCHAR})
</insert>

  1. 自动生成主键

    Mybatis 配置文件 useGeneratedKeys 参数只针对 insert 语句生效,默认为 false。当设置为 true 时,表示如果插入的表以自增列为主键,则允许 JDBC 支持自动生成主键,并可将自动生成的主键返回。

      <insert id="insert" useGeneratedKeys="true" keyProperty="id">
          insert into flower(name,price) values(#{flowerName},#{flowerPrice})
      </insert>
  或者分成**两次查询**:**\<selectKey>**标签
      <insert id="insert">
          <selectKey order="AFTER" keyProperty="id" resultType="int">
              select @@identity;
          </selectKey>
          insert into flower(name,price) values(#{flowerName},#{flowerPrice})
      </insert>
      after是插入之后查询id返回给实体类
  1. 注解自动生成主键
@Mapper
public interface UserMapper
{
	@Insert("insert into tbl_user (name, age) values (#{name}, #{age})")
	@Options(useGeneratedKeys=true, keyProperty="userId", keyColumn="id")
	void insertUser(User user);
} 

  1. select语句太长,或者需要条件判断

    解决方法:OGNL标签动态生成sql语句。

  2. 多表查询

    • 分为联合查询嵌入查询

      • 联合查询,sql语句多表查询,查询的内容一起嵌入到一个resultMapper里面

      • 嵌入查询,查询里面指定另一个查询并指定连接词column。因为一般属性在字段设计的时候,每个类会有一个连接属性,数据库中不存在,需要嵌入一层查询。

       <select id="selectAll"  resultMap="rm2">
           select  * from class;
       </select>
       
       <resultMap id="rm2" type="class">
           <id property="classno" column="classno"/>
           <result property="className" column="className"/>
           <collection property="studentList" column="classno" ofType="stu" select="com.test.mapper.StuDao.selectByClassNo" fetchType="lazy"/>
       </resultMap>
           <select id="selectByClassNo" resultType="stu">
               select <include refid="stu"/> from stu
               <where>
                   classno = #{param1}
               </where>
           </select>
       
       collection中可以再配置,实现映射。
       javaType彻底封装之后的返回值,相当于二次封装。
       column连接属性
  • 注解驱动

  • @Result 实现结果集封装
    @Results 可以与@Result 一起使用,封装多个结果集
    @ResultMap 实现引用@Results 定义的封装
    • @Result代替了ResultMapper,本质是同一个东西
    • @Result代替了所有属性的配对
    • id代表主键
    • one一对一查询
    • many1对多查询
     @Results(id = "rm9",value ={
             @Result(id = true, property = "sid", column = "sid"),
             @Result(property = "sname",column = "sname"),
             @Result(property = "sex", column = "sex"),
             @Result(property = "classno",column = "classno"),
             @Result(property = "aClass", javaType = Class.class,column = "classno", one=@One(select = "com.test.mapper.ClassDao2.selectOne",fetchType = FetchType.LAZY))
     })//只需要配特殊的映射
     @Select("select sid, sname, sex, classno from stu")
     List<Student> selectAll1();
     
     
     @Select("select * from class where classno = #{param1}")
     public Class selectOne1();
  • 一对多查询的注意点

    • javaType="list"实际的返回类型
    • ofType:子元素类型,所有的名称要么是全类名,要么是别名
    <collection property="studentList" column="classno" javaType="list" ofType="stu" select="com.test.mapper.StuDao.selectByClassNo" fetchType="lazy"/>
    

1.配置文件的参数

mapper

package包扫描

	<mappers>
<!--		<mapper resource="com/test/mapper/StudentMapper.xml"/>-->
<!--		<mapper class="com.test.mapper.Student2"/>-->
		<package name="com.test.mapper"/>
	</mappers>

工作原理

  • 先去找所有的接口
  • 然后根绝接口名称去找对应的xml.
  • 所以接口名称必须和xml保持一致。
    • 如果不是包扫描没必要保持一致,只需要域名和接口的完全限定名一致就好了。

image

扫描xml

根据域名去找对应的接口。所以域名和接口要一致。

2.映射器配置文件和全局配置文件

parameterType

可写可以不写,会自动识别输入的参数。要写就需要写对,可省略

传参的三种方法

  • 传入普通数据类型
  • 传入对象
  • 传入HashMap,通过#{关键字};

传播参数的个数只能是一个。

ResultType

为int的时候可以省略

ResultMapper

如果类的属性和表格的字段不对应,我们需要构造一个属性映射器,ResultMapper的作用。

  • id类型映射器的标识符
  • type映射到类。
  • <id>和<reuslt>主键标签和元素标签。
  • collection是类对象的映射标签

在select标签中结果自动映射到映射器上。

不能自动对应就是Null

解决自动映射的方法

  1. mysql查询的表格取别名
  2. 手动映射ResultType

3.1映射器

可以构造一个接口名字和xml类的id相同,输入参数的类型相同,返回值的类型也相同(可以是单个实体也可以是数组),函数的名字也相同。xml文件生成的类会自动继承接口函数。然后就可以通过调用接口函数来调用语句。

1.insert

  1. 正常的插入

  2. 有主键自增的情况下,可以正常添加,或者向mybatis声明谁是自增主键,mybatis会自动调用jdbc.getGeneratedKeys()获取主键的自增数,然后赋值给当前的对象stuId,并添加数据。

image

stuId被自动赋值。

2.sql语句定义被重复使用的语句块<include/>

<sql id="userColumns"> id,username,password</sql>
<select id="selectUser" parameterType="int" resultType="hashmap">
select <include refid="userColumns"/> 
    from userinfo where id = #{id};
</select>

#和$的区别

#{}代表占位符,由preparedStatement自动通过调用setXXX()自动填装。

${}相当于拼接, 会自动取出数据,进行字符串的拼接,一般需要加引号

3.日志文件log4j

image

文件配置类log4j.properties一般放到src下面就是源目录下面,可以叫java也可以叫src

日志级别

日志有多个级别。低级别的可以输出高级别的问题。

例如上面的debug可以输出所有类型的信息而error只能输出error和fatal两类的信息。

全局级别和局部级别

image

  • log4j.rootLogger是全局级别的
  • 下面的两条是局部级别的debug,写的是命名空间,是我们mapper类配置文件的命名空间,该命名空间下的所有方法自动调整级别。
  • 命名空间可以自己随意定义

为什么是针对xml的namespace。

xml的域名就是xml的名字。如果使用mapper接口代理。

域名就不能随便叫了,可以看成接口的实现类,所以域名需要和接口的名字相同


4.typeAliases

属性值:

<typeAliase type="" alias="">

  • type 原始的类的完全限定
  • alias 别名的名称

给多个类取别名

直接导入包

  • <package name="">
  • alias不用写,别名就是类名不区分任何大小写

基本类型

已经给基本类型自己取了别名。所以可以直接用,首字母都是小写的。

如果有list,要写全路径

@Alias()

自动覆盖typeAliase里面的配置,使之失效。

3.2属性文件properties

dtd是规定标签的排放顺序的。

根据错误提示重新排放标签。

再mybatis.xml通过properties标签引用,resources,文件的路径

image

日志类型可以指定也可以不指定,不指定他会自己去找

4.基操--增删改查

三种类型:

  1. selectList("id的全限定")。

  2. selectOne("")

  3. 返回值为map。 selectMap("id",”key是哪个字段“)

image

优点:方便找对应的值,如果用主键作为key,可以快速的找到对应主键的内容

1.模糊查询

  • 注意点,字符串中#{}不会被解析,会被当做字符窜
  • 传入拼好的参数 “%baidu%” 或者传入再拼接 concat('%',param,'%')

2.增删改

修改手动提交

  • sqlSession.close()
  • 或者我们在构造openSession的时候传入true,openSession(true)

image

3.自动获取主键自增的值

可以用useGeneralKey自动调用主键自增函数,并赋值给插入对象。然后我们插入到对象就有主键了。

给对象获取主键。

一共两种方案,第一种方案更安全,本质是插入之后在查询,在多线程中是安全的。

​ 当多个人一起操作的时候,获取到的主键值可能出现错乱,故第二种方案不一定正确。

image

5.Mapper高级代理

mapper代理就是一种接口的绑定

  • 域名和类名保持一致
  • id需要和方法名保持一致

返回值了类型可以一定程度的不一致,包括参数类型。

传入多个参数

​ 在xml文件中,参数可以不写,会自动识别,如果写输入参数只能写一个,因此我们需要对输出的参数进行包装,可以用hashMap来包装。

​ 如果时用mapper的代理接口,我们可以在代理接口中设计诸多的参数,然后传入多个参数,此时在xml中不必写参数。

xml和mapper接口中的参数如何配对

  1. 在xml中使用paramx指代mapper接口方法中的参数。x代表的是位置。从1开始。
  2. 也可以使用argx代表。x从0开始

image

  1. 通过注解实现。注解告诉xml,每个参数在xml中对应的参数名。
  2. 传入一个类,自动拆包,将属性对应起来。如果传入多个类,则是需要用arg或param或注解指定哪个类的属性。

image

4.动态sql

所有标签都是为了构造合法的sql语句,主要用于动态的生成字符窜。

1.if标签

  • test是判断条件,里面写OGNL
    <select id="selectMore" resultMap="flowerInfo">
        select * from flower where 1=1
--         OGNL表达式
        <if test="param1!=null and param1!=''">
            and name=#{param1}
        </if>
        <if test="param2!=null and param2!=0">
            and price=#{param2}
        </if>
    </select>

2.where标签

作用,自动去添加where关键字。自动的去掉第一个and。

    <select id="selectMore" resultMap="flowerInfo">
        select * from flower 
--         OGNL表达式
        <where>
            <if test="param1!=null and param1!=''">
                and name=#{param1}
            </if>
            <if test="param2!=null and param2!=0">
                and price=#{param2}
            </if>
        </where>
    </select>

3.choose标签

多种情况选择一种,类似于if else

    <select id="selectMore2" resultMap="flowerInfo">
        select * from flower
        <where>
            <choose>
                <when test="param1!=null and param1=''">
                    name=#{param1}
                </when>
                <!--成立了就不继续执行-->
                <when test="param2!=0">
                    and price=#{param2}
                </when>
                <otherwise>
                    1 = 1
                </otherwise>
            </choose>
        </where>
    </select>

4.set标签

只能用在修改里面。

会自动的去添加set关键字并删除最后的逗号。

<update>
update flower
    <set>
    	<if test="param1!=null">
        	name = #{param1},
        </if>
        <!--自动去除的逗号-->
        <if test="param2!=0">
        	price=#{param2}
        </if>
    </set>
    where id = #{param3};
</update>

5.trim标签

前后缀处理

  • prefix添加前缀
  • prefixOverrides去除前缀,如果存在
  • suffix添加后缀
  • suffixOverrides去除后缀,如果存在
  • 功能和where和set重合
<update>
update flower
    <!--添加前缀set,去除可能的后缀,-->
    <trim prefix="set" suffixOverrides=",">
    	<if test="param1!=null">
        	name = #{param1},
        </if>
        <!--自动去除的逗号-->
        <if test="param2!=0">
        	price=#{param2}
        </if>
    </trim>
    where id = #{param3};
</update>

6.forEach

如何删除的内容是个数组或者集合,那么sql语句就很难写,因为数组或者集合中的元素没任何规律。

需要对集合进行遍历,forEach将集合的元素变成一个字符窜。

实际上传入多个值的时候,mybatis自动封装成一个map,List的关键字是list,数组的关键字是array,所以我们通过关键字使用变量。

<delete>
	delete from flower where id in
   <!-- collection 集合类型 list或者array item指向集合中的每个元素-->
    <foreach collection="list" item="i" open="("  close=")", separator=",">
        ${i}
    </foreach>
</delete>

7.Bind

拼接字符窜的形成一个新串的工具,功能类似于concat

value值的书写就是字符窜相加。

<select id="selectMore4" resultType="flowerInfo">
    select * from flower
    <where>
        <if test="param1!=null">
            <!--新的变量名和变量值,OGNL语言字符窜需要用‘’标识出来-->
        	<bind name="pa" value="'%'+param1+'%'"/>
            name like #{pa}
        </if>
    </where>
</select>

8.sql标签

select语句查*号效率比较低。

用来规定公共的代码片段,通过include来引用

<include refid="sqa"/>

sql语句可以定义代码块

<sql id="sqa">
	id,name,price,production
</sql>

<select id="" resultType="flowerInfo">
	select <include refid="sqa"/> from flower
</select>

5.多表查询

image

N+1模式

​ 对于数据库中的外键,我们在java类中用一个类引用成员代替外键连接的表来存储两张联合搜索的结果

association一对一关联

在手动映射器上设置关联函数。

  • select 希望执行那一条sql语句。可以直接用namespace+id来定位查询语句。
  • column 作为参数的列(就是连接的列)
  • javaType返回值类型
  • property 结果保存到到哪个属性

利用的是手动映射ResultMap,无法找到某个对应映射的数据的时候,再次激活一个查询语句。

    <resultMap id="rm2" type="stu">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <result property="sex" column="sex"/>
        <result property="classno" column="classno"/>
<!--        aclass属性的类型是class,关联的语句时selectOne(可以是sql语句也可以是引用一个查询块) column是连接字段-->
        <association property="aClass" javaType="class" column="classno" select="com.test.mapper.ClassDao.selectOne"/>
    </resultMap>

    <select id="selectAll" resultMap="rm2">
        select <include refid="stu"/> from stu;
    </select>

association支持多层关联

  • 不仅支持关联函数的返回结果
  • 也可以关联一个手动映射器。
  • 二次关联可以写成嵌套型

分开写

<resultMap type="Card" id="cardMap">
      <id property="cardId" 
            column="card_id"/>
      <result property="cardNum" 
                   column="card_num"/>
      <result property="address" 
                   column="address"/>
</resultMap>


<resultMap type="User" id="userMap">
     <result property="userName" 
                  column="user_name"/>
     <result property="age" 
                  column="age"/>
     <association property="card" 
                             resultMap="cardMap">
     </association>
</resultMap>

嵌套型

<resultMap type="User" id="userMap">
   <result property="userName" 
                  column="user_name"/>
   <result property="age" 
                  column="age"/>
   <association 
          property="card"
          column="card_id" 
          javaType="Card">
      <id property="cardId" 
                column="card_id"/>
      <result property="cardNum"
                       column="card_num"/>
      <result property="address" 
                       column="address"/>
    </association>
</resultMap> 
  • column:当关联的语句需要多个参数的时候,我们用map将多个参数进行封装。

    		<collection property="htAuthorityDTO"  ofType="com.sailod.shiro.dto.HtAuthorityDTO"
    		 select="selectAuthority" column="{htAuthorityId2 = htAuthorityId ,currentUserId2 = currentUserId}"   >
    
  • javaType: 指定返回的类型java.util.ArrayList

  • ofType集合所包含的类型。

collection:一对多关联

对应一个集合,集合对应的是student

<resultMap id="BaseResultMap" type="com.xxx.modules.xxx.entity.Question" >
		<id column="id" 		 property="id"      jdbcType="VARCHAR" />
		<result column="content" property="content" jdbcType="VARCHAR" />
		<result column="type"    property="type"    jdbcType="VARCHAR" />
		<result column="sort"    property="sort"    jdbcType="INTEGER" />
		<collection property="options" javaType="java.util.ArrayList" ofType="com.xxx.modules.data.entity.QuestionOption">
			<id column="o_id" property="id" jdbcType="VARCHAR" />
			<result column="o_content" property="content" jdbcType="VARCHAR" />
			<result column="o_sort" property="sort" jdbcType="INTEGER" />
		</collection>
		<!-- 列的别名 o_id,o_content,o_sort , 起别名是因为主子表都有这几个字段
			 这里要写 ofType, javaType还是可以不写 -->
	</resultMap>

多表查询语句

如果搜索的内容是双表连接,可以把公共字段省略,两张表的搜索结果,我们根据类直接的关系可以分为主类和附加类。 主类的重叠字段不重复,附加类自动变成列表,接收字段一个都不能

<resultMap id="rm3" type="class">
	<id property="classno" column="classno"/>
    <result property="className" column="className"/>
    <collection property="list" ofType="stu">
    	<id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <result property="sex" column="sex"/>
        <result property="classno" column="classno"/>
    </collection>
</resultMap>

总结

  1. 业务代码,通过java代码实现
  2. N+1,用association 和collection集合实现,多次查询
  3. 多表查询SQL。(ResultMap可以通过注解实现。)

image

加载方式

加载方式分为两种:延迟加载积极加载,就是加载属性时使用,一般用于多表查询,先查询一部分结果。

积极加载

如上面的例子,如果我们使用了sql关联语法,在选所有班级时候对应的学生也会加载 ,这叫积极加载

延迟记载就是不先加载关联。

  • 通过全局配置实现, setting name="lazyLoadingEnabled" value="true",这个每个属性只有用的时候才加载
  • 关闭每个属性的懒加载。<setting name="aggressiveLazyLoading" value="false"/>
  • 开启特殊属性进行懒加载。使用标签(collection)的 fetchType="lazy"
<setting name="lazyLoadingEnabled" value="true"/>


<setting name="aggressiveLazyLoading" value="false"/>

特点:

懒加载就是使用属性时加载对应的属性

缓存

​ 直接从缓存中拿数据,减小服务器的压力,尤其是查询次数多的情况。mybatis分为一级缓存二级缓存

List<Class> classes = classDao.selectAll();
classDao.selectAll();

第二次查询在缓存中读取。 一级缓存自动开启,为sqlSession缓存。缓存的级别在sqlSession这个级别

不同的sqlSession不能共用缓存

二级缓存

缓存:缓存的是记录。

称为sqlSessionFactory级别的缓存,可以实现不同sqlSession之间的数据缓存。

开启二级缓存

实际上是要先开启全局的缓存(默认是开启的)“cacheEnabled”,再开启单个文件的。

在映射文件mapper里面加<cache>

缓存之间的关系

image

查询的时候,二级缓存-->一级缓存-->DB,其中数据查找到的先保存在一级缓存,当close()或者commit的时候一级缓存的内容移动到二级缓存中

image

image

cache的细节

  • 开启二级缓存

    @CacheNamespace(blocking = true)
    
  • type自定义缓存类,需要继承缓存类org.apache.ibatis.cache.Cache

  • readOnly缓存内容只读。

  • 缓存策略:什么时候缓存被清空,例如LRU,最近最少使用。

  • flushInterval刷新的间隔,毫秒为单位。添加删除修改会导致缓存中的数据消失,缓存清空。

  • 关闭单个查询语句的缓存,通过语句的useCache标签。

注解代替

@Select

将语句设置到方法上面

注解一般只是用单表的增删改查和多条件查询。

@Update

@Delete
@Insert

@Result

源码解读

Mybatis.XML的解析

1.InputStream,

读取xml文件,变成文件输入流

2.设计模式

工厂设计模式,用工厂批量的生成对象。

build(inputStream)-->xmlConfigBuilder()进行解析--->解析根节点,获得节点对象,然后用反射去解析,获取配置类Configuration。--->然后用配置类构造工厂

3.openSession()

利用默认工厂类DefaultSqlSessionFactory创建openSession()。 -->创建一个DefaultSqlSession-->

4.getMapper

采用动态代理模式,也是一种设计模式

帮我们动态创建实现类。

Configration中调用getMapper

产生代理对象,帮我们产生实现类:(JDK动态代理)

image

MapperProxy:

  • MapperMethod,里面有excute方法

标签中定义的 insert update delete 都会调用调用sqlSession中对应的方法update()方法。

查询都是调用selectList()方法。

update()

image

插入空值

mybatis在插入数据的时候,如果是空值会报错,解决这个的方法是,在插入的变量后面交代清楚输入的sql类型。例如

    @Insert("insert into tbl_user(id,email,username,password,imageId,personalInfo,createTime) values(#{id},#{email},#{username},#{password},#{imageId,jdbcType=VARCHAR},#{personalInfo,jdbcType=VARCHAR},#{createTime})")
    void insert(User user);
posted on 2021-11-24 19:03  很绝望  阅读(239)  评论(0编辑  收藏  举报