mybatis中mapper实现及事务实现原理

1.mybatis的mapper是怎样实现的?为什么只用写接口而不用写实现类就可以执行sql?

jdk动态代理实现,mapper是操作数据库的代理对象,是从SqlSession中获取,SqlSession是暴露给我们的操作数据库的API库

//SqlSession接口中方法
<T> T getMapper(Class<T> type);
//DefaultSqlSession类中方法,入参就是mapper接口的Class
@Override
public <T> T getMapper(Class<T> type) {
  // 从Configuration对象中,根据Mapper接口,获取Mapper代理对象
  return configuration.getMapper(type, this);
}

Configuration是全局配置类,在mybatis启动时,会把所有配置信息加载进这个类中,其中也包括mapper接口的代理对象。在mybatis启动时,会扫描mapper接口并通过一个映射注册器类(MapperRegistry)注册mapper接口的代理对象工厂到一个这个类的属性map中。而Configuration也包含了属性MapperRegistry,所以我们通过Configuration中的属性MapperRegistry去找mapper接口的代理对象。

//Configuration类中,传sqlsession只是为了得到全局配置类configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

mapperRegistry维护的map相当于一个缓存的作用,为了减少代理对象的创建的时间消耗。

//mapperRegistry维护的map,key是mapper接口类,value是其代理对象的创建工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
//MapperProxyFactory调用newInstance创建代理对象
public T newInstance(SqlSession sqlSession) {
    // InvocationHandler接口的实现类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
//重载方法
protected T newInstance(MapperProxy<T> mapperProxy) {
  // 使用JDK动态代理方式,生成代理对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

代理对象如何执行数据库操作的呢?

代理对象执行代理逻辑,它会解析原始mapper接口的方法的类型(select|insert|delete|update),对应的MapperStatement 的id,以及返回值类型,放入这些信息到MapperMethod类中,当方法执行时,会进到MapperMethod类中的execute方法,这个方法会对方法类型判断,不同类型走不同分支调用SqlSession的api执行数据库操作

//MapperProxy类中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 如果是 Object 定义的方法,直接调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {
      // 代理逻辑在这,cachedInvoker是一个包装
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {

    ......
        
    // methodCache,缓存(暂时不管),先在缓存中找是否有该方法,如果没有就新建代理对象存放map并返回
    return MapUtil.computeIfAbsent(methodCache, method, m -> {
        
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
     
    ......
 
});
  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }
//上面代理逻辑中的核心,PlainMethodInvoker类中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

MapperMethod:里面有两个属性SqlCommand和MethodSignature,

SqlCommand里面记录了mapper中的方法的类型(select|insert|delete|update)以及该方法所对应的MapperStatement的id(id是用于找出该mapper的此方法对应的在全局配置类里面的MapperStatement);

xxxmapper接口对应xxxmapper.xml文件,mybatis启动时会解析xml文件里的标签(如select标签,update标签等等)并把sql语句信息封装进MappedStatement对象,然后

1.MappedStatement会被存放到全局配置类的map中,key为MappedStatement的id(MappedStatement的id属性由mapper接口的全限定名+方法名组成,所以mapper接口不能方法重载),value为MappedStatement对象

2.全局配置类中的MapperRegistry会注册Mapper接口

MethodSignature则是记录mapper方法的返回值类型;

//MapperMethod中
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  // 判断mapper中的方法类型
  switch (command.getType()) {
    // 添加
    case INSERT: {
      // 转换参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 最终调用的还是sqlSession中的方法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    // 更新
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    // 删除
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    // 查询
    case SELECT:
       // 无返回结果,并且有ResultHandler方法参数,将查询结果交给ResultHandler进行处理
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
        // 执行查询、返回列表
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
        // 执行查询、返回Map
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
        // 执行查询、返回Cursor
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        // 转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 查询单条
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

2.mybatis中的事务是怎么实现?

mybatis本质上是对jdbc操作数据库的封装,Mybatis的JdbcTransaction(含有datasoruce和connection属性),和纯粹的Jdbc事务,几乎没有差别,它仅是扩展支持了连接池的connection。

//DefaultSqlSessionFactory用于创建SqlSession,SqlSession中封装了含有事务对象的Executor,事务对象可以创建含有事务管理的connection
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // 从configuration对象中获取environment对象
    final Environment environment = configuration.getEnvironment();
    // 获得事务工厂对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 构建事务对象
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 创建执行器对象
    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();
  }
}
//Mybaits中的事务接口Transaction
public interface Transaction {
    Connection getConnection() throws SQLException;
    void commit() throws SQLException;
    void rollback() throws SQLException;
    void close() throws SQLException;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

事务中真正操作数据库连接的方法只有commit、rollback和setAutoCommit,它们是JDBC事务的真实命令,而close只是是为了把connection放回数据库连接池中,供下一次使用,与事务毫无关系。

无论是SqlSession,还是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务提交、回滚的。

            // 执行了connection.setAutoCommit(false),并返回
            SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
		try {
			StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
			
			Student student = new Student();
			student.setName("yy");
			student.setEmail("email@email.com");
			student.setDob(new Date());
			student.setPhone(new PhoneNumber("123-2568-8947"));
			
			studentMapper.insertStudent(student);
			// 提交
			sqlSession.commit();
			
			studentMapper.insertStudent(student);
			// 多次提交
			sqlSession.commit();
		} catch (Exception e) {
		        // 回滚,只能回滚当前未提交的事务
			sqlSession.rollback();
		} finally {
			sqlSession.close();
		}

注:Executor在执行insertStudent(student)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需要像上面的代码一样,手动显示调用commit()、rollback()、close()等方法。

因此,后续在分析到类似insert()、update()等方法内部时,需要忘记事务的存在,不要试图在insert()等方法内部寻找有关事务的任何方法。

一个conn生命周期内,可以存在无数多个事务。在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。而rollback(),只能回滚当前未提交的事务。

nt)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需要像上面的代码一样,手动显示调用commit()、rollback()、close()等方法。

因此,后续在分析到类似insert()、update()等方法内部时,需要忘记事务的存在,不要试图在insert()等方法内部寻找有关事务的任何方法。

一个conn生命周期内,可以存在无数多个事务。在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。而rollback(),只能回滚当前未提交的事务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值