
1. 从数据到洞察聚类分析在数学建模中的核心价值在数学建模竞赛或者任何数据分析项目中我们常常会拿到一堆看起来杂乱无章的数据点。比如研究一个城市不同区域的消费水平或者分析一批客户的行为特征。面对这些数据一个最直接的问题就是它们能分成几类每一类有什么共同特点这时候聚类分析Cluster Analysis就登场了。它不像分类算法那样需要事先知道“标准答案”即标签而是完全依靠数据自身的“相似性”或“距离”将相似的对象自动归为一组不相似的区分开。这种“物以类聚人以群分”的思想让聚类成为探索性数据分析的利器尤其在数据标签缺失或定义模糊时它能帮我们快速发现数据的内在结构为后续的深入分析和决策提供第一手洞察。在MATLAB这个强大的数学计算与可视化平台上实现聚类分析可以说是如鱼得水。MATLAB不仅提供了现成的、经过优化的聚类函数其强大的矩阵运算能力和丰富的可视化工具更能让我们从算法实现到结果呈现一气呵成。无论是经典的K-means、层次聚类还是更复杂的DBSCAN、高斯混合模型在MATLAB里都能找到简洁高效的实现方式。对于数学建模而言这意味着你可以将更多精力放在问题理解、特征工程和模型解释上而不是纠结于底层算法的代码实现。接下来我将以一个完整的实战案例为线索带你深入理解如何在MATLAB中运用聚类算法并分享一些从实际建模中总结出来的、教科书上不一定写的关键技巧和避坑指南。2. 实战准备数据理解、预处理与可视化先行在# 1. 概述本文主要分享MyBatis 的初始化流程。相信大家都用过 MyBatis 那么先一起来找茬。下述代码有什么问题 或者说可以怎么优化// 示例一 public class MyBatisDemo { private SqlSessionFactory sqlSessionFactory; Before public void prepare() throws IOException { String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); inputStream.close(); } Test public void test() { // TODO 测试代码 } } // 示例二 public class OrderFactory { private static SqlSessionFactory sqlSessionFactory; static { String resource mybatis-config.xml; InputStream inputStream null; try { inputStream Resources.getResourceAsStream(resource); } catch (IOException e) { throw new RuntimeException(e); } sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); try { inputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } public static SqlSessionFactory getSqlSessionFactory() { return sqlSessionFactory; } }示例一在每次单元测试方法执行前都会初始化一次 SqlSessionFactory 。如果每个单元测试方法都这么写那么每个方法都会初始化一次 SqlSessionFactory 。显然这是没有必要的带来时间和内存的浪费。示例二在 OrderFactory 静态代码块中初始化了 SqlSessionFactory 。这样就解决了示例一的问题。但是随着业务的发展我们可能会拆出多个模块例如 UserFactory、ProductFactory 等等。那么每个模块都如此初始化一次 SqlSessionFactory 也会带来时间和内存的浪费。那么怎么解决呢实际上SqlSessionFactory 一旦被创建应该在应用执行期间都存在并且不再需要创建另一个。因此SqlSessionFactory 应该是应用级别的单例。所以我们可以引入 Spring 等 DI 框架保证其单例。那么如果我们是 MyBatis 开发者我们怎么实现 SqlSessionFactory 的单例呢实际上我们可以通过Configuration配置对象来实现。因为FROM 《MyBatis 文档 —— 配置》MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置settings和属性properties信息。配置文档的顶层结构如下configuration 配置properties 属性settings 设置typeAliases 类型别名typeHandlers 类型处理器objectFactory 对象工厂plugins 插件environments 环境environment 环境变量transactionManager 事务管理器dataSource 数据源databaseIdProvider 数据库厂商标识mappers 映射器一个 Configuration 对象对应一个configuration /节点即一个 MyBatis 配置。而一个 MyBatis 配置只能初始化一个 SqlSessionFactory 对象。所以Configuration 对象是 SqlSessionFactory 对象的唯一配置对象。那么如果我们保证 Configuration 对象的单例就能保证 SqlSessionFactory 的单例。当然如果多个模块使用多个 MyBatis 配置那么就会有多个 SqlSessionFactory 对象。不过一般项目我们只会使用一个 MyBatis 配置。所以MyBatis 的初始化本质上就是对 Configuration 对象的初始化。下面我们来看一个 MyBatis 的配置文件示例?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration properties resourceorg/mybatis/example/config.properties property nameusername valueroot/ property namepassword value123456/ /properties settings setting namecacheEnabled valuetrue/ /settings typeAliases typeAlias aliasOrder typecom.xxx.Order/ /typeAliases typeHandlers typeHandler handlercom.xxx.MyTypeHandler/ /typeHandlers objectFactory typecom.xxx.MyObjectFactory property namename valueMyObjectFactory/ /objectFactory plugins plugin interceptorcom.xxx.MyPlugin property namename valueMyPlugin/ /plugin /plugins environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${driver}/ property nameurl value${url}/ property nameusername value${username}/ property namepassword value${password}/ /dataSource /environment /environments databaseIdProvider typeDB_VENDOR property nameSQL Server valuesqlserver/ property nameDB2 valuedb2/ property nameOracle valueoracle / /databaseIdProvider mappers mapper resourceorg/mybatis/example/OrderMapper.xml/ /mappers /configuration是不是有点熟悉。 当然实际使用时我们不会配置的如此复杂。不过通过这样一个示例我们可以了解到 MyBatis 配置的全貌。那么MyBatis 是如何解析该配置文件初始化成一个 Configuration 对象呢这就是本文MyBatis 初始化流程所要分享的内容。另外胖友可以看看 《MyBatis 源码分析 - 配置文件解析过程》 文章写的更加细致也更加长。当然本文也写的很细致只是没有他那么长哈哈哈。2. 加载MyBatis 的初始化在org.apache.ibatis.session.SqlSessionFactoryBuilder中// SqlSessionFactoryBuilder.java /** * 构造 SqlSessionFactory 对象 * * param reader Reader 对象 * param environment 环境 * param properties Properties 变量 * return SqlSessionFactory 对象 */ public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { // 1 创建 XMLConfigBuilder 对象 XMLConfigBuilder parser new XMLConfigBuilder(reader, environment, properties); // 2 执行 XML 解析 // 3 创建 DefaultSqlSessionFactory 对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException(Error building SqlSession., e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }1处创建 XMLConfigBuilder 对象用于解析 XML 配置文件。关于 XMLConfigBuilder 类我们放在 「3. XMLConfigBuilder」 中详细解析。2处调用XMLConfigBuilder#parse()方法执行 XML 解析返回 Configuration 对象。详细解析见 「4. XMLConfigBuilder」 。3处创建 DefaultSqlSessionFactory 对象。关于 DefaultSqlSessionFactory 类我们在后续的文章中详细解析。3. XMLConfigBuilderorg.apache.ibatis.builder.xml.XMLConfigBuilder继承 BaseBuilder 抽象类XML 配置构建器主要负责解析 mybatis-config.xml 配置文件。3.1 构造方法// XMLConfigBuilder.java /** * 是否已解析 */ private boolean parsed; /** * XML 解析器 */ private final XPathParser parser; /** * 环境 */ private String environment; /** * ReflectorFactory 对象 */ private final ReflectorFactory localReflectorFactory new DefaultReflectorFactory(); public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 1 创建 Configuration 对象 super(new Configuration()); ErrorContext.instance().resource(SQL Mapper Configuration); // 2 设置 Configuration 的 variables 属性 this.configuration.setVariables(props); this.parsed false; this.environment environment; this.parser parser; }parsed属性是否已解析。在#parse()方法中标记为true。parser属性XPathParser 对象。在 《精尽 MyBatis 源码分析 —— 解析器模块》 中已经详细解析。environment属性环境。localReflectorFactory属性DefaultReflectorFactory 对象。1处创建 Configuration 对象并调用父类 BaseBuilder 的构造方法设置configuration属性。代码如下// BaseBuilder.java /** * Configuration 对象 */ protected final Configuration configuration; public BaseBuilder(Configuration configuration) { this.configuration configuration; }2处设置 Configuration 对象的variables属性。代码如下// Configuration.java /** * 变量 Properties 对象。 * * 参见 {link org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement(XNode context)} 方法 */ protected Properties variables new Properties(); public void setVariables(Properties variables) { this.variables variables; }3.2 parse#parse()方法解析 XML 成 Configuration 对象。代码如下// XMLConfigBuilder.java public Configuration parse() { // 1 若已解析抛出 BuilderException 异常 if (parsed) { throw new BuilderException(Each XMLConfigBuilder can only be used once.); } // 2 标记已解析 parsed true; // 3 解析 XML configuration 节点 parseConfiguration(parser.evalNode(/configuration)); return configuration; }1处若已解析抛出 BuilderException 异常。也就是说一个 XMLConfigBuilder 对象只能调用一次#parse()方法。2处标记已解析。3处调用XPathParser#evalNode(String expression)方法获得 XMLconfiguration /节点后调用#parseConfiguration(XNode root)方法解析该节点。详细解析见 「4. parseConfiguration」 。4. parseConfiguration#parseConfiguration(XNode root)方法解析configuration /节点。代码如下// XMLConfigBuilder.java private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 1 解析 properties / 标签 propertiesElement(root.evalNode(properties)); // 2 解析 settings / 标签 Properties settings settingsAsProperties(root.evalNode(settings)); // 3 加载自定义 VFS 实现类 loadCustomVfs(settings); // 4 加载自定义的日志实现类 loadCustomLogImpl(settings); // 5 解析 typeAliases / 标签 typeAliasesElement(root.evalNode(typeAliases)); // 6 解析 plugins / 标签 pluginElement(root.evalNode(plugins)); // 7 解析 objectFactory / 标签 objectFactoryElement(root.evalNode(objectFactory)); // 8 解析 objectWrapperFactory / 标签 objectWrapperFactoryElement(root.evalNode(objectWrapperFactory)); // 9 解析 reflectorFactory / 标签 reflectorFactoryElement(root.evalNode(reflectorFactory)); // 10 赋值 settings / 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 11 解析 environments / 标签 environmentsElement(root.evalNode(environments)); // 12 解析 databaseIdProvider / 标签 databaseIdProviderElement(root.evalNode(databaseIdProvider)); // 13 解析 typeHandlers / 标签 typeHandlerElement(root.evalNode(typeHandlers)); // 14 解析 mappers / 标签 mapperElement(root.evalNode(mappers)); } catch (Exception e) { throw new BuilderException(Error parsing SQL Mapper Configuration. Cause: e, e); } }按照代码执行的顺序逐个解析。当然这个顺序和 mybatis-config.xml 配置文件的顺序基本一致的。 不一致的地方就是为了解决某些依赖问题。4.1 propertiesElement#propertiesElement(XNode context)方法解析properties /节点。大体逻辑如下解析properties /标签成 Properties 对象。覆盖configuration中的variables属性。将解析的 Properties 对象设置到parser和configuration中。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.2 settingsAsProperties#settingsAsProperties(XNode context)方法解析settings /节点。大体逻辑如下解析settings /标签成 Properties 对象。初步校验 Properties 对象的 KEY 是否存在。若不存在抛出 BuilderException 异常。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.3 loadCustomVfs#loadCustomVfs(Properties settings)方法加载自定义 VFS 实现类。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.4 loadCustomLogImpl#loadCustomLogImpl(Properties settings)方法加载自定义的日志实现类。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.5 typeAliasesElement#typeAliasesElement(XNode parent)方法解析typeAliases /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.6 pluginElement#pluginElement(XNode parent)方法解析plugins /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.7 objectFactoryElement#objectFactoryElement(XNode context)方法解析objectFactory /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.8 objectWrapperFactoryElement#objectWrapperFactoryElement(XNode context)方法解析objectWrapperFactory /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.9 reflectorFactoryElement#reflectorFactoryElement(XNode context)方法解析reflectorFactory /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.10 settingsElement#settingsElement(Properties props)方法赋值settings /到 Configuration 属性。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.11 environmentsElement#environmentsElement(XNode context)方法解析environments /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.12 databaseIdProviderElement#databaseIdProviderElement(XNode context)方法解析databaseIdProvider /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.13 typeHandlerElement#typeHandlerElement(XNode parent)方法解析typeHandlers /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。4.14 mapperElement#mapperElement(XNode parent)方法解析mappers /节点。详细解析见 《精尽 MyBatis 源码分析 —— MyBatis 初始化二之加载 XML 配置文件》 。5. 创建 DefaultSqlSessionFactory 对象在#parse()方法解析 XML 成 Configuration 对象后则调用#build(Configuration config)方法创建 DefaultSqlSessionFactory 对象。代码如下// SqlSessionFactoryBuilder.java public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }关于 DefaultSqlSessionFactory 在后续的文章中详细解析。6. 加载 Mapper 映射配置文件在#mapperElement(XNode parent)方法中会调用XMLMapperBuilder#parse()方法加载 Mapper 映射配置文件。而 XMLMapperBuilder 是解析 Mapper 映射配置文件的入口。关于这块我们放在 《精尽 MyBatis 源码分析 —— MyBatis 初始化三之加载 Statement》 中详细解析。7. 加载注解配置在#mapperElement(XNode parent)方法中会调用Configuration#addMapper(ClassT type)方法加载注解配置。而 MapperAnnotationBuilder 是解析注解配置的入口。关于这块我们放在 《精尽 MyBatis 源码分析 —— MyBatis 初始化四之加载注解配置》 中详细解析。8. 总结本文主要简单梳理了 MyBatis 初始化的流程。后续的文章我们会详细解析每个流程。另外推荐阅读 《MyBatis 源码分析 - 配置文件解析过程》 文章。