spring事务源码分析结合mybatis源码(三)
下面将结合mybatis源码来分析下,这种持久化框架是如何对connection使用,来达到spring事务的控制。
想要在把mybatis跟spring整合都需要这样一个jar包:mybatis-spring-x.x.x.jar,这里面定义了一些主要的整合信息。
在spring配置文件中需要配置如下两个bean:
<!-- mybatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="configLocation" value="classpath:mybatis.xml"></property>
<!-- mybatis配置文件 -->
<property name="mapperLocations" value="classpath:com/blackbread/dao/mapper/*.xml" />
</bean>
<!--mapper scanning -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.blackbread.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
首先让我们来看org.mybatis.spring.SqlSessionFactoryBean,在这个类需要注入跟之前tx配置中一样的dataSource。
SqlSessionFactoryBean类实现了InitializingBean接口,所以会执行afterPropertiesSet方法,在afterPropertiesSet方法中会执行buildSqlSessionFactory方法生成一个sqlSessionFactory对象,让我们看下buildSqlSessionFactory方法:由于主要看的是跟spring tx结合的方式,所以代码看不上很细,如有疏漏,望不吝赐教。
      protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
            Configuration configuration;
            XMLConfigBuilder xmlConfigBuilder = null;
            //初始化一个configuration
            if (this.configLocation != null) {
              xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
              configuration = xmlConfigBuilder.getConfiguration();
            } else {
              configuration = new Configuration();
              configuration.setVariables(this.configurationProperties);
            }
            if (this.objectFactory != null) {
              configuration.setObjectFactory(this.objectFactory);
            }
            if (this.objectWrapperFactory != null) {
              configuration.setObjectWrapperFactory(this.objectWrapperFactory);
            }
            if (hasLength(this.typeAliasesPackage)) {
              String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                  ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
              for (String packageToScan : typeAliasPackageArray) {
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
              }
            }
            //设置别名
            if (!isEmpty(this.typeAliases)) {
              for (Class<?> typeAlias : this.typeAliases) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
              }
            }
            //装入插件,mybatis的插件都是以拦截器的形式进行的好像,比如分页插件,这里是载入spring中注入的
            if (!isEmpty(this.plugins)) {
              for (Interceptor plugin : this.plugins) {
                configuration.addInterceptor(plugin);
              }
            }
            if (hasLength(this.typeHandlersPackage)) {
              String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                  ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
              for (String packageToScan : typeHandlersPackageArray) {
                configuration.getTypeHandlerRegistry().register(packageToScan);
              }
            }
            if (!isEmpty(this.typeHandlers)) {
              for (TypeHandler<?> typeHandler : this.typeHandlers) {
                configuration.getTypeHandlerRegistry().register(typeHandler);
              }
            }
            //这里将解析mybatis.xml文件,载入所有配置,插件、setting等
            if (xmlConfigBuilder != null) {
              try {
                xmlConfigBuilder.parse();
              } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
              } finally {
                ErrorContext.instance().reset();
              }
            }
            //这个很重要,这里定义了用的transactionFactory为SpringManagedTransactionFactory,这个在获取
            //connection等地方都有用到,是mybatis跟spring的主要链接
            if (this.transactionFactory == null) {
              this.transactionFactory = new SpringManagedTransactionFactory();
            }
            //新建一个Environment对象,并将新建的transactionFactory放入其中
            Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
            configuration.setEnvironment(environment);
            if (this.databaseIdProvider != null) {
              try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
              } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
              }
            }
            if (!isEmpty(this.mapperLocations)) {
              for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                  continue;
                }
                try {
                //这里主要是解析配置的sql mapper配置文件
                  XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                      configuration, mapperLocation.toString(), configuration.getSqlFragments());
                  xmlMapperBuilder.parse();
                } catch (Exception e) {
                  throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                  ErrorContext.instance().reset();
                }
              }
            } else {
              }
            }
            return this.sqlSessionFactoryBuilder.build(configuration);
          }
看着好长的一段啊,其实这里做的工作就是解析配置文件生成configuration对象而已。在
xmlMapperBuilder.parse();
这里将解析sql mapper文件中的映射关系生成MappedStatement对象,并执行 configuration.addMappedStatement(statement);放入到configuration对象中,有兴趣的同学可以仔细看下。
这里主要需要注意的一块就是this.transactionFactory = new SpringManagedTransactionFactory();这里就是mybatis跟spring连接到一起的地方。
接着我们看一下org.mybatis.spring.mapper.MapperScannerConfigurer对象的初始化过程,这个对象实现了BeanDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry方法中初始化一个对象ClassPathMapperScanner,并讲执行scan--->doScan方法,
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
          for (BeanDefinitionHolder holder : beanDefinitions) {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
            //实际就是将扫描到的接口包装成MapperFactoryBean的实现类
            definition.setBeanClass(MapperFactoryBean.class);
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            //注入sqlSessionFactory对象,这个也很重要
            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
              definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
              explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
              definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
              explicitFactoryUsed = true;
            }
            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
              if (explicitFactoryUsed) {
                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
              }
              definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
              explicitFactoryUsed = true;
            } else if (this.sqlSessionTemplate != null) {
              if (explicitFactoryUsed) {
                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
              }
              definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
              explicitFactoryUsed = true;
            }
            if (!explicitFactoryUsed) {
              definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            }
          }
        return beanDefinitions;
      }
这段代码其实主要就是从basePackage中扫描到相应的接口类,并且注册到spring中,并且定义此对象的FactoryBean为:MapperFactoryBean,将返回如下对象
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
最终其实就是生成Handler为MapperProxy的基于mapperInterface的代理类。
同时添加属性:sqlSessionFactory。
这个操作很重要,在后面有关联操作。
这里让我们看下MapperFactoryBean类,这个类继承自SqlSessionDaoSupport而在SqlSessionDaoSupport中有如下方法:
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
也就是上面调用的添加sqlSessionFactory属性的set操作,在这个方法中初始话sqlSession,利用的是SqlSessionTemplate对象。
接下来让我们看下SqlSessionTemplate的初始化过程:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
SqlSessionTemplate其实是实现了SqlSession接口的,在初始化的时候将生成一个sqlSessionProxy 代理类,可以查看下SqlSessionTemplate里面的所有与数据库相关的操作都是通过sqlSessionProxy 这个代理类实现的。
接着看下sqlSessionProxy 的实际handler:
 private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
这里首先需要获取一个SqlSession对象:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
            SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
            if (holder != null && holder.isSynchronizedWithTransaction()) {
              if (holder.getExecutorType() != executorType) {
                throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
              }
              holder.requested();
              return holder.getSqlSession();
            }
            SqlSession session = sessionFactory.openSession(executorType);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
              Environment environment = sessionFactory.getConfiguration().getEnvironment();
              if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
                holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
                TransactionSynchronizationManager.bindResource(sessionFactory, holder);
                TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
                holder.setSynchronizedWithTransaction(true);
                holder.requested();
              } else {
                if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
                } else {
                  throw new TransientDataAccessResourceException(
                      "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
                }
              }
            } else {
            }
            return session;
          }
这里将会获取一个SqlSessionHolder并判断是否已经存在,如果不存在将会初始化一个新的,我们这里只分析第一次调用过程,也就是将会执行
SqlSession session = sessionFactory.openSession(executorType);
--->openSessionFromDataSource
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, autoCommit);
          return new DefaultSqlSession(configuration, executor);
        } 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();
        }
      }
这里是主要的跟spring结合部分,让我们仔细分析下,首先这里将获取TransactionFactory: final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);这里将得到我们之前初始化时候加入的SpringManagedTransactionFactory。然后将初始化当前的tx:
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
然后将生成一个Executor ,将final Executor executor = configuration.newExecutor(tx, execType, autoCommit);这里在之前指定了execType为Simple,所以在这里将生成一个SimpleExecutor: executor = new SimpleExecutor(this, transaction);并将transaction加入属性。
到这里SqlSession的初始化也就完成了,接下来就是通过反射进行实际方法的执行了:
Object result = method.invoke(sqlSession, args);
以一个update操作来说明:
public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
这里首先将从configuration中根据操作的statement获取映射内容MappedStatement ,
getMappedStatement(String id)---->getMappedStatement(String id, boolean validateIncompleteStatements)
接着将执行executor.update(ms, wrapCollection(parameter)),也就是实际的数据库操作了,记得之前初始化的executor么,这里就是对应的SimpleExecutor
 public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
这里主要是看prepareStatement方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }
然后看Connection 方法:
protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog);
    } else {
      return connection;
    }
  }
到这里看到了真正的Connection获取方法: transaction.getConnection();也就是通过之前注入的transaction中获取connection,而这个transaction也就是对应的SpringManagedTransaction,他的调用过程getConnection()---->openConnection()
 private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  }
这里其实就是调用了DataSourceUtils.getConnection(this.dataSource);来获取一个Connection。
看看DataSourceUtils的getConnection(DataSource dataSource)--->doGetConnection(DataSource dataSource)
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        //从TransactionSynchronizationManager中获取ConnectionHolder,这个对象也就是之前我们第一次分析spring tx的时候
        //持有ConnectionHolder的对象了
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        //由于在前面的切面中已经开启事务,并且初始化了ConnectionHolder所以这里将直接返回ConnectionHolder中的connection
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                conHolder.setConnection(dataSource.getConnection());
            }
            return conHolder.getConnection();
        }
        Connection con = dataSource.getConnection();
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            ConnectionHolder holderToUse = conHolder;
            if (holderToUse == null) {
                holderToUse = new ConnectionHolder(con);
            }
            else {
                holderToUse.setConnection(con);
            }
            holderToUse.requested();
            TransactionSynchronizationManager.registerSynchronization(
                    new ConnectionSynchronization(holderToUse, dataSource));
            holderToUse.setSynchronizedWithTransaction(true);
            if (holderToUse != conHolder) {
                TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
            }
        }
        return con;
    }
是不是感觉这段代码很眼熟,对了,因为这里有我们第一篇里面非常熟悉的TransactionSynchronizationManager,在spring tx中也是通过这个类中的resources (ThreadLocal对象)对ConnectionHolder进行持有的。
在这里将获取到之前持有的ConnectionHolder对象,并从中获取到Connection 对象然后返回,这样就保证了spring tx中控制的Connection 跟实际调用的Connection为同一个Connection,也就可以通过spring tx对事务进行管理了。
后续的对数据的操作有兴趣的可以自己读一下,感觉mybatis的源码没有spring的那么清晰,还是需要仔细分析下才能整合到一起。
看的比较粗略,难免有疏漏地方,望不吝赐教。
spring事务源码分析结合mybatis源码(三)的更多相关文章
- spring事务源码分析结合mybatis源码(一)
		
最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...
 - spring事务源码分析结合mybatis源码(二)
		
让我们继续上篇,分析下如果有第二个调用进入的过程. 代码部分主要是下面这个: if (isExistingTransaction(transaction)) { return handleExisti ...
 - 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
		
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
 - Spring Ioc源码分析系列--Ioc源码入口分析
		
Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...
 - NIO 源码分析(05) Channel 源码分析
		
目录 一.Channel 类图 二.begin 和 close 是什么 2.1 AbstractInterruptibleChannel 中的 begin 和 close 2.2 Selector 中 ...
 - NIO 源码分析(02-2) BIO 源码分析 Socket
		
目录 一.BIO 最简使用姿势 二.connect 方法 2.1 Socket.connect 方法 2.2 AbstractPlainSocketImpl.connect 方法 2.3 DualSt ...
 - NIO 源码分析(02-1) BIO 源码分析
		
目录 一.BIO 最简使用姿势 二.ServerSocket 源码分析 2.1 相关类图 2.2 主要属性 2.3 构造函数 2.4 bind 方法 2.5 accept 方法 2.6 总结 NIO ...
 - JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
		
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
 - JUC源码分析-线程池篇(三)Timer
		
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
 
随机推荐
- 【转】Android调用Sqlite数据库时自动生成db-journal文件的原因
			
数据库为了更好实现数据的安全性,一半都会有一个Log文件方便数据库出现意外时进行恢复操作等.Sqlite虽然是一个单文件数据库,但麻雀虽小五脏俱全,它也会有相应的安全机制存在 这个journal文件便 ...
 - 返回数组中指定的一列,将键值作为元素键名array_column
			
array_column() 函数 从记录集中取出 last_name 列: <?php // 表示由数据库返回的可能记录集的数组 $a = array( array( 'id' => 5 ...
 - 012_py之证书过期监测及域名使用的py列表的并集差集交集
			
一.由于线上域名证书快要过期,需要进行监测,顾写了一个方法用于线上证书过期监测,如下: import ssl,socket,pprint def check_domain_sslexpired(dom ...
 - node+mysql 数据库连接池
			
1. 什么是数据库连接池? 数据库连接池是程序启动时建立足够的数据库连接,并将这些连接组成一个池,由程序动态地对池中的连接进行申请,使用和释放. 2. 使用数据库连接池原理及优点是什么? 数据库连接池 ...
 - Sqlserver查询死锁及杀死死锁的方法
			
-- 查询死锁 select request_session_id spid, OBJECT_NAME(resource_associated_entity_id) tableName from sy ...
 - Visual Studio 2017 设置透明背景图
			
一.前言 给大家分享一下,如何为VS2017设置透明背景图.下面是一张设置前和设置后的图片. 设置前: 设置后: 二.设置背景图片的扩展程序 我们打开VS的扩展安装界面:[工具]->[扩展和更新 ...
 - python 判断网络通断同时检测网络的状态
			
思路:通过http判断网络通断,通过ping获取网络的状态 注意:不同平台下,调用的系统命令返回格式可能不同,跨平台使用的时候,注意调整字符串截取的值 主程序:network_testing_v0.3 ...
 - EChars学习之路1
			
引入echarts.min.js或者使用CDN https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js 为ECharts准备一个具备大小(宽高 ...
 - Vue项目搭建与部署
			
Vue项目搭建与部署 一,介绍与需求 1.1,介绍 Vue 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue两大核心思想:组件化和数据驱动.组 ...
 - 整合Spring5+Struts2.5+Hibernate5+maven
			
1. 使用Eclipse创建Maven项目 2. 配置pom.xml引入需要的依赖包 <dependencies> <dependency> <groupId>ju ...