在ibatis2.0配上mybatis3.0的mapper机制

今天说说MyBatis3iBatis2。大家现在正常的项目都应该使用MyBatis3了。动态代理的Mapper机制,只要考虑3个要素(xxxMapper.java, xxxMapper.xml, xxxPojo.java),完全面向对象的开发方式,用起来相当爽。更重要的是,还有Mybatis Generator工具可以自动生成DAO层。

MyBatis3之前还有iBatis2,有些比较年轻的程序员可能都没有用过这个东西。有些比较老的工程可能还在用,我之前在某银行的时候就用iBatis2,写到DAO层就想睡觉。吐槽下iBatis2开发恶心的地方。

  1. 表的映射配置,表很大,如果有20个字段。都得逐个copy(调试的时候就怕哪个字段错了)
  2. sqlid是随便起名字的,表与表之前也没有严格的sql范围划分。
  3. 写调用代码时是面向过程的,queryForObject出来的对象需要强制转换。
  4. java代码中各种硬编码
  5. 大家为了当下省事,在调用时常常使用map进,map出。特别难看
iBatis2的示意代码
public Student findStudentById(int studentId) {
   
		SqlMapClient sqlMapClient = MyBatisUtil.getSqlSqlMapClient();
		SqlMapSession sqlMapSession = sqlMapClient.openSession();
		try {
   
			Student student = (Student) sqlMapSession.queryForObject("student.queryByPrimaryKey", studentId);
		} finally {
   
			sqlMapSession.close();
		}
	}
MyBatis的示意代码
public Student findStudentById(int studentId) {
   
  SqlSession sqlSession = MyBatisUtil.getSqlSession();
  try {
   
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.queryByPrimaryKey(studentId);
    return student;
  } finally {
   
    sqlSession.close();
  }
}

对比之后会发现MyBatis3是面向对象的,使用Mapper机制,可以使得表之前的sql有一定的隔离。入参和出餐都可以尽量使用面向对象的方式。硬编码也没有了。


苦逼程序员在代码还得写,ibatis2还得用,怎么办?能不能在ibatis2基础上做一个适配层,使得我们在业务代码中可以像使用MyBatis3一样面向对象的方式去使用ibatis2呢?

答案当然是可以,要不然我也不会写这文章了。不仅可以像MyBatis3一样调用,连MyBatis Generator的自动生成器也可以拿过来改吧改吧适配iBatis2。下面我们来详细说说。

要干此事的前提是,要先了解MyBatis3Mapper映射部分的原理。

MyBatis3 源代码分析

Mapper机制从流程上分为3个步骤

1.启动时做初始化动作

2.运行时使用getMapper方法生成动态代理

3.调用sql执行过程(这部分的核型实际与iBatis差不多)

Mapper机制初始化

系统启动首先会初始化Configuration,我们从Configuration的初始化位置开始看。请先找到XMLConfigBuilder类。

  public Configuration parse() {
   
    if (parsed) {
   
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

这段方法,是xml解析过程。重点关注parseConfiguration

private void parseConfiguration(XNode root) {
   
    try {
   
      // 此处省略部分代码
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
   
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

继续跟进mapperElement

private void mapperElement(XNode parent) throws Exception {
   
    if (parent != null) {
   
      for (XNode child : parent.getChildren()) {
   
        		// 此处省略部分代码
            configuration.addMapper(mapperInterface);
          } else {
   
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

继续跟进configuration.addMapper(mapperInterface);一路可以跟进到MapperRegistry

  public void addMappers(String packageName) {
   
    addMappers(packageName, Object.class);
  }

这个方法是要加载某个包下面的所有xxxMapper.java(也就是我们所写的比如UserMapper.java,这种DAO行为接口),为后续运行时生成动态代理类做准备。(我们在业务代码中常写的sqlSession.getMapper方法,就是在生成动态代理)

public class MapperRegistry {
   
  
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  
	public <T> void addMapper(Class<T> type) {
   
    if (type.isInterface()) {
   
      if (hasMapper(type)) {
   
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
   
        // knownMappers<Mapper接口类型, Mapper代理的工场对象>
        // 初始化时,会把这些东西缓存起来。为后续生成动态代理做准备。
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
   
        if (!loadCompleted) {
   
          knownMappers.remove(type);
        }
      }
    }
  }
}

关注下knownMappers,这里面的keyvalue分别是<Mapper接口类型, Mapper代理的工场对象>

初始化时,会把这些东西缓存起来。为后续生成动态代理做准备。

使用getMapper方法生成动态代理

还是在MapperRegistry类中,调用getMapper方法生成动态代理类。关于动态代理不太熟悉的同学,可以相关设计模式,这个模式非常重要。

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,我把其中不太重要的部分去掉了

public class MapperProxyFactory<T> {
   

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  // 去掉了一些不太重要的部分代码
  
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
   
    // 实际使用了JDK的动态代理,接口类型就是初始化时候放进knownMappers中的某一个。
    // 具体类型是在getMapper的时候传入的。
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
   
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

实际使用了JDK的动态代理,接口类型就是初始化时候放进knownMappers中的某一个。具体类型是在getMapper的时候传入的。具体我们再回顾下上面那个MyBatis的调用案例。

MyBatis的示意代码
public Student findStudentById(int studentId) {
   
  SqlSession sqlSession = MyBatisUtil.getSqlSession();
  try {
   
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.queryByPrimaryKey(studentId);
    return student;
  } finally {
   
    sqlSession.close();
  }
}

从这边可以看到。在调用getMapper时,会传入StudentMapper接口的类型。MyBatis会为这个接口生成动态代理类。下面我们看看这个动态代理实际执行了什么操作。找到MapperProxy类

public class MapperProxy<T> implements InvocationHandler, Serializable {
   

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值