Mybatis学习笔记


MyBatis框架

支持SQL的持久层框架,使用XML或注解配置
MyBatis3.5.0核心包及第三方支持
中文文档

JDBC的劣势:

  1. 频繁创建,释放连接
  2. 硬编码

MyBatis

使用数据库连接池管理数据库连接

使用XML映射文件配置SQL语句


MyBatis工作原理

在这里插入图片描述

读取MyBatis的配置文件。

mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。

加载映射文件。

映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

构造会话工厂。

通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。

创建会话对象。

由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。

Executor执行器。

MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。

MappedStatement对象。

在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。

输入参数映射。

输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。

输出结果映射。

输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

MyBatis工作原理

在这里插入图片描述

使用方法

  1. 配置文件:SqlMapConfing.xml

    <?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 resource="db.properties"/>
      <!--包别名-->
        <typeAliases>
            <package name="cn.edu.xynu.ssm.POJO"/>
        </typeAliases>
    
        <!-- 和spring整合后 environments将废除 -->
        <environments default="development">
            <environment id="development">
                <!-- 使用JDBC事务管理,事务控制由mybatis -->
                <transactionManager type="JDBC"/>
                <!-- 数据库连接池,由mybatis -->
                <dataSource type="POOLED">
                    <!--                com.mysql.cj.jdbc.Driver,5.1以上版本mySqlDriver-->
                    <!--                注意要引入lib包-->
                    <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>
            <!--单个文件映射:resource属性一次加载一个文件,指定mapper.xml文件位置,
            mapper.xml文件通过namespace来查找mapper.java接口文件-->
            <mapper resource="config/mapper/UserMapper.xml"/>
            <!--class方式映射:通过mapper接口映射单个文件
           遵循规则:mapper.java和mapper.xml放在同一个目录下,且命名相同-->
            <!--        <mapper class="com.wyscoder.mybatis.mapper.StudentMapper"/>-->
            <!--批量mapper接口的扫描
            遵循规则:mapper.java的接口文件和mapper.xml放在同一目录下,且命名相同-->
            <!--        <package name="com.wyscoder.mybatis.mapper"/>-->
        </mappers>
    
    </configuration>
    

    db.properties

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/db_mybatis?characterEncoding=utf-8
    jdbc.username=root
    jdbc.password=54931090534
    
  2. 映射文件

    <?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.edu.xync.ssm.dao.UserMapper">
    <!--    表别名映射-->
        <resultMap id="UserResultMap" type="TUserEntity">
            <id column="idn" property="id"/>
            <result column="w" property="workId"/>
            <result column="n" property="name"/>
            <result column="pw" property="password"/>
    <!--        <result column="em" property="email"/>-->
    <!--        <result column="ph" property="phone"/>-->
        </resultMap>
    
    <!--    id与接口文件方法名一致-->
    <!--    parameterType:表示输入参数的类型-->
    <!--    resultType:表示输出参数的类型-->
        <select id="selectUserById"  resultType="TUserEntity">
            select * from t_user where id = #{id}
        </select>
    
        <select id="findAll" resultMap="UserResultMap">
    --         select u.id as idn,u.workId as w,u.name as n,u.password as pw,u.email as em,u.phone as ph from t_user u
            select u.id as idn,u.name as n,u.workId as w,u.password as pw from t_user u
        </select>
    <!--#{占位符},占位符可以随便写-->
        <insert id="UserSave" parameterType="TUserEntity">
            insert into t_user(id,workId,name,password)
            values (#{id},#{workId},#{name},#{password})
        </insert>
    <!--    parameterType只有一个参数时,参数类型,(可用map传多参,也可以传基本类型,包装类)-->
    <!--或在接口中使用@param注解-->
        <update id="UserUpdateById" parameterType="map">
            update t_user set name = #{name}
            where id = #{id}
        </update>
    
        <delete id="UserDeleteById" parameterType="int">
            delete from t_user where id = #{id}
        </delete>
    <!--UserDTO 包装类-->
        <select id="findUserByGoodsAndPhone" parameterType="UserDTO" resultType="TUserEntity">
            select * from t_user u,t_user_goods ug where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%'
        </select>
    </mapper>
    

    3.会话工厂

package util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {

    private static SqlSession sqlSession = null;

    static {    //静态代码段
        //        读取全局配置文件
        String path = "SqlMapConfig.xml";
//        通过Resources获取配置文件流
        InputStream resourceAsStream = null;
        try {
            resourceAsStream = Resources.getResourceAsStream(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
//        创建会话工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//        创建会话
        sqlSession = sqlSessionFactory.openSession();

    }

    public static SqlSession getSqlSession(){
        return sqlSession;
    }

}

4:test

  1. @Before:该标签函数在每个Test前执行
  2. @After:每个Test后执行
  3. @BeforeClass 使用此注解的方法在测试类被调用之前执行
  4. @AfterClass 使用此注解的方法在测试类被调用结束退出之前执行

获取sql对象

    private SqlSession sqlSession = MybatisUtil.getSqlSession();

代理模式:

引用大佬的好文Lin_Dong_Tian

  1. Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。
  2. 在初始化SqlSessionFactory时,Mapper 接口进行注册,注册在了名为 MapperRegistry 类的 HashMap中,
  3. key = Mapper class, value = 创建当前Mapper的工厂。
  4. SqlSessionFactory创建SqlSession。
  5. SqlSession中可以通过getMapper()拿到代理对象,SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
  6. 动态代理的 代理类是 MapperProxy ,这里边mapperMethod.execute(sqlSession, args)最终完成了增删改查方法的调用。
    @Test
    public void selectUserById() {
//        代理模式创建代理类
//通过getMapper方法使用动态代理实现对应的mapper接口实现类的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        TUserEntity User = mapper.selectUserById(1);
        System.out.println(User);
    }

原始调用:

    @Test
    public void findAll(){
//        原始调用
//未明确接口标签时,使用全路径
        List<TUserEntity> users = sqlSession.selectList("cn.edu.xync.ssm.dao.UserMapper.findAll");
        for (TUserEntity s:users){
            System.out.println(s);
        }
    }

动态SQL

if
<!--UserDTO 包装类-->
    <select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
        select * from t_user u
        <where>
          <if test="user!=null">
              <if test="user.email!=null and user.email=!''">
               and u.email like '%${user.email}'
              </if
          </if>
          <if test="user!=null">
              <if test="user.phone!=null and user.phone=!''">
                and u.phone like '%${user.phone}'
              </if>
          </if>
        </where>
        <!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
    </select>
SQL片段
<!--必须有一个唯一ID-->
<sql id = user_query_statement>
  <if test="user!=null">
    <if test="user.email!=null and user.email=!''">
      and u.email like '%${user.email}'
    </if>
  </if>
  <if test="user!=null">
    <if test="user.phone!=null and user.phone=!''">
      and u.phone like '%${user.phone}'
    </if>
  </if>
</sql>


  <select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
    select * from t_user u
    <where>
      <!--使用include包含-->
      <include refid="user_query_statement"></include>
    </where>
  <!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
  </select>
forEach片段
<sql id = user_query_statement>
  <if test="user!=null">
    <if test="user.email!=null and user.email=!''">
      and u.email like '%${user.email}'
    </if>
  </if>
  <if test="user!=null">
    <if test="user.phone!=null and user.phone=!''">
      and u.phone like '%${user.phone}'
    </if>
  </if>
  
  
  <if test="ids!=null">
    <!--collection 和dto对应 item 临时中间变量 open 循环体开始 separator 间隔符-->
    <foreach collection="ids" item = "user_id" open = "and (" close=")" separator="or">
      id=#{user_id}
    </foreach>
  </if>
  
    <if test="ids!=null">
    <!--collection 和dto对应 item 临时中间变量 open 循环体开始 separator 间隔符-->
    <foreach collection="ids" item = "user_id" open = "and id in(" close=")" separator=",">
      id=#{user_id}
    </foreach>
  </if>
</sql>
<!--ids属于UserDTO的属性-->
  <select id="findUserByPhoneAndEmail" parameterType="UserDTO" resultType="TUserEntity">
    select * from t_user u
    <where>
      <!--使用include包含-->
      <include refid="user_query_statement"></include>
    </where>
  <!--where u.id = ug.userId and ug.goodsId=#{goods.id} and u.phone like '${user.phone}%' -->
  </select>

高级映射-业务模型扩展

主键,外键,关系数据库

引用数据库案例
在这里插入图片描述

高级映射-ResultType实现一对一关系映射

以resultType实现的映射,需要创建对应的POJO/DTO

<!--在查询标签中写入对应sql的resultType路径-->
<select id="selectUserById"  resultType="TUserEntity">
<!--此处是SqlMapConfing.xml中定义了包别名,故上方不必添加具体路径-->
  <!--包别名-->
    <typeAliases>
        <package name="cn.edu.xynu.ssm.POJO"/>
        <package name="cn.edu.xynu.ssm.DTO"/>
    </typeAliases>

高级映射-ResultMap实现一对一关系映射

需要在对应的实体类中添加关联的实体生成get set方法,重写tostring();

    <resultMap id="OrderUserResultMap" type="Oreder2">
        <id column="id" property="id"/>
        <result column="user_id" property="user_id"/>
        <result column="number" property="number"/>
        <result column="create_time" property="create_time"/>
        <!--association映射关联的属性//一对一-->
        <association property="user" javaType="User">
          <id column="user_id" property="id"/>
          <result column="worId" property="workId"/>
          <result column="name" property="name"/>
        </association>
    </resultMap>

高级映射-ResultType和ResultMap的区别

使用type需要添加新的类,但使用简单

使用Map需要在实体中添加关联的实体,写出对应的ResultMap,用 < a s s o c i a t o n g > <associatong> <associatong>关联,但更加灵活

高级映射-ResultMap实现一对多关系映射

在一对多关系中,给1的对象实体添加list类型的多实体对象,生成get set方法,重写tostring();

    <resultMap id="OrderAndOrderDetailResultMap" type="Oreder2">
        <id column="id" property="id"/>
        <result column="user_id" property="user_id"/>
        <result column="number" property="number"/>
        <result column="create_time" property="create_time"/>
        <!--collection映射关联的list属性//一对多-->
        <collection property="orderDetail" ofType="OrderDetail">
          <id column="orderDetail_id" property="orderDetail_id"/>
          <result column="order_id" property="order_id"/>
          <result column="goods_id" property="goods_id"/>
          <result column="goods_number" property="goods_number"/>
        </collection>
    </resultMap>

resultMap可以被继承

    <resultMap id="OrderAndOrderDetailResultMap" type="Oreder2" extends="OrderUserResultMap">
        <!--collection映射关联的list属性//一对多-->
        <collection property="orderDetail" ofType="OrderDetail">
          <id column="orderDetail_id" property="orderDetail_id"/>
          <result column="order_id" property="order_id"/>
          <result column="goods_id" property="goods_id"/>
          <result column="goods_number" property="goods_number"/>
        </collection>
    </resultMap>

高级映射-ResultMap实现多对多关系映射

找准主表及其关联表,对应的sql语句表头定义别名

将用户信息映射到UserInfo中;
在UserInfo中添加订单列表属性(List orders);将用户创建的订单映射到orders;
在Order中添加订单明细列表属性(List orderDetails);将订单对应的订单明细映射到orderDetails;
在OrderDetail中添加商品属性(GoodsInfo goodsInfo);将订单明细对应的商品映射到goodsInfo中。

生成对应get()set(),重写tostring

 <select id="findUserAndGoodsResultMap" resultMap="userAndGoodsResultMap">
     SELECT 
	  u.id u_id,
	  u.workId,
	  u.name,
	  o.id o_id,
	  o.user_id o_user_id,
	  o.create_time o_create_time,
	  od.order_id od_order_id,
	  od.orderdetail_id,
	  g.id g_id,
	  g.name g_name,
	  g.price 
	FROM
	  t_user u,
	  t_order o,
	  t_orderdetail od,
	  t_goods g 
	WHERE u.id = o.user_id 
	  AND od.order_id = o.id 
	  AND od.goods_id = g.id 
  </select> 

定义对应的resultMap

  <resultMap type="edu.xync.pro.entity.UserInfo" id="userAndGoodsResultMap">
    <!--
       (1) 配置映射的订单信息 
        id:指定查询结果中的唯一标识,也就是用户表的唯一标识(如果唯一标识由多列构成,需要配置多个id)
        column:订单信息的唯一标识列名
        property:对应POJO类中的属性名称
     -->
      <id column="u_id" property="id"/>
      <result column="workId" property="workId"/>
      <result column="name" property="name"/>
     <!-- 
       (2)配置映射关联的订单信息
                   一个用户可以创建多个订单,使用collection
        collection:将多个关联查询出的记录映射到集合对象中
        property:表示关联查询的订单关联到UserInfo中的哪个属性
        ofType:表示映射到的集合中所指定的POJO类型
      -->
      <collection property="orders" ofType="edu.xync.pro.entity.Order">
        <!-- 
           id:订单的唯一标识
           column:订单唯一标识的名称
           property:对应到订单对应的POJO中的哪个属性
         -->
        <id column="o_id" property="id"/>
         <result column="o_user_id" property="user_id"/>
         <result column="o_create_time" property="create_time"/>
         <!-- 
           (3)配置映射关联的订单明细信息
                              一个订单包含多个订单明细,使用collection
            collection:将多个关联查询出的记录映射到集合对象中
            property:表示关联查询的订单明细关联到Orders中的哪个属性
            ofType:表示映射到的集合中所指定的POJO类型
          -->
           <collection property="orderDetails" ofType="edu.xync.pro.entity.OrderDetail">
             <!-- 
                id:订单明细的唯一标识
                column:订单明细唯一标识的名称
                 property:对应到订单明细对应的POJO中的哪个属性
            -->
            <id column="orderdetail_id" property="orderdetail_id"/>
             <result column="od_order_id" property="order_id"/>
             <!-- 
		       (4)配置映射关联的商品信息
		        association:用于映射关联查询单个的对象
		        property:表示关联查询的商品信息关联到OrderDetail中的哪个属性
		      -->
		      <association property="goodsInfo" javaType="edu.xync.pro.entity.GoodsInfo">
		        <!-- id:关联查询的商品对象的唯一标识
		             column:指定唯一标识商品信息的列
		             property:对应到商品信息的POJO中的哪个属性
		         -->
		        <id column="g_id" property="id"/>
		        <result column="g_name" property="name"/>
		        <result column="price" property="price"/>
		      </association>
          </collection>
      </collection>
      
  </resultMap>

多表查询,需要定义别名,以保证不会混淆,嵌套的使用collection和association,关联对象

高级映射-ResultType和ResultMap

resultType:

查询结果与POJO属性一一映射。单表使用最佳

一个Type一个类
resultMap:

使用association和collection完成一对一和一对多高级映射。

association完成关联信息映射到一个POJO对象中。

collection完成关联信息映射到一个List集合中。

定义好与数据库对应的POJO后依照表关系添加合适的关联属性,私以为适用于常见的大多数多表查询.

同时具备延迟加载的功能
使用association实现延迟加载功能

正常书写查询语句

在ResultMap中使用assocation 通过select 和column属性指定延迟加载的查询语句及其参数

<!-- 延迟加载用户的resultMap -->
  <resultMap type="edu.xync.pro.entity.Order" id="orderAndUserLazyLoading">
    <!-- 对订单信息进行映射配置 -->
      <id column="id" property="id"/>
      <result column="user_id" property="user_id"/>
      <result column="number" property="number"/>
      <result column="create_time" property="create_time"/>
      <result column="note" property="note"/>
    <!-- 实现对用户信息延迟加载 
       select:指定延迟加载需要执行的statement的id
 使用UserMapper.xml中statement为findUserById完成对用户信息的查询,不在同一个命名空间的话,需要加namespace名称
       column:订单信息中需要关联用户信息需要查询的列,是user_id   
    -->
     <association property="userInfo" javaType="edu.xync.pro.entity.UserInfo" select="edu.xync.pro.dao.mapper.UserMapper.findUserById" column="user_id">
      </association>
  </resultMap>

SqlMapConfig.xml配置文件中配置好延迟加载开关

    <settings>
       <!-- 打开延迟加载的开关 -->
       <setting name="lazyLoadingEnabled" value="true"/>
       <!-- 关闭积极加载改为消极加载,等到执行get方法时再去延迟加载 -->
       <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

测试中直接调用关联的属性的get方法即可获取到延迟加载的信息

缓存

缓存中有查询语句对应的数据时,就不用再访问数据库了

一级缓存

sqlSession,操作数据库时创建sqlSession对象,内部有一个HashMap用户存储数据,不同的sqlsession有各自的HashMap

二级缓存

Mapper多个selSession使用同一个Mapper的缓存

一级缓存

MyBatis默认开启一级缓存,无需额外修改配置

在这里插入图片描述

当SqlSession执行了commit操作(插入,删除,更新),清空一级缓存,保持缓存与数据库一致,避免脏数据

在这里插入图片描述

二级缓存

MyBatis默认关闭二级缓存

二级缓存范围更大,多个sqlsession可以共享一个mapper下的二级缓存。每一个namespace下的mapper都有一个二级缓存区域,如果两个不同的mapper具有相同的namespace,则这两个mapper执行查询操作将数据存放到同一个二级缓存中。

在这里插入图片描述

配置文件

SqlMapConfig.xml

<settings>
       <!-- 打开延迟加载的开关 -->
       <setting name="lazyLoadingEnabled" value="true"/>
       <!-- 关闭积极加载改为消极加载,等到执行get方法时再去延迟加载 -->
       <setting name="aggressiveLazyLoading" value="false"/>
       <!-- 开启二级缓存 -->
       <setting name="cacheEnabled" value="true"/>
</settings>

Mapper.xml

<mapper namespace="edu.xync.pro.dao.mapper.UserMapper">
     <!--将对应的mapper映射文件开启二级缓存-->
<cache/>

对应的实体类需要实现序列化接口Serializable,并生成默认版本号:,使当前实体采用默认的序列化方式,接口本身是一个空接口,相当于一个标记

在这里插入图片描述

Commit清空缓存
在这里插入图片描述

useCache

设置为false,不使用缓存,每次查询数据库

useCache默认为true,表示会将本条语句的结果进行二级缓存。

问题:设置为false时,是否会将数据存入Sqlsession?

 <select id="findUserById" parameterType="java.lang.Integer" resultType="UserInfo" useCache="false">
     select * from t_user where id=#{id}
</select>

在这里插入图片描述


flushCache

默认刷新缓存

flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。

在insert、update、delete语句时: flushCache默认为true , 为false就不会刷新缓存

那么使用缓存时手动修改了数据表中的数据,缓存没有刷新,就会出现脏读。

在这里插入图片描述

Ehcache:轻量式分布式缓存

MyBatis与Ehcache整合包的下载地址:
http://www.java2s.com/ref/jar/download-mybatisehcache110jar-file.html
用到的是两个jar包** ehcache-core-2.6.6.jarmybatis-ehcache-1.1.0.jar**。

配置mapper中cache的type为Ehcache

 <mapper namespace="edu.xync.pro.dao.mapper.UserMapper">
  
     <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

在classpath中加入Ehcache的配置文件。在config目录下创建ehcache.xml文件:

  <?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\\ehcache" />
    <defaultCache
            maxElementsInMemory="10000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

我是片尾
笔记有限而学无止境
诸君共勉


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

twfplayer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值