SSM-Mybatis学习

SSM-Mybatis

中文文档:https://mybatis.org/mybatis-3/zh/getting-started.html

github:https://github.com/mybatis/mybatis-3

  • MyBatis 是一款优秀的持久层框架,

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

过程

  • 开发流程

    ​ 编写配置文件->编写mapper(可以使用注解)->编写xml(建立mapper映射)->配置文件扫描mapper->获取mapper代理或者直接定位xml方法名id->执行sql

  • 执行流程

    1. Resources获取并加载全局配置文件
    2. 实例化SqlSessionFactoryBuilder构造器
    3. build(Stream)方法解析配置文件流XMLConfigBuilderconfiguration对象
    4. 实例化SqlSessionFactory(携带configuration
    5. 执行openSession并创建exector执行器以及Transaction事务管理器,返回DefaultSqlSession
    Transaction tx = null;
    final Environment environment = configuration.getEnvironment(); // 得到环境
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建事务管理器
    final Executor executor = configuration.newExecutor(tx, execType);// 执行器
    return new DefaultSqlSession(configuration, executor, autoCommit);
    
     protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction; // 事务
        this.deferredLoads = new ConcurrentLinkedQueue<>(); 
        this.localCache = new PerpetualCache("LocalCache");// 缓存
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration; // 配置环境
        this.wrapper = this;
    }
    
    1. getMapper获取Mapper代理,代理中会携带SqlSession对象,knownMappers会存放扫描到的Mapper
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // 得到对应mapper的代理工厂
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession); // 获取mapper代理
    
    1. 实现CRUD

      1. selectList为例,会首先根据方法名statement找到对应方法的信息MappedStatement(包括sql,参数,返回值等),mappedStatements会存储所有xml中mapper配置扫描到的方法,然后executor.query执行查询
      selectList(){}
      MappedStatement ms = configuration.getMappedStatement(statement); // 获取执行方法信息
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); // 执行器调用
      
      1. query方法中会解析传进来的MappedStatementboundSql就是sql代码
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
          BoundSql boundSql = ms.getBoundSql(parameterObject);
          CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
          return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
      
      1. 会首先查看缓存有没有,有就直接用,没有再查询调用执行器ExecutordoQuery,并将查询的结果存入缓存
      2. StatementHandler会创建 Statement对象,resultHandler会处理结果
      Cache cache = ms.getCache();
      if (cache != null) { // 会首先查看缓存有没有
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
              ensureNoOutParams(ms, boundSql);
              @SuppressWarnings("unchecked")
              List<E> list = (List<E>) tcm.getObject(cache, key);
              if (list == null) {
                  list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                  tcm.putObject(cache, key, list); // 查询的结果存入缓存
              }
              return list;
          }
      }
      return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 没有再查询
      
    2. 查看是否执行成功,回滚或提交

    3. 关闭openSession,断开连接或者返回连接到连接池

作用域

  • SqlSessionFactoryBuilder用于解析xml创建SqlSessionFactory,一旦创建了 SqlSessionFactory,就不再需要它了,实例的最佳作用域是方法作用域(也就是局部方法变量)
  • SqlSessionFactory应该是单例的,创建SqlSession使用
  • SqlSession应该是线程级别的,类似于一次Http请求,每个SqlSession存储一个数据库连接池的connection
    在这里插入图片描述

使用注解

  • 一个方法如果使用了注解那就不能再在xml中配置sql标签(可以完全删除xml文件,但要在配置文件中扫描mapper),二者会产生冲突,但如果稍微复杂的语句,还是使用xml比较好,二者可以混合
  • 可以使用@Param(name)控制多个参数名入参
  • 即使配置了注解,也要写xml文件注册mapper,以便加载时可以根据namespace扫描到对应的mapper类从而找到注解
  • 注解中使用script标签可以拼接动态sql
  • 后期使用SpringBoot可以扫描到mapper文件夹,自动生成xml

执行sql的两种方法

1. sqlSession.select("方法名或者方法全路径",params) 直接操作xml
       1. 通过Resources.getResourceAsStream(resource)解析xml文件,mapper
2. sqlSession.getMapper(Class<T> var1)方法是如何获取代理对象的?

通过类名和方法名可以唯一定位到一条xml的sql语句,然后处理参数和返回值即可

// 通过cglib我们可以只通过一个class而创建出他的代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserMapper.class);// 设置代理目标
System.out.println(UserMapper.class.getName()); // 获取类的完整路径,映射xml的命名空间namespace
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行了"+method.getName()); // 获取方法名,映射xml的id
        System.out.println("参数:"+Arrays.toString(objects)); // 执行入参
        // 业务逻辑  可以调用 select(method.getName(),objects)等方法
        System.out.println( "返回值:"+method.getReturnType()); // 处理返回结果
        return null;
    }
});  
UserMapper o = (UserMapper)enhancer.create();
System.out.println(o.getUserList());
// jdk动态代理
Class<UserMapper> clazz = UserMapper.class;
	UserMapper mapper = (UserMapper)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  		……
        return null;
    }
});
System.out.println(o.getUserList());
 // 获得sqlSession对象,SqlSession继承了Closeable接口会自动关闭
try (SqlSession sqlSession = MybatisUtils.getSqlSession();){
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    System.out.println(mapper.getUserList()); //方式1
    System.out.println(sqlSession.selectList("getUserList"));//方式2
  System.out.println(sqlSession.selectList("com.kuang.mapper.UserMapper.getUserList"));//方式3
}catch (Exception e){
    e.printStackTrace();
}

注意事项

  • 默认sqlsession是不开启事务的,增删改需要显示commit
  • 当参数不确定或者名称不一致的时候可以使用Map传参,#{key}可以直接去除map对应key的值,等价于实体类的getKey(),如果返回没有合适的pojo模型的话也可以使用Map来接收(@keyMap()映射多条记录)
-- xml
<insert id="insert" parameterType="map">
    insert into user values (#{userId},#{userName});
</insert>
-- java    
HashMap<String, Object> map = new HashMap<>();
map.put("userId", 5);
map.put("userName", "橘右京");
System.out.println(mapper.insert2(map));
  • 模糊查询:like concat("%",#{name},"%")

  • 当返回参数和实体属性名不一致或者返回结果过于复杂(包含多个对象)的时候

    • 可以属性as 别名
    • resultMap标签结果集映射
    • 使用Map接收结果(不推荐)
  • 通过**<sql>标签配合<incude>**可以实现sql代码段的复用,可以指定参数,也可以使用引用环境的参数

<select id="selectById" resultMap="teacherMapper2">
    SELECT * FROM teacher WHERE id = #{id}
    <include refid="limit">
        <property name="offset" value="3"/>
        <property name="pageSize" value="5"/>
    </include>
</select>
<sql id="limit">
    <if test="id != null">
    	limit ${offset},${pageSize}   // ${id}也可以直接使用
    </if>
</sql>
  • 关联查询

    • javaType用于返回java类型,ofType用于返回的集合泛型比如List<T>

    • assocation用于关联单个对象,通常有以下两种方法(一对一,多对一)

      • 子查询:通过select关联一个接口查询
      • 连接查询:直接处理查询出来的结果,通过result映射
      <resultMap id="StudentMapper2" type="Student">
          <id property="id" column="id"></id>
          <association property="teacher" javaType="Teacher">
          	<result property="id" column="tid"/>
          </association>
      </resultMap> //方法1
      <resultMap id="StudentMapper" type="Student">
          <id property="id" column="id"></id>
          <association property="teacher" column="tid" javaType="Teacher"
         		 select="com.kuang.mapper.TeacherMapper.selectById"/>
      </resultMap> //方法2
      
    • assocation用于关联集合对象,通常有以下两种方法同上(一堆多,多对多)

      <resultMap id="teacherMapper2" type="Teacher">
          <id property="id" column="tid"></id>
          <result property="name" column="tname"></result>
          <collection property="students" column="id" ofType="Student">
              <result property="id" column="sid"></result>
          </collection>
      </resultMap> //方法1
      <resultMap id="teacherMapper" type="Teacher">
          <id property="id" column="id"></id>
          <collection property="students" column="id"
          select="com.kuang.mapper.StudentMapper.selectByTid"/>
      </resultMap> //方法2
      

缓存原理

Mybatis缓存分为两种,默认开启一级缓存

  • 一级缓存是SqlSession级别,当在同一个SqlSession中执行两次相同的SQL语句时,会将第一次执行查询的数据存入一级缓存中,第二次查询时会从缓存中获取数据,而不用再去数据库查询,从而提高了查询性能,但如果SqlSession执行了insert,update,delete操作,并提交数据库,或者SqlSession结束后,这个SqlSession中的一级缓存就不存在了,sqlSession.clearCache()也可以情况sqlSession的缓存

  • 二级缓存是mapper级别的缓存,多个SqlSession共用二级缓存(准确来说时同一个mapperProxyFactory对象),他们使用同一个Mapper的SQL语句操作数据库,获得的数据会存放在二级缓存中,通过下列方式配置(有时还需要实现序列化接口)

    配置文件setting:<setting name="cacheEnabled" value="true"/>
    具体mapper:<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    精确到单个方法:<select id="selectById" flushCache="true" useCache="false">    
    
  1. 在创建SqlSession的时候会创建并携带执行器Executor,newExecutor就会创建缓存了,同样executor.clearLocalCache()也能清空缓存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GeKMzeg6-1616324339463)(C:\Users\86173\AppData\Roaming\Typora\typora-user-images\image-20210321170038350.png)]

 protected BaseExecutor(Configuration configuration, Transaction transaction) {
	……
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
	……
}
  1. 执行每一次查询,都会给这次查询生成一个对应的key,包括了方法的信息(ms)、参数、rowBoundsboundSql
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  1. 获取方法的缓存集合 ,如果缓存不为空,查询缓存cache中时候有对应的key,有就直接返回
  2. 没有重新查询,并将查询出来的结果方法缓存中并返回
  3. 如果获取不到方法缓存(该方法没有被执行过),则执行执行器Executor的doQuery后localCache.putObject(key, list)添加缓存
Cache cache = ms.getCache();
if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
            list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
    }
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  1. 但如果是新增,删除,修改操作,会清空缓存
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
      	<flushCacheIfRequired(ms)
            Cache cache = ms.getCache();
            if (cache != null && ms.isFlushCacheRequired()) {
              tcm.clear(cache); // getTransactionalCache(cache).clear();
            }
       	/>
    return delegate.update(ms, parameterObject);
  }
  1. commit和close都会情况缓存
public void commit(boolean required) throws SQLException {
  clearLocalCache();
  	 if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
   	 }	
} // commit
public void close(boolean forceRollback) {
    try {
      ……
    finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
} // close

二级缓存

knownMappers会存储所有的mapperProxyFactory对象,mapperProxyFactory存储methodCache,所有就实现了二级缓存也就是Mapper级别的缓存,尽管代理对象不同,但每个代理对象的MapperProxy(实现了InvocationHandler)都指向同一个methodCache属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0Fu5hhd-1616324339465)(C:\Users\86173\AppData\Roaming\Typora\typora-user-images\image-20210321172812291.png)]

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
// mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        <MapperProxy
          public class MapperProxy<T> implements InvocationHandler, Serializable {
		  private final Map<Method, MapperMethod> methodCache;/>
    return newInstance(mapperProxy);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

信息技术王凤龙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值