pring 的主要作用就是为代码“解耦”,降低代码间的耦合度。
根据功能的不同,可以将一个系统中的代码分为 主业务逻辑 与 系统级业务逻辑 两类。它们各自具有鲜明的特点:主业务代码间逻辑联系紧密,有具体的专业业务应用场景,复用性相对较低;系统级业务相对功能独立,没有具体的专业业务应用场景,主要是为主业务提供系统级服务,如日志、安全、事务等,复用性强。
Spring 根据代码的功能特点,将降低耦合度的方式分为了两类:IoC 与 AOP。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”。而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。简单来说,Spring 是一个分层的 Java SE/EE full-stack(一站式)轻量级开源框架。
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、应用服务器设备管理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。
所谓非侵入式是指,Spring 框架的 API 不会在业务逻辑上出现,即业务逻辑是 POJO。由于业务逻辑中没有 Spring 的 API,所以业务逻辑可以从 Spring 框架快速的移植到其他框架, 即与环境无关。
Spring 作为一个容器,可以管理对象的生命周期、对象与对象之间的依赖关系。可以通过配置文件,来定义对象,以及设置与其他对象的依赖关系。
控制反转(Inversion of Control),即创建被调用者的实例不是由调用者完成,而是由 Spring 容器完成,并注入调用者。
当应用了 IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。即,不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面编程(AOP,Aspect Orient Programming),是一种编程思想,是面向对象编程 OOP 的补充。很多框架都实现了对 AOP 编程思想的实现。Spring 也提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如日志和事务管理)进行开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责其它的系统级关注点,例如日志或事务支持。
我们可以把日志、安全、事务管理等服务理解成一个“切面”,那么以前这些服务一直是直接写在业务逻辑的代码当中的,这有两点不好:首先业务逻辑不纯净;其次这些服务被很多业务逻辑反复使用,完全可以剥离出来做到复用。那么 AOP 就是这些问题的解决方案, 可以把这些服务剥离出来形成一个“切面”,以期复用,然后将“切面”动态的“织入”到业务逻辑中,让业务逻辑能够享受到此“切面”的服务。
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种: 依赖注入和依赖查找。依赖注入方式应用更为广泛。
依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持 POJO 之间依赖关系的管理。
依赖注入是目前最优秀的解耦方式。依赖注入让 Spring 的 Bean 之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。
创建一个工程名为 hello-spring
的项目,pom.xml
文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.luxiugroupId>
<artifactId>hello-springartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.17.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
project>
主要增加了 org.springframework:spring-context
依赖
UserService
接口package com.luxiu.hello.spring.service;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName UserService
* @date 2020/4/13 21:18
* @company /
*/
public interface UserService {
public void sayHi();
}
UserServiceImpl
实现package com.luxiu.hello.spring.service.impl;
import com.luxiu.hello.spring.service.UserService;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName UserServiceImpl
* @date 2020/4/13 21:18
* @company
*/
public class UserServiceImpl implements UserService {
public void sayHi() {
System.out.println("Hello Spring");
}
}
在 src/main/resources
目录下创建 spring-context.xml
配置文件,从现在开始类的实例化工作交给 Spring 容器管理(IoC),配置文件如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.luxiu.hello.spring.service.impl.UserServiceImpl" />
beans>
:用于定义一个实例对象。一个实例对应一个 bean 元素。
id
:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
class
:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
创建一个 UserTests
测试类,测试对象是否能够通过 Spring 来创建,代码如下:
package com.luxiu.hello.spring.tests;
import com.luxiu.hello.spring.service.UserService;
import com.luxiu.hello.spring.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName UserTests
* @date 2020/4/13 21:22
* @company
*/
public class UserTests {
@Test
public void testByNew(){
UserService userService = new UserServiceImpl();
userService.sayHi();
}
@Test
public void testBySpring(){
// 获取 Spring 容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
// 从 Spring 容器中获取对象
UserService userService = (UserService) applicationContext.getBean("userService");
userService.sayHi();
}
}
截止目前为止,咱们 Bean 的装配方式是通过代码 getBean()
的方式从容器获取指定的 Bean 实例,容器首先会调用 Bean 类的无参构造器,创建空值的实例对象。除了使用 getBean()
的装配方式外,还可以使用注解的装配方式。
在学习 Bean 的装配方式之前,我们先了解一下 Bean 的作用域。当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持 5 种作用域。
的实例都是一个新的实例。注意事项:
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 Bean 实例。Spring 中使用注解, 需要在原有 Spring 运行环境基础上再做一些改变
需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
<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:annotation-config />
<context:component-scan base-package="com.luxiu.shop"/>
beans>
需要在类上使用注解 @Component
,该注解的 value 属性用于指定该 bean 的 id 值。
@Component(value = "student")
public class Student {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Spring 还提供了 3 个功能基本和 @Component
等效的注解:
@Repository
:用于对 DAO 实现类进行注解@Service
:用于对 Service 实现类进行注解@Controller
:用于对 Controller 实现类进行注解需要在类上使用注解 @Scope
,其 value 属性用于指定作用域。默认为 singleton。
需要在属性上使用注解 @Value
,该注解的 value 属性用于指定要注入的值。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
需要在域属性上使用注解 @Autowired
,该注解默认使用 按类型自动装配 Bean 的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
需要在域属性上使用注解 @Resource
,该注解有一个 name
属性,可以创建指定的 bean
@Resource(name = "userService")
private UserService userService;
在方法上使用 @PostConstruct
相当于初始化
注解的好处是,配置方便,直观。但其弊端也显而易见:以硬编码的方式写入到了 Java 代码中,其修改是需要重新编译代码的。
XML 配置方式的最大好处是,对其所做修改,无需编译代码,只需重启服务器即可将新的配置加载。
若注解与 XML 同用,XML 的优先级要高于注解。这样做的好处是,需要对某个 Bean 做修改,只需修改配置文件即可。
具体代码参考 https://github.com/luguangdong/spring-transaction.git
事务原本是数据库中的概念,用于数据访问层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
在 Spring 中通常可以通过以下三种方式来实现对事务的管理:
Spring 的事务管理,主要用到两个事务相关的接口。
事务管理器是 PlatformTransactionManager
接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
该接口定义了 3 个事务方法:
void commit(TransactionStatus status)
:事务的提交TransactionStatus getTransaction(TransactionDefinition definition)
:获取事务的状态void rollback(TranscationStatus status)
:事务的回滚PlatformTransactionManager
接口有两个常用的实现类:
DataSourceTransactionManager
:使用 JDBC 或 MyBatis 进行持久化数据时使用。HibernateTransactionManager
:使用 Hibernate 进行持久化数据时使用。Spring 事务的默认回滚方式是:发生运行时异常回滚
事务定义接口 TransactionDefinition
中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 默认为 REPEATABLE_READ;Oracle 默认为:READ_COMMITTED;
READ_UNCOMMITTED:读未提交。未解决任何并发问题。
READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
REPEATABLE_READ:可重复读。解决脏读、不可重复读。存在幻读。
SERIALIZABLE:串行化。不存在并发问题。
即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 a()
调用 B 事务中的方法 b()
,在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
AspectJ 主要是使用 XML 配置顾问方式自动为每个符合切入点表达式的类生成事务代理。创建测试操作步骤如下:
创建一个名为 spring-transaction
项目,pom.xml
文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.luxiu.spring.transactiongroupId>
<artifactId>spring-transactionartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>jarpackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<log4j.version>1.2.17log4j.version>
<slf4j.version>1.7.25slf4j.version>
<spring.version>4.3.17.RELEASEspring.version>
<alibaba-druid.version>1.1.6alibaba-druid.version>
<mysql.version>5.1.46mysql.version>
<mybatis.version>3.2.8mybatis.version>
<mybaits-spring.version>1.3.1mybaits-spring.version>
<junit.version>4.12junit.version>
<lombok.version>1.16.18lombok.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jul-to-slf4jartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${alibaba-druid.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>${mybatis.version}version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>${mybaits-spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
dependencies>
project>
package com.luxiu.spring.transaction.domain;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
*
* Description: 分类管理
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategory
* @date 2020/5/23 21:56
* @company https://www.singlewindow.cn/
*/
@Data
public class TbContentCategory implements Serializable {
/**
* 类目ID
*/
private Long id;
/**
* 父类目ID=0时,代表的是一级的类目
*/
private Long parentId;
/**
* 分类名称
*/
private String name;
/**
* 状态。可选值:1(正常),2(删除)
*/
private Integer status;
/**
* 排列序号,表示同级类目的展现次序,如数值相等则按名称次序排列。取值范围:大于零的整数
*/
private Integer sortOrder;
/**
* 该类目是否为父类目,1为true,0为false
*/
private Boolean isParent;
/**
* 创建时间
*/
private Date created;
/**
* 创建时间
*/
private Date updated;
private static final long serialVersionUID = 1L;
}
package com.luxiu.spring.transaction.domain;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
*
* Description: 内容管理
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContent
* @date 2020/5/23 21:58
* @company https://www.singlewindow.cn/
*/
@Data
public class TbContent implements Serializable {
private Long id;
/**
* 内容类目ID
*/
private Long categoryId;
/**
* 内容标题
*/
private String title;
/**
* 子标题
*/
private String subTitle;
/**
* 标题描述
*/
private String titleDesc;
/**
* 链接
*/
private String url;
/**
* 图片绝对路径
*/
private String pic;
/**
* 图片2
*/
private String pic2;
/**
* 内容
*/
private String content;
private Date created;
private Date updated;
private TbContentCategory tbContentCategory;
private static final long serialVersionUID = 1L;
}
package com.luxiu.spring.transaction.mapper;
import com.luxiu.spring.transaction.domain.TbContentCategory;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryMapper
* @date 2020/5/23 21:56
* @company https://www.singlewindow.cn/
*/
public interface TbContentCategoryMapper {
int deleteByPrimaryKey(Long id);
int insert(TbContentCategory record);
int insertSelective(TbContentCategory record);
TbContentCategory selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(TbContentCategory record);
int updateByPrimaryKey(TbContentCategory record);
}
package com.luxiu.spring.transaction.mapper;
import com.luxiu.spring.transaction.domain.TbContent;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentMapper
* @date 2020/5/23 21:58
* @company https://www.singlewindow.cn/
*/
public interface TbContentMapper {
int deleteByPrimaryKey(Long id);
int insert(TbContent record);
int insertSelective(TbContent record);
TbContent selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(TbContent record);
int updateByPrimaryKey(TbContent record);
}
package com.luxiu.spring.transaction.service;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryService
* @date 2020/5/23 22:02
* @company https://www.singlewindow.cn/
*/
public interface TbContentCategoryService {
void save(TbContentCategory tbContentCategory, TbContent tbContent);
}
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceImpl
* @date 2020/5/23 22:03
* @company https://www.singlewindow.cn/
*/
@Service(value = "TbContentCategoryServiceConf")
class TbContentCategoryServiceConfImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
tbContentCategoryMapper.insert(tbContentCategory);
tbContentService.save(tbContent);
}
}
package com.luxiu.spring.transaction.service;
import com.luxiu.spring.transaction.domain.TbContent;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentService
* @date 2020/5/23 22:05
* @company https://www.singlewindow.cn/
*/
public interface TbContentService {
void save(TbContent tbContent);
}
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.mapper.TbContentMapper;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentServiceImpl
* @date 2020/5/23 22:06
* @company https://www.singlewindow.cn/
*/
@Service
public class TbContentServiceImpl implements TbContentService {
@Autowired
private TbContentMapper tbContentMapper;
public void save(TbContent tbContent) {
tbContentMapper.insert(tbContent);
}
}
spring-context.xml
<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" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.luxiu.spring.transaction">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.luxiu.spring.transaction.service.*.*(..))" />
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut" />
aop:config>
beans>
spring-context-druid.xml
<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 ignore-unresolvable="true" location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.connectionURL}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="${jdbc.pool.init}"/>
<property name="minIdle" value="${jdbc.pool.minIdle}"/>
<property name="maxActive" value="${jdbc.pool.maxActive}"/>
<property name="maxWait" value="60000"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="${jdbc.testSql}"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="filters" value="stat"/>
bean>
beans>
spring-context-mybatis.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.luxiu.spring.transaction.domain"/>
<property name="mapperLocations" value="classpath:/mapper/**/*.xml"/>
<property name="configLocation" value="classpath:/mybatis-config.xml">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.luxiu.spring.transaction.mapper" />
bean>
beans>
mybatis-config.xml
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
<setting name="cacheEnabled" value="false"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="NULL"/>
settings>
configuration>
TbContentCategoryMapper.xml
<mapper namespace="com.luxiu.spring.transaction.mapper.TbContentCategoryMapper">
<resultMap id="BaseResultMap" type="com.luxiu.spring.transaction.domain.TbContentCategory">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="parent_id" jdbcType="BIGINT" property="parentId" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="status" jdbcType="INTEGER" property="status" />
<result column="sort_order" jdbcType="INTEGER" property="sortOrder" />
<result column="is_parent" jdbcType="BOOLEAN" property="isParent" />
<result column="created" jdbcType="TIMESTAMP" property="created" />
<result column="updated" jdbcType="TIMESTAMP" property="updated" />
resultMap>
<sql id="Base_Column_List">
id, parent_id, `name`, `status`, sort_order, is_parent, created, updated
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tb_content_category
where id = #{id,jdbcType=BIGINT}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tb_content_category
where id = #{id,jdbcType=BIGINT}
delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.luxiu.spring.transaction.domain.TbContentCategory" useGeneratedKeys="true">
insert into tb_content_category (parent_id, `name`, `status`,
sort_order, is_parent, created,
updated)
values (#{parentId,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER},
#{sortOrder,jdbcType=INTEGER}, #{isParent,jdbcType=BOOLEAN}, #{created,jdbcType=TIMESTAMP},
#{updated,jdbcType=TIMESTAMP})
insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.luxiu.spring.transaction.domain.TbContentCategory" useGeneratedKeys="true">
insert into tb_content_category
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="parentId != null">
parent_id,
if>
<if test="name != null">
`name`,
if>
<if test="status != null">
`status`,
if>
<if test="sortOrder != null">
sort_order,
if>
<if test="isParent != null">
is_parent,
if>
<if test="created != null">
created,
if>
<if test="updated != null">
updated,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="parentId != null">
#{parentId,jdbcType=BIGINT},
if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
if>
<if test="status != null">
#{status,jdbcType=INTEGER},
if>
<if test="sortOrder != null">
#{sortOrder,jdbcType=INTEGER},
if>
<if test="isParent != null">
#{isParent,jdbcType=BOOLEAN},
if>
<if test="created != null">
#{created,jdbcType=TIMESTAMP},
if>
<if test="updated != null">
#{updated,jdbcType=TIMESTAMP},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.luxiu.spring.transaction.domain.TbContentCategory">
update tb_content_category
<set>
<if test="parentId != null">
parent_id = #{parentId,jdbcType=BIGINT},
if>
<if test="name != null">
`name` = #{name,jdbcType=VARCHAR},
if>
<if test="status != null">
`status` = #{status,jdbcType=INTEGER},
if>
<if test="sortOrder != null">
sort_order = #{sortOrder,jdbcType=INTEGER},
if>
<if test="isParent != null">
is_parent = #{isParent,jdbcType=BOOLEAN},
if>
<if test="created != null">
created = #{created,jdbcType=TIMESTAMP},
if>
<if test="updated != null">
updated = #{updated,jdbcType=TIMESTAMP},
if>
set>
where id = #{id,jdbcType=BIGINT}
update>
<update id="updateByPrimaryKey" parameterType="com.luxiu.spring.transaction.domain.TbContentCategory">
update tb_content_category
set parent_id = #{parentId,jdbcType=BIGINT},
`name` = #{name,jdbcType=VARCHAR},
`status` = #{status,jdbcType=INTEGER},
sort_order = #{sortOrder,jdbcType=INTEGER},
is_parent = #{isParent,jdbcType=BOOLEAN},
created = #{created,jdbcType=TIMESTAMP},
updated = #{updated,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=BIGINT}
update>
mapper>
TbContentMapper.xml
<mapper namespace="com.luxiu.spring.transaction.mapper.TbContentMapper">
<resultMap id="BaseResultMap" type="com.luxiu.spring.transaction.domain.TbContent">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="category_id" jdbcType="BIGINT" property="categoryId" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="sub_title" jdbcType="VARCHAR" property="subTitle" />
<result column="title_desc" jdbcType="VARCHAR" property="titleDesc" />
<result column="url" jdbcType="VARCHAR" property="url" />
<result column="pic" jdbcType="VARCHAR" property="pic" />
<result column="pic2" jdbcType="VARCHAR" property="pic2" />
<result column="content" jdbcType="LONGVARCHAR" property="content" />
<result column="created" jdbcType="TIMESTAMP" property="created" />
<result column="updated" jdbcType="TIMESTAMP" property="updated" />
resultMap>
<sql id="Base_Column_List">
id, category_id, title, sub_title, title_desc, url, pic, pic2, content, created,
updated
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from tb_content
where id = #{id,jdbcType=BIGINT}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from tb_content
where id = #{id,jdbcType=BIGINT}
delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.luxiu.spring.transaction.domain.TbContent" useGeneratedKeys="true">
insert into tb_content (category_id, title, sub_title,
title_desc, url, pic,
pic2, content, created,
updated)
values (#{categoryId,jdbcType=BIGINT}, #{title,jdbcType=VARCHAR}, #{subTitle,jdbcType=VARCHAR},
#{titleDesc,jdbcType=VARCHAR}, #{url,jdbcType=VARCHAR}, #{pic,jdbcType=VARCHAR},
#{pic2,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}, #{created,jdbcType=TIMESTAMP},
#{updated,jdbcType=TIMESTAMP})
insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.luxiu.spring.transaction.domain.TbContent" useGeneratedKeys="true">
insert into tb_content
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="categoryId != null">
category_id,
if>
<if test="title != null">
title,
if>
<if test="subTitle != null">
sub_title,
if>
<if test="titleDesc != null">
title_desc,
if>
<if test="url != null">
url,
if>
<if test="pic != null">
pic,
if>
<if test="pic2 != null">
pic2,
if>
<if test="content != null">
content,
if>
<if test="created != null">
created,
if>
<if test="updated != null">
updated,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="categoryId != null">
#{categoryId,jdbcType=BIGINT},
if>
<if test="title != null">
#{title,jdbcType=VARCHAR},
if>
<if test="subTitle != null">
#{subTitle,jdbcType=VARCHAR},
if>
<if test="titleDesc != null">
#{titleDesc,jdbcType=VARCHAR},
if>
<if test="url != null">
#{url,jdbcType=VARCHAR},
if>
<if test="pic != null">
#{pic,jdbcType=VARCHAR},
if>
<if test="pic2 != null">
#{pic2,jdbcType=VARCHAR},
if>
<if test="content != null">
#{content,jdbcType=LONGVARCHAR},
if>
<if test="created != null">
#{created,jdbcType=TIMESTAMP},
if>
<if test="updated != null">
#{updated,jdbcType=TIMESTAMP},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.luxiu.spring.transaction.domain.TbContent">
update tb_content
<set>
<if test="categoryId != null">
category_id = #{categoryId,jdbcType=BIGINT},
if>
<if test="title != null">
title = #{title,jdbcType=VARCHAR},
if>
<if test="subTitle != null">
sub_title = #{subTitle,jdbcType=VARCHAR},
if>
<if test="titleDesc != null">
title_desc = #{titleDesc,jdbcType=VARCHAR},
if>
<if test="url != null">
url = #{url,jdbcType=VARCHAR},
if>
<if test="pic != null">
pic = #{pic,jdbcType=VARCHAR},
if>
<if test="pic2 != null">
pic2 = #{pic2,jdbcType=VARCHAR},
if>
<if test="content != null">
content = #{content,jdbcType=LONGVARCHAR},
if>
<if test="created != null">
created = #{created,jdbcType=TIMESTAMP},
if>
<if test="updated != null">
updated = #{updated,jdbcType=TIMESTAMP},
if>
set>
where id = #{id,jdbcType=BIGINT}
update>
<update id="updateByPrimaryKey" parameterType="com.luxiu.spring.transaction.domain.TbContent">
update tb_content
set category_id = #{categoryId,jdbcType=BIGINT},
title = #{title,jdbcType=VARCHAR},
sub_title = #{subTitle,jdbcType=VARCHAR},
title_desc = #{titleDesc,jdbcType=VARCHAR},
url = #{url,jdbcType=VARCHAR},
pic = #{pic,jdbcType=VARCHAR},
pic2 = #{pic2,jdbcType=VARCHAR},
content = #{content,jdbcType=LONGVARCHAR},
created = #{created,jdbcType=TIMESTAMP},
updated = #{updated,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=BIGINT}
update>
mapper>
log4j.properties
log4j.rootLogger=INFO, console, file
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=logs/log.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.A3.MaxFileSize=1024KB
log4j.appender.A3.MaxBackupIndex=10
log4j.appender.file.layout.ConversionPattern=%d %p [%c] - %m%n
jdbc.properties
#============================#
#==== Database settings ====#
#============================#
# JDBC
# MySQL 8.x: com.mysql.cj.jdbc.Driver
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://192.168.137.128:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=123456
# JDBC Pool
jdbc.pool.init=1
jdbc.pool.minIdle=3
jdbc.pool.maxActive=20
# JDBC Test
jdbc.testSql=SELECT 'x' FROM DUAL
package com.luxiu.spring.transaction.tests;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TestSpringTransaction
* @date 2020/5/23 22:12
* @company https://www.singlewindow.cn/
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring-context.xml", "classpath:spring-context-druid.xml", "classpath:spring-context-mybatis.xml"})
public class TestSpringTransaction {
@Resource(name = "TbContentCategoryServiceConf")
private TbContentCategoryService TbContentCategoryServiceConf;
/**
* 测试说明
*
* 将spring-context.xml配置文件中关于事务的配置开启
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据回滚,数据没有插入表中,事物生效
*
*
* 将spring-context.xml配置文件中关于事务的配置关闭
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据没有回滚,数据插入表中
*
*
*/
@Test
public void test() {
TbContentCategory tbContentCategory = new TbContentCategory();
tbContentCategory.setId(1L);
tbContentCategory.setName("测试事务分类");
TbContent tbContent = new TbContent();
tbContent.setCategoryId(44L);
tbContent.setTbContentCategory(tbContentCategory);
// 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
tbContent.setTitle("测试事务内容");
TbContentCategoryServiceConf.save(tbContentCategory, tbContent);
}
}
运行观察事务效果:
通过 @Transactional
注解方式,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx
标签,以告诉 Spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器。
<tx:annotation-driven transaction-manager="transactionManager" />
@Transactional
注解简介@Transactional
的所有可选属性:
propagation
:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED
。isolation
:用于设置事务的隔离级别。该属性类型为 Isolation 枚举 ,默认值为 Isolation.DEFAULT
。readOnly
:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false
。timeout
:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。rollbackFor
:指定需要回滚的异常类。类型为 Class[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。rollbackForClassName
:指定需要回滚的异常类类名。类型为 String[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。noRollbackFor
:指定不需要回滚的异常类。类型为 Class[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。noRollbackForClassName
: 指定不需要回滚的异常类类名。类型为 String[]
,默认值为空数组。当然,若只有一个异常类时,可以不使用数组。需要注意的是,@Transactional
若用在方法上,只能用于 public
方法上。对于其他非 public
方法,如果加上了注解 @Transactional
,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public
方法上的 @Transaction
注解。
若 @Transaction
注解在类上,则表示该类上所有的方法均将在执行时织入事务。
@Transaction
注解使用起来很简单,只需要在需要增加事务的业务类上增加 @Transaction
注解即可,案例代码如下:
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceAnnotationImpl
* @date 2020/5/23 23:11
* @company https://www.singlewindow.cn/
*/
@Transactional
@Service(value = "TbContentCategoryServiceAnnotation")
public class TbContentCategoryServiceAnnotationImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
tbContentCategoryMapper.insert(tbContentCategory);
tbContentService.save(tbContent);
}
}
package com.luxiu.spring.transaction.tests;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TestSpringTransaction
* @date 2020/5/23 22:12
* @company https://www.singlewindow.cn/
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring-context.xml", "classpath:spring-context-druid.xml", "classpath:spring-context-mybatis.xml"})
public class TestSpringTransaction {
@Resource(name = "TbContentCategoryServiceConf")
private TbContentCategoryService TbContentCategoryServiceConf;
@Resource(name = "TbContentCategoryServiceAnnotation")
private TbContentCategoryService TbContentCategoryServiceAnnotation;
/**
* 测试说明: 使用配置文件的方式来测试spring的事物
*
* 将spring-context.xml配置文件中关于事务的配置开启
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据回滚,数据没有插入表中,事物生效
*
*
* 将spring-context.xml配置文件中关于事务的配置关闭
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据没有回滚,数据插入表中
*
*
*/
@Test
public void testTbContentCategoryServiceConf() {
TbContentCategory tbContentCategory = new TbContentCategory();
tbContentCategory.setId(1L);
tbContentCategory.setName("测试事务分类");
TbContent tbContent = new TbContent();
tbContent.setCategoryId(45L);
tbContent.setTbContentCategory(tbContentCategory);
// 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
tbContent.setTitle("测试事务内容");
TbContentCategoryServiceConf.save(tbContentCategory, tbContent);
}
/**
* 测试说明: 使用注解的方式来测试spring的事物
*
* 将spring-context.xml配置文件中关于事务的配置开启
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据回滚,数据没有插入表中,事物生效
*
*
* 将spring-context.xml配置文件中关于事务的配置关闭
*
* 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
* tbContent.setTitle("测试事务内容");
* 测试结果:
* tb_content_category 表中的数据没有回滚,数据插入表中
*
*
*/
@Test
public void testTbContentCategoryServiceAnnotation() {
TbContentCategory tbContentCategory = new TbContentCategory();
tbContentCategory.setId(1L);
tbContentCategory.setName("测试事务分类");
TbContent tbContent = new TbContent();
tbContent.setCategoryId(48L);
tbContent.setTbContentCategory(tbContentCategory);
// 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
tbContent.setTitle("测试事务内容");
TbContentCategoryServiceAnnotation.save(tbContentCategory, tbContent);
}
}
运行观察事务效果:
内容实现类中模拟 int i = 1/0异常,该类中没有处理异常
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.mapper.TbContentMapper;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentServiceImpl
* @date 2020/5/23 22:06
* @company https://www.singlewindow.cn/
*/
@Service
@Transactional
public class TbContentServiceImpl implements TbContentService {
@Autowired
private TbContentMapper tbContentMapper;
public void save(TbContent tbContent) {
tbContentMapper.insert(tbContent);
int i = 1/0;
}
}
内容分类实现类TbContentCategoryServiceRollbackOnlyImpl中的save()方法调用内容实现类TbContentServiceImpl的save方法,并且捕获异常
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceRollbackOnlyImpl
* @date 2020/5/23 23:11
* @company https://www.singlewindow.cn/
*/
@Transactional
@Service(value = "tbContentCategoryServiceRollbackOnly")
public class TbContentCategoryServiceRollbackOnlyImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
try {
tbContentCategoryMapper.insert(tbContentCategory);
tbContentService.save(tbContent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
编写测试类来验证
@Resource(name = "tbContentCategoryServiceRollbackOnly")
private TbContentCategoryService tbContentCategoryServiceRollbackOnly;
/**
* Spring事务嵌套引发的血案测试
* Transaction rolled back because it has been marked as rollback-only
*/
@Test
public void testTbContentCategoryServiceByRollbackOnly() {
TbContentCategory tbContentCategory = new TbContentCategory();
tbContentCategory.setId(1L);
tbContentCategory.setName("测试事务分类");
TbContent tbContent = new TbContent();
tbContent.setCategoryId(48L);
tbContent.setTbContentCategory(tbContentCategory);
// 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
tbContent.setTitle("测试事务内容");
tbContentCategoryServiceRollbackOnly.save(tbContentCategory, tbContent);
}
运行结果为:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
spring的@Transactional可以注解到方法上或者类上,并且只有在该类被作为spring容器托管的bean的时候,也就是使用@autowird被注入到其他类中使用,调用该类的方法的时候才生效, 因为此时调用方法会被spring容器的 TransactionInterceptor拦截器拦截并开启事务 。
使用默认的@Transactional 注解 ,事物的传播性默认为Propagation.REQUIRED ,调用其他类的事务方法 tbContentService.save(tbContent); 这个方法开启的事务是默认Propagation.REQUIRED ,也就是沿用外层调用方法tbContentCategoryServiceRollbackOnly.save(tbContentCategory, tbContent)的事务 (如果有事务存在 则使用原事务 如果不存在则开启新事务), 外层方法开启了一个事务 ,内层方法调用的是其他类的事务方法 ,内层方法发现异常了, 会标记整个事务为roll-back ,但是外层方法捕获异常 return的时候 会执行commit事务, 但是此时发现已经标记异常 所以才会出错。
看看处理回滚的源码:
在最后commit方法中,会判断到时rollback,就会调用processRollback(defStatus,true),第二个参数会赋值给unexpectedRollback,所以就会报这个错了。分发
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
} catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
Spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeException的异常
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
语句,手动回滚,这样上层就无需去处理异常(这也是比较推荐的做法)
,这个方法也能解决,但显然影响到全局的事务属性,所以极力不推荐使用。这种方式如果业务场景不需要查看错误,则可以不需要catch后throw new runtimeExcetpion(),换句话就是不做任何处理
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceRollbackOnlyImpl
* @date 2020/5/23 23:11
* @company https://www.singlewindow.cn/
*/
@Transactional
@Service(value = "tbContentCategoryServiceRollbackOnly")
public class TbContentCategoryServiceRollbackOnlyImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
try {
tbContentCategoryMapper.insert(tbContentCategory);
tbContentService.save(tbContent);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceRollbackOnlyImpl
* @date 2020/5/23 23:11
* @company https://www.singlewindow.cn/
*/
@Transactional
@Service(value = "tbContentCategoryServiceRollbackOnly")
public class TbContentCategoryServiceRollbackOnlyImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
try {
tbContentCategoryMapper.insert(tbContentCategory);
tbContentService.save(tbContent);
} catch (Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
外部方法不使用事物
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.mapper.TbContentMapper;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentServiceImpl
* @date 2020/5/23 22:06
* @company https://www.singlewindow.cn/
*/
@Service
public class TbContentServiceImpl implements TbContentService {
@Autowired
private TbContentMapper tbContentMapper;
public void save(TbContent tbContent) {
tbContentMapper.insert(tbContent);
int i = 1/0;
}
}
外部方法开启一个新的事物
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.mapper.TbContentMapper;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description:
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentServiceImpl
* @date 2020/5/23 22:06
* @company https://www.singlewindow.cn/
*/
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TbContentServiceImpl implements TbContentService {
@Autowired
private TbContentMapper tbContentMapper;
public void save(TbContent tbContent) {
tbContentMapper.insert(tbContent);
int i = 1/0;
}
}
配置数据源事物管理器globalRollbackOnParticipationFailure属性为false
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<property name="globalRollbackOnParticipationFailure" value="false" />
bean>
如果是同一个类中的方法调用该类的其他方法 ,第二个方法的事务是不起作用的
package com.luxiu.spring.transaction.service.impl;
import com.luxiu.spring.transaction.domain.TbContent;
import com.luxiu.spring.transaction.domain.TbContentCategory;
import com.luxiu.spring.transaction.mapper.TbContentCategoryMapper;
import com.luxiu.spring.transaction.mapper.TbContentMapper;
import com.luxiu.spring.transaction.service.TbContentCategoryService;
import com.luxiu.spring.transaction.service.TbContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* Description: 测试git合并部分代码
*
*
*
* @author luguangdong
* @version 1.0
* @ClassName TbContentCategoryServiceRollbackOnlyImpl
* @date 2020/5/23 23:11
* @company https://www.singlewindow.cn/
*/
@Transactional
@Service(value = "tbContentCategoryServiceRollbackOnly")
public class TbContentCategoryServiceRollbackOnlyImpl implements TbContentCategoryService {
@Autowired
private TbContentCategoryMapper tbContentCategoryMapper;
@Autowired
private TbContentService tbContentService;
public void save(TbContentCategory tbContentCategory, TbContent tbContent) {
try {
tbContentCategoryMapper.insert(tbContentCategory);
save(tbContent);
} catch (Exception e) {
e.printStackTrace();
}
}
@Autowired
private TbContentMapper tbContentMapper;
public void save(TbContent tbContent) {
tbContentMapper.insert(tbContent);
int i = 1/0;
}
}
此时执行测试类后发现,两张表的数据都入库了
/**
* Spring事务嵌套引发的血案测试
* Transaction rolled back because it has been marked as rollback-only
*/
@Test
public void testTbContentCategoryServiceByRollbackOnly() {
TbContentCategory tbContentCategory = new TbContentCategory();
tbContentCategory.setId(1L);
tbContentCategory.setName("测试事务分类20200706");
TbContent tbContent = new TbContent();
tbContent.setCategoryId(48L);
tbContent.setTbContentCategory(tbContentCategory);
// 在这里你可以将内容设置为超出数据库字段的存储范围来验证事务是否开启
tbContent.setTitle("测试事务内容20200706");
tbContentCategoryServiceRollbackOnly.save(tbContentCategory, tbContent);
}
spring的@Transactional可以注解到方法上或者类上,并且只有在该类被作为spring容器托管的bean的时候,也就是使用@autowird被注入到其他类中使用,调用该类的方法的时候才生效, 因为此时调用方法会被spring容器的 TransactionInterceptor拦截器拦截并开启事务 。
使用默认的@Transactional 注解 ,事物的传播性默认为Propagation.REQUIRED 。
同一个类中的方法调用该类的其他方法 ,第二个方法的事务是不起作用的,所以入库成功。
调用其他类的事务方法 ,其他类的方法如果有事务存在则使用原事务 如果不存在则开启新事务, 外层方法开启了一个事务 ,内层方法调用的是其他类的事务方法 ,内层方法发现异常了, 会标记整个事务为roll-back ,但是外层方法捕获异常 return的时候 会执行commit事务, 但是此时发现已经标记异常 所以才会出错。
启动容器时需要自动装载 ApplicationContext
,Spring 提供的 ContextLoaderListener
就是为了自动装配 ApplicationContext
的配置信息
需要在 pom.xml
增加 org.springframework:spring-web
依赖
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>4.3.17.RELEASEversion>
dependency>
web.xml
web.xml
配置如下
contextConfigLocation
classpath:spring-context*.xml
org.springframework.web.context.ContextLoaderListener
当一个类实现了这个接口(ApplicationContextAware
)之后,这个类就可以方便获得 ApplicationContext
中的所有 bean。换句话说,就是这个类可以直接获取 Spring 配置文件中,所有有引用到的 Bean 对象。
package com.luxiu.beyond.shop.commons.context;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class SpringContext implements ApplicationContextAware, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(SpringContext.class);
private static ApplicationContext applicationContext;
/**
* 获取存储在静态变量中的 ApplicationContext
* @return
*/
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
/**
* 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
* @param name
* @param
* @return
*/
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
* @param clazz
* @param
* @return
*/
public static <T> T getBean(Class<T> clazz) {
assertContextInjected();
return applicationContext.getBean(clazz);
}
/**
* 实现 DisposableBean 接口,在 Context 关闭时清理静态变量
* @throws Exception
*/
public void destroy() throws Exception {
logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext);
applicationContext = null;
}
/**
* 实现 ApplicationContextAware 接口,注入 Context 到静态变量中
* @param applicationContext
* @throws BeansException
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContext.applicationContext = applicationContext;
}
/**
* 断言 Context 已经注入
*/
private static void assertContextInjected() {
Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext");
}
}
还需要在 spring-context.xml
配置文件中装配
;
注意:请将该 Bean 放在配置顶部,否则使用时会报错
需要在 pom.xml
中增加 org.apache.commons:commons-lang3
依赖
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.5version>
dependency>
截止目前所学知识点,完整的 pom.xml
如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.luxiu.beyond.shopgroupId>
<artifactId>shopartifactId>
<version>1.0.0-SNAPSHOTversion>
<packaging>warpackaging>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.17.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.3.17.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jul-to-slf4jartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.5version>
dependency>
dependencies>
project>