MyBatis普通过程中的设计模式

MyBatis普通过程中的设计模式

代码案例

以下是一个没有使用Spring框架整合后的MyBatis简单使用流程:

public void insertUser() throws IOException {
        //获取核心配置文件输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder工厂构建对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //工厂创建SqlSessionFactory
        //需要读取配置文件,来调参创造SqlSession
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
        //创建SqlSession
        SqlSession sqlSession = build.openSession();
        //使用SqlSession的getMapper方法,使用反射机制实现动态代理,返回实例化对象
        //--------代理模式-------------
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //调用userMapper中的insertUser方法
        int count = userMapper.insertUser();
        //提交事务.Mybatis配置文件中environment标签下默认存在事务配置transactionManager标签,每次sql都需要存在事务的提交操作
        sqlSession.commit();
        //打印
        System.out.println("Affected rows:"+count);
    }

我们定义了一个POJO类、一个Mapper映射接口,一个Mappertaining映射xml文件,和核心配置文件。我们只分析上述的代码。

建造者模式

        //获取核心配置文件输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder工厂构建对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //工厂创建SqlSessionFactory
        //需要读取配置文件,来调参创造SqlSession
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);

以上的代码中需要从MyBatis的核心配置文件中获取参数来得到一个SqlSessionFactory工厂。对于一个SqlSessionFactory来说,MyBatis核心配置文件就是它其中的属性,对于我们业务的不同,MyBatis核心配置文件有些标签是必选的,有些标签是不必须的,就比如environments、mappers标签都是必须的,而properties标签和objectFactory标签则是可选的,所以SqlSessionFactory也是如此,而对于这种多属性的复杂对象来说,建造者模式无疑是最好的选择。

我们从SqlSessionFactoryBuilder的build方法作为入口。

在这里插入图片描述

    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

build方法返回了一个SqlSessionFactory对象,同时有调用了重载的build方法,传入其他两个空参值。

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException var13) {
            }

        }

        return var5;
    }

在这个重载的build方法中,首先就声明了一个SqlSessionFactory对象,其次传入输入流和其他两个参数来读取配置文件创建XMLConfigBuilder对象,之后再次调用另一个重载方法,传入XMLConfigBuilder对象parser方法的返回值,一个Configuration对象。

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

而在重载方法build中使用创建匿名对象的方式创建了SqlSessionFactory的子类DefaultSqlSessionFactory,并传入Configuration对象。

再进入DefaultSqlSessionFactory的构造函数之前,我们可以先看一下Configuration对象,Configuration对象作为DefaultSqlSessionFactory函数的参数,我们需要知道它做了什么,和它有些什么字段。

    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
    protected boolean shrinkWhitespacesInSql;
    protected boolean nullableOnForEach;
    protected boolean argNameBasedConstructorAutoMapping;
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    protected Class<?> defaultSqlProviderType;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set<String> lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ResultSetType defaultResultSetType;
    protected ExecutorType defaultExecutorType;
    protected AutoMappingBehavior autoMappingBehavior;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
    protected Properties variables;
    protected ReflectorFactory reflectorFactory;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protected Class<?> configurationFactory;
    protected final MapperRegistry mapperRegistry;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map<String, MappedStatement> mappedStatements;
    protected final Map<String, Cache> caches;
    protected final Map<String, ResultMap> resultMaps;
    protected final Map<String, ParameterMap> parameterMaps;
    protected final Map<String, KeyGenerator> keyGenerators;
    protected final Set<String> loadedResources;
    protected final Map<String, XNode> sqlFragments;
    protected final Collection<XMLStatementBuilder> incompleteStatements;
    protected final Collection<CacheRefResolver> incompleteCacheRefs;
    protected final Collection<ResultMapResolver> incompleteResultMaps;
    protected final Collection<MethodResolver> incompleteMethods;
    protected final Map<String, String> cacheRefMap;

我们进入Configuration类后,就会发现,它的属性足足有53条,除去近一半使用final修饰的"最终属性",还有近30多条,而这些属性名恰恰与我们MyBatis核心配置文件中的标签名近似相同,比如environment、objectFactory、databaseId等等,其中一部分的属性名像是配置文件中的标签名+属性名组合的方式。这也恰恰说明Configuration存储的是核心配置文件中的信息,XMLConfigBuilder对象读取核心配置文件后,构建了Configuration对象。

然后我们继续探究DefaultSqlSessionFactory,首先我们查看DefaultSqlSessionFactory中的属性:

在这里插入图片描述

    private final Configuration configuration;

只有一个Configuration类型的属性,而刚刚则查看了Configuration的作用:Configuration负责存储核心配置文件中的配置信息;而对于建造者模式而言,主要负责构建一些比较复杂的对象,复杂的对象对应的就应该是:多元的、可变的、较多的属性;但是DefaultSqlSessionFactory只有一个Configuration类型的属性,这里就是MyBatis使用了巧妙的方法,**将建造者模式需要建造的属性进行封装,单独构建一个配置类出来,这样就实现了建造者建造功能和属性的分离。**到这里就使用建造者模式构建出了一个复杂的对象。

工厂模式

        //创建SqlSession
        SqlSession sqlSession = build.openSession();

通常来说MyBatis的执行需要SqlSession会话对象,但是MyBatis中的SqlSession代表的是对sql的会话,对于不同的会话所需要的参数也都不尽相同,而为了避免暴露创建一个会话的逻辑参数,MyBatis选择使用工厂模式进行抽象,于是有了SqlSessionFactory对SqlSession进行构建。

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

我们使用DefaultSqlSessionFactory中openSession方法后,会传入configuration属性的defaultExecutorType字段去调用openSessionFromDataSource构建SqlSession会话。

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

再openSessionFromDataSource方法中,首先声明了两个变量,

  • Transaction接口,其中封装执行原生JDBC操作的一些方法和连接对象:
public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
}

其中有Connection数据库连接对象,和事务的提交回滚、连接关闭、耗时计算方法。

  • DefaultSqlSession对象,它是SqlSession接口的实现,来进行sql会话操作。

之后使用configuration.getEnvironment()获取环境配置,传入参数创建TransactionFactory对象,之后就是通过参数传递,创建出DefaultSqlSession对象。

类图:

在这里插入图片描述

代理模式

主要分析下面一行代码:

        //使用SqlSession的getMapper方法,使用反射机制实现动态代理,返回实例化对象
        //--------代理模式-------------
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

在上述的源码中,我们可以得出,sqlSession对象返回的其实是它的实现类DefaultSqlSession,我们直接去看DefaultSqlSession的源码。

    public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

getMapper方法使用泛型和反射机制,调用Configuration中的getMapper方法,并传入自身的实例化对象;

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

而Configuration中的getMapper方法又调用mapperRegistry属性的getMapper方法;

在MapperRegstry类中,它的结构是这样的。

在这里插入图片描述

    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);
            }
        }
    }

首先MapperRegistry有knownMappers属性,是HashMap类型,用来获取mapperProxyFactory工厂类

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

之后会进行判断,是否有这个类工厂,这个类工厂添加Mapper集合的方法对应Configuration中addMapper()方法,还记得之前的XMLConfigBuilder类吗,其中parse方法会被用来加载Configuration对象,加载时会调用parseConfiguration方法,而parseConfiguration方法就是加载配置文件中所有的标签的。

    private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            //加载mappers标签,调用mapperElements方法。
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

而在加载标签时,mappers中的标签会根据package和mapper标签不同来进行不同方式的加载

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);

                            try {
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } catch (Throwable var12) {
                                if (inputStream != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var10) {
                                        var12.addSuppressed(var10);
                                    }
                                }

                                throw var12;
                            }

                            if (inputStream != null) {
                                inputStream.close();
                            }
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);

                            try {
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } catch (Throwable var13) {
                                if (inputStream != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var11) {
                                        var13.addSuppressed(var11);
                                    }
                                }

                                throw var13;
                            }

                            if (inputStream != null) {
                                inputStream.close();
                            }
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

其中都会使用this.configuration.addMapper这个方法将其加入到MapperRegistry中的knownMappers集合中。

所以到这里,getMapper方法的作用已经很明确了,就是用来判断有没有这个类对应的mapperProxyFactory对象,有则返回,没有则返回null。

如果有之后则会去调用mapperProxyFactory中newInstance方法通过反射获取实体对象。

public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }   

protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

之后就是java.lang包下的Proxy类的底层了。

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
类图:

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值