Mybatis:半自动,轻量级的框架

一、Mybatis入门

mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
步骤:

  1. 根据xml配置文件(全局配置信息)创建一个SqlSessionFact
    有数据源的一些运行环境信息
  2. sql映射文件;配置了每一个sql,以及sql的分装规则
  3. 将sql映射文件注册在全局配置文件中
  4. 写代码:
    1)根据全局配置文件得到SqlSessionFactory
    2)使用SqlSession工厂,获取到的sqlSession对象
    一个sqlSession就是代表和数据库的一次会话,用完关
    3)使用sql的唯一标志来告诉MyBatis执行哪个sql
  1. 接口式编程
    原生: Dao ===》 DaoImpl
    mybatis:Mapper ===》 xxMapper.xml
  2. SqlSession代表和数据库的一次会话:用完必须关闭
  3. SqlSession和Connection一样都是线程不安全的,每次使用都应该去获取一个新的对象
  4. mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象
    (将接口和XXX.xml绑定)
    AdminMapper adminMapper = session.getMapper(AdminMapper.class);
  5. 两个重要的配置文件:
    mybatis的全局配置文件:包含了数据库连接池信息,事务管理器信息等…系统运行环境信息
    sql映射文件:保存了每一个sql语句的映射信息, 将sql抽取出来

二、MyBatis-全局配置文件

  • MyBatis的配置文件包含了影响MyBatis行为甚深的设置(settings)和属性(properties)信息。文本档的顶层结构如下:
    在这里插入图片描述

配置文件详细信息:https://mybatis.org/mybatis-3/zh/configuration.html

三、MyBatis映射文件

1. 基本概况

在这里插入图片描述

  1. MyBatis允许增删改直接定义以下类型返回值
    Integer,Long,Boolean,void
  2. `SqlSession session = sqlSessionFactory.openSession(true)) //openSession中的参数为true时,自动commit
  3. 自增主键的获取
    useGeneratedKeys=“true”:使用自增主键的获取
    keyProperty:指定对应的主键属性,也就是mybatis获取到主键以后,将这个值封装给javaBean的哪个属性
<insert id="addAdmin" parameterType="com.zc.bean.Admin"
            useGeneratedKeys="true" keyProperty="id">
        insert into admin(username,password)
        values(#{username},#{password})
</insert>

2.参数处理

  • 单个参数:Mybatis不会做任何特殊处理
    #{参数}: 取出参数值
  • 多个参数:mybatis会做特殊处理
    1. 多个参数会被封装成一个map
    2. key:param1 … paramN
    3. value:传入的参数值
    4. #{}就是从map中获取指定的key

如果用获取单个参数的方法去获取参数值,则会报如下异常

  • 异常:
    rror querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
    Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
  • 操作:
    方法:Admin getAdminByIdAndName(Long id,String username);
    取值:#{id},#{username}
  • 解决方法:
    1. 明确指定封装参数时map的key:@Param(“id”) Long id, @Param(“username”)
    #{对应的key}就可以取出对应的参数值
    2. POJO:如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入POJO
    #{属性名}:取出传入的pojo的参数值
    3. Map:如果多个参数不是业务逻辑的数据模型,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
    #{key}:取出key对应的value
    4. TO如果多个参数不是业务模型中的数据,但是要经常使用,推荐编写一个TO(Transfer Object)数据传输对象
  • 特别注意:如果是Collection(List,Set)类型或者是数组,也会特殊处理,
    1. 也是把传入的list或数组分组在map中
    2. key:
      Collection——collection
      List——list
      数组——array
    3. 方法:Admin getAdminByIds(List<Long> ids)
      取值:取出第一个id的值:#{list[0]}

总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key,#{key}就可以取出map中的值

  • 参数值的获取
    1. #{}:可以获取map中的值或者pojo对象属性的值
    2. ${}:可以获取map中的值或者pojo对象属性的值
    3. 例子:
      ① select * from admin where id = ${id} and username = #{username}
      ② Preparing: select * from admin where id = 23 and username = ?
      ③ Parameters: Tom(String)
    4. 区别
      ① #{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
      ② ${}:取出的值直接拼装在sql语句中;会有安全问题
      ③ 大多数情况下,我们取参数的值都是使用 #{}
      原生 jdbc 不支持占位符的地方我们就可以使用\${}进行取值
      比如分表、排序。。。:按照年份分表拆分
      select * from ${year}_salary where xxx
      select * from tbl_admin order by ${f_name} ${order}
  • #{}:更丰富的用法,规定参数的一些规则:javaType,JdbcType,mode(存储过程),numericScale,resultMap,typeHandle,jdbcTypeName,expression
    1. jdbcType通常需要在某种特定的条件下被设置:在我们数据为null的时候,有些数据库可能无法识别mybatis对null的默认处理。比如Oracle(会报错:JdbcType OTHER:无效的类型),因为mybatis对所有的null都映射的是原生jdbc的OTHER类型,Oracle不能正常处理

    2. 由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;有两种方法解决
      ① #{email,jdbcType = NULL}
      在这里插入图片描述

      ② jdbcTypeForNull = NULL
      在这里插入图片描述

3. select元素

  • id:唯一标识符,用来引用这条语句,需要和接口的方法名一致
  • parameterType:参数类型,可以不传,MyBatis会根据TypeHandle自动推断
  • resultType:返回值类型,别名或者全类名,如果返回的是集合,定义集合中元素的类型,不能和resultMap同时使用
  1. resultType,返回值为集合:
//返回值为List
    List<Admin> getAllAdmin();

    //返回一条记录的Map:key就是列名,值就是对应的值
    Map<String,Object> selectAdminByIdReturnMap(@Param("id") Long id);

    //返回多条记录分装成一个map,map<Long,Admin> :键是这条记录的主键,值是记录封装后的javaBean
    //告诉mybatis封装这个map的时候使用哪个属性作为map的key
    @MapKey("username")
    Map<Long,Admin> selectAdminByUsernameLikeReturnMap(@Param("username") String username);
<!--    返回集合-->
<!--    返回List-->
    <select id="getAllAdmin" resultType="Admin">
        select * from admin
    </select>
<!--    返回单挑记录的Map-->
    <select id="selectAdminByIdReturnMap" resultType="map">
        select * from admin
        where id = #{id}
    </select>
<!--    返回多条记录的Map-->
    <select id="selectAdminByUsernameLikeReturnMap" resultType="Admin">
        select * from admin
        where username like #{username}
    </select>

4. resultMap,自定义resultMap,实现高级结果集映射

在这里插入图片描述

<!--  自定义某个javaBean的映射规则  -->
    <resultMap id="myAdmin" type="com.zc.bean.Admin">
<!--        id 指定主键列的分装
            column:指定哪一列
            property:指定对应的javaBean属性
-->     <id column="id" property="id"/>
<!--        定义普通的封装规则-->
        <result column="username" property="username"/>
        <result column="password" property="password"/>
<!--        其他不指定的列会自动封装,但是建议,只要写resultMap,就把全部的列的映射都写上-->
    </resultMap>
<!--    resultMap:自定义结果集映射规则-->
<!--    Admin getAdminById(Long id);-->
    <select id="getAdminById" resultMap="myAdmin">
        select * from admin where id = #{id}
    </select>

例子一:对象中包含对象

Beauty getBeautyById(Long id);

    List<Beauty> getAllBeauty();
    
    List<Beauty> getAllBeauty();
}

/******************************************/

/**
 * @author ZC
 * @Description
 * @date 2020-07-20 16:55
 */
@Data
public class Beauty {
    private Long id;
    private String name;
    private String sex;
    private Date bornDate;
    private String phone;
    private Boys boyfriend;

}

/*******************************************/
/**
 * @author ZC
 * @Description
 * @date 2020-07-20 17:02
 */
@Data
public class Boys {
    private Long id;
    private String boyName;
    private int userCP;
}
<?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.zc.dao.BeautyMapper">

<!--    联合查询:级联属性封装结果集-->
    <resultMap id="myBeauty1" type="Beauty">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <result column="born_date" property="bornDate"/>
        <result column="phone" property="phone"/>
        <result column="bid" property="boyfriend.id"/>
        <result column="boy_name" property="boyfriend.boyName"/>
        <result column="userCP" property="boyfriend.userCP"/>
    </resultMap>
    <select id="getBeautyById" resultMap="myBeauty1">
        select
        g.id id,
        g.name name,
        g.sex sex,
        g.born_date born_date,
        g.phone phone,
        b.id bid,
        b.boy_name boy_name,
        b.userCP userCP
        from beauty as g
        inner join boys as b on g.boyfriend_id = b.id
        where g.id = #{id}
    </select>

    <!--   使用association定义关联的单个对象的封装规则 -->
    <resultMap id="myBeauty2" type="Beauty">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <result column="born_date" property="bornDate"/>
        <result column="phone" property="phone"/>
<!--        association 可以指定联合的javaBean
            property:指定哪个属性是联合的对象
            javaType:指定这个属性对象的类型(不能省略)
-->     <association property="boyfriend" javaType="Boys">
            <id column="bid" property="id"/>
            <result column="boy_name" property="boyName"/>
            <result column="userCP" property="userCP"/>
        </association>
    </resultMap>
    <select id="getAllBeauty" resultMap="myBeauty2">
        select
        g.id id,
        g.name name,
        g.sex sex,
        g.born_date born_date,
        g.phone phone,
        b.id bid,
        b.boy_name boy_name,
        b.userCP userCP
        from beauty as g
        inner join boys as b on g.boyfriend_id = b.id
    </select>

    <!--   使用association中的select属性查询 -->
    <resultMap id="myBeauty3" type="Beauty">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <result column="born_date" property="bornDate"/>
        <result column="phone" property="phone"/>
        <!--        association 可以指定联合的javaBean
                    select:表明当前属性是调用select查出的结果
                    column:指定将哪一列的值传个这个方法
        -->
        <association property="boyfriend" javaType="Boys"
                     select="com.zc.dao.BoysMapper.getBoyById" column="boyfriend_id"/>
    </resultMap>
<!--   可以使用延迟加载(懒加载、按需加载)
        只需要在使用association的select基础上加上两个配置
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
-->

    <select id="getBeauty" resultMap="myBeauty3">
        select * from beauty where 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.zc.dao.BoysMapper">
    <select id="getBoyById" resultType="Boys">
        select * from boys
        where id = #{id}
    </select>
</mapper>

例子二:对象中包含集合

<!--    collection 嵌套结果集的方式,定义关联的集合类型元素的封装规则-->
    <resultMap id="myBoys1" type="Boys">
        <id column="id" property="id"/>
        <result column="boy_name" property="boyName"/>
        <result column="userCP" property="userCP"/>
        <collection property="beauties" ofType="Beauty">
<!--            collection 定义关联集合类型的属性的封装规则
                ofType:指定集合里面的元素的类型    -->
            <id column="gid" property="id"/>
            <result column="name" property="name"/>
            <result column="sex" property="sex"/>
            <result column="phone" property="phone"/>
            <result column="born_date" property="bornDate"/>
        </collection>
    </resultMap>

    <select id="getBoyByIdPlus" resultMap="myBoys1">
        select b.id id,b.boy_name boy_name,b.userCP userCP,
        g.id gid,g.name name,g.sex sex,g.born_date born_date,g.phone phone
        from boys b
        left join beauty g
        on b.id = g.boyfriend_id
        where b.id = #{id}
    </select>



    <!--    collection分步查询-->
    <resultMap id="myBoys2" type="Boys">
        <id column="id" property="id"/>
        <result column="boy_name" property="boyName"/>
        <result column="userCP" property="userCP"/>
        <collection property="beauties" ofType="Beauty"
                    select="com.zc.dao.BeautyMapper.getBeautyByBoyId"
                    column="id"
                    fetchType="lazy"
                    />
    </resultMap>

    <select id="getBoyByStep" resultMap="myBoys2">
        select * from boys where id = #{id}
    </select>

<!-- BeautyMapper.xml中-->
 <select id="getBeautyByBoyId" resultType="com.zc.bean.Beauty">
        select * from beauty where boyfriend_id = #{boyId}
    </select>

扩展, 如果要传递多列的值:将多列的值封装成map传递
column ="{key1 = column1,key2 = column2}"
如:column ="{boyId = id}”.
fetchType = “lazy”:表示使用延迟加载
- lazy:延迟加载
- eager:立即加载

四、动态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="com.zc.dao.AdminMapperDynamicSQL">
    <!--    测试Mybatis的动态SQL
            if
            choose(when,otherwise):分支选择,switch-case
            trim(where(封装查询条件),set(封装修改条件))
            foreach
    -->
<!--    抽取可重用的sql片段。方便后面引用
        1. sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
        2. include来引用已经抽取的sql
        3. include还可以自定义一些property,sql标签内部就能使用自定义的属性
              取值的方式:${prop}  不能使用#{}
-->
    <sql id="insertColumn">
        username,password
    </sql>

    <select id="getAdminByConditionIf" resultType="com.zc.bean.Admin">
        select * from admin
        <!-- List<Admin> getAdminByConditionIf(Admin admin);
        测试if:
        test:判断表达式(OGNL)
            1.OGNL用法参照官方文档
            2.从参数中取值进行判断
            3.遇见特殊符号应该去写转义字符
            4.OGNL会进行字符串与数字的转换判断 “0”==0
            5.查询的时候如果某些条件没带,可能sql拼装有问题
                解决方法:1. 给where后面加上 1=1 ,之后的条件都 and xxx
                         2. mybatis使用where标签来将所有的查询条件都包括在内,
                            mybatis就会自动将sql中多余的and(第一个多余的and或or)和or去掉
        -->
        <where>
            <if test="id != null">
                and id = #{id}
            </if>
            <if test="username != null and username != ''">
                and username like #{username}
            </if>
            <if test="password != null and password != ''">
                and password like #{password}
            </if>
        </where>
    </select>


    <select id="getAdminByConditionTrim" resultType="com.zc.bean.Admin">
        select * from admin
        <!-- List<Admin> getAdminByConditionTrim(Admin admin);
            测试trim:
            trim标签体中是整个字符串拼串后的内容
            prefix="" :给拼串后的整个字符串加一个前缀
            prefixOverrides="" :前缀覆盖,去掉整个字符串前面多余的字符
            suffix="" :给拼串后的整个字符串加一个后缀
            suffixOverrides="" :后缀覆盖,去掉整个字符串后面多余的字符
        -->
        <trim prefix="where" prefixOverrides="and">
            <if test="id != null">
                and id = #{id}
            </if>
            <if test="username != null and username != ''">
                and username like #{username}
            </if>
            <if test="password != null and password != ''">
                and password like #{password}
            </if>
        </trim>
    </select>


    <select id="getAdminByConditionChoose" resultType="com.zc.bean.Admin">



        select * from admin
        <!--List<Admin> getAdminByConditionChoose(Admin admin);
            测试choose
            分支查询,相当于java中带了break的switch-case
        -->
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <when test="username != null and username != ''">
                    <!-- bind:可以将OGNL表达式的值绑定带一个变量中,方便后来引用这个变量的值 -->
                    <bind name="_username" value="'%'+username+'%'"/>
                    username like #{_username}
                </when>
                <when test="password != null and password != ''">
                    <bind name="_password" value="'%'+password+'%'"/>
                    password like #{_password}
                </when>
                <otherwise>
                    1 = 1
                </otherwise>
            </choose>
        </where>
    </select>

    <select id="getAdminByConditionForeach" resultType="com.zc.bean.Admin">
        select * from admin where id in
        <!--List<Admin> getAdminByConditionForeach(Admin admin);
            测试foreach
            collection:指定要遍历的集合:
                list类型的参数会特殊处理封装在map,map的key就叫list
            item:将当前遍历出的元素赋值给指定的变量
            #{变量名}就能取出变量的,也就是当前遍历出的元素
            separator:每个元素之间的分隔符
            open:遍历出所有结果拼接一个开始的字符
            close:遍历出所有结果拼接一个结束的字符
            index:遍历list的时候index是索引,item是当前值
                    遍历map的时候index表示的就是map的key,item就是map的value
        -->
        <foreach collection="ids" item="item_id" separator="," open="(" close=")">
            #{item_id}
        </foreach>

    </select>


    <update id="updateAdmin">
        update admin
        <!--void updateAdmin(Admin admin);
            测试set和if的结合,可根据传递过来的参数进行更新
            set可以去除掉拼接后的字符串中多余的 “,”
        -->
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            </if>
            <if test="password != null and password != ''">
                password = #{password},
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

    <insert id="addAdmins">
    <!--void addAdmins(@Param("admins") List<Admin> admins)
        测试使用foreach批量保存
        MySQL下批量保存:可以foreach遍历,mysql支持values(),(),()语法
    -->
        insert into admin(
            <!--引用外部定义的sql-->
            <include refid="insertColumn"/>
        )
        values
        <foreach collection="admins" item="admin" separator=",">
            (#{admin.username},#{admin.password})
        </foreach>
    </insert>
<!--    <insert id="addAdmins">-->
<!--        &lt;!&ndash;void addAdmins(@Param("admins") List<Admin> admins)-->
<!--            这种方式需要数据库里连接属性allowMultiQueries=true-->
<!--                这种分号分隔多个sqk语句,可以用于其他批量操作(批量删除或批量修改)-->
<!--        &ndash;&gt;-->
<!--        <foreach collection="admins" item="admin" separator=";">-->
<!--            insert into admin(username,password)-->
<!--            values(#{admin.username},#{admin.password})-->
<!--        </foreach>-->
<!--    </insert>-->

    <!--两个内置参数
	不只是方法传递过来的参数可以被用来判断,取值
	mybatis还有两个内置参数:
	1. _parameter:代表整个参数
			单个参数:_parameter就是这个参数
			多个参数:参数会被封装为一个map:_parameter就是代表这个map
	2. _databaseId:如果配置了databaseIdProvider标签
			_databaseId就是代表当前数据库的别名
-->

</mapper>

五、MyBatis-缓存机制

在这里插入图片描述

1.两级缓存

  1. 一级缓存(本地缓存):sqlSession级别的缓存。一级缓存是一直开启的,本质上是sqlSession的一个Map
    与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
    一级缓存失效情况:(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)
    ①:sqlSession不同
    ②:sqlSession相同,查询条件不同(当前一级缓存中没有此数据)
    ③:sqlSession相同,两次查询期间执行了增删改(这次增删改可能对当前数据有影响)
    ④:SqlSession相同,手动清除了一级缓存

  2. 二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存
    1.工作机制:
    ①:一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中
    ②:如果会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话信息,就可以参照二级缓存中的数据
    ③:sqlSession ===》AdminMapper ===》Admin
    sqlSession ===》 BeautyMapper ===》 Beauty
    不同namespace查出的数据会放在自己对应的缓存中(map)
    ④:效果:数据会从二级缓存中获取,查出的数据都会默认先放在一级缓存中,只有会话提交或关闭以后,一级缓存中的数据才会转移到二级缓存中

    1. 使用:
      ① 开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
      ② 去XxxMapper.xml中配置二级缓存<cache/>,详细信息查看myBatis的官方文档
      mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html

2. 和缓存相关的设置/属性

① 全局配置:cacheEnabled=true/false(关闭二级缓存,一级缓存一直可用)
② 每个select标签都有useCache=“true”
false:不使用缓存(关闭的是二级缓存,一级缓存一直可用)
③ 每个增删改标签都有 flushCache="true":即增删改执行完后会清除缓存(两级缓存全部清掉)
查询(select)标签默认 flushCache="flase":
sqlSession.clearCache():只清除当前session的一级缓存
⑤ 全局配置:localCacheScope=SESSION/STATEMENT:本地缓存作用域:
SESSION:一级缓存,当前会话的所有数据保存在会话缓存中
STATEMENT:可以禁用一级缓存

3. 缓存原理图

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值