第03篇:Mybatis核心类详细介绍

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 前面我们知道Mybatis的解析原理,知道了在 `Configuration` 、`MapperBuilderAssistant` 出现了很多核心的类。正是由这些类来实现了,Mybatis的核心功能。所以要想完全搞懂 Mybatis,这些类就必须要进行深入的研究,废话不多少,直接就开始吧。
核心类介绍
前面我们知道Mybatis的解析原理,知道了在 ConfigurationMapperBuilderAssistant 出现了很多核心的类。
正是由这些类来实现了,Mybatis的核心功能。所以要想完全搞懂 Mybatis,这些类就必须要进行深入的研究,废话不多少,直接就开始吧。

其实这里面的每个类要都能单独拆出来一篇进行详细说明,但是这里我们只取其精华,知道他的作用,及如何使用。和能借鉴的地方就可以了。

一、Configuration

属性 解释
TypeAliasRegistry key是一个别名,value是一个class对象
Properties variables 配置文件中占位符的变量配置
InterceptorChain interceptorChain 拦截链,用于拦截方法,实现插件
ObjectFactory objectFactory 对象实例化统一的工厂方法
ObjectWrapperFactory objectWrapperFactory 扩展使用,允许用户自定义包装对象ObjectWrapper
ReflectorFactory reflectorFactory 反射工厂,用于生成一个反射信息对象
Environment environment 环境信息包含(事务管理器和数据源)
TypeHandlerRegistry typeHandlerRegistry 数据库返回数据类型转换成Java对象的处理器,或是Java数据类型转换jdbc数据类型的处理器
MapperRegistry mapperRegistry Mapper生成的处理类,包含代理的逻辑

1.1 TypeAliasRegistry

key是别名,value是对应的Class<?>

这个在什么时候用的呢? 前面我们通过解析xml,发现很多的dtd约束,文件的值类型都是 CDATA 即 字符串。 但是这些字符串最终是要解析成指定的字节码的。
怎么知道字符串对应的是哪个java类呢? 那么这个功能就交给 TypeAliasRegistry。允许你将一个java类注册一个别名。这样你就可以在配置文件中用别名
来替换java类了。

    @Test
    public void TypeAliasRegistry() {
        TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        System.out.println(typeAliasRegistry.resolveAlias("byte"));
    }

1.2 Properties

这个java类就不用介绍了,在Configuration 就是存储的配置信息,允许你在mybatis中任意地方使用${}进行访问数据。

比如你可以这样用? 配置一个全局的limit限制数量

datasource.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.url=jdbc:mysql://127.0.0.1:3306/test
datasource.username=root
datasource.password=123456
datasource.globalLimit=1000
public interface TUserMapper {
    @Select("select * from t_user where uid = ${id} limit ${datasource.globalLimit} ")
    List<TUser> selectById(Long id);
}    

1.3 InterceptorChain

内容较多,开单独的篇幅进行介绍; 第07篇:Mybatis的插件设计分析

从名字就可以看到是一个拦截链; 主要是实现插件的功能。核心思路是, 通过拦截类的方法来实现插件。

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

1.4 ObjectFactory 对象工厂

在Mybatis中或者说是orm框架中, 使用到反射的地方较多。那么就一定会遇到实例化的问题。具体如何实例化。就是使用对象工厂。
之所以提供个工厂, 小编个人认为还是为了扩展使用。但是实际中一般不会扩展这个类。因为该有的功能默认的就已经具备了。

public interface ObjectFactory {
  
  // 配置信息
  default void setProperties(Properties properties) {}
  // 根据空构造来实例化
  <T> T create(Class<T> type);
  // 根据构造参数来实例化
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
  // 判断是否是Collection子类
  <T> boolean isCollection(Class<T> type);

}

1.5 ObjectWrapperFactory 对象包装工厂

他的作用主要是提供外面的扩展,允许用户自己去创建包装对象。实际框架中不会用到这个对象。我们只要知道他的作用是什么行。
我们重点说一下 ObjectWrapper 。

ObjectWrapper的主要作用是,提供统一的属性操作方法。主要在MetaObject被使用,如下。

public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }
}  

我们看到普通的对象,被包装成 ObjectWrapper后就可以使用通用的API来获取和修改对象数值型,以及可以获取属性值的类型信息,如下面的例子。

    @Test
    public void objectWrapper(){
        // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 获取Mybatis配置信息
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Map<String,String> map = new HashMap<>();
        map.put("name","孙悟空");
        MetaObject metaObject = MetaObject.forObject(map, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        System.out.println(metaObject.getValue("name"));
        // 复制
        metaObject.setValue("age",18);
        // {name=孙悟空, age=18}
        System.out.println(map);

        TUser tUser = new TUser();
        tUser.setName("唐三藏");
        MetaObject tUserMetaObject = MetaObject.forObject(tUser, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        // 唐三藏
        System.out.println(tUserMetaObject.getValue("name"));

        List<TUser> users = new ArrayList<>();
        users.add(tUser);
        MetaObject tUserMetaObjects = MetaObject.forObject(users, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        tUserMetaObjects.add(new TUser());
        // [TUser(tokenId=null, uid=null, name=唐三藏), TUser(tokenId=null, uid=null, name=null)]
        System.out.println(tUserMetaObjects.getOriginalObject());
    }

1.6 ReflectorFactory 反射工厂

从名字看就是反射的工厂,主要是为了生成 Reflector 对象。Reflector 对反射的信息进行了缓存。用的时候直接从缓存中获取。

public interface ReflectorFactory {

  boolean isClassCacheEnabled();

  void setClassCacheEnabled(boolean classCacheEnabled);

  Reflector findForClass(Class<?> type);
}

1.7 Environment 环境

这里面的环境属性,是比较重要。因为他直接决定了你要跟那个数据库交互。以及事务如何处理。

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
}  
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();
    }
  }

1.8 TypeHandlerRegistry

TypeHandler + Registry, 从名字来看又是一个类型注册器用于反射使用。看来mybatis中用于反射的工具类是在太多了。那么TypeHandler究竟有什么用呢?
TypeHandler 是对Statement和ResultSet负责。
ResultSet 是从数据库获取的数据的载体,Statement 是准备向数据库提交数据的载体。TypeHandler 的作用就是
根据数据类型, 处理跟数据的输入和输出信息。看下面接口。

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

这里举一个例子,比如name这个字段在数据库是varchar类型,但是java对象中name是一个Name对象。那么如何处理呢?
我们自定义一个处理器。

public class NameTypeHandler implements TypeHandler<Name> {

    @Override
    public void setParameter(PreparedStatement ps, int i, Name parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.getFirstName() + "-" + parameter.getSurname());
    }

    @Override
    public Name getResult(ResultSet rs, String columnName) throws SQLException {
        String name = rs.getString(columnName);
        String[] split = name.split("-");
        return new Name(split[0], split[1]);
    }
}    

然后在配置文件中声明注册器,用于将java对象转换成jdbc数据库字段类型。同时也将数据库查询到的jdbc类型转换成java对象。

    <configuration>
        <typeHandlers>
            <typeHandler handler="orm.example.dal.type.NameTypeHandler" javaType="orm.example.dal.model.Name"></typeHandler>
        </typeHandlers>
    </configuration>    
    <mapper>
         <insert id="insert" parameterType="orm.example.dal.model.T2User">
            <!--
              WARNING - @mbggenerated
              This element is automatically generated by MyBatis Generator, do not modify.
              This element was generated on Sun Mar 27 23:01:23 CST 2022.
            -->
            insert into T_USER (token_id, uid, name)
            values (#{tokenId,jdbcType=CHAR}, #{uid,jdbcType=INTEGER}, #{name,javaType=orm.example.dal.model.Name })
        </insert>
    </mapper>

我们执行下面代码,可以看到我们将数据类型转换成了jdbc存到了数据库,同时执行查询时候又将jdbc类型转换成了java对象。这就是它的作用。

    @Test
    public void test() {
        // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 获取Mybatis配置信息
        Configuration configuration = sqlSessionFactory.getConfiguration();
        configuration.getTypeHandlerRegistry().register(new NameTypeHandler());
        // 参数: autoCommit,从名字上看就是是否自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        // 获取Mapper
        T2UserMapper mapper = configuration.getMapperRegistry().getMapper(T2UserMapper.class, sqlSession);
        T2User tUser = new T2User();
        Name name = new Name("孙","悟空");
        tUser.setName(name);
        tUser.setTokenId("西天取经");
        mapper.insert(tUser);
        // 获取插入的数据: T2User(tokenId=西天取经, uid=32, name=Name(surname=悟空, firstName=孙))
        System.out.println(mapper.selectByPrimaryKey("西天取经"));
        // 数据插入后,执行查询,然后回滚数据
        sqlSession.rollback();
    }

1.9 MapperRegistry

看到Registry又知道了,这货又是一个类似Map的工具类。肯定是跟Mapper有关系。下面代码关键在于13和17行。
Mybatis中获取Mapper对象都是从 MapperRegistry中获取的。

  • line(13) new MapperProxyFactory<>(type) 接口生成代理对象
  • line(17) MapperAnnotationBuilder 用于解析Mybatis支持的注解,并添加到 Configuration

这两个类比较重要我们开单独的篇幅进行说明。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  
  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.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);
        }
      }
    }
  }
}

1.10 SqlSession

SqlSession相当于一千个桥梁,负责将方法参数,发送给数据库,并且将数据库返回值组装成方法的返回值。

在SqlSession中有几个比较重要的类,如下图。他们负责不同的逻辑。
分别处理入参(ParameterHandler),处理出参(ResultSetHandler),生成Jdbc(StatementHandler),处理缓存相关(Executor)。
是一个非常重要的一个类。后面我们的学习中会经常看到。

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}

我们看增删改查的方法入参无非2个。1个是statement,1个是入参。
其中statement主要是为了获取 MappedStatement。如下

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

另外一个入参是为了组装sql信息。MappedStatement#getBoundSql 获取sql信息。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

二、MapperBuilderAssistant

属性 解释
MapperBuilderAssistant Mapper构建辅助工具类(缓存配置)
CacheRefResolver 决定如何使用缓存
ParameterMapping 参数映射类
ResultMapResolver 返回值映射
Map<String, XNode> sqlFragments sql片段
MappedStatement Mapper方法的所有信息(出参,入参)

2.1 MapperBuilderAssistant

Mapper构建工具类,下面小编列举了几个方法。可以看出来基本都是用于处理sql结果集向java对象转换使用,和对Mapper方法签名分析生成sql的工具。
下面我们一个一个来看看。

public class MapperBuilderAssistant extends BaseBuilder {
    // 确定使用那个缓存
    public Cache useCacheRef(String namespace);
    // 生成2级缓存对象
    public Cache useNewCache(...);
    // 每个参数的信息
    public ParameterMapping buildParameterMapping();
    // 生成结构集
    public ResultMap addResultMap();
    // 鉴别器
    public Discriminator buildDiscriminator();
    // 生成Mapper签名
    public MappedStatement addMappedStatement();
    // 获取方言处理器
    public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass);
}

2.1.1 Cache

Mybatis 缓存的接口定义,用于缓存查询sql的结果。Mybatis中一级缓存和二级缓存是一个面试经常会考的问题。这个类我们也单独开一篇私聊。

2.1.2 ParameterMapping & ResultMapping

从名字中能看到就是对Mapper中方法的入参和出参的映射关系类。

public class ParameterMapping {
  private Configuration configuration;
  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
}  

如图所示,会对方法的每个参数,生成一个 ParameterMapping对象。存储了java类型和db的类型的映射关系。

2.1.3 ResultMap

从名字看就是对jdbc结果集向Mapper返回值的映射关系,用于将jdbc数据重新映射成Java对象。

    @Test
    public void resultSet(){
        // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 获取Mybatis配置信息
        Configuration configuration = sqlSessionFactory.getConfiguration();
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        TUserMapper mapper = configuration.getMapper(TUserMapper.class,sqlSession);
        System.out.println(mapper.selectAll());
        MappedStatement mappedStatement = configuration.getMappedStatement("orm.example.dal.mapper.TUserMapper.selectAll");
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        System.out.println(resultMaps);
    }

2.1.4 LanguageDriver

主要用于生成 SqlSource,动态sql(XMLLanguageDriver)或者静态sql(RawLanguageDriver)


public interface LanguageDriver {
 
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
 
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}

动态sql可以处理下面这些标签

 private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

2.2 CacheRefResolver

确定每个Mapper配置的缓存

public class CacheRefResolver {
  private final MapperBuilderAssistant assistant;
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

2.3 MappedStatement

可以说关于Mapper所有的信息都在这个类里面,包括sql信息、入参及返回值类型、sql类型(SqlCommandType)、是否使用缓存、
是否刷新缓存、StatementType类型。

public final class MappedStatement {
  // mapper/TUserMapper.xml
  private String resource;
  // 全局配置
  private Configuration configuration;
  // orm.example.dal.mapper.TUserMapper.insert
  private String id;
  // 
  private Integer fetchSize;
  // 超时时间
  private Integer timeout;
  // StatementType.PREPARED
  private StatementType statementType;
  // ResultSetType.DEFAULT(-1),
  private ResultSetType resultSetType;
  // RawSqlSource
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  // SqlCommandType( UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH)
  private SqlCommandType sqlCommandType;
  // 生成id
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}  

生成主要有2种方法。

  1. xml的方式 XMLStatementBuilder
  2. 通过注解的方式 MapperAnnotationBuilder
public class XMLMapperBuilder extends BaseBuilder 
    public void parse() {
        // 如果有资源文件先解析xml,并保存到Configuration#addMappedStatement
        if (!configuration.isResourceLoaded(resource)) {
          // XMLStatementBuilder进行解析
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          // 同时使用MapperAnnotationBuilder类解析
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }
}  

public class MapperAnnotationBuilder{
      // 只有包含了下面注解的方法才会被解析
      private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
      .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
          InsertProvider.class, DeleteProvider.class)
      .collect(Collectors.toSet());
      
     public  void parseStatement(Method method) {
        final Class<?> parameterTypeClass = getParameterType(method);
        final LanguageDriver languageDriver = getLanguageDriver(method);
        // 判断是否包含了上面的注解
        getAnnotationWrapper(method, true, statementAnnotationTypes)
        .ifPresent(statementAnnotation -> {})
     }   
}    

Mapper配置文件在解析的时候首先,回去解析xml,然后解析注解。如果两种方式都存在那么就会提示错误。

:::danger
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for orm.example.dal.mapper.TUserMapper.selectAll. please check mapper/TUserMapper.xml and orm/example/dal/mapper/TUserMapper.java (best guess)
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:1014)
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:970)
:::

原因就在 StrictMap。

public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
}

三、可以借鉴的知识点

3.1 包装器模式

ObjectWrapper

ObjectWrapper的主要作用是,提供统一的属性操作方法。主要在MetaObject被使用,如下。

     @Test
    public void objectWrapper() {
        TUser mock = JMockData.mock(TUser.class);
        MetaObject metaObject = MetaObject.forObject(mock, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        boolean name = metaObject.hasGetter("name");
        if (name) {
            // iuslA4Xp
            System.out.println(metaObject.getValue("name"));
        }

        Map<String,Object> map = new HashMap<>();
        map.put("age",18);
        MetaObject metaMap = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        boolean age = metaMap.hasGetter("age");
        if (age) {
            // 18 
            System.out.println(metaMap.getValue("age"));
        }
    }

3.2 MetaClass

反射工具类

    @Test
    public void metaClass()throws Exception{
        MetaClass metaClass = MetaClass.forClass(TUser.class, new DefaultReflectorFactory());
        TUser blankUser = new TUser();
        metaClass.getSetInvoker("name").invoke(blankUser,new Object[]{"孙悟空"});
        // 孙悟空
        System.out.println(blankUser.getName());
    }
相关文章
|
7月前
|
SQL XML Java
mybatis Mapper的概念与实战
MyBatis 是一个流行的 Java 持久层框架,它提供了对象关系映射(ORM)的功能,使得Java对象和数据库中的表之间的映射变得简单。在MyBatis中,Mapper是一个核心的概念,它定义了映射到数据库操作的接口。简而言之,Mapper 是一个接口,MyBatis 通过这个接口与XML映射文件或者注解绑定,以实现对数据库的操作。
198 1
|
2月前
|
Java 数据库连接 容器
Mybatis-Plus核心功能
Mybatis-Plus核心功能
|
6月前
|
Java 数据库连接 mybatis
Mybatis mapper动态代理解决方案
该文介绍了Mybatis中使用Mapper接口的方式代替XML配置执行SQL。Mapper接口规范包括:namespace与接口类路径相同,select ID与接口方法名一致,parameterType和方法参数类型匹配,resultType与返回值类型一致。实现过程中,需配置Mapper.xml,编写Mapper.java接口,并在Mybatis-config.xml中设置。测试类中,通过SqlSession的getMapper方法获取接口的动态代理对象,调用方法执行SQL。
184 0
|
7月前
|
XML Java 数据库连接
MyBatis中的MapperScan的作用是干什么的?
MyBatis中的MapperScan的作用是干什么的?
798 0
|
7月前
|
SQL Java 关系型数据库
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能
324 0
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能
|
7月前
|
SQL Java 数据库连接
|
Java 数据库连接 数据库
Mybatis核心配置
一、environments环境配置 二、propertis资源配置 三、typeAliases类型别名 四、mappers映射 五、其他标签
200 0
Mybatis核心配置
|
SQL Java 关系型数据库
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能(三)
3.3Lambda IService中还提供了Lambda功能来简化我们的复杂查询及更新功能。我们通过两个案例来学习一下。 案例一:实现一个根据复杂条件查询用户的接口,查询条件如下:
209 0
|
SQL Java 数据库连接
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能(一)
刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了。
291 0
|
SQL
【MyBatis-Plus】快速精通Mybatis-plus框架—核心功能(二)
3.2基本用法 由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。
289 0