本文系統整理 Spring 核心實戰技巧:測試整合、Web 環境啟動、多環境配置載入、聲明式事務管理(XML/註解雙模式)、主從資料源只讀路由,附完整程式碼與配置範例。
Maven 依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.0.RELEASE</version> </dependency>
測試類採用註解驅動:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class BeanTest {
@Autowired private Bean bean;
@Test public void testConstruct() {
System.out.println(bean.getCar());
}
}
web.xml 配置 ContextLoaderListener:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param>
Servlet 手動獲取 Bean(實務建議改用 Spring MVC):
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
Bean bean = ctx.getBean("bean", Bean.class);
<context:property-placeholder>(推薦)<context:property-placeholder location="classpath:db.properties"/>
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="${mysql.driver.class}"/>
<property name="jdbcUrl" value="${mysql.url}"/>
</bean>
Java 注入:@Value("${mysql.url}")。
PropertiesFactoryBean 注入 Map<bean id="commonProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"><list><value>classpath*:common.properties</value></list></property> </bean>
SpEL 注入:@Value("#{commonProps['bean.properties.name']}")。
<import> 模組化配置<import resource="spring/datasource.xml"/> <import resource="spring/dubbo-provider.xml"/>
PlatformTransactionManager:事務管理器,依持久層選型(DataSourceTransactionManager/HibernateTransactionManager/JpaTransactionManager/JtaTransactionManager)。TransactionDefinition:隔離級別(DEFAULT/READ_UNCOMMITTED/READ_COMMITTED/REPEATABLE_READ/SERIALIZABLE)、傳播行為(REQUIRED/REQUIRES_NEW/NESTED 等)、超時、只讀。TransactionStatus:運行狀態(是否新事務、是否完成、是否回滾標記)。<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="svcPt" expression="execution(* com.fq.service..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="svcPt"/>
</aop:config>
<tx:annotation-driven transaction-manager="txManager"/>
@Service
public class AccountServiceImpl {
@Transactional(readOnly = false)
public void transfer(String from, String to, Double money) { ... }
}
<!-- 主庫:讀寫 -->
<tx:advice id="txMaster" transaction-manager="txManagerMaster">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 從庫:強制只讀 -->
<tx:advice id="txSlave" transaction-manager="txManagerSlave">
<tx:attributes>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
配合 AOP 切點將查詢類方法導向 Slave,寫入導向 Master,實現讀寫分離。
無事務時:transferOut 成功 → 人為拋出異常 → transferIn 未執行 → 資料不一致。
加上聲明式事務後:異常觸發回滾,雙邊金額不變,保證 ACID。
read-only。read-only=true 防誤寫入。