框架架构

image.png

MappedStatement

Mybatis会把所有的Mapper XML 文件解析为MappedStatement , 并放入一个Map作为缓存。
image.png
MappedStatement的Id为:
Mapper文件中的namespace 加对应curd语句的SQLID

SqlSession#CURD

Mybatis执行所有的SQL都是通过执行最终解析后的SQL,也就是通过SqlSession接口中的API,如:

  1. /**
  2. * Retrieve a single row mapped from the statement key.
  3. * @param <T> the returned object type
  4. * @param statement
  5. * @return Mapped object
  6. */
  7. <T> T selectOne(String statement);
  8. /**
  9. * Retrieve a single row mapped from the statement key and parameter.
  10. * @param <T> the returned object type
  11. * @param statement Unique identifier matching the statement to use.
  12. * @param parameter A parameter object to pass to the statement.
  13. * @return Mapped object
  14. */
  15. <T> T selectOne(String statement, Object parameter);
  16. /**
  17. * Retrieve a list of mapped objects from the statement key and parameter.
  18. * @param <E> the returned list element type
  19. * @param statement Unique identifier matching the statement to use.
  20. * @return List of mapped object
  21. */
  22. <E> List<E> selectList(String statement);
  23. /**
  24. * Retrieve a list of mapped objects from the statement key and parameter.
  25. * @param <E> the returned list element type
  26. * @param statement Unique identifier matching the statement to use.
  27. * @param parameter A parameter object to pass to the statement.
  28. * @return List of mapped object
  29. */
  30. <E> List<E> selectList(String statement, Object parameter);

调用示例

  1. SpringUserDO user2 = sqlSession.selectOne(
  2. "com.example.start.springdemo.mybatis.generator.SpringUserDao.selectByPrimaryKey", 1L);

Mapper接口原理

SqlSession的getMapper(InterfaceName.class) 方法会产生一个接口代理,通过接口方法可以执行SqlMap文件对应的SQL。其原理也是间接查找到最终的statement。 通过接口名称加方法名称拼接全路径的statement

Spring自动扫描

添加如下配置后,Spring会自动扫描该包下的接口。

  1. <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
  2. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  3. <property name="basePackage" value="com.example.start.springdemo.mybatis.generator"/>
  4. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
  5. </bean>

org.mybatis.spring.mapper.MapperScannerConfigurer 是一个Spring生命周期提供的扩展机制里的org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor,用于修改容器里的Bean注册,可以自定义注册Bean。改类的postProcessBeanDefinitionRegistry逻辑如下:
1)采用继承ClassPathBeanDefinitionScanner的自定义ClassPathMapperScanner对包内的接口扫描
默认情况下,扫描所有的接口,其机制为自定义Filter。

  1. public void registerFilters() {
  2. boolean acceptAllInterfaces = true;
  3. // if specified, use the given annotation and / or marker interface
  4. if (this.annotationClass != null) {
  5. addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
  6. acceptAllInterfaces = false;
  7. }
  8. // override AssignableTypeFilter to ignore matches on the actual marker interface
  9. if (this.markerInterface != null) {
  10. addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
  11. @Override
  12. protected boolean matchClassName(String className) {
  13. return false;
  14. }
  15. });
  16. acceptAllInterfaces = false;
  17. }
  18. if (acceptAllInterfaces) {
  19. // default include filter that accepts all classes
  20. addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
  21. }
  22. }

默认情况下,走AcceptAllInterfaces分支。
2)扫描到合适的Bean后,此时BeanClass还是原接口名称,Mybatis动态的修改为:
org.mybatis.spring.mapper.MapperFactoryBean.

  1. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  2. GenericBeanDefinition definition;
  3. for (BeanDefinitionHolder holder : beanDefinitions) {
  4. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  5. String beanClassName = definition.getBeanClassName();
  6. LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
  7. + "' mapperInterface");
  8. // the mapper interface is the original class of the bean
  9. // but, the actual class of the bean is MapperFactoryBean
  10. // 保留原有的Class
  11. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
  12. // 这里修改
  13. definition.setBeanClass(this.mapperFactoryBeanClass);
  14. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  15. boolean explicitFactoryUsed = false;
  16. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  17. definition.getPropertyValues().add("sqlSessionFactory",
  18. new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  19. explicitFactoryUsed = true;
  20. } else if (this.sqlSessionFactory != null) {
  21. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  22. explicitFactoryUsed = true;
  23. }
  24. }

容器在初始化MapperFactoryBean时,会执行FactoryBean的getObject方法,产生一个单例Bean,然后放入容器中。可以看到mapperFactoryBean的getObject方法,正式执行Mybatis的SqlSession.getMapper,产生一个代理类:

  1. @Override
  2. public T getObject() throws Exception {
  3. return getSqlSession().getMapper(this.mapperInterface);
  4. }

Mybatis如何生成代理类

MapperProxyFactory注册

sqlsession.getMapper(Type) -> configuration.getMapper(Type,SqlSession) -> MapperRegistry.getMapper(Type,SqlSession)

org.apache.ibatis.binding.MapperRegistry

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }

MapperRegistry内部的knownMappers在XML解析时加载Namespace时则已注册

  1. org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
  2. private void bindMapperForNamespace() {
  3. String namespace = builderAssistant.getCurrentNamespace();
  4. if (namespace != null) {
  5. Class<?> boundType = null;
  6. try {
  7. boundType = Resources.classForName(namespace);
  8. } catch (ClassNotFoundException e) {
  9. //ignore, bound type is not required
  10. }
  11. if (boundType != null) {
  12. if (!configuration.hasMapper(boundType)) {
  13. // Spring may not know the real resource name so we set a flag
  14. // to prevent loading again this resource from the mapper interface
  15. // look at MapperAnnotationBuilder#loadXmlResource
  16. configuration.addLoadedResource("namespace:" + namespace);
  17. configuration.addMapper(boundType);
  18. }
  19. }
  20. }
  21. }
  22. org.apache.ibatis.binding.MapperRegistry#addMapper
  23. public <T> void addMapper(Class<T> type) {
  24. if (type.isInterface()) {
  25. if (hasMapper(type)) {
  26. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  27. }
  28. boolean loadCompleted = false;
  29. try {
  30. knownMappers.put(type, new MapperProxyFactory<>(type));
  31. // It's important that the type is added before the parser is run
  32. // otherwise the binding may automatically be attempted by the
  33. // mapper parser. If the type is already known, it won't try.
  34. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  35. parser.parse();
  36. loadCompleted = true;
  37. } finally {
  38. if (!loadCompleted) {
  39. knownMappers.remove(type);
  40. }
  41. }
  42. }
  43. }

MappedProxyFactory生成代理

Mybatis默认采用JDK的动态代理机制,基于接口而生成代理类。

  1. public class MapperProxyFactory<T> {
  2. private final Class<T> mapperInterface;
  3. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  4. public MapperProxyFactory(Class<T> mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. public Class<T> getMapperInterface() {
  8. return mapperInterface;
  9. }
  10. public Map<Method, MapperMethod> getMethodCache() {
  11. return methodCache;
  12. }
  13. @SuppressWarnings("unchecked")
  14. protected T newInstance(MapperProxy<T> mapperProxy) {
  15. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  16. }
  17. public T newInstance(SqlSession sqlSession) {
  18. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  19. return newInstance(mapperProxy);
  20. }
  21. }

而MapperProxy则为JDK代理对应的InvocationHandler

  1. org.apache.ibatis.binding.MapperProxy
  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. try {
  4. if (Object.class.equals(method.getDeclaringClass())) {
  5. return method.invoke(this, args);
  6. } else if (method.isDefault()) {
  7. if (privateLookupInMethod == null) {
  8. return invokeDefaultMethodJava8(proxy, method, args);
  9. } else {
  10. return invokeDefaultMethodJava9(proxy, method, args);
  11. }
  12. }
  13. } catch (Throwable t) {
  14. throw ExceptionUtil.unwrapThrowable(t);
  15. }
  16. final MapperMethod mapperMethod = cachedMapperMethod(method);
  17. return mapperMethod.execute(sqlSession, args);
  18. }

代理接口调用

final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);

  1. private MapperMethod cachedMapperMethod(Method method) {
  2. return methodCache.computeIfAbsent(method,
  3. k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  4. }
  1. public class MapperMethod {
  2. private final SqlCommand command;
  3. private final MethodSignature method;
  4. public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  5. this.command = new SqlCommand(config, mapperInterface, method);
  6. this.method = new MethodSignature(config, mapperInterface, method);
  7. }
  8. public Object execute(SqlSession sqlSession, Object[] args) {
  9. Object result;
  10. switch (command.getType()) {
  11. case INSERT: {
  12. Object param = method.convertArgsToSqlCommandParam(args);
  13. result = rowCountResult(sqlSession.insert(command.getName(), param));
  14. break;
  15. }
  16. case UPDATE: {
  17. Object param = method.convertArgsToSqlCommandParam(args);
  18. result = rowCountResult(sqlSession.update(command.getName(), param));
  19. break;
  20. }
  21. case DELETE: {
  22. Object param = method.convertArgsToSqlCommandParam(args);
  23. result = rowCountResult(sqlSession.delete(command.getName(), param));
  24. break;
  25. }
  26. case SELECT:
  27. if (method.returnsVoid() && method.hasResultHandler()) {
  28. executeWithResultHandler(sqlSession, args);
  29. result = null;
  30. } else if (method.returnsMany()) {
  31. result = executeForMany(sqlSession, args);
  32. } else if (method.returnsMap()) {
  33. result = executeForMap(sqlSession, args);
  34. } else if (method.returnsCursor()) {
  35. result = executeForCursor(sqlSession, args);
  36. } else {
  37. Object param = method.convertArgsToSqlCommandParam(args);
  38. result = sqlSession.selectOne(command.getName(), param);
  39. if (method.returnsOptional()
  40. && (result == null || !method.getReturnType().equals(result.getClass()))) {
  41. result = Optional.ofNullable(result);
  42. }
  43. }
  44. break;
  45. case FLUSH:
  46. result = sqlSession.flushStatements();
  47. break;
  48. default:
  49. throw new BindingException("Unknown execution method for: " + command.getName());
  50. }
  51. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  52. throw new BindingException("Mapper method '" + command.getName()
  53. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  54. }
  55. return result;
  56. }
  57. }

通过接口全路径加方法名称查找缓存的StatementId:

String statementId = mapperInterface.getName() + “.” + methodName;

  1. org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement
  2. private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
  3. Class<?> declaringClass, Configuration configuration) {
  4. // 直接拼接
  5. String statementId = mapperInterface.getName() + "." + methodName;
  6. if (configuration.hasStatement(statementId)) {
  7. return configuration.getMappedStatement(statementId);
  8. } else if (mapperInterface.equals(declaringClass)) {
  9. return null;
  10. }
  11. for (Class<?> superInterface : mapperInterface.getInterfaces()) {
  12. if (declaringClass.isAssignableFrom(superInterface)) {
  13. MappedStatement ms = resolveMappedStatement(superInterface, methodName,
  14. declaringClass, configuration);
  15. if (ms != null) {
  16. return ms;
  17. }
  18. }
  19. }
  20. return null;
  21. }
  22. }