(四)MyBatis源码解析之SqlSession

来源:互联网 发布:数据库设计是指 编辑:程序博客网 时间:2024/05/22 09:47

上篇博文将完了MyBatis的配置文件解析的过程,从这篇博文开始分析MyBatis的执行流程

解析完配置文件之后,配置文件中的所有信息就存储到了Configuration对象中了,在创建SqlSessionFactory对象时将Configuration做为SqlSessionFactory对象的一个属性,通过调用SqlSessionFactory的openSession(...),openSession()有很多重载方法,创建SqlSession对象只能通过openSession()和这些重载方法

  @Override  public SqlSession openSession() {    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);  }
下面的代码是实际创建SqlSession的地方,在这里首先要获得解析XML文件时创建的TransactionFacroty对象,其次根据TransactionFactory创建事务对象Transaction,然后通过execType创建Executor,execType的类型决定了创建的Executor对象的类型
  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);      final Executor executor = configuration.newExecutor(tx, execType);      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();    }  }

executorType有三种类型,分别是BATCH,REUSE,SIMPLE,默认的是SIMPLE类型,这三种类型分别对应着三种executor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor就是简单的执行sql操作,除了这三种类型之外Mybatis还支持CacheExecutor,这就是Mybatis内建的缓存机制,在SqlSession级别提供对SQL查询结果的缓存,即:如果你调用了相同的select查询,Mybatis会将放在缓存的结果返回,而不会去再查询数据库,后面具体分析查询过程时会分析Mybatis是如何实现缓存的,在Mybatis中真正负责执行数据库操作的就是这些Executor

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 {      executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {      executor = new CachingExecutor(executor);    }    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }
最后executor对象是可以被插件拦截的,关于插件后面会有专门的博文进行分析

通过代理获得Mapper

Mybatis建议通过mapper对象访问mybatis,使用mapper和使用普通的方式的区别主要有以下几点

    public int getUserInfo(long userId){    return getSqlSession().selectOne("net.klq.inter.po.TbNsBssUserInfoCn.selectUserInfoCn", userId);    }

1、普通的方式在dao层查询的id和namespace是手写的,和xml中的配置文件有可能会对不上,实际工作中经常会碰到这种状况,而使用mapper的时候每个接口在哪个包下是固定的不会随意改变,省去了我们写包名时候容易导致的错误

2、普通的方式入参只能有一个,如果sql需要多个参数,我们需要先把多个参数封装成一个对象或者是放到map中,而使用mapper的时候可以使用任意多个参数

知道了通过Mapper编程的好处之后,思考一个问题mapper是一个接口,没有实现类怎么可以调用该接口的方法呢?答案就是动态代理,使用sqlSession的getMapper(...)方法,在调用该方法的时候传我们需要的Mapper接口的class对象进去,mybatis就可以为我们创建该对象的实现类了,具体是怎么创建的呢,一起来看看

    MessageMapperDao messageMapper = sqlSession.getMapper(MessageMapperDao.class);
Mybatis会去knownMappers中查找是否为该接口创建了一个代理工厂,在Mybatis中使用MapperProxyFactory作为代理工厂,找到代理工厂后会通过jdk的动态代理功能为我们创建一个动态代理对象然后返回,在应用中用接口来引用这个动态代理对象,接下来我们所有的增删改查都是通过这个动态代理对象进行的,Mybatis使用MapperProxy作为动态代理的对象,而且Mybatis在创建代理对象时将sqlSession的信息给了MapperProxy,这就

  @SuppressWarnings("unchecked")  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);    }  }

到目前为止我们已经找到了Mybatis提供的动态代理类了,但是这个动态代理类帮我们做了什么?怎么就实现了调用接口方法就可以执行xml配置文件里的sql了呢,别着急,让我们来看看Mybatis提供的这个动态代理类,确实到目前为止接口和sql还没有关联,但是仔细一看我们已经为关联做好准备了。methodCache属性具有存储Method和MapperMethod对应关系的功能,而MapperMethod中包含Configuration对象,而Configuration对象包含xml配置文件的所有信息,当然也就包含sql的信息,这就为我们建立接口方法和sql的关联关系做好了准备。再接下来就是具体的增删改查功能了,这是mybatis最最核心的功能,也是最复杂的功能,我们会多篇博文来介绍增删改查

public class MapperProxy<T> implements InvocationHandler, Serializable {  private static final long serialVersionUID = -6424540398559729838L;  private final SqlSession sqlSession;  private final Class<T> mapperInterface;  private final Map<Method, MapperMethod> methodCache;  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {    this.sqlSession = sqlSession;    this.mapperInterface = mapperInterface;    this.methodCache = methodCache;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    if (Object.class.equals(method.getDeclaringClass())) {      try {        return method.invoke(this, args);      } catch (Throwable t) {        throw ExceptionUtil.unwrapThrowable(t);      }    }    final MapperMethod mapperMethod = cachedMapperMethod(method);    return mapperMethod.execute(sqlSession, args);  }  private MapperMethod cachedMapperMethod(Method method) {    MapperMethod mapperMethod = methodCache.get(method);    if (mapperMethod == null) {      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());      methodCache.put(method, mapperMethod);    }    return mapperMethod;  }}


在介绍增删改查之前,我们先来看一下为什么通过接口查询可以设置多个参数,而通过普通的方法查询不行,通过普通方法查询时Mybatis是不知道我们到底要传进去多少个参数,每个参数之间的关系是什么样的,所以Mybatis规定只能有一个参数,如果我们想传多个参数有一般是有两种方式,将多个参数封装成一个对象或者将多个参数放到一个map中,这对于开发者来说是很不方便的,既然mybatis规定参数只能是一个为什么通过接口查询就可以是多个参数呢,因为mybatis在内部将我们传进去的多个参数转成了一个map省去我们自己转换的这个步骤,下面看看是如何转换的,jdk的动态代理会将方法的参数转成一个Object[]数组,定义多个参数时必须使用@Param注解Mybatis才可以识别,像下面这样声明多个参数

public List<Message> queryMessageList(@Param(value = "command") String command,@Param(value="description")String description);

在调用增删改查方法时,mybatis通过反射可以知道mybatis有多个参数,如果没有参数下面方法直接返回null,如果有一个参数就返回args[0],如果有多个参数根据@Param的顺序依次放到map中,转换成map之后普通方法执行数据库操作和通过接口操作数据库就没有区别了

public Object convertArgsToSqlCommandParam(Object[] args) {      final int paramCount = params.size();      if (args == null || paramCount == 0) {        return null;      } else if (!hasNamedParameters && paramCount == 1) {        return args[params.keySet().iterator().next().intValue()];      } else {        final Map<String, Object> param = new ParamMap<Object>();        int i = 0;        for (Map.Entry<Integer, String> entry : params.entrySet()) {          param.put(entry.getValue(), args[entry.getKey().intValue()]);          // issue #71, add param names as param1, param2...but ensure backward compatibility          final String genericParamName = "param" + String.valueOf(i + 1);          if (!param.containsKey(genericParamName)) {            param.put(genericParamName, args[entry.getKey()]);          }          i++;        }        return param;      }    }







0 0
原创粉丝点击