`
Donald_Draper
  • 浏览: 979990 次
社区版块
存档分类
最新评论

Mybatis的Environment解析详解

阅读更多
SqlSessionFactory初始化:http://donald-draper.iteye.com/blog/2331917
Mybatis加载解析Mapper(xml)文件第一讲:http://donald-draper.iteye.com/blog/2333125
Mybatis加载解析Mapper(xml)文件第二讲:http://donald-draper.iteye.com/blog/2333191
Mybatis 解析Mapper(class):http://donald-draper.iteye.com/blog/2333293
//全局文件的解析的方法在XMLConfigBuilder中,今天来具体看一下如何解析environments
//XMLConfigBuilder
private void parseConfiguration(XNode root)
    {
        try
        {
            propertiesElement(root.evalNode("properties"));
            typeAliasesElement(root.evalNode("typeAliases"));
            pluginElement(root.evalNode("plugins"));
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            settingsElement(root.evalNode("settings"));
	    //解析environments
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            mapperElement(root.evalNode("mappers"));
        }
        catch(Exception e)
        {
            throw new BuilderException((new StringBuilder()).append("Error parsing SQL Mapper Configuration. Cause: ").append(e).toString(), e);
        }
    }
//解析environments
private void environmentsElement(XNode context)
        throws Exception
    {
        if(context != null)
        {
            if(C == null)
                environment = context.getStringAttribute("default");
            Iterator i$ = context.getChildren().iterator();
	    //遍历environments的child节点environment,并解析
            do
            {
                if(!i$.hasNext())
                    break;
                XNode child = (XNode)i$.next();
                String id = child.getStringAttribute("id");
		//如果environment的id属性,与environments的default相同时,才解析
                if(isSpecifiedEnvironment(id))
                {
		    //解析transactionManager
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    //解析dataSource
		    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    javax.sql.DataSource dataSource = dsFactory.getDataSource();
                    org.apache.ibatis.mapping.Environment.Builder environmentBuilder = (new org.apache.ibatis.mapping.Environment.Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                    configuration.setEnvironment(environmentBuilder.build());
                }
            } while(true);
        }
    }

//解析transactionManager
先看configuration的构造,启动有JDBC,POOLED类型的映射关系,这个在解析environment节点时,需要用到
typeAliasRegistry.registerAlias("JDBC", org/apache/ibatis/transaction/jdbc/JdbcTransactionFactory);
typeAliasRegistry.registerAlias("POOLED", org/apache/ibatis/datasource/pooled/PooledDataSourceFactory);


public Configuration()
    {
        safeRowBoundsEnabled = false;
        safeResultHandlerEnabled = true;
        mapUnderscoreToCamelCase = false;
        aggressiveLazyLoading = true;
        multipleResultSetsEnabled = true;
        useGeneratedKeys = false;//允许JDBC支持生成的键
        useColumnLabel = true;
        cacheEnabled = true;//全局的映射器启用或禁用缓存
        callSettersOnNulls = false;
        localCacheScope = LocalCacheScope.SESSION;//本地一级缓存作用域
        jdbcTypeForNull = JdbcType.OTHER;
        lazyLoadTriggerMethods = new HashSet(Arrays.asList(new String[] {
            "equals", "clone", "hashCode", "toString"
        }));
        defaultExecutorType = ExecutorType.SIMPLE;
        autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        variables = new Properties();
        objectFactory = new DefaultObjectFactory();//默认ObjectFactory
        objectWrapperFactory = new DefaultObjectWrapperFactory();//默认DefaultObjectWrapperFactory
        mapperRegistry = new MapperRegistry(this);
        lazyLoadingEnabled = false;
        interceptorChain = new InterceptorChain();//插件链
        typeHandlerRegistry = new TypeHandlerRegistry();//类型处理注册器
        typeAliasRegistry = new TypeAliasRegistry();//类型别名注册器
        languageRegistry = new LanguageDriverRegistry();
	//mappedStatements Map
        mappedStatements = new StrictMap("Mapped Statements collection");
	//mappedStatements,method,二级缓存
        caches = new StrictMap("Caches collection");
	//MapperInterface 方法返回结果类型Map
        resultMaps = new StrictMap("Result Maps collection");
        //MapperInterface 方法参数类型Map
        parameterMaps = new StrictMap("Parameter Maps collection");
	//主键生成器Map
        keyGenerators = new StrictMap("Key Generators collection");
        loadedResources = new HashSet();
	//Mapper.xml 中statement片段
        sqlFragments = new StrictMap("XML fragments parsed from previous mappers");
	//未解析处理的statements
        incompleteStatements = new LinkedList();
	//未解析处理的CacheRefs
        incompleteCacheRefs = new LinkedList();
	//未解析处理的ResultMaps
        incompleteResultMaps = new LinkedList();
	//未解析处理的Methods
        incompleteMethods = new LinkedList();
        cacheRefMap = new HashMap();
        typeAliasRegistry.registerAlias("JDBC", org/apache/ibatis/transaction/jdbc/JdbcTransactionFactory);
        typeAliasRegistry.registerAlias("MANAGED", org/apache/ibatis/transaction/managed/ManagedTransactionFactory);
        typeAliasRegistry.registerAlias("JNDI", org/apache/ibatis/datasource/jndi/JndiDataSourceFactory);
        typeAliasRegistry.registerAlias("POOLED", org/apache/ibatis/datasource/pooled/PooledDataSourceFactory);
        typeAliasRegistry.registerAlias("UNPOOLED", org/apache/ibatis/datasource/unpooled/UnpooledDataSourceFactory);
        typeAliasRegistry.registerAlias("PERPETUAL", org/apache/ibatis/cache/impl/PerpetualCache);
        typeAliasRegistry.registerAlias("FIFO", org/apache/ibatis/cache/decorators/FifoCache);
        typeAliasRegistry.registerAlias("LRU", org/apache/ibatis/cache/decorators/LruCache);
        typeAliasRegistry.registerAlias("SOFT", org/apache/ibatis/cache/decorators/SoftCache);
        typeAliasRegistry.registerAlias("WEAK", org/apache/ibatis/cache/decorators/WeakCache);
        typeAliasRegistry.registerAlias("VENDOR", org/apache/ibatis/mapping/VendorDatabaseIdProvider);
        typeAliasRegistry.registerAlias("XML", org/apache/ibatis/scripting/xmltags/XMLLanguageDriver);
        typeAliasRegistry.registerAlias("RAW", org/apache/ibatis/scripting/defaults/RawLanguageDriver);
        typeAliasRegistry.registerAlias("SLF4J", org/apache/ibatis/logging/slf4j/Slf4jImpl);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", org/apache/ibatis/logging/commons/JakartaCommonsLoggingImpl);
        typeAliasRegistry.registerAlias("LOG4J", org/apache/ibatis/logging/log4j/Log4jImpl);
        typeAliasRegistry.registerAlias("JDK_LOGGING", org/apache/ibatis/logging/jdk14/Jdk14LoggingImpl);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", org/apache/ibatis/logging/stdout/StdOutImpl);
        typeAliasRegistry.registerAlias("NO_LOGGING", org/apache/ibatis/logging/nologging/NoLoggingImpl);
        typeAliasRegistry.registerAlias("CGLIB", org/apache/ibatis/executor/loader/CglibProxyFactory);
        typeAliasRegistry.registerAlias("JAVASSIST", org/apache/ibatis/executor/loader/JavassistProxyFactory);
        languageRegistry.setDefaultDriverClass(org/apache/ibatis/scripting/xmltags/XMLLanguageDriver);
        languageRegistry.register(org/apache/ibatis/scripting/defaults/RawLanguageDriver);
    }

从上可以看出JDBC,POOLED类型别名的映射关系如下:
JDBC,JdbcTransactionFactory
POOLED,PooledDataSourceFactory


<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
</environments>


private TransactionFactory transactionManagerElement(XNode context)
        throws Exception
    {
        if(context != null)
        {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
	    //从类型解决器中,获取type对应的类型的实例,从上面的配置来看
	    //实际为JdbcTransactionFactory
            TransactionFactory factory = (TransactionFactory)resolveClass(type).newInstance();
	    //设置JdbcTransactionFactory属性
            factory.setProperties(props);
            return factory;
        } else
        {
            throw new BuilderException("Environment declaration requires a TransactionFactory.");
        }
    }


//JdbcTransactionFactory
public class JdbcTransactionFactory
    implements TransactionFactory
{
    public void setProperties(Properties properties)
    {
    }
   //根据数据连接新建事务
    public Transaction newTransaction(Connection conn)
    {
        return new JdbcTransaction(conn);
    }
    //根据数据源和事务级别和是否自动提交,新建事务
    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit)
    {
        return new JdbcTransaction(ds, level, autoCommit);
    }
}


//TransactionFactory
public interface TransactionFactory
{
    public abstract void setProperties(Properties properties);
    public abstract Transaction newTransaction(Connection connection);
    public abstract Transaction newTransaction(DataSource datasource, TransactionIsolationLevel transactionisolationlevel, boolean flag);
}

现在来看一下jdbc事务

public class JdbcTransaction
    implements Transaction
{
    private static final Log log = LogFactory.getLog(org/apache/ibatis/transaction/jdbc/JdbcTransaction);
    protected Connection connection;//数据库连接
    protected DataSource dataSource;//数据源
    protected TransactionIsolationLevel level;//事务级别
    protected boolean autoCommmit;//自动提交属性

    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit)
    {
        dataSource = ds;
        level = desiredLevel;
        autoCommmit = desiredAutoCommit;
    }

    public JdbcTransaction(Connection connection)
    {
        this.connection = connection;
    }

    public Connection getConnection()
        throws SQLException
    {
        if(connection == null)
            openConnection();
        return connection;
    }

    public void commit()
        throws SQLException
    {
        if(connection != null && !connection.getAutoCommit())
        {
            if(log.isDebugEnabled())
                log.debug((new StringBuilder()).append("Committing JDBC Connection [").append(connection).append("]").toString());
            connection.commit();
        }
    }

    public void rollback()
        throws SQLException
    {
        if(connection != null && !connection.getAutoCommit())
        {
            if(log.isDebugEnabled())
                log.debug((new StringBuilder()).append("Rolling back JDBC Connection [").append(connection).append("]").toString());
            connection.rollback();
        }
    }

    public void close()
        throws SQLException
    {
        if(connection != null)
        {
            resetAutoCommit();
            if(log.isDebugEnabled())
                log.debug((new StringBuilder()).append("Closing JDBC Connection [").append(connection).append("]").toString());
            connection.close();
        }
    }

    protected void setDesiredAutoCommit(boolean desiredAutoCommit)
    {
        try
        {
            if(connection.getAutoCommit() != desiredAutoCommit)
            {
                if(log.isDebugEnabled())
                    log.debug((new StringBuilder()).append("Setting autocommit to ").append(desiredAutoCommit).append(" on JDBC Connection [").append(connection).append("]").toString());
                connection.setAutoCommit(desiredAutoCommit);
            }
        }
        catch(SQLException e)
        {
            throw new TransactionException((new StringBuilder()).append("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: ").append(desiredAutoCommit).append(".  Cause: ").append(e).toString(), e);
        }
    }

    protected void resetAutoCommit()
    {
        try
        {
            if(!connection.getAutoCommit())
            {
                if(log.isDebugEnabled())
                    log.debug((new StringBuilder()).append("Resetting autocommit to true on JDBC Connection [").append(connection).append("]").toString());
                connection.setAutoCommit(true);
            }
        }
        catch(SQLException e)
        {
            log.debug((new StringBuilder()).append("Error resetting autocommit to true before closing the connection.  Cause: ").append(e).toString());
        }
    }
    //获取connection
    protected void openConnection()
        throws SQLException
    {
        if(log.isDebugEnabled())
            log.debug("Openning JDBC Connection");
        connection = dataSource.getConnection();
        if(level != null)
            connection.setTransactionIsolation(level.getLevel());
        setDesiredAutoCommit(autoCommmit);
    }
}


//Transaction
public interface Transaction
{

    public abstract Connection getConnection()
        throws SQLException;

    public abstract void commit()
        throws SQLException;

    public abstract void rollback()
        throws SQLException;

    public abstract void close()
        throws SQLException;
}

从上面可以看出jdbc对应的事务工厂为JdbcTransactionFactory,由JdbcTransactionFactory,根据数据源,事务级别以及自动提交属性来,创建JdbcTransaction,而JdbcTransaction的事务管理,是通过数据连接connection,而connection是从datasource获取,JdbcTransaction的事务管理,就是依靠相应的数据库driver,来管理,实际依赖于数据库。

//解析dataSource
private DataSourceFactory dataSourceElement(XNode context)
        throws Exception
    {
        if(context != null)
        {
	    //获取数据源的类型,和属性
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
	    //根据type从configuration的类型处理器,获取具体的DataSourceFactory
	    //从上文,我们看一看到type为POOLED,对应的是PooledDataSourceFactory
            DataSourceFactory factory = (DataSourceFactory)resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        } else
        {
            throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }
    }

// PooledDataSourceFactory
public class PooledDataSourceFactory extends UnpooledDataSourceFactory
{

    public PooledDataSourceFactory()
    {
        dataSource = new PooledDataSource();
    }
}


//UnpooledDataSourceFactory
public class UnpooledDataSourceFactory
    implements DataSourceFactory
{
    private static final String DRIVER_PROPERTY_PREFIX = "driver.";
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();
    protected DataSource dataSource;

    public UnpooledDataSourceFactory()
    {
        dataSource = new UnpooledDataSource();
    }

    public void setProperties(Properties properties)
    {
        Properties driverProperties = new Properties();
	//获取dataSource的对象描述
        MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
	//获取dataSource的所有属性,并设置
        for(Iterator i$ = properties.keySet().iterator(); i$.hasNext();)
        {
            Object key = i$.next();
            String propertyName = (String)key;
            if(propertyName.startsWith("driver."))
            {
                String value = properties.getProperty(propertyName);
                driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
            } else
            if(metaDataSource.hasSetter(propertyName))
            {
                String value = (String)properties.get(propertyName);
                Object convertedValue = convertValue(metaDataSource, propertyName, value);
                metaDataSource.setValue(propertyName, convertedValue);
            } else
            {
                throw new DataSourceException((new StringBuilder()).append("Unknown DataSource property: ").append(propertyName).toString());
            }
        }
        //设置metaDataSource的driverProperties属性
        if(driverProperties.size() > 0)
            metaDataSource.setValue("driverProperties", driverProperties);
    }

    public DataSource getDataSource()
    {
        return dataSource;
    }
    //将value转化为metaDataSource的对应setValue的参数类型
    private Object convertValue(MetaObject metaDataSource, String propertyName, String value)
    {
        Object convertedValue = value;
        Class targetType = metaDataSource.getSetterType(propertyName);
        if(targetType == java/lang/Integer || targetType == Integer.TYPE)
            convertedValue = Integer.valueOf(value);
        else
        if(targetType == java/lang/Long || targetType == Long.TYPE)
            convertedValue = Long.valueOf(value);
        else
        if(targetType == java/lang/Boolean || targetType == Boolean.TYPE)
            convertedValue = Boolean.valueOf(value);
        return convertedValue;
    }

}


//PooledDataSource
public class PooledDataSource
    implements DataSource
{
    private final PoolState state;
    private final UnpooledDataSource dataSource;
    protected int poolMaximumActiveConnections;
    protected int poolMaximumIdleConnections;
    protected int poolMaximumCheckoutTime;
    protected int poolTimeToWait;
    protected String poolPingQuery;
    protected boolean poolPingEnabled;
    protected int poolPingConnectionsNotUsedFor;
    private int expectedConnectionTypeCode;

}

从上面可以看出解析datasource节点,就是解析器属性,并初始化datasource,
PooledDataSourceFactory继承UnpooledDataSourceFactory,改变的只是数据源类型,
PooledDataSourceFactory对应的数据源为PooledDataSource,而UnpooledDataSourceFactory对应的为,UnpooledDataSource;
我们再来看解析environment节点是,environment的构造
 org.apache.ibatis.mapping.Environment.Builder environmentBuilder = 
 (new org.apache.ibatis.mapping.Environment.Builder(id)).transactionFactory(txFactory).dataSource(dataSource);


//Environment
public final class Environment
{
    private final String id;
    private final TransactionFactory transactionFactory;
    private final DataSource dataSource;

    public static class Builder
    {
        public Environment build()
        {
            return new Environment(id, transactionFactory, dataSource);
        }

        private String id;
        private TransactionFactory transactionFactory;
        private DataSource dataSource;
        public Builder(String id)
        {
            this.id = id;
        }
    }
}

再来看,Configuration设置environment
public void setEnvironment(Environment environment)
    {
        this.environment = environment;
    }


总结:
事务类型为jdbc对应的事务工厂为JdbcTransactionFactory,由JdbcTransactionFactory,根据数据源,事务级别以及自动提交属性来,创建JdbcTransaction,而JdbcTransaction的事务管理,是通过数据连接connection,而connection是从datasource获取,JdbcTransaction的事务管理,就是依靠相应的数据库driver,来管理,实际依赖于数据库;解析datasource节点,就是解析器属性,并初始化datasource,PooledDataSourceFactory继承UnpooledDataSourceFactory,改变的只是数据源类型,PooledDataSourceFactory对应的数据源为PooledDataSource,而UnpooledDataSourceFactory对应的为,UnpooledDataSource。
0
1
分享到:
评论

相关推荐

    传智播客mybatis笔记

    ### MyBatis基础知识与应用详解 #### 一、MyBatis概述 MyBatis是一个优秀的持久层框架,它简化了Java应用程序与关系型数据库交互的复杂性。原名为iBatis,起初作为Apache的一个开源项目,后于2010年迁移到Google ...

    mybatis通用mapper笔记

    ### MyBatis通用Mapper知识点详解 #### 一、MyBatis通用Mapper简介 MyBatis通用Mapper是一种基于MyBatis框架的扩展插件,它能够为开发者提供一系列方便的CRUD(创建、读取、更新、删除)操作方法,极大地简化了...

    MyBatis3.2.6

    ### MyBatis 3.2.6 相关知识点详解 #### 一、MyBatis 概述 **MyBatis** 是一款优秀的持久层框架,它支持普通的 SQL 查询、存储过程以及高级映射功能。该框架的主要目标是简化 JDBC 代码的编写,并消除手动设置参数...

    mybatis的Helloworld

    本篇文章将详细解析MyBatis的“Hello World”实例,帮助初学者快速入门。 【描述】"初始的mybatis的helloworld文档" MyBatis的“Hello World”教程是学习其基本概念和配置的绝佳起点。通过这个简单的例子,我们...

    Mybatis学习手册

    ### Mybatis学习手册知识点解析 #### 一、MyBatis简介 - **定义与功能**:MyBatis是一个优秀的持久层框架,支持SQL查询、存储过程及高级映射功能。其核心价值在于几乎消除了所有JDBC代码和参数手工设置以及结果集...

    mybatis用户手册

    ### MyBatis用户手册知识点详解 #### 一、MyBatis概述 - **定义与特点:** - **定义:** MyBatis是一款优秀的持久层框架,它支持使用普通的SQL查询、存储过程以及高级映射功能。通过使用简单的XML或注解进行配置...

    mybatis两天视频教程+源码+笔记

    #### 二、MyBatis核心概念解析 **1. SqlSessionFactoryBuilder:** - **功能:**创建SqlSessionFactory的工厂类。 - **使用场景:**在应用程序启动时通过配置文件或者直接指定配置的方式初始化。 **2. ...

    spring-boot-starter-mybatis-spring-boot-1.0.2.zip

    《Spring Boot集成MyBatis详解》 在Java开发领域,Spring Boot以其简洁的配置和快速的启动特性,已经成为构建微服务应用的首选框架。而MyBatis作为一款强大的持久层框架,深受开发者喜爱,它提供了灵活的SQL映射...

    mybatis详细示例操作

    ### MyBatis详细示例操作知识点解析 #### 一、MyBatis简介及特性 - **定义**:MyBatis是一种优秀的对象关系映射(Object Relational Mapping, ORM)框架,支持基本SQL查询、存储过程及高级映射等功能。通过减少...

    MyBatis Properties及别名定义实例详解

    MyBatis Properties 及别名定义实例详解 MyBatis 是一个基于 Java 的持久层框架,它提供了一个简单的方式来访问数据库。MyBatis 的配置文件是 mybatis-configuration.xml,这个文件中包含了数据库的连接信息、...

    mybatis 项目配置文件实例详解

    本文将深入解析MyBatis项目的配置文件实例,帮助开发者更好地理解和使用这个强大的持久层框架。 配置文件通常以`mybatis-config.xml`为名,它是MyBatis的核心配置文件,包含了环境设置、映射器配置等关键信息。以下...

    Mybatis讲义laodu

    通过理解MyBatis的工作原理,可以自己动手实现一个简化版的MyBatis框架,涉及到XML解析、SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession等核心组件的构建。 **6. 在Web中应用MyBatis(使用MVC架构)** ...

    MyBatis配置dad的方式

    ### MyBatis配置详解 #### 一、简介 在学习如何配置MyBatis的过程中,我们需要了解两个关键的配置文件:`Mapper`配置文件与`config`配置文件。这两个文件是MyBatis框架运行的核心,它们定义了数据访问层(Data ...

    《MyBatis笔记 2022年版.md》原版!

    ### MyBatis 核心配置文件详解 #### 文件命名与结构 在 MyBatis 的学习过程中,一个重要的起点就是理解其核心配置文件的作用与结构。按照惯例,此文件通常命名为 `mybatis-config.xml`,它负责管理全局配置,如...

    MyBatis面试题(2022最新版)

    ### MyBatis面试题知识点详解 #### 1. MyBatis简介 - **定义**: MyBatis是一款优秀的持久层框架,支持定制化的SQL、存储过程以及高级映射。 - **功能**: 避免了大量的JDBC代码和手动设置参数及获取结果集的操作,...

    Mybatis基础教程

    配置文件详解 Mybatis的核心配置文件是`mybatis-config.xml`,其中包含了数据库连接信息以及其他全局配置。以下是该文件的基本结构: ```xml &lt;!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//...

    MyBatis.pdf

    #### 二、MyBatis核心配置文件解析 MyBatis的核心配置文件主要由以下几部分组成: 1. **`&lt;configuration&gt;`**:根元素,包含了其他所有元素。 2. **`&lt;settings&gt;`**:用于改变MyBatis运行时的行为,例如开启二级缓存...

    浅谈mybatis如何半自动化解耦(推荐)

    【mybatis半自动化解耦详解】 在Java的持久层框架中,Mybatis因其灵活和高效的特点,成为许多开发者的选择。本文将深入探讨Mybatis如何实现半自动化解耦,并通过具体的代码示例解析其CRUD(Create, Read, Update, ...

    Maven下SpringMVC+MyBatis最精简的例子

    **SpringMVC与MyBatis整合精简实例详解** 在Java Web开发中,SpringMVC作为主流的MVC框架,负责处理HTTP请求并转发到相应的控制器,而MyBatis作为一个轻量级的持久层框架,简化了SQL操作。将两者结合使用,可以构建...

Global site tag (gtag.js) - Google Analytics