zoukankan      html  css  js  c++  java
  • 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 属性在该情况下没有
  • 相关阅读:
    Representation Data in OpenCascade BRep
    Render OpenCascade Geometry Surfaces in OpenSceneGraph
    Render OpenCascade Geometry Curves in OpenSceneGraph
    OpenCascade Shape Representation in OpenSceneGraph
    Geometry Surface of OpenCascade BRep
    Geometry Curve of OpenCascade BRep
    Tyvj2017清北冬令营入学测试
    Spfa算法模板
    洛谷1016 旅行家的预算
    洛谷1290 欧几里得的游戏
  • 原文地址:https://www.cnblogs.com/huanggy/p/15151140.html
Copyright © 2011-2022 走看看