Spring 实践 -拾遗
Spring 实践
标签: Java与设计模式
Junit集成
前面多次用到@RunWith
与@ContextConfiguration
,在测试类添加这两个注解,程序就会自动加载Spring配置并初始化Spring容器,方便Junit与Spring集成测试.使用这个功能需要在pom.xml中添加如下依赖:
- pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
- 以
@RunWith
和@ContextConfiguration
加载Spring容器
/**
* Spring 整合 Junit
* Created by jifang on 15/12/9.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class BeanTest {
@Autowired
private Bean bean;
@Test
public void testConstruct() {
Car car = bean.getCar();
System.out.println(car);
}
}
Web集成
我们可以利用ServletContext
容器保存数据的唯一性, 以及ServletContextListener
会在容器初始化时只被调用一次的特性. 在web.xml中配置spring-web包下的ContextLoaderListener
来加载Spring配置文件/初始化Spring容器:
- pom.xml/spring-web
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
- 配置监听器(web.xml)
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 加载Spring配置文件
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
附: 完整web.xml文件git地址.
- 测试Servlet
@WebServlet(urlPatterns = "/servlet")
public class Servlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
Bean bean = context.getBean("bean", Bean.class);
Car car = bean.getCar();
System.out.println(car);
}
}
在应用中,普通的JavaBean由Spring管理,可以使用
@Autowired
自动注入.但Filter
与Servlet
例外,他们都是由Servlet容器管理,因此其属性不能用Spring注入,所以在实际项目中,一般都不会直接使用Servlet,而是用SpringMVC/WebX/Struts2之类的MVC框架以简化开发,后面会有专门的博客介绍这类框架,在此就不做深入介绍了.
- 注: 运行Servlet不要忘记添加servlet-api依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
文件加载
1. 引入properties
可以将需要经常修改的属性参数值放到properties文件, 并在Spring文件中引入.
- db.properties
## Data Source
mysql.driver.class=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://host:port/db?useUnicode=true&characterEncoding=UTF8
mysql.user=user
mysql.password=password
注意: value后不能有空格.
1.1 property-placeholde引入
在Spring配置文件中使用<context:property-placeholder/>
标签引入properties文件,XML文件可通过${key}
引用, Java可通过@Value("${key}")
引用:
- XML
<context:property-placeholder location="classpath:common.properties"/>
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="${mysql.driver.class}"/>
<property name="jdbcUrl" value="${mysql.url}"/>
<property name="username" value="${mysql.user}"/>
<property name="password" value="${mysql.password}"/>
</bean>
- Java
@Component
public class AccessLog {
@Value("${mysql.url}")
private String value;
// ...
}
1.2 PropertiesFactoryBean
引入
Spring提供了org.springframework.beans.factory.config.PropertiesFactoryBean
,以加载properties文件, 方便在JavaBean中注入properties属性值.
- XML
<bean id="commonProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath*:common.properties</value>
</list>
</property>
</bean>
- Java
@Controller
public class Bean {
@Value("#{commonProperties['bean.properties.name']}")
private String name;
// ...
}
2. import其他Spring配置
如果Spring的配置项过多,可以按模块将配置划分多个配置文件(-datasource.xml/-dubbo-provider.xml/-bean.xml), 并由主配置applicationContext.xml文件引用他们,此时可用<import/>
标签引入:
<import resource="applicationContext-bean.xml"/>
<import resource="applicationContext-dubbo-provider.xml"/>
<import resource="applicationContext-dubbo-consumer.xml"/>
事务管理
Spring事务管理高层抽象主要由
PlatformTransactionManager
/TransactionDefinition
/TransactionStatus
三个接口提供支持:
PlatformTransactionManager(事务管理器)
PlatformTransactionManager
的主要功能是事务管理,Spring为不同的持久层框架提供了不同的PlatformTransactionManager
实现:
事务 | 描述 |
---|---|
DataSourceTransactionManager |
JDBCTemplate/MyBatis/iBatis持久化使用 |
HibernateTransactionManager |
Hibernate持久化使用 |
JpaTransactionManager |
JPA持久化使用 |
JdoTransactionManager |
JDO持久化使用 |
JtaTransactionManager |
JTA实现管理事务,一个事务跨越多个资源时使用 |
因此使用Spring管理事务,需要为不同持久层配置不同事务管理器实现.
TransactionDefinition(事务定义信息)
TransactionDefinition
提供了对事务的相关配置, 如事务隔离级别/传播行为/只读/超时等:
- 隔离级别(isolation)
为解决事务并发引起的问题(脏读/幻读/不可重复读),引入四个隔离级别:
隔离级别 | 描述 |
---|---|
DEFAULT |
使用数据库默认的隔离级别 |
READ_UNCOMMITED |
读未提交 |
READ_COMMITTED |
读已提交(Oracle默认) |
REPEATABLE_READ |
可重复读(MySQL默认) |
SERIALIZABLE |
串行化 |
关于事务隔离级别的讨论, 可参考我的博客JDBC基础-事务隔离级别部分.
- 传播行为(propagation)
传播行为不是数据库的特性, 而是为了在业务层解决两个事务相互调用的问题:
传播类型 | 描述 |
---|---|
REQUIRED |
支持当前事务,如果不存在就新建一个(默认) |
SUPPORTS |
支持当前事务,如果不存在就不使用事务 |
MANDATORY |
支持当前事务,如果不存在则抛出异常 |
REQUIRES_NEW |
如果有事务存在,则挂起当前事务新建一个 |
NOT_SUPPORTED |
以非事务方式运行,如果有事务存在则挂起当前事务 |
NEVER |
以非事务方式运行,如果有事务存在则抛出异常 |
NESTED |
如果当前事务存在,则嵌套事务执行(只对DataSourceTransactionManager 有效) |
- 超时时间(timeout)
- 只读(read-only)
只读事务, 不能执行INSERT
/UPDATE
/DELETE
操作.
TransactionStatus(事务状态信息)
获得事务执行过程中某一个时间点状态.
声明式事务管理
Spring声明式事务管理:无需要修改原来代码,只需要为Spring添加配置(XML/Annotation),就可以为目标代码添加事务管理功能.
需求: 转账案例(使用MyBatis).
- AccountDAO
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fq.dao.AccountDAO">
<update id="transferIn">
UPDATE account
SET money = money + #{0}
WHERE name = #{1};
</update>
<update id="transferOut">
UPDATE account
SET money = money - #{0}
WHERE name = #{1};
</update>
</mapper>
/**
* @author jifang
* @since 16/3/3 上午11:16.
*/
public interface AccountDAO {
void transferIn(Double inMoney, String name);
void transferOut(Double outMoney, String name);
}
- Service
public interface AccountService {
void transfer(String from, String to, Double money);
}
@Service("service")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDAO dao;
@Override
public void transfer(String from, String to, Double money) {
dao.transferOut(money, from);
// 此处抛出异常, 没有事务将导致数据不一致
int a = 1 / 0;
dao.transferIn(money, to);
}
}
- mybatis-configuration.xml/applicationContext-datasource.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 加载mapper映射文件 -->
<mappers>
<mapper resource="mybatis/mapper/AccountDAO.xml"/>
</mappers>
</configuration>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<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}"/>
<property name="username" value="${mysql.user}"/>
<property name="password" value="${mysql.password}"/>
<property name="maximumPoolSize" value="5"/>
<property name="maxLifetime" value="700000"/>
<property name="idleTimeout" value="600000"/>
<property name="connectionTimeout" value="10000"/>
<property name="dataSourceProperties">
<props>
<prop key="dataSourceClassName">com.mysql.jdbc.jdbc2.optional.MysqlDataSource</prop>
<prop key="cachePrepStmts">true</prop>
<prop key="prepStmtCacheSize">250</prop>
<prop key="prepStmtCacheSqlLimit">2048</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig"/>
</bean>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-configuration.xml"/>
</bean>
<!-- 基于包扫描的mapper配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.fq.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
- applicationContext.xml(没有事务)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.fq.service"/>
<import resource="applicationContext-datasource.xml"/>
</beans>
- Client
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class SpringClient {
@Autowired
private AccountService service;
@Test
public void client() {
service.transfer("from", "to", 10D);
}
}
执行以上代码, 将会导致数据前后不一致.
XML配置
Spring事务管理依赖AOP,而AOP需要定义切面(Advice+PointCut),在Spring内部提供了事务管理的默认Adviceorg.springframework.transaction.interceptor.TransactionInterceptor
,并且Spring为了简化事务配置,引入tx标签:
- 引入tx的命名空间,配置Advice:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务配置属性, 对什么方法应用怎样的配置, 成为TransactionDefinition对象 -->
<tx:attributes>
<!--
name: 方法名, 支持通配符
isolation: 隔离级别
propagation: 传播行为
timeout: 超时时间
read-only: 是否只读
rollback-for: 配置异常类型, 发生这些异常回滚事务
no-rollback-for: 配置异常类型, 发生这些异常不回滚事务
-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
</tx:attributes>
</tx:advice>
- 配置切面
Spring事务管理Advice基于SpringAOP,因此使用<aop:advisor/>
配置:
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.fq.service.impl.AccountServiceImpl.*(..))"/>
</aop:config>
注解配置
使用注解配置事务, 可以省略切点的定义(因为注解放置位置就已经确定了PointCut的置), 只需配置Advice即可:
- 激活注解事务管理功能
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在需要管理事务的业务类/业务方法上添加
@Transactional
注解
@Override
@Transactional(transactionManager = "transactionManger", readOnly = true)
public void transfer(String from, String to, Double money) {
// ...
}
可以在注解
@Transactional
中配置与XML相同的事务属性(isolation/propagation等).
实践
更推荐使用XML方式来配置事务,实际开发时一般将事务集中配置管理. 另外, 事务的isolation/propagation一般默认的策略就已经足够, 反而我们需要配置是否只读(比如MySQL主从备份时,主库一般提供读写操作,而从库只提供读操作), 因此其配置可以如下:
<!-- 配置声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 定义方法的过滤规则 -->
<tx:attributes>
<!-- 定义所有get开头的方法都是只读的 -->
<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 -->
<aop:config>
<!-- 定义切点 -->
<aop:pointcut id="dao" expression="execution (* com.fq.core.dao.*.*(..))"/>
<!-- 为切点定义通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="dao"/>
</aop:config>
- 主从
<tx:advice id="txAdvice_slave" transaction-manager="transactionManager_slave">
<!-- 定义方法的过滤规则 -->
<tx:attributes>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<tx:advice id="txAdvice_master" transaction-manager="transactionManager_slave">
<!-- 定义方法的过滤规则 -->
<tx:attributes>
<!-- 定义所有get开头的方法都是只读的 -->
<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>
Spring 实践 -拾遗的更多相关文章
- Spring 实践 -IoC
Spring 实践 标签: Java与设计模式 Spring简介 Spring是分层的JavaSE/EE Full-Stack轻量级开源框架.以IoC(Inverse of Control 控制反转) ...
- Spring 实践 -AOP
Spring 实践 标签: Java与设计模式 AOP引介 AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控 ...
- Spring实践系列-入门篇(一)
本文主要介绍了在本地搭建并运行一个Spring应用,演示了Spring依赖注入的特性 1 环境搭建 1.1 Maven依赖 目前只用到依赖注入的功能,故以下三个包已满足使用. <properti ...
- logback+spring实践
配置文件名称使用: logback-spring.xml 配置user.home是jvm传过来的系统参数,可以直接使用 <property name="LOG_PATH&quo ...
- Java Web 深入分析(10) Spring 实践
Spring helloworld [http://wiki.jikexueyuan.com/project/spring/hello-world-example.html] HelloWorld.j ...
- 最全 HTTP 安全响应头设置指南
销售“安全记分卡”的公司正在崛起,并已开始成为企业销售的一个因素.这些公司组合使用 HTTP 安全报头和 IP 信誉来进行评级.不过,在很大程度上,公司的得分取决于对外开放网站上设置的安全响应报头.本 ...
- SpringCache整合Redis
之前一篇文章 SpringBoot整合Redis 已经介绍了在SpringBoot中使用redisTemplate手动 操作redis数据库的方法了.其实这个时候我们就已经可以拿redis来做项目了, ...
- Spring Batch在大型企业中的最佳实践
在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...
- Spring Batch实践
Spring Batch在大型企业中的最佳实践 在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后 ...
随机推荐
- ATT GATT Profile
Bluetooth: ATT and GATT Bluetooth 4.0, which includes the Low Energy specification, brings two new c ...
- 一张思维导图说明jQuery的AJAX请求机制
比文字描述清晰多了吧?而且越是复杂的逻辑,思维导图的作用就越大,同时对阅读源码也是一种快捷的方法. 看不清楚的话可以右键,在新标签页中打开图片,或者保存本地.
- 提高Python运行效率的六个窍门
曾灵敏 - MAY 18, 2015 Python是一门优秀的语言,它能让你在短时间内通过极少量代码就能完成许多操作.不仅如此,它还轻松支持多任务处理,比如多进程. 不喜欢Python的人经常会吐嘈P ...
- POJ 3336 Count the string (KMP+DP,好题)
参考连接: KMP+DP: http://www.cnblogs.com/yuelingzhi/archive/2011/08/03/2126346.html 另外给出一个没用dp做的:http:// ...
- mvc5 错误页如何定义
项目根目录下的Web.config <system.web> <customErrors mode="On" defaultRedirect="~/Er ...
- Xamarin for Visual Studio 3.11.666 稳定版 破解补丁 Version 3
前提概要 1.全新安装请参考 安装 Xamarin for Visual Studio. 2.本次补丁包含: ① Xamarin for Visual Studio 3.11.666 ② Xamari ...
- PHP5.4最新特性
PHP5.4最新特性 官网:ChangeLog-5.php#5.4.0 原文Oracle:LAMP 体系有了新的竞争,但此版本中的特性使 PHP 再次挑战极限. 稍微做了修改.: 概述总结:1. ...
- 用eclipse创建maven项目
Maven是基于项目对象模型(POM),也可以进行模块化开发.并且是个强大的管理工具.本经验用eclipse来创建maven项目 步骤: 1.下载并正确安装eclipse 2.在eclipse上成功安 ...
- 华为上机:求2的N次幂的值
求2的N次幂的值 描述: 求2的N次幂的值(N最大不超过31,用位运算计算,结果以十六进制进行显示). 运行时间限制: 无限制 内存限制: 无限制 输入: 数字N 输出: 2的N次方(16进制,需要按 ...
- Tomcat打印运行时日志(控制台),访问日志,启动日志
1.sh catlina.sh run以控制台形式输出 2.sever.xml.配置acesslog,设置访问日志输出 Tomcat的访问日志是靠org.apache.catalina.valves. ...