• MyBatis基础


    原理

    使用端

    • 引入框架依赖
    • 提供两部分配置信息:数据库配置信息 和 sql 配置信息
      • 数据库配置信息:mybatis-config.xml。不仅仅是存放数据库配置信息,还指定了 sql 配置信息文件路径
      • sql 配置信息:mapper.xml(对应 dao 层的 xml)

    框架底层

    准备工作(项目启动时完成)

    • 第一步:读取配置信息(因为 mybatis-config.xml 还指定了 mapper.xml 路径,所以一次读取所有的配置文件)得到一个输入流
    • 第二步:创建 Configuration
      • 包含所有 mybatis 的配置,比如数据库,sql配置等,还会维护 mppedStatements,这是个 map,key:mapper全限定名+方法名(比如 com.study.UserMapper.selectById),value:MppedStatement
    • 第三步:创建 SqlSession
      • 根据上一步得到的 Configuration 创建,方法:SqlSessionFactory build(Configuration config)
    • 第四步:为 mapper 生成代理对象并保存
      • 通过 JDK 代理生成 mapper 接口的对象,保存在 MapperRegistry.knownMappers 中(mybatis-config.xml 中通过 mappers 标签指定所有 mapper.xml,每个 mapper.xml 有 namespace 属性指定 mapper 接口全限定名)

    执行 mapper 方法

    • 第一步:根据 SqlSession 获取 Mapper 接口代理对象
      • 调用 SqlSession.getMapper(Mapper.class)。其实获取的就是 MapperRegistry.knownMappers 中的对象,没获取到会抛出异常
    • 第二步:执行 mapper 方法
      • 执行的方法分为两种情况,一个是 Object 类的方法就直接执行,比如 toString、equals 等
      • 如果是不是 Objcet 类的方法,会继续往下走
    • 第三步:主要是代理对象的方法和 MppedStatement 关系
      • 方法名和 mapper 接口全限定名 是 MppedStatement 的 key,而 MppedStatement 里面有代理对象方法 MapperMethod ,这个方法里面有 sql 信息和 返回信息

    源码

    • 创建 SqlSessionFactory:SqlSessionFactoryBuilder.build(InputStream inputStream)
      // 方法1,使用配置文件的流创建创建 SqlSessionFactory
      public SqlSessionFactory build(InputStream inputStream) {
          // 调用方法 2
          return build(inputStream, null, null);
      }
      // 方法2
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
          try {
              // 解析配置文件
              XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
              // 调用方法3。parser.parse() 返回 Configuration(mybatis-config.xml里面的每个标签和值设置到 Configuration 对象)
              return build(parser.parse());
          } catch (Exception e) {
              throw ExceptionFactory.wrapException("Error building SqlSession.", e);
          } finally {
              ErrorContext.instance().reset();
              try {
                  inputStream.close();
              } catch (IOException e) {
                  // Intentionally ignore. Prefer previous error.
              }
          }
      }
      // 方法3,最后是这里返回 SqlSessionFactory(如果是springboot,跳过方法1和方法2,直接调用这里,原因也很简单,因为 springboot 压根就没有 mybatis 的配置文件嘛)
      public SqlSessionFactory build(Configuration config) {
          return new DefaultSqlSessionFactory(config);
      }
      
    • 创建 SqlSession 流程:DefaultSqlSessionFactory.openSession()
      // 自动提交
      public SqlSession openSession() {
          // 参数就是解析配置文件后得到的 Configuration 对象里的 defaultExecutorType 属性
          return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
      // 不会自动提交
      public SqlSession openSession(boolean autoCommit) {
      return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
      }
      /**
       + ExecutorType 指定Executor的类型,分为三种:SIMPLE, REUSE, BATCH,默认使用的是SIMPLE
       + TransactionIsolationLevel 指定事务隔离级别,使用null,则表示使用数据库默认的事务隔离界别
       + autoCommit 是否自动提交,传过来的参数为false,表示不自动提交
       */
      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
          Transaction tx = null;
          try {
              final Environment environment = configuration.getEnvironment();
              final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
              tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
              // 创建Executor,即执行器。它是真正用来Java和数据库交互操作的类
              final Executor executor = configuration.newExecutor(tx, execType);
              // 创建 DefaultSqlSession
              return new DefaultSqlSession(configuration, executor, autoCommit);
          } catch (Exception e) {
              closeTransaction(tx); // may have fetched a connection so lets call close()
              throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
          } finally {
              ErrorContext.instance().reset();
          }
      }
      
      
    • 创建执行器:Configuration.newExecutor(Transaction transaction, ExecutorType executorType)
      // 创建执行器(这个方法属于 Configuration 类,所以这个类里面能拿到所有的配置信息,包括缓存、驼峰、事务隔离等等)
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
          executorType = executorType == null ? defaultExecutorType : executorType;
          executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
          Executor executor;
          if (ExecutorType.BATCH == executorType) {
              executor = new BatchExecutor(this, transaction);
          } else if (ExecutorType.REUSE == executorType) {
              executor = new ReuseExecutor(this, transaction);
          } else {
              // ExecutorType 默认是 SIMPLE,所以默认的执行器是 SimpleExecutor
              executor = new SimpleExecutor(this, transaction);
          }
          // 是否开启缓存(二级缓存)
          if (cacheEnabled) {
              // 如果开启了,使用装饰器模式添加二级缓存功能
              executor = new CachingExecutor(executor);
          }
          executor = (Executor) interceptorChain.pluginAll(executor);
          return executor;
      }
      

    应用

    基于 sqlSession

    // 常用方法
    <T> T selectOne(String statement, Object parameter)
    <E> List<E> selectList(String statement, Object parameter)
    int insert(String statement, Object parameter)
    int update(String statement, Object parameter)
    int delete(String statement, Object parameter)
    
    void commit()
    void rollback()
    

    示例

    //加载核⼼配置⽂件
    InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");
    //获得sqlSession⼯⼚对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    //获得sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //sqlSession 的第一个参数是 MppedStatement ID 即 接口全限定名+方法名
    List<User> userList = sqlSession.selectList("com.study.mapper.userMapper.findAll");
    //打印结果
    System.out.println(userList);
    //释放资源
    sqlSession.close();
    

    基于 mapper

    通过实现类实现

    // mapper 接口
    public interface UserDao {
        List<User> findAll() throws IOException;
    }
    
    // mapper 实现类
    public class UserDaoImpl implements UserDao {
        public List<User> findAll() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            List<User> userList = sqlSession.selectList("userMapper.findAll");
            sqlSession.close();
            return userList;
        }
    }
    
    // 测试
    @Test
    public void testTraditionDao() throws IOException {
        UserDao userDao = new UserDaoImpl();
        List<User> all = userDao.findAll();
        System.out.println(all);
    }
    

    基于代理(主流方式)

    • Mapper.xml ⽂件中的 namespace 与 mapper 接⼝的全限定名相同
    • Mapper 接⼝⽅法名和 Mapper.xml 中定义的每个 statement 的 id 相同
    • Mapper 接⼝⽅法的输⼊参数类型和 mapper.xml 中定义的每个sql的parameterType的类型相同
    • Mapper 接⼝⽅法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
    // mapper 接口
    public interface UserDao {
        List<User> findById(int id) throws IOException;
    }
    
    <!-- mapper.xml -->
    <mapper namespace="com.stydu.mapper.UserMapper">
        <select id="findById" parameterType="int" resoult="com.stydu.entity.User">
            SELECT * FROM T_USER WHERE ID = #{id}
        </select>
    </mapper>
    
    // 测试
    @Test
    public void testProxyDao() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获得MyBatis框架⽣成的 UserMapper 接⼝的实现类
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.findById(1);
        System.out.println(user);
        sqlSession.close();
    }
    

    动态 sql

    where 条件

    有的时候 where 1=1 会影像性能,所以最好使用 标签

    <select id="findByCondition" parameterType="user" resultType="user">
    select * from User
        <where>
            <if test="id!=0">
                and id=#{id}
            </if>
            <if test="username!=null">
                and username=#{username}
            </if>
        </where>
    </select>
    

    循环

    … … …
    // 获得 MyBatis 框架⽣成的 UserMapper 接⼝的实现类
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    int[] ids = new int[]{2,5};
    List<User> userList = userMapper.findByIdsAndName(ids, "Milk");
    System.out.println(userList);
    … … …
    
    <select id="findByIds" resultType="user">
        select * from User
        <where>
    	<!-- collection 的值和 mapper 接口指定的参数名相同 -->
            <foreach collection="ids" open="id in(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </where>
        <if test="name != '' and name != null">
            and name = #{name}
        </if>
    </select>
    

    片段抽取

    <!--抽取sql⽚段简化编写-->
    <sql id="selectUser"> select * from User</sql>
    
    <select id="findById" parameterType="int" resultType="user">
        <include refid="selectUser" /> where id=#{id}
    </select>
    

    级联查询

    一对一

    一个订单对应一个用户

    // 订单
    public class Order {
        private int id;
        private Date ordertime;
        private double total;
        //代表当前订单从属于哪⼀个客户
        private User user;
    }
    }
    
    // 用户
    public class User {
        private int id;
        private String username;
        private String password;
        private Date birthday;
    }
    
    <mapper namespace="com.lagou.mapper.OrderMapper">
        <resultMap id="orderMap" type="com.lagou.domain.Order">
            <!-- 主表,订单表 -->
            <result property="id" column="id"></result>
            <result property="ordertime" column="ordertime"></result>
            <result property="total" column="total"></result>
            <!-- 从表,用户表 -->
            <association property="user" javaType="com.lagou.domain.User">
                <!-- 如果两个表的主键都叫 ID,要指定别名 -->
                <result column="uid" property="id"></result>
                <result column="username" property="username"></result>
                <result column="password" property="password"></result>
                <result column="birthday" property="birthday"></result>
            </association>
        </resultMap>
    
        <select id="findAll" resultMap="orderMap">
            <!-- select * from t_order o, t_user u where o.uid=u.id -->
            <!-- 上面使用交叉连接,没有处理别名,我比较喜欢左连接 -->
            select o.*,u.id as uid, u.username, u.password, u.birthday from t_order o left join t_user u on o.uid = u.id 
        </select>
    </mapper>
    

    一对多

    // 订单表
    public class Order {
        private int id;
        private Date ordertime;
        private double total;
    }
    
    // 用户表
    public class User {
        private int id;
        private String username;
        private String password;
        private Date birthday;
        //代表当前⽤户具备哪些订单
        private List<Order> orderList;
    }
    
    <mapper namespace="com.lagou.mapper.UserMapper">
        <!-- 主表,用户 -->
        <resultMap id="userMap" type="com.lagou.domain.User">
            <result column="id" property="id"></result>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="birthday" property="birthday"></result>
            <!-- 从表,订单 -->
            <collection property="orderList" ofType="com.lagou.domain.Order">
                <result column="oid" property="id"></result>
                <result column="ordertime" property="ordertime"></result>
                <result column="total" property="total"></result>
            </collection>
        </resultMap>
    
        <select id="findAll" resultMap="userMap">
            select u.*,o.id as oid, o.ordertime, o.total from t_user left join t_order on u.id = o.uid 
        </select>
    </mapper>
    

    多对多

    同一对多

    缓存

    一级缓存

    是 SqlSession 级别的,对于查询,先查询缓存,如果缓存未查到就查数据库  
    默认开启,如果需要关闭,有两种方式,一种是单独指定某个查询方法,第二种是全局设定
    增、删、改会清空缓存
    
    <!-- flushCache = true,关闭一级缓存,当前方法生效 -->
    <select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap" flushCache="true">
        select 
        <include refid="Base_Column_List" />
        from cbondissuer
        where OBJECT_ID = #{objectId,jdbcType=VARCHAR}
    </select>
    
    <!-- 默认是SESSION,也就是开启一级缓存 -->
    <setting name="localCacheScope" value="STATEMENT"/>
    
    • Executor 里维护了一个 map,具体是 BaseExecutor.localCache
    • Exexutor.query 时会先查询 localCache,没有再查数据库

    二级缓存

    1,是 mapper.xml namespace 级别的,多个 SqlSession 都能查询到某个 mapper.xml 2,namespace 的缓存
    3,当查询时:二级缓存 -> 一级缓存 -> 数据库
    4,默认关闭,需手动开启,在 mybatis-config.xml 和 mapper.xml 中都要配置
    5,增、删、改会默认清空缓存,查询默认使用缓存(可以配置,useCache 和 flushCachs)
    6,如果开启二级缓存,所有的数据库实体必须实现 Serializable 接口
    
    <!-- mybatis-config.xml 中配置打开二级缓存 -->
    <!--开启⼆级缓存,父节点是 settings 标签 -->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    <!-- mapper.xml 中也需要配置 -->
    <!-- 都是用默认配置的话,简单写个 cache 标签即可,父节点是 mapper 标签(和 select result 同级) -->
    <cache/>
    
    • select 标签
      • flushCache 默认为 false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存
      • useCache 默认为 true,表示会将本条语句的结果进行二级缓存
    • insert、update、delete 标签
      • flushCache 默认为 true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空
      • useCache 属性在该情况下没有
  • 相关阅读:
    Vue.js——60分钟组件快速入门(下篇)三
    ASP.NET Core 中的 ORM 之 Dapper
    .Net Core中Dapper的使用详解
    .NetCore与Vue
    Vue 导入文件import、路径@和.的区别
    Git常见命令
    JVM垃圾回收补充知识点
    Java虚拟机垃圾回收(三): 7种垃圾收集器(转载)
    Java虚拟机垃圾回收(二) :垃圾回收算法(转载)
    Java虚拟机垃圾回收:基础点(转载)
  • 原文地址:https://www.cnblogs.com/huanggy/p/15151140.html
Copyright © 2020-2023  润新知