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

简介: 前面我们知道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());
    }
相关文章
|
机器学习/深度学习 算法 搜索推荐
外卖平台推荐算法的优化与实践
外卖平台推荐算法的优化与实践
|
12月前
|
机器学习/深度学习 PyTorch 算法框架/工具
揭秘深度学习中的微调难题:如何运用弹性权重巩固(EWC)策略巧妙应对灾难性遗忘,附带实战代码详解助你轻松掌握技巧
【10月更文挑战第1天】深度学习中,模型微调虽能提升性能,但常导致“灾难性遗忘”,即模型在新任务上训练后遗忘旧知识。本文介绍弹性权重巩固(EWC)方法,通过在损失函数中加入正则项来惩罚对重要参数的更改,从而缓解此问题。提供了一个基于PyTorch的实现示例,展示如何在训练过程中引入EWC损失,适用于终身学习和在线学习等场景。
949 4
揭秘深度学习中的微调难题:如何运用弹性权重巩固(EWC)策略巧妙应对灾难性遗忘,附带实战代码详解助你轻松掌握技巧
|
DataWorks 关系型数据库 MySQL
如何实现同步数据至dataworks?
如何实现同步数据至dataworks?
230 1
|
存储 Java 数据挖掘
阿里云时序数据库简介和购买使用流程
阿里云时序数据库(Lindorm Time Series Database,简称TSDB)是阿里云原生多模数据库Lindorm中的核心组件,专门负责处理时序数据。它具有许多优势,包括高并发写入、高压缩比存储、实时时序指标聚合、统计、预测以及ML/AI计算等强大功能。 时序数据是指表示物理设备、系统、应用过程或行为随时间变化的数据,这类数据在物联网、工业物联网、基础运维系统等场景中有着广泛的应用。阿里云TSDB可以解决大规模时序数据的可靠写入问题,显著降低数据存储成本,并且能够实时灵活地完成业务数据的聚合分析。 TSDB针对不同应用场景,支持多元数据存储与索引,具有高效的写入性能和实时数据分析能
|
机器学习/深度学习 存储 关系型数据库
深入Doris实时数仓:导入本地数据
深入Doris实时数仓:导入本地数据
|
消息中间件 Java API
Java一分钟之-JMS:Java消息服务
【6月更文挑战第11天】Java消息服务(JMS)是企业应用中实现组件解耦和异步通信的标准API。它包含点对点(P2P)和发布/订阅(Pub/Sub)两种消息模型。常见问题包括混淆消息模型、忽略事务管理和资源泄露。解决方法包括明确业务需求选择模型、使用事务确保消息可靠性以及正确关闭资源。文中提供了使用ActiveMQ的P2P模型的生产者和消费者代码示例,强调理解基础概念、避免问题以及实践是使用JMS的关键。
489 2
|
Go 开发者
Golang深入浅出之-Go语言结构体(struct)入门:定义与使用
【4月更文挑战第22天】Go语言中的结构体是构建复杂数据类型的关键,允许组合多种字段。本文探讨了结构体定义、使用及常见问题。结构体定义如`type Person struct { Name string; Age int; Address Address }`。问题包括未初始化字段的默认值、比较含不可比较字段的结构体以及嵌入结构体字段重名。避免方法包括初始化结构体、自定义比较逻辑和使用明确字段选择器。结构体方法、指针接收者和匿名字段嵌入提供了灵活性。理解这些问题和解决策略能提升Go语言编程的效率和代码质量。
292 1
|
机器学习/深度学习 边缘计算 安全
互联网安全的现状与防护策略
互联网安全问题已经成为了一个全球性的挑战,涉及到个人隐私、商业利益和国家安全。为了应对不断增加的威胁,我们需要采取多层次的安全策略,包括强化密码安全性、更新软件、多层次防御和数据加密。随着技术的不断发展,我们有望在未来看到更多创新的安全解决方案的出现,以应对不断变化的网络威胁。
890 1
互联网安全的现状与防护策略
|
JSON API 数据格式
抖音商品详情API接口在电商行业中的重要性及实时数据获取实现
随着移动互联网的快速发展,电商行业不断壮大。抖音作为一款短视频社交应用,近年来在电商领域取得了显著成果。本文将探讨抖音商品详情API接口在电商行业中的重要性,以及如何通过实时数据获取提高业务效率。我们将介绍相关的技术背景、API接口的基础知识、实时数据获取的方法和代码实现,并通过一个案例来展示具体应用。
|
XML 缓存 SpringCloudAlibaba
Spring注解导入:@Import使用及原理详解
`@Import` 是 Spring 基于 Java 注解配置的主要组成部分,`@Import` 注解提供了类似 `@Bean` 注解的功能,向Spring容器中注入bean,也对应实现了与Spring XML中的<import/>元素相同的功能
1129 0