Appearance
CAS 与 MyBatis 数据集成最佳实践:从 XML 配置到 Spring Boot 自动装配
作者: 必码 | bima.cc
一、CAS 数据集成概述
1.1 CAS 默认数据源方案
Apereo CAS(Central Authentication Service)作为企业级单点登录(SSO)解决方案,其核心职责是统一管理用户的认证流程。然而,认证本身离不开用户数据的支撑——CAS 需要从某个数据源中读取用户凭证、验证身份、管理会话状态。因此,数据集成是 CAS 部署中最为关键的环节之一。
CAS 从架构层面提供了多种内置的数据源方案,以适配不同规模、不同技术栈的企业环境。理解这些方案的特性与适用场景,是做出正确技术选型的前提。
JSON 数据源
JSON 是 CAS 中最轻量级的用户存储方案。在小型项目或快速原型验证阶段,开发者可以直接在 JSON 文件中定义用户账户信息。CAS 通过内置的 JsonResource 加载机制读取这些文件,并将其转换为内存中的用户数据结构。
JSON 方案的优势在于零依赖、零配置数据库,部署极其简单。但其局限性也非常明显:数据无法动态更新(修改 JSON 文件后需要重启 CAS 服务)、无法支撑高并发访问、缺乏事务支持、不适合存储大量用户数据。在生产环境中,JSON 数据源通常只用于开发测试或演示场景。
JDBC 数据源
JDBC 是 CAS 中使用最广泛的数据库集成方案。CAS 内置了对关系型数据库的原生支持,通过 JDBC 直接执行 SQL 查询来完成用户认证。CAS 提供了 QueryDatabaseAuthenticationHandler、SearchModeSearchDatabaseAuthenticationHandler 等多种认证处理器,可以灵活配置认证 SQL、密码编码器、字段映射等参数。
JDBC 方案的优势在于通用性强——几乎所有关系型数据库(MySQL、PostgreSQL、Oracle、SQL Server 等)都支持 JDBC 协议。同时,直接 SQL 查询的性能开销较小,认证响应速度快。但其劣势在于 SQL 与 Java 代码耦合度高,复杂查询的可维护性较差,且缺乏对象关系映射(ORM)的便利性。
LDAP 数据源
LDAP(Lightweight Directory Access Protocol)是企业环境中常见的数据源方案,特别是在使用 Active Directory 或 OpenLDAP 作为统一身份管理平台的企业中。CAS 通过 LdapAuthenticationHandler 提供了对 LDAP 的原生支持,支持绑定认证(bind authentication)和搜索认证(search authentication)两种模式。
LDAP 方案的优势在于与现有企业目录服务的无缝集成,支持组织架构的层级查询,适合大型企业的统一身份管理。但其配置复杂度较高,对 LDAP 协议的理解要求较深,且在非 LDAP 环境中无法使用。
REST 数据源
CAS 还支持通过 REST API 进行远程认证。在这种方案中,CAS 将认证请求转发给一个外部 REST 服务,由该服务完成用户验证并返回认证结果。这种方案适合认证逻辑需要与外部业务系统深度集成的场景。
其他方案
除了上述主要方案外,CAS 还支持 MongoDB、Cassandra、Redis、Groovy 脚本等多种数据源方案。这些方案各有其适用场景,但使用频率相对较低。
1.2 为什么选择 MyBatis 而非 JPA/Hibernate
在 CAS 的数据集成场景中,当内置的 JDBC 方案无法满足复杂业务需求时,开发者通常需要引入一个持久层框架。Java 生态中最主流的两个选择是 MyBatis 和 JPA(通常配合 Hibernate 实现)。在 CAS Overlay 项目中,我们选择了 MyBatis,这一决策基于以下几个维度的深入考量。
SQL 可控性
CAS 的认证查询往往有其特殊的性能要求。例如,在高并发登录场景下,认证查询需要在毫秒级完成,这就要求开发者对执行的每一条 SQL 都有精确的控制。MyBatis 采用 SQL 映射的方式,开发者直接编写 SQL 语句,可以针对数据库特性进行优化——使用特定的索引提示、调整 JOIN 策略、控制查询字段等。
相比之下,JPA/Hibernate 通过 HQL 或 Criteria API 生成 SQL,开发者对最终执行的 SQL 控制力较弱。虽然 Hibernate 提供了 @Query 注解支持原生 SQL,但在复杂场景下,JPA 的二级缓存、懒加载、脏检查等机制反而可能带来不可预期的性能问题。
学习曲线与团队适配
MyBatis 的核心概念非常简洁:SQL 映射文件(XML)+ Mapper 接口(Java)。一个有 SQL 基础的开发者可以在一天内上手 MyBatis。而 JPA/Hibernate 的学习曲线则陡峭得多——实体关系映射(@OneToMany、@ManyToMany)、级联操作(CascadeType)、抓取策略(FetchType)、缓存策略(@Cacheable)等概念都需要深入理解才能正确使用。
在企业项目中,团队成员的技术背景往往参差不齐。选择 MyBatis 可以降低团队的学习成本,减少因 ORM 框架误用导致的性能问题和 Bug。
CAS 版本兼容性
CAS 本身的底层框架经历了从 Spring 4 到 Spring 5 再到 Spring 6 的演进。在这个过程中,Hibernate 作为 JPA 的主要实现,其版本也需要同步升级。Hibernate 5 到 Hibernate 6 之间存在大量 Breaking Changes,包括但不限于:HQL 语法的变更、类型系统的重构、元模型 API 的调整等。这些变更在 CAS 升级时会带来额外的迁移成本。
MyBatis 的版本升级则相对平滑。从 MyBatis 3.4 到 3.5,核心 API 几乎没有 Breaking Changes,主要是在性能优化和功能增强方面进行迭代。这使得 MyBatis 在 CAS 跨版本升级时的兼容性风险大大降低。
调试与问题排查
在使用 JPA/Hibernate 时,开发者经常遇到 N+1 查询问题、懒加载导致的 LazyInitializationException、事务边界不清晰导致的脏数据回写等问题。这些问题的排查往往需要深入理解 Hibernate 的 Session 管理机制和 flush 策略。
MyBatis 则不存在这些问题。每一条 SQL 的执行都是显式的,开发者可以清楚地看到每一步数据库操作。在 CAS 这种对认证性能和稳定性要求极高的系统中,这种透明性是至关重要的。
动态 SQL 的灵活性
CAS 的认证场景中经常需要根据不同的条件组合动态生成 SQL。例如,根据用户输入的用户名、手机号、邮箱等不同字段进行认证,或者根据不同的认证策略使用不同的查询逻辑。MyBatis 的 <if>、<choose>、<where>、<foreach> 等动态 SQL 标签为这类需求提供了优雅的解决方案。
JPA 的 Criteria API 虽然也能实现动态查询,但代码的可读性和可维护性远不如 MyBatis 的 XML 方式。而 JPA 的 @Query 注解在处理动态条件时则显得力不从心。
当然,MyBatis 也有其不足之处
需要客观指出的是,MyBatis 并非完美无缺。相比 JPA/Hibernate,MyBatis 缺乏自动化的 Schema 生成和迁移工具(如 Hibernate 的 hbm2ddl),对象关系映射需要手动维护,数据库方言的适配也需要开发者自行处理。但在 CAS 的认证场景中,这些不足的影响是有限的——认证相关的表结构通常较为稳定,不需要频繁变更;SQL 的数据库方言适配可以通过 MyBatis 的 DatabaseIdProvider 机制来解决。
1.3 MyBatis 在 CAS Overlay 中的定位
CAS Overlay 是 Apereo CAS 官方推荐的部署方式。与传统的 WAR 包部署不同,Overlay 采用"分层覆盖"的架构——开发者在一个预构建的 CAS 基础包之上,通过覆盖特定的配置文件、类文件和资源文件来定制 CAS 的行为。
在这种架构下,MyBatis 的定位是业务数据持久层,而非 CAS 内部的认证数据源。需要明确区分两个概念:
- CAS 内置认证数据源:通过 CAS 的
cas.properties或application.yml配置,使用 CAS 内置的QueryDatabaseAuthenticationHandler等组件完成认证。这种方式适合简单的用户名密码认证场景。 - MyBatis 业务数据持久层:通过自定义 Spring 配置引入 MyBatis,用于处理 CAS 认证流程之外的复杂业务数据操作。例如,用户管理后台的数据 CRUD、自定义认证策略中需要的复杂数据查询、Ticket Registry 的持久化等。
在实际项目中,MyBatis 通常承担以下职责:
- 用户管理数据的 CRUD 操作:包括用户的创建、更新、删除、查询等。CAS 内置的 JDBC 认证处理器只支持查询操作,而用户管理后台需要完整的 CRUD 能力。
- 自定义认证逻辑的数据支撑:当认证逻辑超出 CAS 内置处理器的覆盖范围时(例如多因子认证、条件认证策略等),需要通过 MyBatis 查询额外的业务数据。
- Ticket Registry 的数据库持久化:在高可用部署场景中,CAS 的 Ticket(TGT、ST 等)需要持久化到数据库以支持集群共享。虽然 CAS 内置了 JDBC Ticket Registry,但通过 MyBatis 可以实现更灵活的 Ticket 管理策略。
- 审计日志与操作记录:认证相关的审计日志、操作记录等数据的持久化存储。
- 与其他业务系统的数据交互:CAS 作为统一认证中心,经常需要与下游业务系统进行数据同步或查询。
理解 MyBatis 在 CAS Overlay 中的这一定位,对于正确设计数据集成架构至关重要。接下来,我们将从 CAS 5.3 开始,逐步深入解析三个大版本中 MyBatis 集成配置的演进历程。
二、Spring XML 配置方式(CAS 5.3)
2.1 spring-common.xml 完整配置结构
CAS 5.3 时代,Spring Boot 的自动配置能力尚未被 CAS 充分利用。CAS 5.3 仍然大量依赖传统的 Spring XML 配置来管理 Bean 的生命周期和依赖关系。在这一版本中,MyBatis 的集成配置主要集中在 spring-common.xml 文件中。
spring-common.xml 是 CAS Overlay 中自定义 Spring 配置的核心文件。它位于 src/main/resources/ 目录下,通过 CAS 的配置加载机制被自动引入 Spring 应用上下文。这个文件承载了数据源、会话工厂、事务管理器、Mapper 扫描等所有 MyBatis 相关的 Bean 定义。
从整体结构来看,spring-common.xml 可以划分为以下几个逻辑区块:
- 命名空间声明区:定义文件中使用的 XML 命名空间,包括
beans、context、tx、aop等。 - 数据源配置区:定义
BasicDataSourceBean,配置数据库连接参数。 - 会话工厂配置区:定义
SqlSessionFactoryBean,关联数据源和 MyBatis 配置文件。 - Mapper 扫描配置区:定义
MapperScannerConfigurer,指定 Mapper 接口的扫描路径。 - 事务管理配置区:定义
DataSourceTransactionManager,配置事务注解驱动。 - AOP 事务配置区:定义事务通知和切面,实现声明式事务管理。
这种"大一统"的配置方式在 CAS 5.3 中是标准做法。所有与数据持久层相关的配置都集中在一个文件中,虽然文件体积较大,但配置的全局可见性较好,便于理解整体架构。
需要注意的是,spring-common.xml 的加载顺序很重要。CAS 的 Spring 上下文采用层级结构,spring-common.xml 中的 Bean 定义会与 CAS 内部的 Bean 定义共存于同一个应用上下文中。因此,Bean 的命名需要避免与 CAS 内部 Bean 冲突——这一点在后续版本中会变得越来越重要。
2.2 数据源配置:BasicDataSource 详解
CAS 5.3 使用 Apache Commons DBCP 1.4 作为数据库连接池实现。org.apache.commons.dbcp.BasicDataSource 是 DBCP 1.4 中最核心的数据源类,它实现了 javax.sql.DataSource 接口,提供了完整的连接池管理能力。
选择 DBCP 1.4 而非更先进的连接池(如 HikariCP、Druid),主要是出于 CAS 5.3 的兼容性考虑。CAS 5.3 基于 Spring Framework 4.3,其内部的许多组件依赖于 commons-dbcp 的特定行为。同时,DBCP 1.4 的 API 稳定性经过长期验证,在 CAS 这种对稳定性要求极高的系统中是一个安全的选择。
BasicDataSource 的核心配置属性可以按功能分为以下几组:
连接建立参数
driverClassName:JDBC 驱动类的全限定名。例如,MySQL 使用com.mysql.jdbc.Driver(MySQL Connector/J 5.x)或com.mysql.cj.jdbc.Driver(MySQL Connector/J 8.x)。url:数据库连接 URL。需要包含数据库地址、端口、数据库名称以及连接参数。username:数据库用户名。password:数据库密码。
连接池大小参数
initialSize:连接池初始化时创建的连接数。maxActive:连接池中允许的最大活动连接数。minIdle:连接池中保持的最小空闲连接数。maxIdle:连接池中允许的最大空闲连接数。
连接获取参数
maxWait:当连接池中所有连接都被占用时,新请求等待可用连接的最大时间(毫秒)。超过此时间将抛出异常。
连接验证参数
testWhileIdle:是否在连接空闲时进行验证。validationQuery:用于验证连接有效性的 SQL 查询。timeBetweenEvictionRunsMillis:空闲连接回收器运行的间隔时间。minEvictableIdleTimeMillis:连接在池中最小空闲时间,超过此时间的空闲连接将被回收。
连接泄漏检测参数
removeAbandoned:是否移除被遗弃的连接。removeAbandonedTimeout:连接被借出后超过此时间(秒)未归还则视为被遗弃。logAbandoned:是否记录被遗弃连接的堆栈跟踪信息。
下面通过一个教学用途的简化配置来展示 BasicDataSource 的典型配置方式:
xml
<!-- 教学示例:BasicDataSource 核心配置结构 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 连接建立参数 -->
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
<!-- 连接池大小参数 -->
<property name="initialSize" value="5" />
<property name="maxActive" value="10" />
<property name="minIdle" value="5" />
<property name="maxIdle" value="10" />
<!-- 连接获取超时 -->
<property name="maxWait" value="5000" />
<!-- 连接验证 -->
<property name="testWhileIdle" value="true" />
<property name="validationQuery" value="select 1" />
<!-- 连接泄漏检测 -->
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="300" />
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在这个配置中,${db.driver} 等占位符通过 Spring 的 PropertyPlaceholderConfigurer 或 CAS 的配置加载机制进行替换。这种外部化配置的方式使得数据库连接参数可以在不修改 XML 文件的情况下进行调整,符合十二要素应用(Twelve-Factor App)的配置管理原则。
2.3 连接池参数深度解析
连接池参数的合理配置直接影响到 CAS 系统的性能和稳定性。下面我们对每个参数进行深入分析。
initialSize = 5
initialSize 指定了连接池在启动时预先创建的连接数量。设置为 5 是一个保守但合理的选择。在 CAS 启动阶段,这些预创建的连接可以避免首次请求时的连接创建延迟(即"冷启动"问题)。
需要注意的是,initialSize 的值不应设置过大。每个数据库连接都会占用数据库服务器的资源(包括线程、内存、文件描述符等)。如果 CAS 部署了多个实例,所有实例的 initialSize 之和不应超过数据库服务器的最大连接数限制。
maxActive = 10
maxActive 是连接池中允许同时存在的最大活动连接数。当活动连接数达到此上限时,新的连接请求将进入等待队列(受 maxWait 控制)。
将 maxActive 设置为 10 需要结合 CAS 的实际并发量来评估。CAS 的认证请求通常是短连接操作——一次认证查询通常在几十毫秒内完成。因此,10 个连接理论上可以支撑每秒数百次的认证请求(假设每次查询耗时 50ms,则 10 个连接每秒可以处理 200 次请求)。
但在实际场景中,还需要考虑以下因素:
- CAS 的 Ticket 查询和清理操作也会占用连接。
- 如果 MyBatis 同时服务于用户管理后台,后台的复杂查询可能长时间占用连接。
- 数据库服务器的响应延迟会导致连接占用时间延长。
minIdle = 5, maxIdle = 10
minIdle 和 maxIdle 控制连接池中空闲连接的数量范围。当空闲连接数低于 minIdle 时,连接池会创建新连接以维持最小空闲数量。当空闲连接数超过 maxIdle 时,多余的空闲连接将被回收。
将 minIdle 设置为与 initialSize 相同的值(5),可以确保连接池在运行过程中始终保持足够的空闲连接,避免突发流量时的连接创建延迟。将 maxIdle 设置为与 maxActive 相同的值(10),意味着活动连接释放后不会被立即回收,而是转为空闲状态以备复用。这种配置在高并发间歇性场景下可以有效减少连接创建/销毁的开销。
maxWait = 5000
maxWait 设置为 5000 毫秒(5秒)。当连接池中所有连接都被占用时,新的连接请求最多等待 5 秒。如果 5 秒后仍没有可用连接,将抛出 java.sql.SQLException: Cannot get a connection, pool error Timeout waiting for idle object 异常。
5 秒的等待时间在 CAS 认证场景中是一个相对保守的设置。对于用户的登录请求来说,5 秒的等待已经超出了可接受的用户体验范围。但在实际生产环境中,这个值需要结合 maxActive 和系统的峰值并发量来综合评估。如果 maxActive 设置合理,正常情况下不应该出现连接等待的情况。maxWait 更多的是作为一种安全阀,在异常流量场景下快速失败,避免级联故障。
2.4 连接泄漏检测机制
连接泄漏(Connection Leak)是数据库应用中最常见的问题之一。当应用程序从连接池中借出连接后,由于异常、编码错误或逻辑缺陷,未能正确归还连接,就会导致连接泄漏。如果不加以检测和处理,连接泄漏最终会耗尽连接池中的所有连接,导致整个系统不可用。
CAS 5.3 的配置中启用了 DBCP 的连接泄漏检测机制:
xml
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="300" />1
2
2
removeAbandoned = true
启用被遗弃连接的自动移除功能。当此选项设置为 true 时,DBCP 会定期检查所有被借出的连接,如果某个连接被借出的时间超过了 removeAbandonedTimeout 指定的阈值,则认为该连接已被遗弃,并将其强制回收。
removeAbandonedTimeout = 300
设置连接被遗弃的超时时间为 300 秒(5分钟)。这意味着如果一个连接被借出后超过 5 分钟仍未归还,DBCP 将强制回收该连接。
300 秒的超时设置在 CAS 场景中是一个相对宽松的值。正常的认证查询通常在毫秒级完成,即使是最复杂的业务查询也很少超过 30 秒。将超时设置为 300 秒的目的是为了避免误判——某些长时间运行的事务(如批量数据导入、报表生成等)可能需要较长的连接持有时间。
但在实际使用中,如果发现连接泄漏问题,建议将此值适当调低(例如 60 秒),以便更快地检测和回收泄漏的连接。同时,应该配合日志分析,找出连接泄漏的根本原因并修复。
连接泄漏检测的工作原理
DBCP 的连接泄漏检测依赖于其内部的 AbandonedObjectPool 机制。当 removeAbandoned 启用时,每次连接被借出都会记录借出时间戳。DBCP 的空闲连接回收器(由 timeBetweenEvictionRunsMillis 控制运行间隔)在每次运行时,会遍历所有被借出的连接,检查其借出时间是否超过阈值。对于超过阈值的连接,DBCP 会:
- 记录警告日志(如果
logAbandoned设置为true,还会记录连接被借出时的堆栈跟踪信息)。 - 强制关闭底层 JDBC 连接。
- 从活动连接列表中移除该连接。
- 创建新的连接以补充连接池。
需要注意的是,强制回收连接可能会导致正在使用该连接的事务回滚。因此,removeAbandoned 是一种"亡羊补牢"的保护机制,而非连接泄漏的解决方案。正确的做法是找到并修复连接泄漏的代码。
常见连接泄漏原因
在 CAS + MyBatis 的集成场景中,常见的连接泄漏原因包括:
- 在手动管理事务时,忘记在
finally块中关闭连接。 - 使用
TransactionTemplate编程式事务时,回调函数中抛出异常导致连接未正确释放。 - Mapper 方法中执行了长时间运行的操作(如远程调用、文件操作),导致连接被长时间持有。
- 自定义的拦截器或过滤器中获取了数据库连接但未正确释放。
2.5 连接验证策略
数据库连接是网络资源,可能因为各种原因变得"失效"——数据库服务器重启、网络中断、防火墙超时断开、数据库端主动关闭空闲连接等。如果应用程序使用了一个已经失效的连接来执行 SQL,将抛出难以预料的异常。
CAS 5.3 的配置中采用了"空闲时验证"的策略:
xml
<property name="testWhileIdle" value="true" />
<property name="validationQuery" value="select 1" />1
2
2
testWhileIdle = true
启用空闲连接验证。当此选项设置为 true 时,DBCP 的空闲连接回收器在每次运行时,会对池中的空闲连接执行验证查询。只有通过验证的连接才会被保留在池中,未通过验证的连接将被销毁并替换为新的连接。
validationQuery = "select 1"
验证查询使用 select 1,这是最简单也最通用的验证 SQL。几乎所有关系型数据库都支持这条语句,且执行开销极小(不需要访问任何表数据)。
为什么选择 testWhileIdle 而非 testOnBorrow
DBCP 提供了三种连接验证时机:
testOnBorrow:每次从池中借出连接时验证。安全性最高,但性能开销最大——每次数据库操作都会多执行一次验证查询。testOnReturn:每次归还连接到池中时验证。安全性较低,因为归还时验证通过不代表下次借出时仍然有效。testWhileIdle:空闲时验证。在安全性和性能之间取得了较好的平衡。
CAS 5.3 选择 testWhileIdle 的原因是:CAS 的认证操作对性能非常敏感,testOnBorrow 带来的额外查询开销在高并发场景下不可忽视。而 testWhileIdle 通过后台定期验证,可以在不影响正常请求性能的前提下,及时发现和替换失效连接。
验证策略的补充参数
在实际配置中,通常还需要配合以下参数来优化验证策略:
xml
<!-- 空闲连接回收器运行间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 连接最小空闲时间(毫秒) -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<!-- 每次验证的连接数量 -->
<property name="numTestsPerEvictionRun" value="3" />1
2
3
4
5
6
2
3
4
5
6
timeBetweenEvictionRunsMillis 控制空闲连接回收器的运行频率。设置为 60000 毫秒(1分钟)意味着每分钟会执行一次空闲连接验证和回收。这个频率在大多数场景下是合适的。
minEvictableIdleTimeMillis 设置连接在池中的最小空闲时间。超过此时间的空闲连接将被回收(无论是否通过验证)。设置为 300000 毫秒(5分钟)可以防止空闲连接占用数据库资源过久。
numTestsPerEvictionRun 控制每次回收器运行时验证的连接数量。设置为 3 可以在验证效率和资源消耗之间取得平衡。
2.6 SqlSessionFactoryBean 配置
SqlSessionFactoryBean 是 MyBatis-Spring 集成的核心组件。它负责创建 MyBatis 的 SqlSessionFactory——MyBatis 的核心对象,所有的数据库操作都通过它来获取 SqlSession 并执行 SQL。
在 CAS 5.3 的配置中,SqlSessionFactoryBean 的配置如下:
xml
<!-- 教学示例:SqlSessionFactoryBean 核心配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="mybatis/mybatis-config.xml" />
<property name="mapperLocations" value="classpath:mapper/**/*.xml" />
</bean>1
2
3
4
5
6
2
3
4
5
6
dataSource 属性
dataSource 属性关联了前面配置的 BasicDataSource。SqlSessionFactoryBean 会使用这个数据源来创建 SqlSession。需要注意的是,在 CAS 5.3 中,如果存在多个数据源(例如 CAS 内部的数据源和业务数据源),需要确保 sqlSessionFactory 关联的是正确的数据源。
configLocation 属性
configLocation 指定了 MyBatis 的全局配置文件路径。mybatis/mybatis-config.xml 文件通常包含 MyBatis 的全局设置,例如:
settings:MyBatis 的运行时行为配置,包括缓存启用/禁用、延迟加载、日志实现等。typeAliases:Java 类型的别名定义,用于简化 XML 映射文件中的类型引用。typeHandlers:自定义类型处理器,用于 Java 类型和 JDBC 类型之间的转换。plugins:MyBatis 插件配置,用于拦截 SQL 执行并进行增强(如分页、SQL 日志等)。
在 CAS 5.3 中,mybatis-config.xml 通常是一个精简的配置文件,大部分设置使用 MyBatis 的默认值。这是因为 CAS 的运行环境对 MyBatis 的行为有特定要求,过度自定义可能导致不可预期的问题。
mapperLocations 属性
mapperLocations 使用 Ant 风格的路径模式指定 Mapper XML 文件的位置。classpath:mapper/**/*.xml 表示从类路径的 mapper 目录开始,递归搜索所有 .xml 文件。
这种配置方式允许开发者将 Mapper XML 文件组织在任意深度的目录结构中。例如:
src/main/resources/
└── mapper/
├── UserInfoMapper.xml
├── TicketMapper.xml
└── system/
├── RoleMapper.xml
└── PermissionMapper.xml1
2
3
4
5
6
7
2
3
4
5
6
7
所有这些 XML 文件都会被 SqlSessionFactoryBean 自动加载和解析。
关于 typeAliasesPackage 的补充
除了上述三个核心属性外,SqlSessionFactoryBean 还支持 typeAliasesPackage 属性,用于自动扫描指定包下的 Java 类并注册为类型别名。在 CAS 5.3 的配置中,如果实体类都放在统一的包下,可以使用此属性来简化 XML 映射文件中的类型引用:
xml
<property name="typeAliasesPackage" value="cc.bima.cas.model" />1
配置后,在 Mapper XML 中可以直接使用类名的简单名称(如 UserInfo)而非全限定名(如 cc.bima.cas.model.UserInfo)来引用实体类。
2.7 MapperScannerConfigurer 扫描配置
MapperScannerConfigurer 是 MyBatis-Spring 提供的一个便捷组件,用于自动扫描指定包下的 Mapper 接口并将它们注册为 Spring Bean。在 CAS 5.3 的配置中:
xml
<!-- 教学示例:MapperScannerConfigurer 配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cc.bima.cas" />
</bean>1
2
3
4
5
2
3
4
5
sqlSessionFactoryBeanName 属性
sqlSessionFactoryBeanName 指定了 SqlSessionFactory 的 Bean 名称。需要注意的是,这里使用的是 Bean 名称(String 类型)而非 Bean 引用(Object 类型)。这是 MapperScannerConfigurer 的一个特殊设计——因为它实现了 BeanDefinitionRegistryPostProcessor 接口,在 Spring 容器启动的早期阶段就会被执行,此时其他 Bean 可能尚未实例化,因此只能通过名称引用。
basePackage 属性
basePackage 指定了 Mapper 接口的扫描基础包。cc.bima.cas 表示扫描 cc.bima.cas 包及其所有子包下的接口。
在 CAS 5.3 中,basePackage 设置为 cc.bima.cas 是一个相对宽泛的配置。它会扫描该包下的所有接口,包括 Mapper 接口和非 Mapper 接口。MyBatis-Spring 会自动过滤出标注了 @Mapper 注解的接口(如果有的话),或者将所有接口都尝试作为 Mapper 来处理。
这种宽泛的扫描方式在项目初期可能不会带来问题,但随着项目规模的增长,可能会出现以下隐患:
- 扫描范围过大导致启动时间增加。
- 非 Mapper 接口可能被误识别为 Mapper,导致启动错误。
- 不同模块的 Mapper 混在一起,不利于模块化管理。
这些问题在 CAS 6.6 中得到了改善,我们将在后续章节中详细讨论。
2.8 事务管理器配置
事务管理是企业级应用中不可或缺的基础设施。在 CAS + MyBatis 的集成中,事务管理器负责协调数据库连接的获取、提交和回滚。
CAS 5.3 使用 Spring 的 DataSourceTransactionManager 作为事务管理器:
xml
<!-- 教学示例:事务管理器配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>1
2
3
4
5
2
3
4
5
DataSourceTransactionManager 的工作原理
DataSourceTransactionManager 是 Spring 中最简单也最直接的事务管理器。它通过操作 DataSource 获取的数据库连接来管理事务。其核心工作机制如下:
- 当事务开始时,从事务管理器关联的
DataSource中获取一个数据库连接。 - 将连接的自动提交模式设置为
false。 - 将连接绑定到当前线程(通过
ThreadLocal)。 - 在事务范围内,所有通过同一
DataSource获取的连接实际上都是同一个连接(通过 Spring 的TransactionSynchronizationManager实现)。 - 当事务正常完成时,调用连接的
commit()方法。 - 当事务异常回滚时,调用连接的
rollback()方法。 - 无论事务成功还是失败,最终都会将连接归还到连接池。
为什么选择 DataSourceTransactionManager 而非 JpaTransactionManager
DataSourceTransactionManager 和 JpaTransactionManager 都是 Spring 提供的事务管理器实现,但它们适用于不同的持久层技术:
DataSourceTransactionManager:适用于原生 JDBC、MyBatis、iBatis 等基于DataSource的持久层技术。JpaTransactionManager:适用于 JPA/Hibernate 等 ORM 框架。
由于我们选择了 MyBatis 作为持久层框架,DataSourceTransactionManager 是自然的选择。使用 JpaTransactionManager 管理 MyBatis 的事务虽然技术上可行(因为 JpaTransactionManager 也继承了 DataSourceTransactionManager 的能力),但会引入不必要的 JPA 依赖。
2.9 事务注解驱动
CAS 5.3 的配置中启用了 Spring 的注解驱动事务管理:
xml
<tx:annotation-driven transaction-manager="transactionManager" />1
<tx:annotation-driven> 是 Spring 提供的声明式事务管理的入口配置。它的作用是注册一个 AnnotationTransactionAttributeSource、一个 TransactionInterceptor 和若干 BeanPostProcessor,使得开发者可以通过 @Transactional 注解来声明事务边界。
@Transactional 注解的使用方式
启用 <tx:annotation-driven> 后,可以在 Service 层的方法或类上使用 @Transactional 注解:
java
// 教学示例:@Transactional 注解使用方式
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional
public void createUser(UserInfo userInfo) {
userInfoMapper.insertSelective(userInfo);
// 其他数据库操作...
}
@Transactional(readOnly = true)
public UserInfo getUserById(Long id) {
return userInfoMapper.selectByPrimaryKey(id);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
proxy-target-class 属性
在 CAS 5.3 的配置中,<tx:annotation-driven> 通常还需要关注 proxy-target-class 属性:
xml
<tx:annotation-driven transaction-manager="transactionManager"
proxy-target-class="true" />1
2
2
proxy-target-class="true" 表示使用 CGLIB 生成代理类,而非 JDK 动态代理。CGLIB 代理基于类的继承,可以代理类(而非仅接口);JDK 动态代理基于接口,只能代理接口。在 CAS 的某些场景下(如需要代理非接口类型的类),CGLIB 代理是必需的。
order 属性
<tx:annotation-driven> 的 order 属性控制事务拦截器在拦截器链中的执行顺序。在 CAS 中,可能存在多个拦截器(如安全拦截器、日志拦截器等),事务拦截器的执行顺序需要仔细考虑。通常,事务拦截器应该在外层拦截器之后执行,以确保事务边界包含所有业务逻辑。
三、AOP 事务管理(CAS 5.3)
3.1 tx:advice 事务通知配置
CAS 5.3 除了支持 @Transactional 注解驱动的声明式事务外,还通过 Spring AOP 提供了基于 XML 配置的声明式事务管理。这种双重事务管理机制在 CAS 5.3 中并存,各有其适用场景。
<tx:advice> 是 Spring AOP 事务管理的核心配置元素。它定义了一组事务属性,这些属性将被应用到匹配指定切点表达式的所有方法上。
在 CAS 5.3 的配置中,事务通知采用了"全量事务"的策略:
xml
<!-- 教学示例:tx:advice 事务通知配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>1
2
3
4
5
6
2
3
4
5
6
name="*" 的含义
<tx:method name="*" /> 中的 name="*" 是一个通配符,表示匹配所有方法名。这意味着被此事务通知拦截的所有方法,无论其具体名称是什么,都将使用相同的事务属性。
propagation="REQUIRED" 的含义
propagation="REQUIRED" 是最常用的事务传播行为。它的语义是:
- 如果当前已经存在一个事务,则加入该事务。
- 如果当前没有事务,则创建一个新事务。
这种传播行为适用于大多数业务方法——它们要么在调用者的事务中执行,要么自行启动一个新事务。
"全量事务"策略的利弊分析
CAS 5.3 采用的"所有方法都使用 REQUIRED 传播行为"的策略,是一种简单粗暴但有效的方式:
优势:
- 配置简单,不易出错。
- 确保所有被拦截的方法都在事务中执行,避免了"忘记加事务"的问题。
- 对于 Ticket Registry 等关键操作,事务的完整性得到了保障。
劣势:
- 查询方法也被包裹在事务中,带来了不必要的事务开销。
- 只读查询无法利用数据库的只读优化(如 MySQL 的 InnoDB 引擎对只读事务的优化)。
- 事务范围过大可能导致数据库锁的持有时间延长,影响并发性能。
这些劣势在 CAS 6.6 中通过精细化事务配置得到了解决。
3.2 aop:config 切面配置
事务通知定义了"如何管理事务",而 <aop:config> 则定义了"对哪些方法管理事务"。两者结合,构成了完整的 AOP 事务管理方案。
在 CAS 5.3 的配置中:
xml
<!-- 教学示例:aop:config 切面配置 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut-ref="ticketRegistryPointcut" />
</aop:config>
<aop:pointcut id="ticketRegistryPointcut"
expression="execution(* org.apereo.cas.ticket.registry..*.*(..))" />1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
aop:advisor 元素
<aop:advisor> 将事务通知(advice-ref="txAdvice")与切点(pointcut-ref="ticketRegistryPointcut")关联起来。当被切点表达式匹配的方法被调用时,Spring AOP 会自动应用事务通知中定义的事务属性。
aop:pointcut 元素
<aop:pointcut> 定义了一个命名的切点,可以在多个 <aop:advisor> 中复用。id="ticketRegistryPointcut" 为这个切点指定了一个唯一的标识符。
3.3 切点表达式解析
切点表达式是 AOP 的核心概念之一。CAS 5.3 中使用的切点表达式值得深入分析:
execution(* org.apereo.cas.ticket.registry..*.*(..))1
这个表达式由以下几个部分组成:
execution(...)
execution 是最常用的切点指示符(Pointcut Designator),用于匹配方法的执行。
*第一个 (返回类型)
第一个 * 表示匹配任意返回类型。无论是 void、Ticket、Collection<Ticket> 还是其他任何返回类型,都会被匹配。
org.apereo.cas.ticket.registry..*(类匹配)
org.apereo.cas.ticket.registry..* 表示匹配 org.apereo.cas.ticket.registry 包及其所有子包下的所有类。其中 .. 是一个通配符,表示"当前包及所有子孙包"。因此,以下包中的类都会被匹配:
org.apereo.cas.ticket.registryorg.apereo.cas.ticket.registry.supportorg.apereo.cas.ticket.registry.implorg.apereo.cas.ticket.registry.custom(如果存在)
*第二个 (方法名匹配)
第二个 * 表示匹配任意方法名。
(..)(参数匹配)
(..) 表示匹配任意参数列表。无论方法是否有参数、有多少参数、参数类型是什么,都会被匹配。
综合理解
整个表达式的含义是:匹配 org.apereo.cas.ticket.registry 包及其子包下所有类的所有方法。这是一个非常宽泛的切点,它将事务管理应用到 Ticket Registry 的所有操作上。
3.4 为什么对 Ticket Registry 包做事务切面
CAS 的 Ticket Registry 是整个认证系统的核心组件之一。它负责管理所有类型的 Ticket——包括 Ticket Granting Ticket(TGT)、Service Ticket(ST)、Proxy Ticket(PT)等。每个 Ticket 的创建、查询、更新、删除操作都通过 Ticket Registry 来完成。
Ticket 操作的事务性需求
Ticket 的操作具有强事务性需求,原因如下:
TGT 创建的原子性:当用户通过认证后,CAS 需要创建一个 TGT。这个操作可能涉及多个数据库写入——插入 TGT 记录、更新用户最后登录时间、记录审计日志等。这些操作必须在同一个事务中完成,以确保数据一致性。
Ticket 清理的原子性:CAS 会定期清理过期的 Ticket。清理操作可能涉及批量删除 TGT、ST 等多种类型的 Ticket。如果清理过程中出现异常,需要回滚所有已执行的删除操作,避免部分清理导致的数据不一致。
并发安全:在高并发场景下,多个请求可能同时操作同一个 TGT(例如,用户在多个浏览器中同时登录)。事务的隔离级别可以确保并发操作的正确性。
故障恢复:如果 CAS 实例在 Ticket 操作过程中崩溃,事务可以确保数据库不会处于不一致的状态。未提交的事务会在数据库服务器重启后自动回滚。
为什么使用 AOP 而非 @Transactional
既然 @Transactional 注解也可以实现声明式事务管理,为什么 CAS 5.3 还要使用 AOP 配置来管理 Ticket Registry 的事务呢?
核心原因是:Ticket Registry 的实现类是 CAS 内部代码,开发者无法直接修改这些类来添加 @Transactional 注解。
CAS 的 Ticket Registry 接口(org.apereo.cas.ticket.registry.TicketRegistry)及其实现类(如 JpaTicketRegistry、DefaultTicketRegistry 等)是 CAS 框架的一部分,位于 CAS 的 JAR 包中。开发者无法在这些类上添加注解。
通过 Spring AOP,开发者可以在不修改 CAS 源代码的情况下,对外部类的所有方法"注入"事务管理能力。这正是 AOP(面向切面编程)的核心价值——将横切关注点(如事务管理)从业务逻辑中分离出来,以声明式的方式应用到目标对象上。
AOP 事务的局限性
需要注意的是,Spring AOP 默认使用基于代理的 AOP,它只能拦截通过代理对象调用的方法。以下场景中的方法调用不会被 AOP 拦截:
- 类内部的方法自调用(
this.method())。 - 通过
new关键字直接创建的对象的方法调用。 final方法或final类的方法调用。
在 CAS 的 Ticket Registry 场景中,这些局限性通常不会造成问题,因为 Ticket Registry 的方法调用都是通过 Spring 容器管理的代理对象进行的。
3.5 AOP 事务的执行链路分析
为了更深入地理解 CAS 5.3 中 AOP 事务的工作机制,我们来分析一个典型的 Ticket 创建操作的执行链路。
当用户提交认证请求时,CAS 的处理流程大致如下:
- HTTP 请求到达 CAS Servlet。
- CAS 的认证处理器(AuthenticationHandler)验证用户凭证。
- 认证成功后,CAS 的 Ticket 工厂创建 TGT。
- TGT 被存储到 Ticket Registry 中。
- CAS 生成 ST 并存储到 Ticket Registry 中。
- CAS 返回认证响应。
在这个流程中,步骤 4 和步骤 5 涉及 Ticket Registry 的写入操作。由于我们配置了 AOP 事务切面,这些操作的执行链路如下:
客户端请求
↓
CAS Servlet
↓
AuthenticationHandler.authenticate()
↓
TicketFactory.create(TicketGrantingTicket.class)
↓
[Spring AOP 代理拦截]
↓
TransactionInterceptor.invoke()
↓
├── 获取数据库连接
├── 关闭自动提交
├── 绑定连接到当前线程
↓
TicketRegistry.addTicket(tgt) // 实际的 Ticket 存储操作
↓
├── [如果成功] TransactionInterceptor.commit()
│ └── connection.commit()
└── [如果异常] TransactionInterceptor.rollback()
└── connection.rollback()
↓
释放连接(归还到连接池)
↓
返回认证响应1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在这个执行链路中,TransactionInterceptor 是 Spring 事务管理的核心拦截器。它在目标方法执行前开启事务,在目标方法执行后根据执行结果提交或回滚事务。整个过程中,开发者无需编写任何事务管理代码——这就是声明式事务管理的魅力。
四、事务管理精细化(CAS 6.6)
4.1 精细化事务通知设计
从 CAS 5.3 到 CAS 6.6,事务管理策略发生了显著的变化。最核心的改变是从"全量事务"转向"精细化事务"。
CAS 5.3 的事务通知配置中,所有方法都使用 propagation="REQUIRED",这意味着即使是纯查询方法也会被包裹在事务中。CAS 6.6 对此进行了精细化改进:
xml
<!-- 教学示例:精细化事务通知配置 -->
<tx:advice id="txAdvice" transaction-manager="ticketTransactionManager">
<tx:attributes>
<!-- 写操作:使用 REQUIRED 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<!-- 读操作:使用 SUPPORTS 传播行为,标记为只读 -->
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
写操作的事务配置
CAS 6.6 将写操作细分为六类方法名模式:
save*:保存操作,通常对应 INSERT 语句。update*:更新操作,通常对应 UPDATE 语句。delete*:删除操作,通常对应 DELETE 语句。add*:添加操作,语义与save*类似,通常用于集合型操作。remove*:移除操作,语义与delete*类似,通常用于集合型操作。create*:创建操作,通常对应 INSERT 语句,语义上强调对象的创建。
这六类方法统一使用 propagation="REQUIRED",确保写操作在事务中执行。如果调用链中已经存在事务,则加入现有事务;否则创建新事务。
读操作的事务配置
除了上述六类写操作外,其他所有方法(由 name="*" 通配符匹配)使用 propagation="SUPPORTS" 和 read-only="true"。
propagation="SUPPORTS" 的语义是:
- 如果当前已经存在一个事务,则加入该事务。
- 如果当前没有事务,则以非事务方式执行。
read-only="true" 向数据库驱动和连接池发出信号,表明当前事务只包含读操作。这个标志可以触发以下优化:
- MySQL InnoDB:只读事务不需要维护 undo log 的数据版本链,可以跳过一些锁检查,减少内存开销。
- PostgreSQL:只读事务不会阻塞其他事务的写操作。
- Oracle:只读事务可以利用读一致性快照,避免不必要的块重做。
精细化配置的注意事项
这种基于方法名模式的事务配置有一个重要的前提:方法命名必须遵循约定的命名规范。如果某个写操作的方法名不符合上述六种模式(例如命名为 processOrder、handleRequest),它将被 name="*" 匹配,以只读方式执行,可能导致数据不一致。
因此,在采用这种配置方式的项目中,团队需要建立严格的方法命名规范,并通过代码审查确保所有写操作的方法名都以 save、update、delete、add、remove 或 create 开头。
4.2 MapperScannerConfigurer 精准扫描
CAS 6.6 对 MapperScannerConfigurer 的配置进行了收窄:
xml
<!-- 教学示例:精准 Mapper 扫描配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cc.bima.cas.dao" />
<property name="annotationClass" value="org.apache.ibatis.annotations.Mapper" />
</bean>1
2
3
4
5
6
2
3
4
5
6
相比 CAS 5.3 的配置,CAS 6.6 做了两个关键改进:
basePackage 从 cc.bima.cas 收窄为 cc.bima.cas.dao
CAS 5.3 扫描整个 cc.bima.cas 包,而 CAS 6.6 将扫描范围精确到 cc.bima.cas.dao 子包。这个改进的意义在于:
- 减少扫描范围:只扫描 DAO 层的包,避免扫描 Service 层、Controller 层、Model 层等无关包。这可以显著缩短 Spring 的启动时间,特别是在项目规模较大时。
- 明确职责边界:通过包名约定明确 Mapper 接口的归属,避免将 Mapper 接口散落在不同的包中。
- 降低误识别风险:缩小扫描范围可以减少非 Mapper 接口被误识别的概率。
新增 annotationClass 属性
annotationClass 属性指定了 Mapper 接口必须标注的注解类型。设置为 org.apache.ibatis.annotations.Mapper 后,只有标注了 @Mapper 注解的接口才会被注册为 Mapper Bean。
这个改进提供了双重过滤机制:
- 首先,接口必须位于
cc.bima.cas.dao包或其子包下。 - 其次,接口必须标注
@Mapper注解。
只有同时满足这两个条件的接口才会被 MyBatis-Spring 注册为 Mapper Bean。这种双重过滤机制进一步降低了误识别的风险,同时也为 Mapper 接口的识别提供了更明确的标记。
@Mapper 注解的使用
在 CAS 6.6 的项目中,每个 Mapper 接口都需要标注 @Mapper 注解:
java
// 教学示例:@Mapper 注解使用
package cc.bima.cas.dao;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
// Mapper 方法定义...
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
4.3 事务管理器命名规范化
CAS 6.6 将事务管理器的 Bean 名称从 transactionManager 改为 ticketTransactionManager:
xml
<!-- CAS 5.3 -->
<bean id="transactionManager" ...>
<!-- CAS 6.6 -->
<bean id="ticketTransactionManager" ...>1
2
3
4
5
2
3
4
5
命名变更的原因
这个看似简单的命名变更,实际上反映了 CAS 内部架构的一个重要变化。从 CAS 6.x 开始,CAS 内部引入了多个事务管理器,分别用于不同的功能模块:
ticketTransactionManager:用于 Ticket Registry 的事务管理。- CAS 内部可能还有其他事务管理器,用于不同的数据访问场景。
将事务管理器命名为 ticketTransactionManager,可以清晰地表明其用途——专门服务于 Ticket Registry 的事务管理。这种命名规范在多事务管理器的场景下尤为重要,可以避免 Bean 名称冲突和注入混淆。
对配置的影响
事务管理器名称的变更会影响以下配置:
<tx:advice>的transaction-manager属性。<tx:annotation-driven>的transaction-manager属性。- AOP 切面配置中的引用。
所有引用事务管理器的地方都需要同步更新为新的名称。遗漏任何一处都可能导致事务管理失效。
4.4 会话超时策略调整
CAS 6.6 对会话超时时间进行了大幅调整——从 300 秒延长至 86400 秒(24小时)。
CAS 5.3 的会话超时配置
在 CAS 5.3 中,会话超时通常设置为 300 秒(5分钟)。这个值对于传统的 Web 应用来说是合理的——用户在 5 分钟内没有操作,会话自动过期,需要重新登录。
CAS 6.6 的会话超时调整
CAS 6.6 将会话超时延长至 86400 秒(24小时),这一调整反映了 CAS 使用场景的变化:
- 移动端和 SPA 应用:现代 Web 应用(特别是单页应用和移动应用)通常采用 Token 认证机制,用户不需要频繁登录。长时间有效的会话可以提供更好的用户体验。
- SSO 场景:作为单点登录系统,CAS 的会话有效性直接影响用户在所有接入系统中的登录状态。过短的会话超时会导致用户频繁被要求重新登录,严重影响用户体验。
- 安全策略的平衡:虽然延长会话超时会增加安全风险(如会话劫持),但通过配合 HTTPS、SameSite Cookie、设备指纹验证等安全措施,可以在安全性和用户体验之间取得平衡。
对数据库连接的影响
会话超时的延长对数据库连接管理有间接影响。更长的会话超时意味着 Ticket 在数据库中的存活时间更长,Ticket 表的数据量更大。这可能导致:
- Ticket 清理操作的执行时间增加。
- Ticket 查询操作需要扫描更多的数据。
- 数据库的存储空间需求增加。
因此,在延长会话超时的同时,需要确保 Ticket 的清理策略和索引设计能够支撑更大的数据量。
4.5 从粗放到精细的设计哲学转变
从 CAS 5.3 到 CAS 6.6 的事务管理演进,体现了一种从"粗放"到"精细"的设计哲学转变。
CAS 5.3 的设计哲学:简单优先
CAS 5.3 的事务配置遵循"简单优先"的原则:
- 所有方法统一使用
REQUIRED传播行为,无需关心方法的具体用途。 - Mapper 扫描范围宽泛,减少配置工作量。
- 事务管理器使用通用名称,无需考虑命名冲突。
这种设计哲学的优势在于配置简单、不易出错,适合项目初期或小规模部署。但随着项目规模的增长,其劣势逐渐显现——性能浪费、职责不清、可维护性差。
CAS 6.6 的设计哲学:精确控制
CAS 6.6 的事务配置转向"精确控制":
- 读写操作使用不同的事务传播行为,优化性能。
- Mapper 扫描范围收窄,明确职责边界。
- 事务管理器命名规范化,支持多事务管理器共存。
这种设计哲学要求开发者对系统有更深入的理解,配置工作量也更大。但它带来了更好的性能、更清晰的架构和更强的可维护性。对于生产环境中的大规模部署,这种精细化配置是必要的。
五、Spring Boot 自动配置(CAS 7.3)
5.1 spring-common.xml 大幅简化
CAS 7.3 代表了 CAS 项目在架构层面的重大转变——从传统的 Spring XML 配置全面转向 Spring Boot 的自动配置机制。在这一版本中,spring-common.xml 的内容被大幅简化。
CAS 5.3 时代的 spring-common.xml 包含了数据源、会话工厂、Mapper 扫描、事务管理器、AOP 事务等所有配置,文件体积庞大、结构复杂。而 CAS 7.3 的 spring-common.xml 只保留了极少量的自定义配置,大部分工作交由 Spring Boot 的自动配置机制完成。
简化的核心驱动力
CAS 7.3 基于 Spring Boot 3.x 和 Spring Framework 6.x。Spring Boot 的核心理念是"约定优于配置"(Convention over Configuration),通过自动配置(Auto-Configuration)大幅减少显式配置的需求。CAS 7.3 充分利用了这一能力,将许多在旧版本中需要手动配置的组件改为由 Spring Boot 自动装配。
简化后的配置结构
CAS 7.3 的 spring-common.xml 主要保留了以下几类配置:
- 组件扫描路径的微调。
- 数据源的特殊参数配置(如果默认值不满足需求)。
- MyBatis 的特殊配置(如自定义插件、类型处理器等)。
- 业务特定的 Bean 定义。
而以下配置在 CAS 7.3 中不再需要手动声明:
DataSourceBean(由 Spring Boot 的DataSourceAutoConfiguration自动创建)。SqlSessionFactoryBean(由 MyBatis-Spring-Boot-Starter 的MybatisAutoConfiguration自动创建)。MapperScannerConfigurer(由 MyBatis-Spring-Boot-Starter 自动配置)。PlatformTransactionManagerBean(由 Spring Boot 的TransactionAutoConfiguration自动创建)。<tx:annotation-driven>(Spring Boot 自动启用)。<aop:config>事务切面(完全移除,依赖 Spring Boot 自动配置)。
5.2 组件扫描替代手动 Bean 注册
CAS 7.3 使用 Spring 的组件扫描机制替代了旧版本中的手动 Bean 注册:
xml
<!-- 教学示例:组件扫描配置 -->
<context:component-scan base-package="cc.bima.cas" />1
2
2
组件扫描的工作原理
<context:component-scan> 会扫描指定包及其子包下的所有类,自动注册标注了以下注解的类为 Spring Bean:
@Component:通用组件注解。@Service:业务逻辑层组件。@Repository:数据访问层组件。@Controller/@RestController:Web 层组件。@Configuration:配置类。
通过组件扫描,开发者不再需要在 XML 中逐一声明每个 Bean。只需在类上添加相应的注解,Spring 就会自动发现并注册它们。
与 CAS 5.3 的对比
在 CAS 5.3 中,所有的 Bean 都需要在 XML 中显式声明:
xml
<!-- CAS 5.3:手动声明每个 Bean -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">...</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">...</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">...</bean>
<bean id="transactionManager" class="...DataSourceTransactionManager">...</bean>1
2
3
4
5
2
3
4
5
而在 CAS 7.3 中,这些 Bean 由 Spring Boot 自动创建,开发者只需关注业务组件的注册。
组件扫描与自动配置的协同
需要注意的是,<context:component-scan> 和 Spring Boot 的自动配置是两个不同层次的机制:
<context:component-scan>负责扫描和注册开发者自定义的 Bean。- Spring Boot 的自动配置(
@EnableAutoConfiguration)负责根据 classpath 中的依赖和配置文件自动创建框架级别的 Bean。
两者协同工作,共同构建完整的 Spring 应用上下文。
5.3 commons-dbcp2 2.10.0 升级详解
CAS 7.3 将连接池实现从 commons-dbcp 1.4 升级到 commons-dbcp2 2.10.0。这不是一个简单的版本升级,而是涉及 API 变更、配置属性重命名和功能增强的重大变更。
dbcp 1.4 到 dbcp2 的核心变化
commons-dbcp2 是 commons-dbcp 的下一代版本,它基于 Java 7+ 构建,引入了许多改进:
- 包名变更:从
org.apache.commons.dbcp变更为org.apache.commons.dbcp2。 - 连接池实现类变更:从
BasicDataSource变更为BasicDataSource(同名但不同包)。 - 配置属性重命名:多个属性名称发生了变化,以更符合 Java Bean 命名规范。
关键属性名称变更
| dbcp 1.4 属性 | dbcp2 属性 | 说明 |
|---|---|---|
maxActive | maxTotal | 最大活动连接数 |
maxWait | maxWaitMillis | 最大等待时间(毫秒) |
initialSize | initialSize | 无变化 |
minIdle | minIdle | 无变化 |
maxIdle | maxIdle | 无变化 |
maxTotal 替代 maxActive
maxActive 在 dbcp2 中被重命名为 maxTotal。这个命名变更更加准确地表达了属性的语义——它控制的是连接池中允许的连接总数(包括活动连接和空闲连接),而非仅仅是"活动"连接数。
xml
<!-- dbcp 1.4 (CAS 5.3/6.6) -->
<property name="maxActive" value="10" />
<!-- dbcp2 (CAS 7.3) -->
<property name="maxTotal" value="10" />1
2
3
4
5
2
3
4
5
maxWaitMillis 替代 maxWait
maxWait 在 dbcp2 中被重命名为 maxWaitMillis。这个命名变更消除了歧义——明确表示单位是毫秒。在 dbcp 1.4 中,maxWait 的单位也是毫秒,但属性名没有体现这一点,容易引起混淆。
xml
<!-- dbcp 1.4 (CAS 5.3/6.6) -->
<property name="maxWait" value="5000" />
<!-- dbcp2 (CAS 7.3) -->
<property name="maxWaitMillis" value="5000" />1
2
3
4
5
2
3
4
5
dbcp2 的新增功能
除了属性重命名外,dbcp2 还引入了许多新功能:
- 连接池暂停/恢复:支持通过 JMX 暂停和恢复连接池,便于运维操作。
- 更细粒度的连接验证:支持
testOnCreate(创建时验证)、testOnBorrow(借出时验证)、testOnReturn(归还时验证)、testWhileIdle(空闲时验证)四种验证时机。 - 异步连接获取:支持异步方式获取连接,适用于非阻塞 I/O 场景。
- 更完善的 JMX 支持:提供更丰富的 JMX 属性和操作,便于监控和管理。
- 连接生命周期监听器:支持注册连接生命周期事件监听器,可以在连接创建、销毁、借出、归还等时机执行自定义逻辑。
CAS 7.3 中的 dbcp2 配置示例
xml
<!-- 教学示例:dbcp2 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
<!-- dbcp2 属性名称 -->
<property name="initialSize" value="5" />
<property name="maxTotal" value="10" />
<property name="minIdle" value="5" />
<property name="maxIdle" value="10" />
<property name="maxWaitMillis" value="5000" />
<!-- 连接验证 -->
<property name="testWhileIdle" value="true" />
<property name="validationQuery" value="select 1" />
<!-- 连接泄漏检测 -->
<property name="removeAbandonedOnBorrow" value="true" />
<property name="removeAbandonedOnMaintenance" value="true" />
<property name="removeAbandonedTimeout" value="300" />
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意 dbcp2 中连接泄漏检测的属性名称也发生了变化:
removeAbandoned被拆分为removeAbandonedOnBorrow和removeAbandonedOnMaintenance,分别控制在借出连接时和维护连接池时是否检测泄漏连接。
5.4 AOP 事务配置的完全移除
CAS 7.3 完全移除了 AOP 事务配置(<tx:advice> 和 <aop:config>)。这是一个重大的架构决策,其背后的原因是多方面的。
Spring Boot 事务自动配置的成熟
Spring Boot 的 TransactionAutoConfiguration 提供了完善的事务自动配置能力。当 classpath 中存在 Spring JDBC 或 MyBatis 等依赖时,Spring Boot 会自动:
- 创建
PlatformTransactionManagerBean。 - 启用
@Transactional注解驱动。 - 配置事务的默认属性(如隔离级别、传播行为、超时时间等)。
这意味着开发者只需要在需要事务的方法上添加 @Transactional 注解,无需任何额外的 XML 配置。Spring Boot 的自动配置已经足够满足 CAS 的事务管理需求。
AOP 事务的局限性
CAS 5.3/6.6 中使用的 AOP 事务配置虽然功能强大,但存在以下局限性:
- 配置复杂:需要理解 Spring AOP 的切点表达式语法,配置门槛较高。
- 调试困难:AOP 的"隐式"特性使得事务边界的追踪变得困难,开发者不容易判断某个方法是否被事务切面拦截。
- 与 Spring Boot 理念冲突:Spring Boot 倡导"约定优于配置"和"注解优于 XML",AOP 事务配置与这一理念相悖。
- 维护成本高:随着 CAS 版本的升级,Ticket Registry 的包结构可能发生变化,切点表达式需要同步调整。
替代方案:@Transactional 注解
移除 AOP 事务配置后,CAS 7.3 完全依赖 @Transactional 注解来管理事务。对于 CAS 内部的 Ticket Registry 操作,如果需要事务管理,可以通过以下方式实现:
- 自定义 Ticket Registry 实现:继承 CAS 的 Ticket Registry 接口,在自定义实现类的方法上添加
@Transactional注解。 - 使用 AOP 注解:通过
@Aspect注解定义切面,使用@Around注解实现事务拦截(替代 XML 方式的<aop:config>)。 - 依赖 CAS 内部的事务管理:CAS 7.3 内部已经对关键操作进行了事务管理,开发者通常不需要额外配置。
5.5 MyBatis 3.5.16 + MyBatis-Spring 3.0.3 大版本跳跃
CAS 7.3 将 MyBatis 从 3.4.x/3.5.x 早期版本升级到 3.5.16,同时将 MyBatis-Spring 从 2.x 升级到 3.0.3。这次升级涉及多个 Breaking Changes,需要开发者特别注意。
MyBatis-Spring 3.0.3 的重大变更
MyBatis-Spring 3.0.x 是一个重大版本升级,主要变更包括:
- 最低要求 Java 17:MyBatis-Spring 3.0.x 基于 Spring Framework 6.x 构建,要求 Java 17 或更高版本。
- Jakarta EE 迁移:从
javax.*命名空间迁移到jakarta.*命名空间。这影响了SqlSessionFactory的创建方式、事务管理器的配置等。 - 移除已废弃的 API:许多在 2.x 中标记为
@Deprecated的方法和类在 3.0.x 中被移除。 - 对 Spring Boot 3.x 的原生支持:
mybatis-spring-boot-starter3.0.x 与 Spring Boot 3.x 完全兼容。
MyBatis 3.5.16 的新特性
MyBatis 3.5.16 是 3.5.x 系列的较新版本,相比早期版本引入了以下改进:
- 性能优化:SQL 解析和参数映射的性能提升。
- 更好的错误信息:SQL 执行异常时提供更详细的错误信息,便于排查问题。
- 支持 Java 8 日期时间类型:原生支持
LocalDate、LocalDateTime、Instant等 Java 8 日期时间类型。 - 支持 Optional 返回类型:Mapper 方法可以返回
Optional<T>类型,避免空指针异常。 - 支持流式查询:Mapper 方法可以返回
Cursor<T>类型,支持流式处理大量数据。
对现有代码的影响
从旧版本升级到 MyBatis 3.5.16 + MyBatis-Spring 3.0.3,可能需要以下代码调整:
- import 语句更新:将
javax.sql.DataSource替换为jakarta.sql.DataSource(如果使用了 Jakarta 命名空间)。 - Mapper XML 中的类型处理器:如果使用了自定义类型处理器,需要检查其兼容性。
- Spring 配置文件:XML 配置中的命名空间可能需要更新。
- 依赖管理:
pom.xml或build.gradle中的依赖坐标需要更新。
5.6 MapperScannerConfigurer 简化
CAS 7.3 中 MapperScannerConfigurer 的配置得到了进一步简化:
xml
<!-- CAS 5.3 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cc.bima.cas" />
</bean>
<!-- CAS 6.6 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cc.bima.cas.dao" />
<property name="annotationClass" value="org.apache.ibatis.annotations.Mapper" />
</bean>
<!-- CAS 7.3 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cc.bima.cas.dao" />
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
简化的原因
CAS 7.3 中 MapperScannerConfigurer 不再需要指定 sqlSessionFactoryBeanName 和 annotationClass,原因是:
sqlSessionFactoryBeanName 不再需要:在 Spring Boot 环境下,
SqlSessionFactory通常只有一个 Bean 实例(由MybatisAutoConfiguration自动创建)。MapperScannerConfigurer可以自动发现并使用这个唯一的SqlSessionFactoryBean,无需显式指定名称。annotationClass 不再需要:在 CAS 7.3 中,
basePackage已经精确到cc.bima.cas.dao,这个包下的接口通常都是 Mapper 接口。通过包名约定已经足以区分 Mapper 接口和非 Mapper 接口,无需额外的注解过滤。
简化带来的风险
虽然配置简化了,但也带来了一些潜在风险:
- 多 SqlSessionFactory 场景:如果项目中存在多个
SqlSessionFactory(例如多数据源场景),不指定sqlSessionFactoryBeanName可能导致MapperScannerConfigurer绑定了错误的SqlSessionFactory。 - 非 Mapper 接口的误扫描:如果
cc.bima.cas.dao包下存在非 Mapper 接口(如常量接口、回调接口等),它们可能被误注册为 Mapper Bean,导致启动错误。
因此,在 CAS 7.3 中,虽然配置简化了,但开发者需要更加注意包结构的规范性和 Mapper 接口的组织方式。
5.7 Spring Boot 自动配置原理与 CAS 的融合
CAS 7.3 基于 Spring Boot 3.x 构建,深度集成了 Spring Boot 的自动配置机制。理解这一机制,有助于开发者更好地掌握 CAS 7.3 的配置方式。
Spring Boot 自动配置的核心机制
Spring Boot 的自动配置基于 @EnableAutoConfiguration 注解和 spring.factories(Spring Boot 2.x)或 org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)机制。其工作流程如下:
- 类路径扫描:Spring Boot 扫描 classpath 中的所有 JAR 包,查找包含自动配置类的 JAR 包。
- 条件评估:对于每个自动配置类,Spring Boot 评估其
@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等),判断是否需要启用该自动配置。 - Bean 注册:对于满足条件的自动配置类,Spring Boot 注册其中定义的 Bean。
- 属性绑定:自动配置类中的属性通过
@ConfigurationProperties与application.yml或application.properties中的配置项绑定。
MyBatis-Spring-Boot-Starter 的自动配置
mybatis-spring-boot-starter 提供了以下自动配置类:
- MybatisAutoConfiguration:自动创建
SqlSessionFactory和SqlSessionTemplateBean。 - MybatisPlusAutoConfiguration(如果使用 MyBatis-Plus):自动创建 MyBatis-Plus 的扩展 Bean。
- MapperScannerRegistrarNotFoundConfiguration:当没有找到
@MapperScan注解时,提供默认的 Mapper 扫描配置。
CAS 7.3 中的自动配置覆盖
CAS 7.3 作为 Spring Boot 应用,会自动加载所有相关的自动配置。但对于某些需要自定义的配置,CAS 7.3 通过以下方式进行覆盖:
- application.yml 中的属性配置:通过
spring.datasource.*、mybatis.*等配置前缀覆盖自动配置的默认值。 - 自定义 Configuration 类:通过
@Configuration类提供自定义的 Bean 定义,覆盖自动配置的 Bean。 - spring-common.xml 中的 Bean 定义:通过 XML 配置提供自定义的 Bean 定义。
自动配置的调试
Spring Boot 提供了 --debug 启动选项,可以在日志中输出自动配置的报告。报告中会列出所有自动配置类的评估结果,包括哪些被启用了、哪些被跳过了以及跳过的原因。这对于排查 CAS 7.3 中的配置问题非常有帮助。
六、MyBatis Generator 代码生成
6.1 generatorConfig.xml 配置解析
MyBatis Generator(MBG)是 MyBatis 官方提供的代码生成工具,可以根据数据库表结构自动生成实体类(Model)、Mapper 接口和 Mapper XML 文件。在 CAS 5.3 和 CAS 6.6 项目中,MBG 被广泛用于加速开发过程。
MBG 的核心配置文件是 generatorConfig.xml,它定义了代码生成的所有参数。下面我们通过一个教学用途的简化配置来解析其核心结构:
xml
<!-- 教学示例:generatorConfig.xml 核心结构 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="casContext" targetRuntime="MyBatis3">
<!-- 数据库连接配置 -->
<jdbcConnection driverClass="${db.driver}"
connectionURL="${db.url}"
userId="${db.username}"
password="${db.password}" />
<!-- Java 类型解析器 -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- Java 模型生成器 -->
<javaModelGenerator targetPackage="cc.bima.cas.model"
targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL 映射文件生成器 -->
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper 接口生成器 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cc.bima.cas.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 数据库表配置 -->
<table tableName="user_info" domainObjectName="UserInfo" />
</context>
</generatorConfiguration>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
context 元素
<context> 是 MBG 配置的核心容器。id 属性为上下文指定唯一标识符,targetRuntime 属性指定代码生成的目标运行时。
jdbcConnection 元素
<jdbcConnection> 配置数据库连接信息,MBG 通过这个连接读取数据库的表结构元数据(列名、列类型、主键、索引等),然后根据这些元数据生成代码。
javaTypeResolver 元素
<javaTypeResolver> 负责将数据库列类型映射为 Java 类型。forceBigDecimals 属性控制是否将所有数值类型都映射为 BigDecimal。设置为 false 时,MBG 会根据列的精度和范围选择更合适的 Java 类型(如 Integer、Long、Float、Double 等)。
javaModelGenerator 元素
<javaModelGenerator> 控制实体类的生成。targetPackage 指定实体类的包名,targetProject 指定源代码目录。trimStrings 属性控制是否对字符串类型的 setter 方法进行 trim 处理。
sqlMapGenerator 元素
<sqlMapGenerator> 控制 Mapper XML 文件的生成。targetPackage 指定 XML 文件的包路径(对应资源目录下的路径)。
javaClientGenerator 元素
<javaClientGenerator> 控制 Mapper 接口的生成。type 属性指定 Mapper 接口的类型,XMLMAPPER 表示生成纯接口(SQL 全部在 XML 文件中定义)。
table 元素
<table> 指定需要生成代码的数据库表。tableName 是数据库表名,domainObjectName 是生成的实体类名称。
6.2 MyBatis3 targetRuntime 详解
MBG 的 targetRuntime 属性决定了生成代码的风格和功能集。MyBatis3 是最常用的 targetRuntime,它生成的代码具有以下特点:
生成的实体类
MBG 会为每个表生成一个实体类(如 UserInfo)和一个条件查询类(如 UserInfoExample)。实体类包含表的所有列对应的属性,以及对应的 getter/setter 方法。条件查询类提供了类型安全的方式来构建 WHERE 条件。
生成的 Mapper 接口
Mapper 接口包含以下方法(以 user_info 表为例):
countByExample(UserInfoExample example):按条件统计记录数。deleteByExample(UserInfoExample example):按条件删除记录。deleteByPrimaryKey(Long id):按主键删除记录。insert(UserInfo record):插入一条记录(所有字段)。insertSelective(UserInfo record):选择性插入记录(只插入非 null 字段)。selectByExample(UserInfoExample example):按条件查询记录列表。selectByPrimaryKey(Long id):按主键查询记录。updateByExampleSelective(...):按条件选择性更新记录。updateByExample(...):按条件更新记录。updateByPrimaryKeySelective(UserInfo record):按主键选择性更新记录。updateByPrimaryKey(UserInfo record):按主键更新记录。
Select vs Selective 的区别
MBG 生成的每个操作都有两个版本——完整版和 Selective 版本:
- 完整版(如
insert、updateByPrimaryKey):操作所有字段,即使某些字段的值为 null,也会在 SQL 中包含这些字段(设置为 NULL)。 - Selective 版本(如
insertSelective、updateByPrimaryKeySelective):只操作非 null 字段,null 字段不会出现在 SQL 中。
Selective 版本在实际开发中更为常用,因为它可以保留数据库中已有的值(不会被 null 覆盖),同时减少 SQL 的长度和数据传输量。
6.3 user_info 表到 UserInfo 实体类的映射
MBG 根据数据库表结构自动生成实体类。以 user_info 表为例,MBG 生成的 UserInfo 实体类大致如下:
java
// 教学示例:MBG 生成的实体类结构(简化版)
public class UserInfo {
private Long id;
private String uuid;
private String name;
private String password;
private String passwordSalt;
private String nickname;
private String telphone;
private String email;
private Date timeCreate;
private Date timeUpdate;
private Long adminId;
private String adminName;
private Integer isUsed;
private String remark;
// getter 和 setter 方法...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
命名映射规则
MBG 默认使用"下划线转驼峰"的命名策略将数据库列名映射为 Java 属性名:
user_info→UserInfo(表名到类名)id→id(无下划线,保持不变)uuid→uuidpassword_salt→passwordSalttime_create→timeCreateadmin_id→adminId
类型映射规则
MBG 的默认类型映射规则如下:
| 数据库类型 | Java 类型 |
|---|---|
bigint | Long |
varchar | String |
int / integer | Integer |
datetime / timestamp | java.util.Date |
decimal / numeric | BigDecimal(取决于 forceBigDecimals 设置) |
tinyint | Integer 或 Boolean |
text | String |
6.4 XMLMAPPER 类型分析
<javaClientGenerator type="XMLMAPPER" /> 中的 XMLMAPPER 是 MBG 支持的 Mapper 接口类型之一。MBG 支持以下几种 Mapper 接口类型:
XMLMAPPER
生成纯接口,所有 SQL 语句都在对应的 Mapper XML 文件中定义。这是最常用的类型,也是 CAS 项目中使用的类型。其优势在于:
- SQL 与 Java 代码完全分离,便于 DBA 审查和优化 SQL。
- 可以充分利用 MyBatis 的动态 SQL 功能。
- SQL 的修改不需要重新编译 Java 代码。
ANNOTATEDMAPPER
生成带有注解的接口,SQL 语句通过 @Select、@Insert、@Update、@Delete 等注解直接定义在 Mapper 接口中。其优势在于:
- 不需要额外的 XML 文件,配置更紧凑。
- SQL 与接口方法在同一处,便于阅读和理解。
劣势在于:
- 复杂 SQL 的可读性较差(特别是多行 SQL)。
- 动态 SQL 的注解方式不如 XML 方式灵活。
- SQL 的修改需要重新编译 Java 代码。
MIXEDMAPPER
混合模式,简单的 SQL 使用注解,复杂的 SQL 使用 XML。这种模式在实际项目中较少使用,因为混合方式增加了维护的复杂度。
为什么选择 XMLMAPPER
在 CAS 项目中选择 XMLMAPPER 的原因是:
- CAS 的认证相关 SQL 通常较为复杂,需要使用动态 SQL。
- SQL 的可维护性要求较高,DBA 需要能够直接审查和优化 SQL。
- 与 CAS 5.3/6.6 的现有代码风格保持一致。
6.5 代码生成最佳实践
增量生成策略
MBG 默认会覆盖已生成的文件。在实际项目中,建议采用增量生成策略:
- 将 MBG 生成的代码放在独立的包或目录中,与手写代码分离。
- 使用 MBG 的
overwrite属性控制是否覆盖已有文件。 - 对于需要自定义的方法,在生成的 Mapper 接口之外创建扩展接口(使用 MyBatis 的 Mapper 继承机制)。
生成代码的审查
MBG 生成的代码虽然可以正常工作,但不一定是最优的。建议在生成后进行以下审查:
- SQL 性能审查:检查生成的 SQL 是否使用了合适的索引。
- 命名规范审查:检查生成的类名、方法名、属性名是否符合项目规范。
- 类型映射审查:检查数据库类型到 Java 类型的映射是否正确。
- 条件查询类审查:检查
Example类的生成是否符合预期。
MBG 与版本控制的协作
在团队协作中,MBG 生成的代码应该纳入版本控制。但需要注意:
- 不要在每次构建时都运行 MBG,避免不必要的代码变更。
- 将 MBG 的运行作为独立的构建步骤,由开发者手动触发。
- 在代码审查时,区分 MBG 生成的代码和手写的代码。
6.6 版本差异:CAS 7.3 中 Generator 的移除
在 CAS 7.3 项目中,generatorConfig.xml 被移除。这一决策的原因是多方面的:
通用封装模式的成熟:CAS 7.3 项目中已经建立了完善的通用 Service/Mapper 封装模式(BaseMapper、BaseService 等),MBG 生成的代码与这套封装模式存在冲突。MBG 生成的 Mapper 接口包含大量 CRUD 方法,而通用封装已经提供了这些方法,导致方法重复。
代码生成策略的转变:随着项目架构的成熟,实体类和 Mapper 接口的设计更加注重业务语义和通用性,而非简单地映射数据库表结构。手动编写(或使用自定义模板生成)的代码更能体现业务需求。
维护成本的考量:MBG 的配置文件本身也需要维护。当数据库表结构发生变化时,需要同步更新 MBG 配置并重新生成代码。在通用封装模式下,表结构的变化只需要修改对应的 Mapper XML 和实体类,维护成本更低。
Spring Boot 生态的影响:Spring Boot 生态中出现了许多替代 MBG 的代码生成方案(如 MyBatis-Plus 的代码生成器、MapStruct 等),这些方案与 Spring Boot 的集成更加紧密。
七、通用 Service/Mapper 封装模式
7.1 BaseMapper 泛型接口设计
在 CAS 项目中,随着业务模块的增加,每个模块的 Mapper 接口都包含大量相似的 CRUD 方法。为了避免代码重复、统一操作规范,CAS 项目设计了一套泛型封装模式。
BaseMapper<T, Q extends PageCustom> 是所有业务 Mapper 接口的父接口,它定义了通用的数据访问方法:
java
// 教学示例:BaseMapper 泛型接口结构
public interface BaseMapper<T, Q extends PageCustom> {
// 插入操作
int insertSelective(T record);
// 删除操作
int deleteByPrimaryKey(Object id);
// 查询操作
T selectByPrimaryKey(Object id);
List<T> selectBy(T query);
int countBy(T query);
T uniqueBy(T query);
// 更新操作
int updateByPrimaryKeySelective(T record);
int updateByPrimaryKey(T record);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
泛型参数说明
T:实体类型,如UserInfo、TicketRecord等。Q extends PageCustom:分页查询参数类型,必须继承PageCustom。
方法设计理念
BaseMapper 的方法设计遵循以下原则:
Selective 优先:
insertSelective和updateByPrimaryKeySelective是推荐使用的方法,它们只操作非 null 字段,避免覆盖已有数据。按主键操作:
deleteByPrimaryKey、selectByPrimaryKey、updateByPrimaryKey、updateByPrimaryKeySelective提供了基于主键的 CRUD 操作,这是最基本也最常用的操作模式。条件查询:
selectBy、countBy、uniqueBy提供了基于实体属性的条件查询。传入的实体对象中,非 null 属性将被作为 WHERE 条件。selectBy返回列表,countBy返回计数,uniqueBy返回唯一结果(如果结果不唯一将抛出异常)。
BaseMapper 与 MyBatis 的集成
BaseMapper 本身只是一个 Java 接口,它不包含任何 SQL 定义。SQL 定义在对应的 Mapper XML 文件中。MyBatis-Spring 会自动将 BaseMapper 的方法与 Mapper XML 中的 SQL 语句关联起来。
对于 BaseMapper 中定义的通用方法,通常在一个公共的 Mapper XML 文件中定义通用的 SQL 片段,然后在各模块的 Mapper XML 中引用这些片段。这种方式既保证了通用性,又允许各模块进行自定义。
7.2 BaseService 抽象类设计
BaseService<M extends BaseMapper<T, Q>, T, Q extends PageCustom> 是所有业务 Service 类的父类,它封装了通用的业务逻辑:
java
// 教学示例:BaseService 抽象类结构
public abstract class BaseService<M extends BaseMapper<T, Q>, T, Q extends PageCustom> {
@Autowired
protected M mapper;
public int insertSelective(T record) {
return mapper.insertSelective(record);
}
public int deleteByPrimaryKey(Object id) {
return mapper.deleteByPrimaryKey(id);
}
public T selectByPrimaryKey(Object id) {
return mapper.selectByPrimaryKey(id);
}
public int updateByPrimaryKeySelective(T record) {
return mapper.updateByPrimaryKeySelective(record);
}
public int updateByPrimaryKey(T record) {
return mapper.updateByPrimaryKey(record);
}
public List<T> selectBy(T query) {
return mapper.selectBy(query);
}
public int countBy(T query) {
return mapper.countBy(query);
}
public T uniqueBy(T query) {
return mapper.uniqueBy(query);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
泛型参数说明
M:Mapper 接口类型,必须继承BaseMapper<T, Q>。T:实体类型。Q:分页查询参数类型。
三层泛型嵌套的设计考量
BaseService 的三层泛型参数设计看起来复杂,但它提供了极强的类型安全性:
M限制了 Mapper 接口的类型,确保BaseService只能注入正确的 Mapper。T确保所有方法操作的是正确的实体类型。Q确保分页查询参数的类型安全。
通过这种泛型设计,业务 Service 类可以非常简洁:
java
// 教学示例:业务 Service 类
@Service
public class UserInfoService extends BaseService<UserInfoMapper, UserInfo, UserInfoQuery> {
// 继承了所有通用 CRUD 方法
// 只需添加业务特定的方法
public UserInfo findByName(String name) {
UserInfo query = new UserInfo();
query.setName(name);
return uniqueBy(query);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
BaseService 的优势
- 代码复用:所有模块的 Service 类共享相同的 CRUD 方法实现,避免了大量重复代码。
- 统一接口:所有 Service 类对外提供一致的 CRUD 方法签名,降低了学习成本。
- 类型安全:泛型确保了编译期的类型检查,避免了运行时的类型转换错误。
- 可扩展性:业务 Service 可以在继承通用方法的基础上,添加业务特定的方法。
BaseService 的劣势
- 继承耦合:所有 Service 类必须继承
BaseService,这限制了 Service 类的继承层次(Java 不支持多继承)。 - 泛型复杂度:三层泛型参数增加了代码的理解难度,特别是对于不熟悉泛型的开发者。
- 方法爆炸:
BaseService暴露了所有通用方法,某些业务 Service 可能不需要全部方法(如只读 Service 不需要 insert/update/delete 方法)。
7.3 PageCustom 分页参数模型
PageCustom 是分页查询的参数基类,它封装了分页查询的通用参数:
java
// 教学示例:PageCustom 分页参数模型
public class PageCustom {
/** 查询字段(逗号分隔),用于控制 SELECT 的列 */
private String fields;
/** 排序表达式(如 "time_create DESC, name ASC") */
private String order;
/** 是否分页 */
private Boolean isPage;
/** 当前页码(从 1 开始) */
private Integer page;
/** 每页记录数 */
private Integer pageSize;
// getter 和 setter 方法...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fields 属性
fields 属性允许客户端指定查询返回的字段列表。例如,fields="id,name,nickname" 表示只查询这三个字段。这种设计可以减少数据传输量,提高查询性能。
在 Mapper XML 中,fields 参数通常通过 MyBatis 的 <if> 标签来实现动态列选择:
xml
<!-- 教学示例:动态列选择 -->
<select id="selectByPage" resultType="UserInfo">
SELECT
<choose>
<when test="query != null and query.fields != null and query.fields != ''">
${query.fields}
</when>
<otherwise>
*
</otherwise>
</choose>
FROM user_info
...
</select>1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
order 属性
order 属性允许客户端指定排序方式。例如,order="time_create DESC" 表示按创建时间降序排列。
安全警告:fields 和 order 属性使用了 ${} 而非 #{} 进行参数替换。${} 是字符串直接替换,存在 SQL 注入风险。在实际使用中,必须对这两个参数进行严格的白名单校验,只允许包含合法的列名和排序方向。
isPage 属性
isPage 属性控制是否进行分页。当设置为 false 时,查询返回所有符合条件的记录(不分页)。这在导出数据、统计报表等场景中非常有用。
page 和 pageSize 属性
page 指定当前页码(从 1 开始),pageSize 指定每页记录数。这两个属性配合使用,通过 SQL 的 LIMIT offset, size(MySQL)或 OFFSET offset FETCH NEXT size ROWS ONLY(SQL Server)来实现分页查询。
7.4 PageBean 分页结果封装
PageBean<T> 是分页查询的结果封装类,它包含了分页数据和分页信息:
java
// 教学示例:PageBean 分页结果封装
public class PageBean<T> {
/** 当前页的数据列表 */
private List<T> list;
/** 当前页码 */
private Integer page;
/** 每页记录数 */
private Integer pageSize;
/** 总记录数 */
private Long count;
// getter 和 setter 方法...
/** 计算总页数 */
public int getTotalPages() {
if (pageSize == null || pageSize <= 0) return 0;
return (int) Math.ceil((double) count / pageSize);
}
/** 是否有下一页 */
public boolean hasNextPage() {
return page < getTotalPages();
}
/** 是否有上一页 */
public boolean hasPreviousPage() {
return page > 1;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
PageBean 的使用方式
PageBean 通常由 Service 层的方法返回:
java
// 教学示例:分页查询方法
public PageBean<UserInfo> queryPage(UserInfoQuery query) {
PageBean<UserInfo> pageBean = new PageBean<>();
pageBean.setPage(query.getPage());
pageBean.setPageSize(query.getPageSize());
// 查询总记录数
long count = mapper.countByCondition(query);
pageBean.setCount(count);
// 查询当前页数据
if (query.getIsPage() && count > 0) {
int offset = (query.getPage() - 1) * query.getPageSize();
List<UserInfo> list = mapper.selectByCondition(query, offset, query.getPageSize());
pageBean.setList(list);
} else {
pageBean.setList(Collections.emptyList());
}
return pageBean;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
两次查询的分页模式
PageBean 的实现采用了"两次查询"的分页模式:
- 第一次查询:执行
COUNT查询获取总记录数。 - 第二次查询:执行
SELECT查询获取当前页的数据。
这种模式虽然需要两次数据库查询,但它的优势在于:
- 总记录数可以用于计算总页数,前端可以据此渲染分页控件。
- 不依赖于数据库特定的分页语法(如 MySQL 的
SQL_CALC_FOUND_ROWS)。 - 在大多数场景下,COUNT 查询的开销远小于数据查询。
7.5 BaseResult 统一返回封装
BaseResult<T> 是统一的 API 返回结果封装类,它为所有 API 响应提供了一致的结构:
java
// 教学示例:BaseResult 统一返回封装
public class BaseResult<T> {
/** 状态码 */
private int code;
/** 提示信息 */
private String message;
/** 返回数据 */
private T data;
// getter 和 setter 方法...
/** 成功(带数据) */
public static <T> BaseResult<T> success(T data) {
BaseResult<T> result = new BaseResult<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
/** 成功(无数据) */
public static <T> BaseResult<T> success() {
return success(null);
}
/** 失败 */
public static <T> BaseResult<T> fail(String message) {
BaseResult<T> result = new BaseResult<>();
result.setCode(500);
result.setMessage(message);
return result;
}
/** 失败(带状态码) */
public static <T> BaseResult<T> fail(int code, String message) {
BaseResult<T> result = new BaseResult<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BaseResult 的设计理念
BaseResult 的设计遵循以下原则:
- 统一结构:所有 API 响应都使用相同的 JSON 结构,前端可以统一处理。
- 泛型支持:
data字段使用泛型,可以承载任意类型的返回数据。 - 工厂方法:通过静态工厂方法(
success、fail)创建实例,确保返回结果的格式一致。 - 状态码标准化:使用 HTTP 状态码作为业务状态码,便于理解和对接。
BaseResult 的使用示例
java
// 教学示例:BaseResult 使用方式
@RestController
@RequestMapping("/api/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@GetMapping("/{id}")
public BaseResult<UserInfo> getUser(@PathVariable Long id) {
UserInfo user = userInfoService.selectByPrimaryKey(id);
if (user == null) {
return BaseResult.fail("用户不存在");
}
return BaseResult.success(user);
}
@PostMapping
public BaseResult<Void> createUser(@RequestBody UserInfo userInfo) {
int rows = userInfoService.insertSelective(userInfo);
if (rows > 0) {
return BaseResult.success();
}
return BaseResult.fail("创建用户失败");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
7.6 泛型封装的设计考量与权衡
CAS 项目中的泛型封装模式(BaseMapper、BaseService、PageCustom、PageBean、BaseResult)构成了一个完整的分层架构。这种架构的设计涉及多方面的考量与权衡。
泛型 vs 组合
Java 的泛型提供了编译期的类型安全,但泛型的嵌套层次过深会增加代码的理解难度。CAS 项目选择了三层泛型嵌套(BaseService<M, T, Q>),这在类型安全性和代码可读性之间取得了平衡。
另一种可选的方案是使用组合而非继承——即 BaseService 不使用泛型,而是通过构造函数注入 Mapper 和实体类的 Class 对象。这种方案避免了泛型的复杂性,但牺牲了编译期的类型检查。
约定 vs 配置
BaseMapper 中的方法名(如 selectBy、countBy、uniqueBy)是一种约定。这些方法名在 Mapper XML 中需要对应相同 ID 的 SQL 语句。这种约定减少了配置的工作量,但增加了命名规范的重要性——如果 Mapper XML 中的 SQL ID 与接口方法名不一致,运行时将抛出异常。
通用 vs 专用
BaseMapper 和 BaseService 提供的是通用的 CRUD 操作。但在实际业务中,某些模块可能需要特殊的操作(如批量插入、级联查询、聚合统计等)。这些特殊操作应该在业务 Mapper/Service 中单独定义,而非强行塞入通用封装中。
封装层次的选择
CAS 项目选择了"Mapper 接口 + BaseService 抽象类"的两层封装。另一种可选的方案是使用 MyBatis-Plus 等增强框架,它们提供了更丰富的通用 CRUD 功能(如 IService<T>、BaseMapper<T> 等)。但 CAS 项目选择了自研封装,原因是:
- 更轻量级,不引入额外的框架依赖。
- 更灵活,可以根据 CAS 的特殊需求进行定制。
- 更可控,不受第三方框架版本升级的影响。
八、数据库设计
8.1 user_center 数据库规划
在 CAS 项目中,认证相关的业务数据通常存储在独立的数据库中。user_center 是用户中心数据库,它承载了用户信息管理、角色权限管理等核心业务数据。
独立数据库的优势
将用户中心数据存储在独立数据库中(而非与 CAS 内部的 Ticket 数据混在同一个数据库中),有以下优势:
- 职责分离:用户中心数据与 CAS Ticket 数据的生命周期和管理需求不同,独立数据库可以分别进行优化和运维。
- 安全性:用户中心数据(如密码、手机号等敏感信息)需要更严格的访问控制。独立数据库可以配置独立的访问权限。
- 扩展性:用户中心数据库可以独立进行分库分表、读写分离等扩展操作,不影响 CAS 的 Ticket 存储。
- 备份策略:用户中心数据和 Ticket 数据可以采用不同的备份策略。Ticket 数据可以接受更频繁的备份,而用户中心数据可能需要更长的保留周期。
8.2 user_info 表结构详解
user_info 表是 user_center 数据库中的核心表,存储了用户的基本信息。下面逐一解析每个字段的含义和设计考量。
id(bigint,主键)
主键使用 bigint 类型,自增或雪花算法生成。使用 bigint 而非 int 的原因是:
bigint的范围远大于int(约 9.2 x 10^18 vs 2.1 x 10^9),可以支撑更长时间的数据增长。- 在分布式场景中,
bigint可以容纳雪花算法等分布式 ID 生成方案产生的 ID。
uuid(varchar)
UUID 字段存储用户的唯一标识符。UUID 是一个与业务无关的全局唯一标识,通常在用户注册时由系统生成。UUID 的作用包括:
- 作为对外暴露的用户标识(避免暴露自增 ID)。
- 在分布式系统中标识用户身份。
- 支持数据迁移和系统间数据同步。
name(varchar,UNIQUE)
name 字段存储用户的登录名(用户名),设置为 UNIQUE 约束确保唯一性。在 CAS 的认证场景中,用户名是最常用的认证标识。
password(varchar,SHA-256 格式)
password 字段存储用户的密码哈希值。密码以 {SHA256}hash 的格式存储,其中 {SHA256} 是算法标识前缀,hash 是密码的 SHA-256 哈希值。
使用算法标识前缀的好处是支持多种密码编码算法的共存。当系统需要升级密码编码算法时,可以通过前缀区分不同算法编码的密码,实现平滑迁移。
password_salt(varchar(8),固定值 bima.cc)
password_salt 字段存储密码盐值,固定为 bima.cc。盐值的作用是在密码哈希之前与原始密码拼接,增加彩虹表攻击的难度。
使用固定盐值是一种简化的安全策略。更安全的做法是为每个用户生成唯一的随机盐值,并将盐值与哈希值一起存储。但固定盐值在某些场景下(如需要支持多种客户端的密码验证)可以简化实现。
nickname(varchar)
nickname 字段存储用户的昵称/显示名。昵称不需要唯一,用户可以自由修改。
telphone(varchar)
telphone 字段存储用户的手机号码。手机号可以作为辅助认证方式(短信验证码登录),也可以用于密码找回。
email(varchar)
email 字段存储用户的电子邮箱地址。邮箱可以用于密码找回、通知推送等场景。
time_create / time_update(datetime)
time_create 和 time_update 分别记录用户的创建时间和最后更新时间。这两个字段对于审计追踪和数据生命周期管理非常重要。
admin_id / admin_name
admin_id 和 admin_name 记录了创建或最后修改该用户记录的管理员信息。这两个字段用于操作审计——当用户信息被修改时,可以追溯到是哪个管理员进行的操作。
is_used(int)
is_used 字段是用户的启用/禁用标志。通常使用 1 表示启用,0 表示禁用。禁用的用户无法通过 CAS 认证。
remark(varchar)
remark 字段存储备注信息,用于记录用户的特殊说明(如禁用原因、特殊权限说明等)。
8.3 密码加密存储方案
CAS 项目中的密码加密存储方案基于 SHA-256 哈希算法,采用"盐值 + 哈希"的方式存储密码。
密码存储格式
密码以 {SHA256}hash 的格式存储在 password 字段中。例如:
{SHA256}a3f2b8c9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f91
密码验证流程
当用户提交登录请求时,CAS 的认证处理器执行以下步骤:
- 从数据库中查询用户记录,获取存储的密码哈希值。
- 解析密码哈希值,提取算法标识(
SHA256)和哈希值。 - 将用户输入的密码与盐值(
bima.cc)拼接。 - 使用 SHA-256 算法计算拼接后的字符串的哈希值。
- 将计算得到的哈希值与数据库中存储的哈希值进行比较。
- 如果两者一致,认证通过;否则认证失败。
安全考量
需要指出的是,SHA-256 虽然是一种安全的哈希算法,但它并非专为密码存储设计的。更安全的密码存储方案应该使用专门设计的密码哈希算法,如:
- PBKDF2(Password-Based Key Derivation Function 2)
- bcrypt
- scrypt
- Argon2
这些算法内置了"工作因子"(work factor)参数,可以增加暴力破解的计算成本。同时,它们自动处理盐值的生成和管理。
CAS 本身支持通过 Spring Security 的 PasswordEncoder 接口集成多种密码编码算法。在实际项目中,建议使用 BCryptPasswordEncoder 替代 SHA-256,以获得更高的安全性。
8.4 索引设计策略
合理的索引设计是数据库性能优化的关键。对于 user_info 表,以下索引设计策略值得考虑:
主键索引
id 字段作为主键,自动创建聚簇索引(InnoDB 引擎)。主键索引的查询效率最高,所有基于主键的查询都可以直接定位到数据行。
唯一索引
name 字段设置了 UNIQUE 约束,自动创建唯一索引。这个索引用于:
- 用户登录时的用户名查找(最频繁的查询场景)。
- 用户名唯一性校验(注册时检查用户名是否已存在)。
辅助索引
根据查询模式,可能需要创建以下辅助索引:
telphone索引:如果支持手机号登录,需要为telphone字段创建索引。email索引:如果支持邮箱登录,需要为email字段创建索引。is_used索引:如果经常需要查询启用/禁用状态的用户,可以创建此索引。time_create索引:如果需要按创建时间排序或范围查询,可以创建此索引。
复合索引
在某些查询场景中,可能需要创建复合索引。例如,如果经常需要查询"某个管理员创建的所有用户",可以创建 (admin_id, time_create) 复合索引。
索引设计的注意事项
- 避免过度索引:每个索引都会增加写入操作的开销(INSERT、UPDATE、DELETE 时需要同步更新索引)。索引数量应该根据实际的查询模式来决定,而非为每个字段都创建索引。
- 覆盖索引:如果查询只需要索引列的数据,可以利用覆盖索引(Covering Index)避免回表查询,提高查询性能。
- 索引选择性:索引的选择性(不重复值与总记录数的比值)越高,索引的效果越好。
name字段的选择性为 1(UNIQUE),是最理想的索引列。is_used字段的选择性很低(只有 0 和 1 两个值),通常不适合单独创建索引。
8.5 数据库版本管理与迁移
在 CAS 项目的长期维护中,数据库表结构不可避免地会发生变化——新增字段、修改字段类型、创建新表、删除旧表等。这些变化需要通过数据库迁移(Migration)来管理。
数据库迁移的最佳实践
- 版本化迁移脚本:每个数据库变更都应该对应一个版本化的迁移脚本,脚本文件名包含时间戳或版本号(如
V20240101__add_user_info_email_index.sql)。 - 向前兼容:迁移脚本应该向前兼容——新版本的代码应该能够运行在旧版本的数据库上(通过特性开关或默认值实现),直到迁移脚本执行完毕。
- 可回滚:每个迁移脚本都应该提供对应的回滚脚本,以便在迁移失败时恢复到之前的状态。
- 测试验证:迁移脚本应该在测试环境中充分验证后再应用到生产环境。
常用的迁移工具
- Flyway:基于版本化 SQL 脚本的数据库迁移工具,与 Spring Boot 集成良好。
- Liquibase:基于 XML/YAML/JSON 的数据库迁移工具,支持更复杂的迁移操作(如条件迁移、上下文相关的迁移等)。
九、跨版本配置迁移实战
9.1 CAS 5.3 到 6.6 的迁移路径
从 CAS 5.3 升级到 CAS 6.6,MyBatis 集成配置需要进行以下调整:
事务通知精细化
CAS 5.3 的"全量事务"配置需要改为 CAS 6.6 的"精细化事务"配置。具体变更包括:
- 在
<tx:advice>中,将name="*"拆分为写操作和读操作两组方法模式。 - 写操作方法(
save*、update*、delete*、add*、remove*、create*)使用propagation="REQUIRED"。 - 读操作方法(
name="*")使用propagation="SUPPORTS" read-only="true"。
方法命名规范审查
精细化事务配置要求所有写操作的方法名必须以指定的前缀开头。在迁移过程中,需要审查所有 Service 类的方法名,确保符合命名规范。对于不符合规范的方法,需要重命名并更新所有调用点。
Mapper 扫描收窄
将 MapperScannerConfigurer 的 basePackage 从 cc.bima.cas 收窄为 cc.bima.cas.dao。这需要确保所有 Mapper 接口都已经迁移到 cc.bima.cas.dao 包下。
同时,需要为所有 Mapper 接口添加 @Mapper 注解,并配置 annotationClass 属性。
事务管理器重命名
将事务管理器的 Bean 名称从 transactionManager 改为 ticketTransactionManager。需要同步更新所有引用该名称的配置:
<tx:advice>的transaction-manager属性。<tx:annotation-driven>的transaction-manager属性。- AOP 切面配置中的引用。
- 自定义代码中通过
@Qualifier注解引用事务管理器的地方。
会话超时调整
将会话超时从 300 秒延长至 86400 秒。这个变更需要根据业务需求来决定是否采纳。如果业务场景不需要长时间会话,可以保持原有的 300 秒设置。
9.2 CAS 6.6 到 7.3 的迁移路径
从 CAS 6.6 升级到 CAS 7.3,涉及更多底层框架的变更,迁移工作量也更大。
dbcp 到 dbcp2 的升级
这是最直接的配置变更,需要修改数据源配置:
- 将
BasicDataSource的全限定名从org.apache.commons.dbcp.BasicDataSource改为org.apache.commons.dbcp2.BasicDataSource。 - 将
maxActive改为maxTotal。 - 将
maxWait改为maxWaitMillis。 - 将
removeAbandoned拆分为removeAbandonedOnBorrow和removeAbandonedOnMaintenance。
同时,需要在 pom.xml 或 build.gradle 中更新依赖:
- 移除
commons-dbcp依赖。 - 添加
commons-dbcp2依赖。
AOP 事务配置的移除
CAS 7.3 完全移除了 AOP 事务配置。在迁移过程中:
- 删除
<tx:advice>配置。 - 删除
<aop:config>配置。 - 删除
<aop:pointcut>配置。 - 确保所有需要事务管理的方法都添加了
@Transactional注解。 - 对于 CAS 内部的 Ticket Registry 操作,评估是否需要自定义事务管理。
MyBatis 大版本升级
MyBatis 3.5.16 + MyBatis-Spring 3.0.3 的升级需要:
- 更新
pom.xml中的依赖版本。 - 检查并更新所有
javax.*到jakarta.*的 import 语句。 - 检查自定义类型处理器、插件等组件的兼容性。
- 更新
mybatis-config.xml中的配置(如果有使用已废弃的配置项)。
XML 配置简化
CAS 7.3 的 spring-common.xml 大幅简化。在迁移过程中:
- 移除
SqlSessionFactoryBean的手动配置(由 Spring Boot 自动创建)。 - 简化
MapperScannerConfigurer的配置(移除sqlSessionFactoryBeanName和annotationClass)。 - 添加
<context:component-scan>配置(如果之前没有)。 - 将数据库连接参数从 XML 配置迁移到
application.yml。
Java 版本升级
CAS 7.3 基于 Spring Boot 3.x,要求 Java 17 或更高版本。如果项目之前使用 Java 8 或 Java 11,需要:
- 升级 JDK 到 17 或更高版本。
- 检查并更新所有使用了 Java 版本特定 API 的代码。
- 更新构建工具(Maven、Gradle)的配置。
9.3 迁移过程中的常见问题与解决方案
问题一:Bean 名称冲突
在 CAS 7.3 中,Spring Boot 的自动配置会创建一些与手动配置同名的 Bean(如 dataSource、sqlSessionFactory、transactionManager 等)。如果 spring-common.xml 中仍然定义了这些 Bean,会导致冲突。
解决方案:
- 优先使用 Spring Boot 的自动配置,移除
spring-common.xml中的重复 Bean 定义。 - 如果需要自定义 Bean,使用
@Primary注解或@ConditionalOnMissingBean条件来控制 Bean 的优先级。
问题二:Mapper 接口未被发现
在 CAS 7.3 中,如果 MapperScannerConfigurer 的 basePackage 配置不正确,或者 Mapper 接口不在扫描范围内,会导致 Mapper Bean 未被注册,运行时抛出 NoSuchBeanDefinitionException。
解决方案:
- 检查
basePackage配置是否正确。 - 使用 Spring Boot 的
--debug选项查看自动配置报告,确认 Mapper 是否被正确扫描。 - 确保 Mapper 接口位于正确的包下。
问题三:事务不生效
从 AOP 事务迁移到 @Transactional 注解后,如果事务不生效,可能的原因包括:
@Transactional注解添加在了private方法上(Spring AOP 无法拦截 private 方法)。@Transactional注解添加在了同一个类内部调用的方法上(自调用绕过了代理)。@Transactional注解的transactionManager属性未正确指定(在多事务管理器场景下)。- Spring Boot 的事务自动配置未启用(classpath 中缺少必要的依赖)。
解决方案:
- 确保
@Transactional注解添加在public方法上。 - 将自调用改为通过注入的代理对象调用。
- 在多事务管理器场景下,显式指定
@Transactional("ticketTransactionManager")。 - 检查
pom.xml中是否包含spring-boot-starter-jdbc或mybatis-spring-boot-starter依赖。
问题四:连接池参数不生效
从 dbcp 升级到 dbcp2 后,如果连接池参数不生效,可能的原因是使用了旧的属性名称。
解决方案:
- 检查是否使用了 dbcp 1.4 的属性名(如
maxActive、maxWait),替换为 dbcp2 的属性名(如maxTotal、maxWaitMillis)。 - 检查 Spring Boot 的
application.yml中是否有覆盖连接池参数的配置。
9.4 版本兼容性矩阵
为了方便开发者进行跨版本迁移,以下总结了三个 CAS 版本中 MyBatis 集成配置的关键差异:
| 配置项 | CAS 5.3 | CAS 6.6 | CAS 7.3 |
|---|---|---|---|
| 连接池 | commons-dbcp 1.4 | commons-dbcp 1.4 | commons-dbcp2 2.10.0 |
| 最大连接数属性 | maxActive | maxActive | maxTotal |
| 最大等待属性 | maxWait | maxWait | maxWaitMillis |
| 事务通知 | 全量 REQUIRED | 精细化读写分离 | 移除(依赖 @Transactional) |
| AOP 事务 | 有 | 有 | 移除 |
| Mapper 扫描包 | cc.bima.cas | cc.bima.cas.dao | cc.bima.cas.dao |
| @Mapper 注解 | 不要求 | 要求 | 不要求 |
| 事务管理器名称 | transactionManager | ticketTransactionManager | 自动配置 |
| MyBatis 版本 | 3.4.x | 3.5.x | 3.5.16 |
| MyBatis-Spring 版本 | 1.x/2.x | 2.x | 3.0.3 |
| Java 版本 | 8+ | 11+ | 17+ |
| Spring 版本 | 4.3.x | 5.3.x | 6.x |
| Spring Boot | 不使用/部分使用 | 部分使用 | 完全使用 |
| MBG | 有 | 有 | 移除 |
| 会话超时 | 300秒 | 86400秒 | 可配置 |
十、最佳实践与踩坑总结
10.1 多数据源注意事项
在 CAS 项目中,可能需要同时使用多个数据源——CAS 内部的 Ticket 数据源和业务数据源(user_center)。多数据源配置是 MyBatis 集成中的一个难点。
多 SqlSessionFactory 配置
当存在多个数据源时,需要为每个数据源创建独立的 SqlSessionFactory:
xml
<!-- 教学示例:多数据源配置结构 -->
<!-- 业务数据源 -->
<bean id="businessDataSource" class="...BasicDataSource">
<property name="url" value="${business.db.url}" />
...
</bean>
<!-- 业务 SqlSessionFactory -->
<bean id="businessSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="businessDataSource" />
<property name="mapperLocations" value="classpath:mapper/business/**/*.xml" />
</bean>
<!-- 业务 Mapper 扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="businessSqlSessionFactory" />
<property name="basePackage" value="cc.bima.cas.business.dao" />
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
事务管理器的隔离
多数据源场景下,每个数据源需要独立的事务管理器。跨数据源的事务(分布式事务)需要使用 JTA(Java Transaction API)实现,但这会显著增加系统的复杂性。在 CAS 场景中,通常建议避免跨数据源事务,通过业务设计将事务范围限制在单个数据源内。
@Transactional 的 transactionManager 指定
在多事务管理器场景下,@Transactional 注解需要显式指定使用哪个事务管理器:
java
// 教学示例:指定事务管理器
@Transactional("ticketTransactionManager")
public void createTicket(Ticket ticket) {
// 使用 Ticket 数据源的操作...
}1
2
3
4
5
2
3
4
5
10.2 Mapper 接口与 XML 的对应关系
MyBatis 要求 Mapper 接口与 Mapper XML 之间存在严格的对应关系。理解这种对应关系,有助于快速定位和解决配置问题。
对应规则
namespace 一致:Mapper XML 文件的
namespace属性必须与 Mapper 接口的全限定名一致。xml<!-- Mapper XML --> <mapper namespace="cc.bima.cas.dao.UserInfoMapper">1
2方法 ID 一致:Mapper XML 中的 SQL 语句 ID 必须与 Mapper 接口中的方法名一致。
xml<!-- Mapper XML 中的 SQL ID --> <select id="selectByPrimaryKey" ...>1
2参数类型一致:Mapper XML 中的
parameterType必须与 Mapper 接口方法的参数类型一致。返回类型一致:Mapper XML 中的
resultType或resultMap必须与 Mapper 接口方法的返回类型一致。
常见问题
XML 文件未被加载:如果 Mapper XML 文件不在
mapperLocations指定的路径下,或者文件名不符合 Ant 风格的模式,XML 中的 SQL 语句将不会被加载。此时调用 Mapper 方法会抛出BindingException: Invalid bound statement。namespace 拼写错误:namespace 与接口全限定名不一致(如大小写错误、包名错误),也会导致
BindingException。方法签名不匹配:Mapper 接口方法的参数类型或返回类型与 XML 中的定义不一致,会在运行时抛出类型转换异常。
排查建议
当遇到 Mapper 相关的异常时,建议按以下步骤排查:
- 检查 Mapper 接口是否被 Spring 扫描到(查看 Spring 启动日志中的 Mapper Bean 注册信息)。
- 检查 Mapper XML 文件是否被
SqlSessionFactory加载(在 MyBatis 的配置中开启 SQL 日志,查看已加载的 Mapper 列表)。 - 检查 namespace 和方法 ID 是否与接口全限定名和方法名一致。
- 检查参数类型和返回类型是否匹配。
10.3 事务传播行为的正确选择
Spring 提供了 7 种事务传播行为(Propagation Behavior),正确选择传播行为对于保证数据一致性和系统性能至关重要。
常用的传播行为
| 传播行为 | 说明 | 适用场景 |
|---|---|---|
REQUIRED | 有事务则加入,无事务则创建 | 大多数业务方法 |
SUPPORTS | 有事务则加入,无事务则非事务执行 | 查询方法 |
REQUIRES_NEW | 总是创建新事务 | 独立事务的操作(如审计日志) |
NOT_SUPPORTED | 总是非事务执行 | 不需要事务的操作 |
NEVER | 总是非事务执行,有事务则抛异常 | 强制非事务操作 |
MANDATORY | 必须在事务中执行,无事务则抛异常 | 必须有事务的操作 |
NESTED | 嵌套事务(保存点) | 部分回滚场景 |
CAS 场景中的传播行为选择
在 CAS + MyBatis 的集成场景中,推荐的事务传播行为选择如下:
- Service 层的写方法:使用
REQUIRED。这是最常用的传播行为,确保写操作在事务中执行。 - Service 层的读方法:使用
SUPPORTS(配合readOnly=true)。如果调用链中已有事务,则加入;否则以非事务方式执行,减少事务开销。 - 审计日志记录:使用
REQUIRES_NEW。审计日志应该在独立的事务中写入,即使主事务回滚,审计日志也应该保留。 - Ticket 操作:使用
REQUIRED。Ticket 的创建、更新、删除操作必须在事务中执行。 - 缓存更新:使用
REQUIRES_NEW或NOT_SUPPORTED。缓存更新通常不需要与主事务绑定。
嵌套事务的使用场景
NESTED 传播行为支持嵌套事务(基于数据库保存点),可以实现部分回滚。例如:
java
// 教学示例:嵌套事务
@Transactional
public void batchCreateUsers(List<UserInfo> users) {
for (UserInfo user : users) {
try {
createUserInNestedTransaction(user);
} catch (Exception e) {
// 单个用户创建失败不影响其他用户
log.error("创建用户失败: {}", user.getName(), e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void createUserInNestedTransaction(UserInfo user) {
userInfoMapper.insertSelective(user);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
需要注意的是,NESTED 传播行为依赖于数据库的保存点(Savepoint)支持。并非所有数据库都支持保存点(如 MySQL 的 MyISAM 引擎不支持)。
10.4 连接池参数调优建议
连接池参数的合理配置对系统性能至关重要。以下是基于实际项目经验的调优建议。
根据并发量确定连接池大小
连接池的大小应该根据系统的峰值并发量来确定。一个常用的经验公式是:
连接池大小 = 峰值并发请求数 × 平均请求处理时间(秒) + 预留缓冲1
例如,如果峰值并发为 100 TPS,平均请求处理时间为 0.05 秒,则:
连接池大小 = 100 × 0.05 + 5 = 101
避免连接池过大
连接池并非越大越好。过大的连接池会带来以下问题:
- 数据库服务器压力增大:每个连接都会占用数据库服务器的线程和内存资源。
- 上下文切换开销增加:大量活跃连接会导致数据库服务器的线程上下文切换频繁。
- 连接泄漏的影响放大:连接池越大,连接泄漏达到阈值所需的时间越长,问题发现越晚。
根据数据库服务器的硬件配置,一般建议单个应用实例的连接池大小不超过以下值:
- MySQL:20-50
- PostgreSQL:50-100
- Oracle:30-80
监控连接池状态
在生产环境中,应该监控连接池的以下指标:
- 活跃连接数:当前正在使用的连接数。
- 空闲连接数:当前空闲的连接数。
- 等待线程数:正在等待获取连接的线程数。
- 连接泄漏数:被检测为泄漏的连接数。
dbcp2 提供了 JMX 支持,可以通过 JMX 监控这些指标。也可以通过 Spring Boot Actuator 的 /metrics 端点获取连接池的状态信息。
动态调优
连接池参数不应该一成不变。随着业务的发展和系统架构的变化,连接池参数需要定期评估和调整。建议:
- 在每次大版本发布前评估连接池参数。
- 在业务高峰期(如促销活动、开学季等)前临时调大连接池。
- 根据监控数据和历史趋势,制定连接池参数的基线和告警阈值。
10.5 SQL 注入防护
SQL 注入是最常见也最危险的 Web 安全漏洞之一。在 CAS + MyBatis 的集成中,SQL 注入防护需要特别关注。
MyBatis 的参数绑定机制
MyBatis 提供了两种参数绑定方式:
#{}(预编译参数绑定):使用 JDBC 的PreparedStatement参数绑定,自动对参数值进行转义。这是安全的参数绑定方式,可以防止 SQL 注入。
xml
<!-- 安全:使用 #{} -->
<select id="selectByName" resultType="UserInfo">
SELECT * FROM user_info WHERE name = #{name}
</select>1
2
3
4
2
3
4
${}(字符串直接替换):将参数值直接拼接到 SQL 字符串中,不进行任何转义。这种方式存在 SQL 注入风险。
xml
<!-- 危险:使用 ${} -->
<select id="selectByColumn" resultType="UserInfo">
SELECT * FROM user_info ORDER BY ${columnName}
</select>1
2
3
4
2
3
4
何时使用 ${}
${} 并非完全不可用。在某些场景下,${} 是必要的:
- 动态列名(如
ORDER BY ${columnName})。 - 动态表名(如
SELECT * FROM ${tableName})。 IN子句的参数列表(虽然 MyBatis 的<foreach>标签可以处理这种情况)。
使用 ${} 的安全措施
当必须使用 ${} 时,应该采取以下安全措施:
- 白名单校验:在 Java 代码中对参数值进行白名单校验,只允许预定义的值。
java
// 教学示例:白名单校验
private static final Set<String> ALLOWED_COLUMNS =
Set.of("id", "name", "time_create", "time_update");
public List<UserInfo> selectByColumn(String columnName) {
if (!ALLOWED_COLUMNS.contains(columnName)) {
throw new IllegalArgumentException("不支持的排序列: " + columnName);
}
return mapper.selectByColumn(columnName);
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 正则校验:对于更灵活的场景,可以使用正则表达式校验参数值。
java
// 教学示例:正则校验
private static final Pattern COLUMN_PATTERN =
Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
public List<UserInfo> selectByColumn(String columnName) {
if (!COLUMN_PATTERN.matcher(columnName).matches()) {
throw new IllegalArgumentException("非法的列名: " + columnName);
}
return mapper.selectByColumn(columnName);
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 避免将用户输入直接传入
${}:用户输入的值应该经过严格的校验和清洗后才能用于${}。理想情况下,用户输入只通过#{}传递。
PageCustom 中的 SQL 注入风险
前面提到的 PageCustom 类中的 fields 和 order 属性使用了 ${} 进行参数替换。这两个属性存在 SQL 注入风险,必须进行严格的校验。
建议的实现方式:
fields属性:使用白名单校验,只允许预定义的列名。order属性:使用正则校验,确保格式为column_name [ASC|DESC]。
10.6 总结与展望
本文从 CAS 5.3 出发,经过 CAS 6.6,最终到达 CAS 7.3,详细解析了 MyBatis 数据集成在三个大版本中的配置演进。回顾这段历程,我们可以总结出以下关键趋势:
配置方式从 XML 到注解再到自动配置
CAS 5.3 依赖大量的 Spring XML 配置来管理 MyBatis 的集成。CAS 6.6 开始引入注解驱动的配置(如 @Mapper 注解)。CAS 7.3 则全面拥抱 Spring Boot 的自动配置机制,大幅减少了手动配置的工作量。这一趋势反映了 Java 生态中配置方式的演进方向——从显式配置到约定优于配置。
事务管理从粗放到精细
CAS 5.3 的"全量事务"策略虽然简单,但带来了不必要的性能开销。CAS 6.6 的精细化事务配置通过读写分离优化了性能。CAS 7.3 则完全移除了 AOP 事务配置,依赖 @Transactional 注解和 Spring Boot 自动配置。这一趋势体现了对系统性能和代码可维护性的持续追求。
连接池从 dbcp 到 dbcp2
commons-dbcp2 相比 dbcp 1.4 在性能、功能和可维护性方面都有显著提升。属性命名的规范化(maxTotal、maxWaitMillis)消除了歧义,新增的功能(如异步连接获取、连接生命周期监听器)为高级场景提供了支持。
代码生成从依赖到自主
CAS 5.3/6.6 依赖 MyBatis Generator 进行代码生成,而 CAS 7.3 移除了 MBG,转而使用自研的通用封装模式。这一转变反映了项目架构的成熟——从依赖工具生成代码到建立自主的代码规范和封装模式。
展望未来
随着 CAS 和 Spring Boot 的持续演进,MyBatis 数据集成将继续面临新的挑战和机遇:
- 响应式编程:Spring WebFlux 和 R2DBC 的兴起可能推动 MyBatis 向响应式方向发展。
- 云原生适配:Kubernetes 环境下的连接池管理、服务发现、配置中心等云原生特性需要更深入的集成。
- 多租户支持:SaaS 化的 CAS 部署可能需要 MyBatis 提供更完善的多租户数据隔离方案。
- AI 辅助:AI 技术可能被应用于 SQL 优化、索引推荐、异常诊断等场景。
无论技术如何演进,理解底层原理、掌握配置细节、遵循最佳实践,始终是构建稳健 CAS 系统的基石。希望本文能够为正在进行 CAS + MyBatis 集成的开发者提供有价值的参考。
版权声明: 本文为必码(bima.cc)原创技术文章,仅供学习交流。
本文内容基于实际项目源码解析整理,代码示例均为教学简化版本,仅供学习参考。
文档内容提取自项目源码与配置文件,如需获取完整项目代码,请访问 bima.cc。