7. AOP 面向切面编程
7.1 AOP概述
AOP英文名为Aspect Oriented Programming,意为面向切面编程,通过预编译方式和运行期间动态代理实现程序功能统一维护的一种技术。AOP是OOP的延续,是Spring框架中的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
7.2 AOP的作用
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
7.3 AOP应用场景
应用场景:
-
事务管理
-
记录日志
-
监测性能(统计方法运行时间)
-
权限控制
-
缓存
主要目的:
将日志记录,性能统计,安全控制,事务处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
7.4 Spring AOP的术语
-
**Joinpoint(连接点)😗*所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点.
-
**Pointcut(切入点)😗*所谓的切入点是指我们要对哪些Joinpoint进行拦截的定义
-
**Advice(通知/增强)😗*所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的事情)
-
**Introduction(引介)😗*引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field
-
Target代理的目标对象
-
**Weaving(织rget(目标):入)😗*是指把增强应用到目标对象来创建的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
-
**Proxy(代理)😗*一个类被AOP织入增强后,就产生一个结果代理类
-
**Aspect(切面)😗*是切入点和通知(引介)的结合
建议采用图解方式理解:
7.5 基于AspectJ的AOP编程
Spring AOP通知分类:
通知类型 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法正常执行,返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法包装起来 |
7.5.1 AspectJ各种通知编程案例(xml方式 )
- 导入依赖 spring-aop spring-aspects (整合AspectJ) AspectJ的框架依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
-
编码
编写各种通知的日志记录类
在所有增强方法中,都可以接受JoinPoint类型连接点参数对象,可以获取当前增强是哪个类的哪个方法,还可以获取调用目标方法传递参数
package org.aop.aspectJ.logger;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
@Slf4j
public class UserBizLogger {
/**
* 前置增强
* @param jp
*/
public void before(JoinPoint jp){
log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));
}
/**
* 后置增强
* @param jp
* @param result 返回值
*/
public void afterReturning(JoinPoint jp,Object result){
log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法,方法的返回值:"+result);
}
/**
* 异常增强
* @param jp
* @param e 增强的异常类型
*/
public void afterThrowing(JoinPoint jp,SQLException e){
log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法发生异常"+e);
}
/**
* 最终增强
* @param jp
*/
public void after(JoinPoint jp){
log.info(jp.getSignature().getName()+"方法执行结束");
}
/**
* 环绕增强
* @param jp
* @throws Throwable
*/
public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{
log.info(jp.getSignature().getName()+"方法开始执行");
try {
//执行目标方法
Object rs = jp.proceed();
log.info(jp.getSignature().getName()+"方法正常执行完");
return rs;
} catch (SQLException e) {
log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法发生异常"+e);
throw e;
}
}
}
-
配置切入点和切面
引入aop命名空间:
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
配置:
<!-- 配置增强方法所在的Bean -->
<bean id="userBizLogger" class="org.aop.aspectJ.logger.UserBizLogger"></bean>
<!-- 配置AspectJ的AOP -->
<aop:config>
<aop:pointcut expression="execution( * org.aop.service..*.*(..))" id="pointcut"/>
<!-- 引用增强方法的bean -->
<aop:aspect ref="userBizLogger">
<!--
前置增强, method: 前置增强的方法
-->
<aop:before method="before" pointcut-ref="pointcut"/>
<!--
后置增强
returning: 返回值,对应方法参数
-->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<!--
异常增强
throwing: 异常的类型, 对应方法的异常参数
-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<!--
最终增强
-->
<aop:after method="after" pointcut-ref="pointcut"/>
<!--
环绕增强
-->
<aop:around method="aroundLogger" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
切入点说明
execution(修饰符? 返回值 方法名(参数) 异常?)
execution(* *(…)) 匹配所有spring管理对象所有方法, 第一个*任意返回值 ,第二个*任意方法, … 任意参数
execution(* org.suke.spring…*.*(…) ):匹配org.suke.spring包及其子包所有类的所有方法
execution(* org.suke.spring.*.*(…)) 匹配org.suke.spring包中所有对象所有方法
execution(* org.suke.spring.UserService.s*(…)) 匹配org.suke.spring包UserService中s开头方法
execute( public * addUser(entity.User)) 匹配addUser方法,返回值任意,参数为entity包User对象
execute( public void *(entity.User)) 匹配返回值为void,参数为entity包User的所有方法
execute( public void addUser(…)) 匹配返回值为void,参数任意的addUser方法
测试效果:
7.5.2 AspectJ AOP注解编程
编写增强类:
package org.aop.aspectJ.logger;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //把该类定义为切面类
public class UserBizLogger {
private static final Logger log =Logger.getLogger(UserBizLogger.class);
/**
* 前置增强
* @param jp
*/
@Before("execution( * org.aop.service..*.*(..))") //使用@Before 定义该方法为前置增强
public void before(JoinPoint jp){
log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));
}
/**
* 后置增强
* @param jp
* @param result 返回值
*/
//使用@AfterReturning 定义该方法为后置增强方法
@AfterReturning(pointcut="execution( * org.aop.service..*.*(..))",returning="result")
public void afterReturning(JoinPoint jp,Object result){
log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法,方法的返回值:"+result);
}
/**
* 异常增强
* @param jp
* @param e 增强的异常类型
*/
//使用@AfterThrowing 定义该方法为异常增强
@AfterThrowing(pointcut="execution( * org.aop.service..*.*(..))",throwing="e")
public void afterThrowing(JoinPoint jp,SQLException e){
log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法发生异常"+e);
}
/**
* 最终增强
* @param jp
*/
//使用@After 定义该方法为最终增强
@After("execution( * org.aop.service..*.*(..))")
public void after(JoinPoint jp){
log.info(jp.getSignature().getName()+"方法执行结束");
}
/**
* 环绕增强
* @param jp
* @throws Throwable
*/
//使用@Around 定义该方法为环绕增强
@Around("execution( * org.aop.service..*.*(..))")
public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{
log.info(jp.getSignature().getName()+"方法开始执行");
try {
//执行目标方法
Object rs = jp.proceed();
log.info(jp.getSignature().getName()+"方法正常执行完");
return rs;
} catch (SQLException e) {
log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()
+"方法发生异常"+e);
throw e;
}
}
}
配置:
<!-- 配置增强方法所在的Bean -->
<bean id="userBizLogger" class="org.aop.aspectJ.logger.UserBizLogger"></bean>
<!-- 启动对于AspectJ的注解支持 -->
<aop:aspectj-autoproxy/>
8. Spring与Mybatis整合
由于Spring框架提供了对象的管理,切面编程等非常实用的功能,如果把mybatis的对象交给Spring容器进行解偶合管理,不仅能大大增强系统的灵活性,便于功能扩展,还能通过Spring提供的服务简化编码,减小开发工作量,提高开发效率.实现整合的主要工作就是把Mybatis中的对象配置到Spring容器中,交给Spring来管理.比如说Mapper对象,以及业务层的事物管理。
8.1 基于xml的配置方式
-
导入相关依赖
<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring-webdemo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>spring-webdemo Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.2.15.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!--spring-web--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!-- servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- jsp-api --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--log4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.21</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.21</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.21</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.21</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency> </dependencies> <build> <finalName>spring-webdemo</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
-
编写User实体类和UserMapper接口,以及UserService,UserServlet
package com.suke.pojo; import lombok.Data; @Data public class User { private Integer id; private String name; private String gender; private Integer age; private String address; private String email; private String qq; private String photo; }
package com.suke.mapper; import com.suke.pojo.User; public interface UserMapper { public void insert(User user); public void delete(int id); public User selectById(int id); }
package com.suke.service; import com.suke.pojo.User; public interface UserService { public void addUser(User user); public void deleteUser(Integer uid); public User queryById(Integer uid); }
package com.suke.service.impl; import com.suke.mapper.UserMapper; import com.suke.pojo.User; import com.suke.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void addUser(User user) { userMapper.insert(user); } @Override public void deleteUser(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } userMapper.delete(uid); } @Override public User queryById(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } return userMapper.selectById(uid); } }
package com.suke.web; import com.alibaba.fastjson.JSON; import com.suke.pojo.User; import com.suke.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet(name = "UserServlet",value = "/userServlet") public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); UserService userService = applicationContext.getBean("userService", UserService.class); int id = Integer.parseInt(request.getParameter("id")); User user = userService.queryById(id); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); String json = JSON.toJSONString(user); out.print(json); out.flush(); out.close(); } }
- 在src/main/resource导入spring,log4j等配置文件
-
log4j.properties
log4j.rootCategory=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
-
配置SqlSessionFactory
使用Spring整合Mybatis,首先应将诸如JDBC DataSource 或者MyBatis的SqlSessionFactory等数据访问资源以Bean的形式定义在Spring容器中,交由Spring容器进行管理.
1)把jdbc的参数配置到一个properties的文件,便于修改:
db.properties
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=123
#当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3
jdbc.acquireIncrement=5
#初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3
jdbc.initialPoolSize=5
#连接池中保留的最小连接数。
jdbc.minPoolSize=3
#连接池中保留的最大连接数。Default: 15
jdbc.maxPoolSize=10
因为我们在spring的配置文件需要配置大量的bean,为了方便管理,我们把spring的配置文件拆分成几个文件: applicationnContext-mapper.xml和applicationnContext-tx.xml, 然后在applicationnContext.xml使用import导入上面两个配置文件:
<import resource="applicationContext-mapper.xml"/>
<import resource="applicationContext-tx.xml"/>
然后在 applicationnContext-mapper.xml配置与mybatis相关的配置:
<?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:p="http://www.springframework.org/schema/p"
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">
<!--加载properties
如果是多个properties文件, 文件路径与文件路径使用逗号分割 classpath:db.properties,classpath:log4j.properties
-->
<context:property-placeholder location="classpath:db.properties" file-encoding="UTF-8"/>
<!--配置一个C3p0数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--数据库四大参数-->
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<!--连接池相关的参数-->
<!--初始连接数-->
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
<!--增量, 每次创建几个连接-->
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
<!--最大连接数-->
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
<!--最小连接数 默认值等于初始连接数-->
<property name="minPoolSize" value="${jdbc.minPoolSize}"/>
</bean>
<!--配置SqlSessionFactoryBean-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--加载mybatis全局配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--配置别名-->
<property name="typeAliasesPackage" value="com.suke.pojo"/>
<!--加载sql映射文件 * 任意 一定要注意: classpath*: 如果不写, 后面*无效-->
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"/>
</bean>
<!--配置mapper扫描器 扫描指定包下所有的Mapper接口
创建Mapper接口代理对象, sqlSession.getMapper()并且保存到Spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描那个包下Mapper接口-->
<property name="basePackage" value="com.suke.mapper"/>
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
</beans>
我们导入mybatis的主配置文件,虽然我们不会在该配置文件添加配置,但是还是推荐保留:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration SYSTEM "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
<!-- 全局的setting配置 -->
<!-- 全局的别名配置 -->
<!--映射文件 -->
</configuration>
-
编写UserMapper.xml的sql映射文件
<?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.suke.mapper.UserMapper"> <select id="selectById" resultType="User"> select * from tb_userinfo where id = #{id} </select> <insert id="insert"> <selectKey resultType="int" order="AFTER" keyProperty="id"> SELECT LAST_INSERT_ID() </selectKey> insert into tb_userinfo(name,gender,age,address,email,qq,photo) values(#{name,jdbcType=VARCHAR},#{gender,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{address,jdbcType=VARCHAR} ,#{email,jdbcType=VARCHAR},#{qq,jdbcType=VARCHAR},#{photo,jdbcType=VARCHAR}) </insert> <delete id="delete"> delete from tb_userinfo where id = #{id} </delete> </mapper>
-
在applicationContext-tx.xml配置声明式事务管理
- PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc
或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager
- TransactionDefinition
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
- TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
基于XML的声明式事务控制
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
-
事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
-
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
声明式事务底层就是AOP
<?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:p="http://www.springframework.org/schema/p" 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:component-scan base-package="com.suke"/> <!--配置声明式事务管理--> <!--配置一个事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <!--配置事务策略: 对业务层的方法进行怎么样事务管理--> <tx:attributes> <!-- 对业务层类的具体的方法的设置事务策略 name: 业务层类的方法名, 支持通配符 * 要求你业务层类的方法必须按照你配置和名字起名 查询方法的策略 read-only: 是否只读: true只读, false不只读 默认值, 查询效率,对查询方法设置只读, 增删改方法,一定不能设置只读 propagation: 事务传播机制: REQUIRED:默认值,表示如果存在一个事务,则支持当前事务,如果当前没有事务,则开启一个新的事务. 增删改 SUPPORTS:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则按非事务方式执行, 查询 rollback-for="在那些异常下进行回滚" 默认值 RuntimeException rollback-for="java.lang.Exception" 所有异常都回滚 no-rollback-for="那些异常不回滚" timeout: 超时 默认 -1 永不超时 --> <tx:method name="query*" read-only="true" propagation="SUPPORTS"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> <tx:method name="select*" read-only="true" propagation="SUPPORTS"/> <tx:method name="add*" rollback-for="java.lang.Exception"/> <tx:method name="update*" rollback-for="java.lang.Exception"/> <tx:method name="del*" rollback-for="java.lang.Exception"/> <tx:method name="*" rollback-for="java.lang.Exception"/> </tx:attributes> </tx:advice> <!--把事务通知类txAdvice与业务层的类方法进行织入: AOP的技术--> <aop:config> <!--配置切入点--> <aop:pointcut id="mypointcut" expression="execution(* com.suke.service..*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut"/> </aop:config> </beans>
注意:我们可以通过tx:attributes子标签定制事务属性.事务属性通过tx:method标签进行设置,Spring支持对不同的方法设置不同的事务属性, tx:method标签中的name是必须的用于指定匹配的方法,这里需要对方法名进行约定,可以使用通配符 ( * ).其他属性均可选,用于指定具体的事务机制,这些属性解释如下:
- propagation:事务传播机制:该属性可选值如下:
- REQUIRED:默认值,表示如果存在一个事务,则支持当前事务,如果当前没有事务,则开启一个新的事务. 增删改
- SUPPORTS:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则按非事务方式执行, 查询
- MANDATORY:表示如果存在一个事务,则支持当前事务,如果当前没有事务,则抛出异常
- REQUIRES_NEW:表示总是开启一个新的事务,如果当前有一个事务,则将当前事务挂起,开启新事物执行方法.
- NOT_SUPPORTED:表示总是以非事务方式执行.如果一个事务已经存在,则将这个存在的事务挂起,然后执行方法.
- NEVER:表示总是以非事务方式执行.如果当前存在一个活动的事务,则抛出异常
- NESTED:表示如果当前存在一个活动的事务,则创建一个事务作为当前事务的嵌套事务运行,如果没有当前事务,该取值与REQUIRED相同.
注意:在实际开发中, REQUIRED能够满足大多数的事务需求,可以作为首选的事务传播行为.
- isolation:事务隔离级别.即当前事务和其他事务的隔离程度,在并发事务处理的情况下需要考虑它的设置,该属性可选的值如下:
- DEFAULT:默认值,表示使用数据库默认的事务隔离级别
- READ_UNCOMMITTED:读未提交
- READ_COMMITTED:读已提交 oracle
- REPEATABLE_READ: 可重复读 mysql
- SERIALIZABLE: 串行读
- timeout:事务的超时时间,允许事务运行的最长时间,以秒作为单位,超过给定的时间自动回滚,防止事务执行时间过长而影响到系统性能.该属性需要底层的实现支持,默认为-1,表示不超时.
- rollback-for: 回滚控制, 默认是只对RuntimeException异常进行回滚
- read-only: 是否只读, 默认值:false, 如果是true: 表示只读
-
编写测试类
package com.suke.service; import com.suke.pojo.User; 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; import static org.junit.Assert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class UserServiceTest { @Autowired private UserService userService; @Test public void addUser() { User user = new User(); user.setName("李琦"); user.setGender("男"); user.setAge(21); user.setAddress("北京"); user.setEmail("liqi@163.com"); user.setQq("2312311"); userService.addUser(user); System.out.println(user.getId()); } @Test public void deleteUser() { userService.deleteUser(6); } }
-
基于注解的声明式事务控制
-
在业务实现类的方法上添加
@Transactional
注解进行事务控制package com.suke.service.impl; import com.suke.mapper.UserMapper; import com.suke.pojo.User; import com.suke.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service("userService") @Transactional public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @Transactional(propagation = Propagation.REQUIRED) public void addUser(User user) { userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void deleteUser(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } userMapper.delete(uid); } @Override @Transactional(propagation = Propagation.SUPPORTS,readOnly = true) public User queryById(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } return userMapper.selectById(uid); } }
-
编写 applicationContext.xml 配置文件
<!--事务的注解驱动--> <tx:annotation-driven/> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
e(“李琦”);
user.setGender(“男”);
user.setAge(21);
user.setAddress(“北京”);
user.setEmail(“liqi@163.com”);
user.setQq(“2312311”);
userService.addUser(user);
System.out.println(user.getId());
}
@Test
public void deleteUser() {
userService.deleteUser(6);
}
}
```
基于注解的声明式事务控制
-
在业务实现类的方法上添加
@Transactional
注解进行事务控制package com.suke.service.impl; import com.suke.mapper.UserMapper; import com.suke.pojo.User; import com.suke.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service("userService") @Transactional public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @Transactional(propagation = Propagation.REQUIRED) public void addUser(User user) { userMapper.insert(user); } @Override @Transactional(propagation = Propagation.REQUIRED) public void deleteUser(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } userMapper.delete(uid); } @Override @Transactional(propagation = Propagation.SUPPORTS,readOnly = true) public User queryById(Integer uid) { if(uid == null){ throw new RuntimeException("id不能为空!"); } return userMapper.selectById(uid); } }
-
编写 applicationContext.xml 配置文件
<!--事务的注解驱动--> <tx:annotation-driven/> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>