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

举报
西魏陶渊明 发表于 2022/09/25 02:22:05 2022/09/25
【摘要】 核心类介绍 前面我们知道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类了。


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

# 1.2 Properties

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

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


    
  1. datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  2. datasource.url=jdbc:mysql://127.0.0.1:3306/test
  3. datasource.username=root
  4. datasource.password=123456
  5. datasource.globalLimit=1000
1 2 3 4 5

    
  1. public interface TUserMapper {
  2. @Select("select * from t_user where uid = ${id} limit ${datasource.globalLimit} ")
  3. List<TUser> selectById(Long id);
  4. }
1 2 3 4

# 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)

    
  1. public class InterceptorChain {
  2. private final List<Interceptor> interceptors = new ArrayList<>();
  3. public Object pluginAll(Object target) {
  4. for (Interceptor interceptor : interceptors) {
  5. target = interceptor.plugin(target);
  6. }
  7. return target;
  8. }
  9. public void addInterceptor(Interceptor interceptor) {
  10. interceptors.add(interceptor);
  11. }
  12. public List<Interceptor> getInterceptors() {
  13. return Collections.unmodifiableList(interceptors);
  14. }
  15. }
  16. public interface Interceptor {
  17. Object intercept(Invocation invocation) throws Throwable;
  18. default Object plugin(Object target) {
  19. return Plugin.wrap(target, this);
  20. }
  21. default void setProperties(Properties properties) {
  22. // NOP
  23. }
  24. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

# 1.4 ObjectFactory 对象工厂

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


    
  1. public interface ObjectFactory {
  2. // 配置信息
  3. default void setProperties(Properties properties) {}
  4. // 根据空构造来实例化
  5. <T> T create(Class<T> type);
  6. // 根据构造参数来实例化
  7. <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
  8. // 判断是否是Collection子类
  9. <T> boolean isCollection(Class<T> type);
  10. }
1 2 3 4 5 6 7 8 9 10 11 12 13

# 1.5 ObjectWrapperFactory 对象包装工厂

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

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


    
  1. public class MetaObject {
  2. private final Object originalObject;
  3. private final ObjectWrapper objectWrapper;
  4. private final ObjectFactory objectFactory;
  5. private final ObjectWrapperFactory objectWrapperFactory;
  6. private final ReflectorFactory reflectorFactory;
  7. private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  8. this.originalObject = object;
  9. this.objectFactory = objectFactory;
  10. this.objectWrapperFactory = objectWrapperFactory;
  11. this.reflectorFactory = reflectorFactory;
  12. if (object instanceof ObjectWrapper) {
  13. this.objectWrapper = (ObjectWrapper) object;
  14. } else if (objectWrapperFactory.hasWrapperFor(object)) {
  15. this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  16. } else if (object instanceof Map) {
  17. this.objectWrapper = new MapWrapper(this, (Map) object);
  18. } else if (object instanceof Collection) {
  19. this.objectWrapper = new CollectionWrapper(this, (Collection) object);
  20. } else {
  21. this.objectWrapper = new BeanWrapper(this, object);
  22. }
  23. }
  24. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

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


    
  1. @Test
  2. public void objectWrapper(){
  3. // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录)
  4. InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
  5. // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行
  6. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
  7. // 获取Mybatis配置信息
  8. Configuration configuration = sqlSessionFactory.getConfiguration();
  9. Map<String,String> map = new HashMap<>();
  10. map.put("name","孙悟空");
  11. MetaObject metaObject = MetaObject.forObject(map, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
  12. System.out.println(metaObject.getValue("name"));
  13. // 复制
  14. metaObject.setValue("age",18);
  15. // {name=孙悟空, age=18}
  16. System.out.println(map);
  17. TUser tUser = new TUser();
  18. tUser.setName("唐三藏");
  19. MetaObject tUserMetaObject = MetaObject.forObject(tUser, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
  20. // 唐三藏
  21. System.out.println(tUserMetaObject.getValue("name"));
  22. List<TUser> users = new ArrayList<>();
  23. users.add(tUser);
  24. MetaObject tUserMetaObjects = MetaObject.forObject(users, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
  25. tUserMetaObjects.add(new TUser());
  26. // [TUser(tokenId=null, uid=null, name=唐三藏), TUser(tokenId=null, uid=null, name=null)]
  27. System.out.println(tUserMetaObjects.getOriginalObject());
  28. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# 1.6 ReflectorFactory 反射工厂

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


    
  1. public interface ReflectorFactory {
  2. boolean isClassCacheEnabled();
  3. void setClassCacheEnabled(boolean classCacheEnabled);
  4. Reflector findForClass(Class<?> type);
  5. }
1 2 3 4 5 6 7 8

# 1.7 Environment 环境

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


    
  1. public final class Environment {
  2. private final String id;
  3. private final TransactionFactory transactionFactory;
  4. private final DataSource dataSource;
  5. }
1 2 3 4 5

    
  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. try {
  4. final Environment environment = configuration.getEnvironment();
  5. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  6. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  7. final Executor executor = configuration.newExecutor(tx, execType);
  8. return new DefaultSqlSession(configuration, executor, autoCommit);
  9. } catch (Exception e) {
  10. closeTransaction(tx); // may have fetched a connection so lets call close()
  11. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  12. } finally {
  13. ErrorContext.instance().reset();
  14. }
  15. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# 1.8 TypeHandlerRegistry

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


    
  1. public interface TypeHandler<T> {
  2. void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  3. /**
  4. * Gets the result.
  5. *
  6. * @param rs
  7. * the rs
  8. * @param columnName
  9. * Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
  10. * @return the result
  11. * @throws SQLException
  12. * the SQL exception
  13. */
  14. T getResult(ResultSet rs, String columnName) throws SQLException;
  15. T getResult(ResultSet rs, int columnIndex) throws SQLException;
  16. T getResult(CallableStatement cs, int columnIndex) throws SQLException;
  17. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

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


    
  1. public class NameTypeHandler implements TypeHandler<Name> {
  2. @Override
  3. public void setParameter(PreparedStatement ps, int i, Name parameter, JdbcType jdbcType) throws SQLException {
  4. ps.setString(i, parameter.getFirstName() + "-" + parameter.getSurname());
  5. }
  6. @Override
  7. public Name getResult(ResultSet rs, String columnName) throws SQLException {
  8. String name = rs.getString(columnName);
  9. String[] split = name.split("-");
  10. return new Name(split[0], split[1]);
  11. }
  12. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14

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


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

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


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

# 1.9 MapperRegistry

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

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

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


    
  1. public class MapperRegistry {
  2. private final Configuration config;
  3. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  4. public <T> void addMapper(Class<T> type) {
  5. if (type.isInterface()) {
  6. if (hasMapper(type)) {
  7. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  8. }
  9. boolean loadCompleted = false;
  10. try {
  11. knownMappers.put(type, new MapperProxyFactory<>(type));
  12. // It's important that the type is added before the parser is run
  13. // otherwise the binding may automatically be attempted by the
  14. // mapper parser. If the type is already known, it won't try.
  15. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  16. parser.parse();
  17. loadCompleted = true;
  18. } finally {
  19. if (!loadCompleted) {
  20. knownMappers.remove(type);
  21. }
  22. }
  23. }
  24. }
  25. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

# 1.10 SqlSession

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

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


    
  1. public interface SqlSession extends Closeable {
  2. <T> T selectOne(String statement);
  3. <T> T selectOne(String statement, Object parameter);
  4. int insert(String statement);
  5. int insert(String statement, Object parameter);
  6. int update(String statement);
  7. int update(String statement, Object parameter);
  8. int delete(String statement);
  9. int delete(String statement, Object parameter);
  10. void commit();
  11. void commit(boolean force);
  12. void rollback();
  13. void rollback(boolean force);
  14. List<BatchResult> flushStatements();
  15. @Override
  16. void close();
  17. void clearCache();
  18. Configuration getConfiguration();
  19. <T> T getMapper(Class<T> type);
  20. Connection getConnection();
  21. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

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


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

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


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

# 二、MapperBuilderAssistant

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

# 2.1 MapperBuilderAssistant

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


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

# 2.1.1 Cache

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

# 2.1.2 ParameterMapping & ResultMapping

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


    
  1. public class ParameterMapping {
  2. private Configuration configuration;
  3. private String property;
  4. private ParameterMode mode;
  5. private Class<?> javaType = Object.class;
  6. private JdbcType jdbcType;
  7. private Integer numericScale;
  8. private TypeHandler<?> typeHandler;
  9. private String resultMapId;
  10. private String jdbcTypeName;
  11. private String expression;
  12. }
1 2 3 4 5 6 7 8 9 10 11 12

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

# 2.1.3 ResultMap

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


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

# 2.1.4 LanguageDriver

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


    
  1. public interface LanguageDriver {
  2. ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  3. SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  4. SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
  5. }
1 2 3 4 5 6 7 8 9 10

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


    
  1. private void initNodeHandlerMap() {
  2. nodeHandlerMap.put("trim", new TrimHandler());
  3. nodeHandlerMap.put("where", new WhereHandler());
  4. nodeHandlerMap.put("set", new SetHandler());
  5. nodeHandlerMap.put("foreach", new ForEachHandler());
  6. nodeHandlerMap.put("if", new IfHandler());
  7. nodeHandlerMap.put("choose", new ChooseHandler());
  8. nodeHandlerMap.put("when", new IfHandler());
  9. nodeHandlerMap.put("otherwise", new OtherwiseHandler());
  10. nodeHandlerMap.put("bind", new BindHandler());
  11. }
1 2 3 4 5 6 7 8 9 10 11

# 2.2 CacheRefResolver

确定每个Mapper配置的缓存


    
  1. public class CacheRefResolver {
  2. private final MapperBuilderAssistant assistant;
  3. private final String cacheRefNamespace;
  4. public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
  5. this.assistant = assistant;
  6. this.cacheRefNamespace = cacheRefNamespace;
  7. }
  8. public Cache resolveCacheRef() {
  9. return assistant.useCacheRef(cacheRefNamespace);
  10. }
  11. }
1 2 3 4 5 6 7 8 9 10 11 12 13

# 2.3 MappedStatement

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


    
  1. public final class MappedStatement {
  2. // mapper/TUserMapper.xml
  3. private String resource;
  4. // 全局配置
  5. private Configuration configuration;
  6. // orm.example.dal.mapper.TUserMapper.insert
  7. private String id;
  8. //
  9. private Integer fetchSize;
  10. // 超时时间
  11. private Integer timeout;
  12. // StatementType.PREPARED
  13. private StatementType statementType;
  14. // ResultSetType.DEFAULT(-1),
  15. private ResultSetType resultSetType;
  16. // RawSqlSource
  17. private SqlSource sqlSource;
  18. private Cache cache;
  19. private ParameterMap parameterMap;
  20. private List<ResultMap> resultMaps;
  21. private boolean flushCacheRequired;
  22. private boolean useCache;
  23. private boolean resultOrdered;
  24. // SqlCommandType( UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH)
  25. private SqlCommandType sqlCommandType;
  26. // 生成id
  27. private KeyGenerator keyGenerator;
  28. private String[] keyProperties;
  29. private String[] keyColumns;
  30. private boolean hasNestedResultMaps;
  31. private String databaseId;
  32. private Log statementLog;
  33. private LanguageDriver lang;
  34. private String[] resultSets;
  35. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

生成主要有2种方法。

  1. xml的方式 XMLStatementBuilder
  2. 通过注解的方式 MapperAnnotationBuilder

    
  1. public class XMLMapperBuilder extends BaseBuilder
  2. public void parse() {
  3. // 如果有资源文件先解析xml,并保存到Configuration#addMappedStatement
  4. if (!configuration.isResourceLoaded(resource)) {
  5. // XMLStatementBuilder进行解析
  6. configurationElement(parser.evalNode("/mapper"));
  7. configuration.addLoadedResource(resource);
  8. // 同时使用MapperAnnotationBuilder类解析
  9. bindMapperForNamespace();
  10. }
  11. parsePendingResultMaps();
  12. parsePendingCacheRefs();
  13. parsePendingStatements();
  14. }
  15. }
  16. public class MapperAnnotationBuilder{
  17. // 只有包含了下面注解的方法才会被解析
  18. private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
  19. .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
  20. InsertProvider.class, DeleteProvider.class)
  21. .collect(Collectors.toSet());
  22. public void parseStatement(Method method) {
  23. final Class<?> parameterTypeClass = getParameterType(method);
  24. final LanguageDriver languageDriver = getLanguageDriver(method);
  25. // 判断是否包含了上面的注解
  26. getAnnotationWrapper(method, true, statementAnnotationTypes)
  27. .ifPresent(statementAnnotation -> {})
  28. }
  29. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

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

警告

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 S t r i c t M a p . p u t ( C o n f i g u r a t i o n . j a v a : 1014 ) a t o r g . a p a c h e . i b a t i s . s e s s i o n . C o n f i g u r a t i o n StrictMap.put(Configuration.java:1014) at org.apache.ibatis.session.Configuration StrictMap.put(Configuration.java:1014)atorg.apache.ibatis.session.ConfigurationStrictMap.put(Configuration.java:970)

原因就在 StrictMap。


    
  1. public V put(String key, V value) {
  2. if (containsKey(key)) {
  3. throw new IllegalArgumentException(name + " already contains value for " + key
  4. + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
  5. }
  6. if (key.contains(".")) {
  7. final String shortKey = getShortName(key);
  8. if (super.get(shortKey) == null) {
  9. super.put(shortKey, value);
  10. } else {
  11. super.put(shortKey, (V) new Ambiguity(shortKey));
  12. }
  13. }
  14. return super.put(key, value);
  15. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# 三、可以借鉴的知识点

# 3.1 包装器模式

ObjectWrapper

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


    
  1. @Test
  2. public void objectWrapper() {
  3. TUser mock = JMockData.mock(TUser.class);
  4. MetaObject metaObject = MetaObject.forObject(mock, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
  5. boolean name = metaObject.hasGetter("name");
  6. if (name) {
  7. // iuslA4Xp
  8. System.out.println(metaObject.getValue("name"));
  9. }
  10. Map<String,Object> map = new HashMap<>();
  11. map.put("age",18);
  12. MetaObject metaMap = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
  13. boolean age = metaMap.hasGetter("age");
  14. if (age) {
  15. // 18
  16. System.out.println(metaMap.getValue("age"));
  17. }
  18. }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# 3.2 MetaClass

反射工具类


    
  1. @Test
  2. public void metaClass()throws Exception{
  3. MetaClass metaClass = MetaClass.forClass(TUser.class, new DefaultReflectorFactory());
  4. TUser blankUser = new TUser();
  5. metaClass.getSetInvoker("name").invoke(blankUser,new Object[]{"孙悟空"});
  6. // 孙悟空
  7. System.out.println(blankUser.getName());
  8. }
1 2 3 4 5 6 7 8

文章来源: springlearn.blog.csdn.net,作者:西魏陶渊明,版权归原作者所有,如需转载,请联系作者。

原文链接:springlearn.blog.csdn.net/article/details/125858061

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。