框架架构
MappedStatement
Mybatis会把所有的Mapper XML 文件解析为MappedStatement , 并放入一个Map作为缓存。
MappedStatement的Id为:
Mapper文件中的namespace 加对应curd语句的SQLID
SqlSession#CURD
Mybatis执行所有的SQL都是通过执行最终解析后的SQL,也就是通过SqlSession接口中的API,如:
/*** Retrieve a single row mapped from the statement key.* @param <T> the returned object type* @param statement* @return Mapped object*/<T> T selectOne(String statement);/*** Retrieve a single row mapped from the statement key and parameter.* @param <T> the returned object type* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @return Mapped object*/<T> T selectOne(String statement, Object parameter);/*** Retrieve a list of mapped objects from the statement key and parameter.* @param <E> the returned list element type* @param statement Unique identifier matching the statement to use.* @return List of mapped object*/<E> List<E> selectList(String statement);/*** Retrieve a list of mapped objects from the statement key and parameter.* @param <E> the returned list element type* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @return List of mapped object*/<E> List<E> selectList(String statement, Object parameter);
调用示例
SpringUserDO user2 = sqlSession.selectOne("com.example.start.springdemo.mybatis.generator.SpringUserDao.selectByPrimaryKey", 1L);
Mapper接口原理
SqlSession的getMapper(InterfaceName.class)方法会产生一个接口代理,通过接口方法可以执行SqlMap文件对应的SQL。其原理也是间接查找到最终的statement。 通过接口名称加方法名称拼接全路径的statement
Spring自动扫描
添加如下配置后,Spring会自动扫描该包下的接口。
<!-- DAO接口所在包名,Spring会自动查找其下的类 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.example.start.springdemo.mybatis.generator"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property></bean>
org.mybatis.spring.mapper.MapperScannerConfigurer 是一个Spring生命周期提供的扩展机制里的org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor,用于修改容器里的Bean注册,可以自定义注册Bean。改类的postProcessBeanDefinitionRegistry逻辑如下:
1)采用继承ClassPathBeanDefinitionScanner的自定义ClassPathMapperScanner对包内的接口扫描
默认情况下,扫描所有的接口,其机制为自定义Filter。
public void registerFilters() {boolean acceptAllInterfaces = true;// if specified, use the given annotation and / or marker interfaceif (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}// override AssignableTypeFilter to ignore matches on the actual marker interfaceif (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}if (acceptAllInterfaces) {// default include filter that accepts all classesaddIncludeFilter((metadataReader, metadataReaderFactory) -> true);}}
默认情况下,走AcceptAllInterfaces分支。
2)扫描到合适的Bean后,此时BeanClass还是原接口名称,Mybatis动态的修改为:org.mybatis.spring.mapper.MapperFactoryBean.
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();String beanClassName = definition.getBeanClassName();LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName+ "' mapperInterface");// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBean// 保留原有的Classdefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59// 这里修改definition.setBeanClass(this.mapperFactoryBeanClass);definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory",new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}}
容器在初始化MapperFactoryBean时,会执行FactoryBean的getObject方法,产生一个单例Bean,然后放入容器中。可以看到mapperFactoryBean的getObject方法,正式执行Mybatis的SqlSession.getMapper,产生一个代理类:
@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}
Mybatis如何生成代理类
MapperProxyFactory注册
sqlsession.getMapper(Type) -> configuration.getMapper(Type,SqlSession) -> MapperRegistry.getMapper(Type,SqlSession)
org.apache.ibatis.binding.MapperRegistry
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);}}
MapperRegistry内部的knownMappers在XML解析时加载Namespace时则已注册
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespaceprivate void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}}org.apache.ibatis.binding.MapperRegistry#addMapperpublic <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.put(type, new MapperProxyFactory<>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}
MappedProxyFactory生成代理
Mybatis默认采用JDK的动态代理机制,基于接口而生成代理类。
public class MapperProxyFactory<T> {private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public Class<T> getMapperInterface() {return mapperInterface;}public Map<Method, MapperMethod> getMethodCache() {return methodCache;}@SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}}
而MapperProxy则为JDK代理对应的InvocationHandler
org.apache.ibatis.binding.MapperProxy
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {if (privateLookupInMethod == null) {return invokeDefaultMethodJava8(proxy, method, args);} else {return invokeDefaultMethodJava9(proxy, method, args);}}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}
代理接口调用
final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
private MapperMethod cachedMapperMethod(Method method) {return methodCache.computeIfAbsent(method,k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}
public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);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:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} 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;}}
通过接口全路径加方法名称查找缓存的StatementId:
String statementId = mapperInterface.getName() + “.” + methodName;
org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatementprivate MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,Class<?> declaringClass, Configuration configuration) {// 直接拼接String statementId = mapperInterface.getName() + "." + methodName;if (configuration.hasStatement(statementId)) {return configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;}for (Class<?> superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = resolveMappedStatement(superInterface, methodName,declaringClass, configuration);if (ms != null) {return ms;}}}return null;}}
