美文网首页
Springboot + Mybatis mapper注入(sq

Springboot + Mybatis mapper注入(sq

作者: 代码届的泥石流sc | 来源:发表于2023-02-17 08:06 被阅读0次

    总结一下Mapper的创建过程。自己手画流程图:dv


    image.png

    完整图请查阅:
    https://www.processon.com/view/5f3f372fe0b34d071180341b

    1.@MapperScan(com.dv.package) 类

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({**MapperScannerRegistrar.class**})
    public @interface MapperScan {}
    

    2.MapperScannerRegistrar类 执行doScan()方法即会调用ClassPathMapperScanner的doScan()方法

    public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
        private ResourceLoader resourceLoader;
    
        public MapperScannerRegistrar() {
        }
        //执行该方法
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    ...
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
    
    

    3.ClassPathMapperScanner会将所有的Mapper扫描进来,并且将每个Mapper包装成一个类型为MapperFactoryBean的BeanDefinition,注册到IoC容器中。

    MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,是一个BeanFactoryPostProcessor,它的功能就是在容器启动阶段动态向容器中注册BeanDefinition。经过MapperScannerConfigurer处理后,所有Mapper接口的BeanDefinition就以MapperFactoryBean的形式注册到Spring IoC容器中了。 代码可见ClassPathMapperScanner#processBeanDefinitions()

    方法一:
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
            if (beanDefinitions.isEmpty()) {
                this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
            } else {
                //并且将每个Mapper包装成一个类型为MapperFactoryBean的BeanDefinition,注册到IoC容器中。
                this.processBeanDefinitions(beanDefinitions);
            }
            return beanDefinitions;
        }
    方法二:
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
            Iterator var3 = beanDefinitions.iterator();
    
            while(var3.hasNext()) {
                BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
                GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
                }
    
                definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
                definition.setBeanClass(this.mapperFactoryBean.getClass());
                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) {}
       .......
    }
    

    4.mapperFactoryBean,为什么要将接口的Bean设置为MapperFactoryBean呢,就是因为当IOC容器中Bean实现了FactoryBean后,通过getBean获取到的Bean对象并不是FactoryBean的实现类对象,而是实现类getObject方法返回的对象。MapperFactoryBean类的getObject方法如下:

    public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
        private Class<T> mapperInterface;
        private boolean addToConfig = true;
    
        public MapperFactoryBean() {
        }
        ....
        public MapperFactoryBean(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
        ...
        public T getObject() throws Exception {
            return this.getSqlSession().getMapper(this.mapperInterface);
        }
    }
    

    4.1 Spring FactoryBean和BeanFactory 区别

    public interface FactoryBean<T> {
        T getObject() throws Exception;
        Class<?> getObjectType();
        boolean isSingleton();
    }
    

    总结:
    BeanFactory是个bean 工厂,是一个工厂类(接口), 它负责生产和管理bean的一个工厂
    是ioc 容器最底层的接口,是个ioc容器,是spring用来管理和装配普通bean的ioc容器(这些bean成为普通bean)。
    FactoryBean是个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理后,生产的对象是由getObject()方法决定的
    用来自定义bean。
    *

    5.sqlsession是一个接口,我们来看看它的实现类:DefaultSqlSession

    image.png
    public class DefaultSqlSession implements SqlSession {
    ....
     public <T> T getMapper(Class<T> type) {
            return this.configuration.getMapper(type, this);
     }
    ...
    }
    

    6.看来是调用了Configuration的getMapper方法,还不是DefaultSqlSession实现了getMapper。接着再看Configuration的getMapper方法:

    public class Configuration {
    ...
    protected final MapperRegistry mapperRegistry;
    ...
     public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            return this.mapperRegistry.getMapper(type, sqlSession);
        }
    ...
    }
    

    7.那么所有的Mapper都要一个地方去注册(在我们的mybytis-config.xml里),注册好过后需要的时候再去查找是否已经注册,那么就是MapperRegistry,所以取一个好的变量名是非常重要的。

    public class MapperRegistry {  //其实就是mapper的注册类
        private final Configuration config;
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
        public MapperRegistry(Configuration config) {
            this.config = config;
        }
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
            if (mapperProxyFactory == null) {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            } else {
                try {
                    return mapperProxyFactory.newInstance(sqlSession);
                } catch (Exception var5) {
                    throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
                }
            }
        }
        public <T> boolean hasMapper(Class<T> type) {
            return this.knownMappers.containsKey(type);
        }
        public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {
                if (this.hasMapper(type)) {
                    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                }
                boolean loadCompleted = false;
                try {
                    this.knownMappers.put(type, new MapperProxyFactory(type));
                    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                    parser.parse();
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {
                        this.knownMappers.remove(type);
                    }
                }
            }
        }
    

    8.当我们一切正确时,我们就能获取到一个MapperProxyFactory实例。想必MapperProxy代理类的生成正是通过MapperProxyFactory工厂类构建的,即第8行代码。进入MapperProxyFactory类。

    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 this.mapperInterface;
        }
       protected T newInstance(MapperProxy<T> mapperProxy) {
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }
    

    9.找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。

    public class MapperProxy<T> implements InvocationHandler, Serializable {
        private static final long serialVersionUID = -6424540398559729838L;
        private final SqlSession sqlSession;
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache;
    
        public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
            this.sqlSession = sqlSession;
            this.mapperInterface = mapperInterface;
            this.methodCache = methodCache;
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
    
                if (this.isDefaultMethod(method)) {
                    return this.invokeDefaultMethod(proxy, method, args);
                }
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);  //传入sqlSession 和 参数
        }
        private MapperMethod cachedMapperMethod(Method method) {
            MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
            if (mapperMethod == null) {
                mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
                this.methodCache.put(method, mapperMethod);
            }
    
            return mapperMethod;
        }
        @UsesJava7
        private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
            Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
            if (!constructor.isAccessible()) {
                constructor.setAccessible(true);
            }
            Class<?> declaringClass = method.getDeclaringClass();
            return ((Lookup)constructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
        }
        private boolean isDefaultMethod(Method method) {
            return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
        }
    }
    

    10.MapperMethod类

    MapperMethod是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用

    ublic class MapperMethod {
    ....
    public Object execute(SqlSession sqlSession, Object[] args) {
            Object param;
            Object result;
            switch(this.command.getType()) {
            case INSERT:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
            }
    }
    
    
        private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
            Object param = this.method.convertArgsToSqlCommandParam(args);
            List result;
            if (this.method.hasRowBounds()) {
                RowBounds rowBounds = this.method.extractRowBounds(args);
                result = sqlSession.selectList(this.command.getName(), param, rowBounds);
            } else {
                result = sqlSession.selectList(this.command.getName(), param);
            }
    
            if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
                return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            } else {
                return result;
            }
        }
    
    

    通过上面对SqlCommand和MethodSignature的简单分析,我们很容易理解这段代码,首先它根据SqlCommand中解析出来的方法类型来选择对应的SqlSession中的方法,即如果是INSERT类型的,就选择SqlSession.insert方法来执行数据库操作。其次,它通过MethodSignature将参数值转换为Map<Key,Value>的映射,Key是方法的参数名称,Value是参数的值,最后将方法名和方法参数传入对应的SqlSession的方法中执行。至于我们在配置文件中定义的sql语句,则是缓存在了SqlSession的成员变量Configuration中


    image.png

    在Configuration中有着非常多的参数,其中有一个参数是mappedStatements,这里面保存了我们在配置文件中定义的所有方法,我们可以点开其中的一个方法,查看mappedStatement的内部结构


    image.png

    里面保存了我们在配置文件中定义的各种参数,包括sql语句。到这里,我们应该对mybatis中如何通过将配置与dao接口映射起来,如何通过代理模式生成代理对象来执行数据库读写操作有了较为宏观的认识,至于sqlSession中如果将参数与sql语句结合,组装成完整的sql语句,以及如何将数据库字段与java对象映射,这些内容不在本文的范围之内,感兴趣的同学可以自行阅读相关的源码。


    image.png
    image.png

    11.我们看看sqlession执行过程

    实现类是DefaultSqlSession,SqlSession的四大对象:Executor、StatemenHandler、ParameterHandler(参数处理器)、ResultHandler

    public class DefaultSqlSession implements SqlSession {
            ...
            public <E> List<E> selectList(String statement, Object parameter) {
                return this.selectList(statement, parameter, RowBounds.DEFAULT);
            }
    
            public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
                List var5;
                try {
                    MappedStatement ms = this.configuration.getMappedStatement(statement);
                    var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
                } catch (Exception var9) {
                    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
                } finally {
                    ErrorContext.instance().reset();
                }
    
                return var5;
            }
        }
    
    

    12.Executor 它的实现类 SimpleExecutor(默认)

    image.png

    a.执行query()其实调用BaseExecutor 的doQuery()方法

    rivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
            List list;
            try {
                list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
            } finally {
                this.localCache.removeObject(key);
            }
     ....
            return list;
        }
    

    b.之后再调用SimpleExecutor的doQuery()方法

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            Statement stmt = null;
            List var9;
            try {
                Configuration configuration = ms.getConfiguration();
                StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                stmt = this.prepareStatement(handler, ms.getStatementLog());
                var9 = handler.query(stmt, resultHandler);
            } finally {
                this.closeStatement(stmt);
            }
            return var9;
        }
    
    image.png

    c.这里调用的是PreparedStatementHandler中的query()方法, 这个方法如下:对sql进行预编译

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            PreparedStatement ps = (PreparedStatement)statement;
            ps.execute(); // 这里看到了我们熟悉的jdbc方法, 也就是在这里实际调用了JDBC底层
            return this.resultSetHandler.handleResultSets(ps);
        }
    

    Executor会先调用StatementHandler的prepare()方法预编译sql语句,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterHandler设置参数,完成预编译,跟着就是执行查询,而update()也是这样的,最后如果需要查询,我们就用ResultSetHandler封装结果返回给调用者。

    d.ParameterHandler参数处理器

    在前面中我们看到mybatis是通过参数处理器ParameterHandler对预编译语句进行参数设置的。它的作用是很明显的,那就是完成会预编译参数的设置。下面看看接口的定义:

    public interface ParameterHandler {
        Object getParameterObject();
     
        void setParameters(PreparedStatement var1) throws SQLException;
    }
    

    主要看setParameters方法,可以看到它还是从parameterObject对象中取参数,然后使用typeHandler进行参数处理。typeHandler也是在mybatis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。

    public class DefaultParameterHandler implements ParameterHandler {
    ...
    public void setParameters(PreparedStatement ps) {
            ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
            List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
            if (parameterMappings != null) {
                for(int i = 0; i < parameterMappings.size(); ++i) {
                    ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        String propertyName = parameterMapping.getProperty();
                        Object value;
                        if (this.boundSql.hasAdditionalParameter(propertyName)) {
                            value = this.boundSql.getAdditionalParameter(propertyName);
                        } else if (this.parameterObject == null) {
                            value = null;
                        } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                            value = this.parameterObject;
                        } else {
                            MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                            value = metaObject.getValue(propertyName);
                        }
    
                        TypeHandler typeHandler = parameterMapping.getTypeHandler();
                        JdbcType jdbcType = parameterMapping.getJdbcType();
                        if (value == null && jdbcType == null) {
                            jdbcType = this.configuration.getJdbcTypeForNull();
                        }
    
                        try {
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);
                        } catch (TypeException var10) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                        } catch (SQLException var11) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
                        }
                    }
                }
            }
        }
    }
    

    e.接下来我们看看ResultHandler


    image.png
    public interface ResultSetHandler {
        <E> List<E> handleResultSets(Statement var1) throws SQLException;
    
        <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
    
        void handleOutputParameters(CallableStatement var1) throws SQLException;
    }
    
    public class DefaultResultSetHandler implements ResultSetHandler {
    ...
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
            ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
            List<Object> multipleResults = new ArrayList();
            int resultSetCount = 0;
            ResultSetWrapper rsw = this.getFirstResultSet(stmt);
            List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
            int resultMapCount = resultMaps.size();
            this.validateResultMapsCount(rsw, resultMapCount);
    
            while(rsw != null && resultMapCount > resultSetCount) {
                ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
                this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
                rsw = this.getNextResultSet(stmt);
                this.cleanUpAfterHandlingResultSet();
                ++resultSetCount;
            }
    
            String[] resultSets = this.mappedStatement.getResultSets();
            if (resultSets != null) {
                while(rsw != null && resultSetCount < resultSets.length) {
                    ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
                    if (parentMapping != null) {
                        String nestedResultMapId = parentMapping.getNestedResultMapId();
                        ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                        this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
                    }
    
                    rsw = this.getNextResultSet(stmt);
                    this.cleanUpAfterHandlingResultSet();
                    ++resultSetCount;
                }
            }
    
            return this.collapseSingleResultList(multipleResults);
        }
    }
    

    对结果进行封装以及映射

    总结

    MyBatis的SqlSession的四大对象:Executor、StatemenHandler、ParameterHandler、ResultHandler。
    Executor:代表执行器,由它来调度StatementHandler,ParameterHandler和ResultHandler等来执行对应的SQL。
    StatementHandler:作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用。
    ParameterHandler:用于SQL对参数的处理。
    ResultHandler:是进行最后数据集(ResultSet)的封装返回处理的。
    SpringBoot在启动时MybatisAutoConfiguration自动配置Mybatis的参数。通过SqlSessionFactoryBean.getObject获取SqlSessionFactory对象。

    1、IOC通过注解扫描指定包,在初始化的时候调用@MapperScan注解,执行doScan方法,将所有的Mapper接口的Bean定义为MapperFactoryBean,并将SqlSessionTemplate添加到该类中。
    2.SpringIOC在实例化该Bean的时候,需要传入接口类型,并将SqlSessionFactory和SqlSessionTemplate注入到Bean中,并调用addmapper方法,解析配置文件。
    3、当调用MapperFactoryBean的getObject方法的时候,事实上是调用SqlSession的getMapper方法。这个方法返回一个动态代理对象,所有这个代理对象的方法调用都是底层的SqlSession的方法。

    相关文章

      网友评论

          本文标题:Springboot + Mybatis mapper注入(sq

          本文链接:https://www.haomeiwen.com/subject/uyspkdtx.html