Spring-拾遗

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实现:

JDBCTemplate/MyBatis/iBatis持久化使用

因此使用Spring管理事务,需要为不同持久层配置不同事务管理器实现.

TransactionDefinition(事务定义信息)

TransactionDefinition提供了对事务的相关配置, 如事务隔离级别/传播行为/只读/超时等:

隔离级别(isolation)

为解决事务并发引起的问题(脏读/幻读/不可重复读),引入四个隔离级别:

使用数据库默认的隔离级别

关于事务隔离级别的讨论, 可参考我的博客JDBC基础-事务隔离级别部分.

传播行为(propagation)

传播行为不是数据库的特性, 而是为了在业务层解决两个事务相互调用的问题:

支持当前事务,如果不存在就新建一个(默认)

超时时间(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>

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2017-01-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

gradle中使用嵌入式(embedded) tomcat, debug 启动

在gradle项目中使用embedded tomcat。 最开始部署项目需要手动将web项目打成war包,然后手动上传到tomcat的webapp下,然后启动t...

51190
来自专栏码神联盟

重磅来袭,抱歉,来晚啦

来一波 、基本概念 1.1、spring Spring 是一个开源框架, Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Ro...

363110
来自专栏一个会写诗的程序员的博客

第11章 Spring Boot应用监控第11章 Spring Boot应用监控小结

在实际的生产系统中,我们怎样知道我们的应用运行良好呢?我们往往需要对系统实际运行的情况(各种cpu,io,disk,db,业务功能等指标)进行监控运维。这需要耗...

32230
来自专栏林德熙的博客

WPF 只允许打开一个实例

我们有时候只希望我们的程序只打开一个实例,也就是我们的软件只有一次被打开。 那么我们可以通过一个办法知道,在这个软件打开前是不是打开过一个,还没关闭。也就是是否...

21710
来自专栏架构师之旅

Spring框架知识总结-Spring事务配置的五种方式

前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识。通过这次的学习发觉Spring的事...

18090
来自专栏程序猿DD

Spring Cloud构建微服务架构:分布式服务跟踪(跟踪原理)

通过上一篇《分布式服务跟踪(入门)》的例子,我们已经通过Spring Cloud Sleuth往微服务应用中添加了实现分布式跟踪具备的基本要素。下面通过本文来详...

34750
来自专栏Java技术分享

SSM三大框架整合详细总结(Spring+SpringMVC+MyBatis)

使用 SSM ( Spring 、 SpringMVC 和 Mybatis )已经很久了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当...

2.2K130
来自专栏流柯技术学院

linux下安装rzsz

wget http://freeware.sgi.com/source/rzsz/rzsz-3.48.tar.gz

63410
来自专栏一个会写诗的程序员的博客

《Kotin 极简教程》第11章 使用Kotlin 集成 SpringBoot开发Web服务端第11章 使用Kotlin集成SpringBoot开发Web服务端《Kotlin极简教程》正式上架:

我们在前面第2章 “ 2.3 Web RESTFul HelloWorld ” 一节中,已经介绍了使用 Kotlin 结合 SpringBoot 开发一个RES...

9010
来自专栏云原生架构实践

Jhipster技术栈定制 - 基于UAA的微服务之间安全调用

3个微服务都是通过Jhipster生成。 工程代码生成完之后,根据上一节启动的组件的实际情况,修改微服务配置文件中Eureka和database相关的配置。

1K30

扫码关注云+社区

领取腾讯云代金券