第五章:Spring下
第五章:Spring下
5.1:AOP
-
场景模拟
-
创建一个新的模块,
spring_proxy_10,并引入下面的jar包。<packaging>jar</packaging><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency> </dependencies> -
声明计算器接口
Calculator,包含加减乘除的抽象方法package com.wang.proxy;public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j); } -
创建带日志功能的实现类
package com.wang.proxy;public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("日志, 方法: add, 参数: " + i + "," + j);int result = i + j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: add, 结果: " + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("日志, 方法: sub, 参数: " + i + "," + j);int result = i - j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: sub, 结果: " + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("日志, 方法: mul, 参数: " + i + "," + j);int result = i * j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: mul, 结果: " + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志, 方法: div, 参数: " + i + "," + j);int result = i / j;System.out.println("方法内部,result: " + result);System.out.println("日志, 方法: div, 结果: " + result);return result;} } -
现有代码缺陷
- 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力。
- 附加功能分散在各个业务功能方法中,不利于统一维护。
-
解决思路
解耦。我们需要把附加功能从业务功能代码中抽取出来。
-
-
代理模式
-
概念
二十三中设计模式中的一种,属于结构性模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时限调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
使用代理之前:

使用代理之后:

代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
目标:被代理"套用"了非核心逻辑代码的类、对象、方法。
-
静态代理
// 创建静态代理类 package com.wang.proxy;public class CalculatorStaticProxy implements Calculator {private CalculatorImpl target;public CalculatorStaticProxy(CalculatorImpl target) {this.target = target;}@Overridepublic int add(int i, int j) {System.out.println("日志, 方法: add, 参数: " + i + "," + j);int result = target.add(i, j);System.out.println("日志, 方法: add, 结果: " + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("日志, 方法: sub, 参数: " + i + "," + j);int result = target.sub(i, j);System.out.println("日志, 方法: sub, 结果: " + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("日志, 方法: mul, 参数: " + i + "," + j);int result = target.mul(i, j);System.out.println("日志, 方法: mul, 结果: " + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志, 方法: div, 参数: " + i + "," + j);int result = target.div(i, j);System.out.println("日志, 方法: div, 结果: " + result);return result;} } 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵性。就拿日志功能来说,将来其他地方也需要附加日志,那还得声明跟多静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
-
动态代理
// 生产代理对象的工厂类(JDK动态代理) package com.wang.proxy;// 动态代理有两种// 1. JDK动态代理: 要求必须有接口, 最终生产的代理类和目标类实现相同的接口, 在com.sun.proxy包下, 类名为$proxy2// 2. cglib动态代理: 最终生产的代理类会继承目标类,并且和目标在相同的包下 public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy() {/*** ClassLoader classLoader: 指定加载动态生成的代理类的类加载器* Class[] interfaces: 获取目标对象的所有接口的class对象的数组* InvocationHandler h: 设置代理类中的抽象方法如何重写*/ClassLoader classLoader = this.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler h = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("日志, 方法: " + method.getName() + ", 参数: " + Arrays.toString(args));// Object proxy: 表示代理对象// Method method: 表示要执行的方法// Object[] args: 表示要执行的方法到的参数列表Object result = method.invoke(target, args);System.out.println("日志, 方法: " + method.getName() + ", 结果: " + result);return result;}};return Proxy.newProxyInstance(classLoader, interfaces, h);} }
-
-
AOP概念及相关术语-
概述
AOP(Aspect Oriemeted Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式来实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。 -
相关术语
-
横切关注点
从每个方法中抽取出来的同一类非核心业务。
-
通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方式。
- 前置通知:在被代理的目标方法前执行。
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)。
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)。
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)。
- 环绕通知:使用
try ... catch ... finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。
-
切面
封装通知方法的类。
-
目标
被代理的目标对象。
-
代理
向目标对象应用通知之后创建的代理对象。
-
连接点
这也是一个纯逻辑概念,不是语法定义的。把方法排成一排,每一个横切位置看成
x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。 -
切入点
定位连接点的方式。
Spring的AOP技术可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
-
-
作用
- 简化代码:把方法中固定位置的重复代码抽取出来,让被抽取的方法更专注于自己的核心功能。
- 代码增强:把特定的功能封装到切面中,看哪里有需要,就网上套,被套用了切面逻辑的方法就被切面增强了。
-
-
基于注解的
AOP-
准备工作
-
创建模块
创建一个新的模块,
spring_aop_11,并引入下面的jar包。<packaging>jar</packaging><dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency> </dependencies> -
导入
spring_proxy_10的Calculator接口和CalculatorImpl实现类并添加@Component注解。 -
创建切面类
package com.wang.aop.annotation;@Component @Aspect public class LoggerAspect { } -
在
spring的配置文件添加包扫描<!-- 创建aop-annotation.xml配置文件--> <!--AOP的注意事项:1. 切面类和目标类都需要交给IOC容器管理2. 切面类都必须通过@Aspect注解标识为一个切面 --> <context:component-scan base-package="com.wang.aop.annotation"></context:component-scan><!-- 开启基于注解的AOP --> <aop:aspectj-autoproxy />
-
-
前置通知
@Before("execution(public int com.wang.aop.annotation.CalculatorImpl.add(int, int))") public void beforeAdviceMethod() {System.out.println("LoggerAspect, 前置通知"); }@Test public void testAOPByAnnotation() {ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");Calculator calculator = ioc.getBean(Calculator.class);calculator.add(1, 1); } -
各种通知
- 前置通知:使用
@Before()注解标识,在被代理的目标方法前执行。 - 返回通知:使用
@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)。 - 异常通知:使用
@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)。 - 后置通知:使用
@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)。 - 环绕通知:使用
@Around注解标识,使用try .. catch .. finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。
- 前置通知:使用
-
各种通知的执行顺序
-
Spring版本5.3.x以前:前置通知 ——> 目标操作 ——> 后置通知 ——> 返回通知或异常通知。
-
Spring版本5.3.x以后:前置通知 ——> 目标操作 ——> 返回通知或异常通知 ——> 后置通知。
-
-
切入点表达式
设置在标识通知的注解的
value属性中。*:表示任意,..表示任意的参数列表。@Before("execution(* com.wang.aop.annotation.*.*(..))") -
重用切入点表达式
@Pointcut("execution(* com.wang.aop.annotation.CalculatorImpl.*(..))") public void pointCut() {} -
获取连接点的信息
在通知方法的参数位置,设置
JoinPoint类型的参数,就可以获取连接点所对应方法的信息。// 获取连接点所对应的签名信息 Signature signature = joinPoint.getSignature(); // 获取连接点所对应方法的参数 Object[] args = joinPoint.getArgs(); -
获取目标方法的返回值
// 只需要通过@AfterReturning注解的returning属性,就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数 @AfterReturning(value = "pointCut()", returning = "result") public void afterReturnAdviceMethod(JoinPoint joinPoint, Object result) {Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect, 方法: " + signature.getName() + ", 结果: " + result); } -
获取目标方法的异常
// 只需要通过AfterThrowing注解的throwing属性,就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数 @AfterThrowing(value = "pointCut()", throwing = "ex") public void afterThrowingAdviceMethod(JoinPoint joinPoint, Exception ex){Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect, 方法: " + signature.getName() + ", 异常通知: " + ex); } -
环绕通知
@Around("pointCut()") public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {Object result = null;try {System.out.println("环绕通知 --> 前置通知");// 表示目标对象方法的执行result = joinPoint.proceed();System.out.println("环绕通知 --> 返回通知");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知 --> 异常通知");} finally {System.out.println("环绕通知 --> 后置通知");}return result; } -
切面的优先级
可以通过
@Order注解的value属性设置优先级,默认值为Integer的最大值,@Order注解的value属性值越小,优先级越高。
-
-
基于
XML的AOP<!-- 复用基于AOP注解环境的方法,删除方法上面的注解,创建aop-xml.xml配置文件--> <!-- 扫描组件 --> <context:component-scan base-package="com.wang.aop.xml"></context:component-scan><aop:config><!-- 一个公共的切入点表达式 --><aop:pointcut id="pointCut" expression="execution(* com.wang.aop.xml.CalculatorImpl.*(..))"/><!-- 将IOC容器中的某个bean设置为切面 --><aop:aspect ref="loggerAspect"><aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before><aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after><aop:after-returning method="afterReturnAdviceMethod" pointcut-ref="pointCut" returning="result"></aop:after-returning><aop:after-throwing method="afterThrowingAdviceMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing><aop:around method="aroundAdviceMethod" pointcut-ref="pointCut" ></aop:around></aop:aspect><aop:aspect ref="validateAspect" order="1"><aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before></aop:aspect> </aop:config>
5.2:声明式事务
-
JdbcTemplate-
简介
Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作。 -
准备工作
-
创建模块
创建一个新的模块,
spring_transaction_12,并引入下面的jar包。<packaging>jar</packaging><dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- Spring 持久化层支持jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.1</version></dependency><!-- Spring 测试相关 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency> </dependencies> -
创建
jdbc.propertiesjdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.username=root jdbc.password=abc123 -
配置
spring的配置文件<!--创建spring-jdbc.xml配置文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"></property><property name="url" value="${jdbc.url}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property> </bean> <!-- 装配JdbcTemplate --> <bean class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property> </bean>
-
-
测试
-
实现添加功能
package com.wang.test;//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方法直接获取IOC容器中bean @RunWith(SpringJUnit4ClassRunner.class) // 设置Spring测试环境的配置文件 @ContextConfiguration("classpath:spring-jdbc.xml") public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testInsert() {String sql = "insert into t_user values(null, ?, ?, ?, ?, ?)";jdbcTemplate.update(sql, "root", "123", 23, "女", "123@qq.com");} } -
实现查询功能
自行创建
User实体类。// 查询一条数据为实体类对象 @Test public void testGetUserById() {String sql = "select * from t_user where id = ?";User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);System.out.println(user); } // 查询多条数据为list集合 @Test public void testGetAllUser() {String sql = "select * from t_user";List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));list.forEach(System.out::println); } // 查询单行单列的值 @Test public void testGetCount() {String sql = "select count(*) from t_user";Integer count = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count); }
-
-
-
声明式事务概念
-
编程式事务
事务概念的相关操作全部通过自己编写代码来实现。
Connection conn = null; try {// 开启事务,关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit(); } catch(Exception e) {// 回滚事务conn.rollBack(); } finally {// 释放资源conn.close(); }编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
-
声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。封装起来,我们只需要在配置文件中进行简单的配置即可完成操作。
好处1:提高开发效率。
好处2:消除了冗余的代码。
好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化。
-
-
基于注解的声明式事务
-
准备工作
-
创建
Spring配置文件:复制spring-jdbc.xml重命名为tx-annotation.xml文件。<context:component-scan base-package="com.wang"></context:component-scan> -
创建表并添加数据
CREATE TABLE t_book(book_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',book_name VARCHAR(20) DEFAULT NULL COMMENT '图书名称',price INT(11) DEFAULT NULL COMMENT '价格',stock INT(10) UNSIGNED DEFAULT NULL COMMENT '库存(无符号)',PRIMARY KEY (`book_id`) ) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO t_book(book_id,book_name,price,stock) VALUES (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);CREATE TABLE t_user(user_id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',username VARCHAR(20) DEFAULT NULL COMMENT '用户名',balance INT(10) UNSIGNED DEFAULT NULL COMMENT '余额(无符号)',PRIMARY KEY (`user_id`) )ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `t_user`(`user_id`,`username`,`balance`) VALUES (1,'admin',50); -
无事务情况
-
dao层:package com.wang.dao;public interface BookDao {// 查询图书价格Integer getPriceByBookId(Integer bookId);// 更新图书的库存void updateStock(Integer bookId);// 更新用户的余额void updateBalance(Integer userId, Integer price); }package com.wang.dao.impl;@Repository public class BookDaoImpl implements BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;// 查询图书价格@Overridepublic Integer getPriceByBookId(Integer bookId) {String sql = "select price from t_book where book_id = ?";return jdbcTemplate.queryForObject(sql, Integer.class, bookId);}// 更新图书的库存@Overridepublic void updateStock(Integer bookId) {String sql = "update t_book set stock = stock - 1 where book_id = ?";jdbcTemplate.update(sql, bookId);}// 更新用户的余额@Overridepublic void updateBalance(Integer userId, Integer price) {String sql = "update t_user balance = balance - ? where user_id = ?";jdbcTemplate.update(sql, price, userId);} } -
service层:package com.wang.service;public interface BookService {// 买书void buyBook(Integer bookId, Integer userId); }package com.wang.service.impl;@Service public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {Integer price = bookDao.getPriceByBookId(bookId);bookDao.updateStock(bookId);bookDao.updateBalance(userId, price);} } -
controller层:package com.wang.controller;@Controller public class BookController {@Autowiredprivate BookService bookService;public void buyBook(Integer bookId, Integer userId) {bookService.buyBook(bookId, userId);} } -
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:tx-annotation.xml") public class TxByAnnotationTest {@Autowiredprivate BookController bookController;@Testpublic void testBuyBook() {bookController.buyBook(1, 1);} }
-
-
声明式事务
-
添加事务配置
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property> </bean><!-- 开启事务的注解驱动:将使用@Transactional注解锁标识的方法或类中所有的方法使用事务进行管理transaction-manager属性的默认值是transactionManager,若事务管理器bean的id是这个默认值,能省略此属性 --> <tx:annotation-driven transaction-manager="transactionManager" />
-
添加事务注解
因为
service表示业务逻辑层,一个方法表示完成一个功能,因此处理事务一般在service层处理,在BookServiceImpl的buyBook方法添加注解@Transactional。 若
@Transactional注解标识在方法上,该方法就会被事务管理,若@Transactional注解标识在类上,则类中所有的方法都会被事务管理。 -
观察结果
由于使用了
Spring的声明式事务,更新库存和更新余额都没有执行。
-
-
事务属性:只读
-
介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作,这样数据库就能够针对查询操作进行优化。
-
使用方式
// 默认值为false @Transactional(readOnly = true)
-
-
事务属性:超时
-
介绍
事务执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题。
此时这个很可能出问题的程序应该被回滚,撤销它已经做的操作,事务结束,把资源让出来,让其他正常程序可以执行。概括来说就是一句话:超时回滚,释放资源。
-
使用方式
// 单位是秒 @Transactional( timeout = 3)
-
-
事务属性:回滚策略
-
介绍:
声明式事务默认只针对运行时异常回滚,编译时异常不会滚。可以通过
Transactional中相关属性设置回滚策略。rollbackFor属性:会造成回滚的异常,需要设置一个Class类型的对象。rollbackForClassName属性:会造成回滚的异常,需要设置一个字符串类型的全类名。noRollbackFor属性::不会造成回滚的异常,需要设置一个Class类型的对象。noRollbackForClassName属性:不会造成回滚的异常,需要设置一个字符串类型的全类名。
-
-
事务属性:事务隔离级别
-
介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 -
使用方式
@Transactional( isolation = Isolation.DEFAULT) // 使用数据库默认的隔离级别 @Transactional( isolation = Isolation.READ_UNCOMMITTED) // 读未提交 @Transactional( isolation = Isolation.READ_COMMITTED) // 读已提交 @Transactional( isolation = Isolation.REPEATABLE_READ) // 可重复读 @Transactional( isolation = Isolation.SERIALIZABLE) // 串行化
-
-
事务属性:事务传播行为
-
介绍
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
-
测试
-
创建接口
CheckoutService和其实现类CheckoutServiceImplpackage com.wang.service;public interface CheckoutService {// 结账void checkout(Integer userId, Integer[] bookIds); }package com.wang.service.impl;@Service public class CheckoutServiceImpl implements CheckoutService {@Autowiredprivate BookService bookService;@Override@Transactionalpublic void checkout(Integer userId, Integer[] bookIds) {for(Integer bookId: bookIds) {bookService.buyBook(bookId, userId);}} } -
在
BookController中添加方法@Autowired private CheckoutService checkoutService;public void checkout(Integer userId, Integer[] bookIds) {checkoutService.checkout(userId, bookIds); } -
在
BookServiceImpl中的buyBook方法上面的@Transactional注解添加propagation属性// 若添加了propagation=Propagation.REQUIRES_NEW则表示结账的事务以buyBook的事务为主【能买一本书是一本】 // 若没有添加, 使用了默认值则表示以结账自己的事务为主【必须把账单里的所有书都买了才行】 @Transactional(propagation = Propagation.REQUIRES_NEW)
-
-
-
-
-
基于
XML的声明式事务<!--创建tx-xml.xml配置文件, 参考基于注解的声明式事务 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property> </bean><!-- 配置事务通知 --> <tx:advice id="tx" transaction-manager="transactionManager"><tx:attributes><!-- name属性:指定方法名,可以使用星号代表多个字符,若下面的方法名在类中没有匹配的方法,则无法进行事务管理 --><tx:method name="buyBook"/></tx:attributes> </tx:advice><aop:config><aop:advisor advice-ref="tx" pointcut="execution(* com.wang.service.impl.*.* (..))"></aop:advisor> </aop:config>
相关文章:
第五章:Spring下
第五章:Spring下 5.1:AOP 场景模拟 创建一个新的模块,spring_proxy_10,并引入下面的jar包。 <packaging>jar</packaging><dependencies><dependency><groupId>junit</groupId><artifactI…...
在CSDN学Golang云原生(Kubernetes基础)
一,k8s集群安装和升级 安装 Golang K8s 集群可以参照以下步骤: 准备环境:需要一组 Linux 服务器,并在每台服务器上安装 Docker 和 Kubernetes 工具。初始化集群:使用 kubeadm 工具初始化一个 Kubernetes 集群。例如&…...
给APK签名—两种方式(flutter android 安装包)
前提:给未签名的apk签名,可以先检查下apk有没有签名 通过命令行查看:打开终端或命令行界面,导入包含APK文件的目录,并执行以下命令: keytool -printcert -jarfile your_app.apk 将 your_app.apk替换为要检查…...
观察者模式、中介者模式和发布订阅模式
观察者模式 定义 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新 观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式…...
PHP-Mysql图书管理系统--【白嫖项目】
强撸项目系列总目录在000集 PHP要怎么学–【思维导图知识范围】 文章目录 本系列校训本项目使用技术 首页phpStudy 设置导数据库后台的管理界面数据库表结构项目目录如图:代码部分:主页的head 配套资源作业: 本系列校训 用免费公开视频&am…...
网络传输层协议:UDP和TCP
背景知识 再谈端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序; 在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过 netstat -…...
ElementUI Select选择器如何根据value值显示对应的label
修改前效果如图所示,数据值状态应显示为可用,但实际上仅显示了状态码1,并没有显示其对应的状态信息。在排查了数据类型对应关系问题后,并没有产生实质性影响,只好对代码进行了如下修改。 修改前代码: <…...
Kotlin 内联函数语法之let、apply、also、run、with的用法与详解
一、介绍 kotlin的语法千奇百怪,今天我们将介绍项目中频率使用比较高的几个内联函数。 二、什么叫内联函数? 内联函数 的语义很简单:把函数体复制粘贴到函数调用处 。使用起来也毫无困难,用 inline关键字修饰函数即可。 语法&a…...
Swift 中如何判断是push 过来的页面 还是present过来的 页面
在 Swift 中,可以通过检查当前视图控制器的 presentingViewController 属性来判断是通过 push 过来的页面还是 present 过来的页面。 下面是一个示例代码,展示如何判断是通过 push 还是 present 过来的页面: if let presentingViewControll…...
基于K8s环境·使用ArgoCD部署Jenkins和静态Agent节点
今天是「DevOps云学堂」与你共同进步的第 47天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文节选自第⑦期DevOps训练营 , 对于训练营的同学实践此文档依赖于基础环境配置文档, 运行K8s集群并配置NFS存储。实际上只要有个K8s集…...
874. 模拟行走机器人
874. 模拟行走机器人 机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands : -2 :向左转 90 度-1 :向右转 90 度1 < x < 9 :…...
【Linux】- RPM 与 YUM
RPM 与 YUM 1.1 rpm 包的管理1.2 rpm 包的简单查询指令1.3 rpm 包的其它查询指令:1.4 卸载 rpm 包:2.1 安装 rpm 包3.1 yum3.2 yum 的基本指令3.3 安装指定的 yum 包3.4 yum 应用实例: 1.1 rpm 包的管理 介绍 rpm 用于互联网下载包的打包及安…...
Visual Studio 2015编译器 自动生成 XXX_EXPORTS宏
XXX_EXPORTS宏 XXX_EXPORTS宏是由Visual Studio 2015编译器自动生成的。这个宏用于标识当前项目是一个导出符号的动态链接库(DLL)项目。在使用Visual Studio 2015创建Win32项目时,编译器会自动添加这个宏到项目的预定义宏中。 这个宏的作用…...
HTML5的应用现状与发展前景
HTML5,作为Web技术的核心,已经深深地改变了我们看待和使用Web的方式。它不仅提供了数不尽的新特性和功能,还使得Web设计和开发更加互动、更加直观。这篇文章将探讨HTML5的当前应用现状,以及它的未来发展前景。 HTML5的应用现状 H…...
day44-Spring_AOP
0目录 1.2.3 1.Spring_AOP 实体类: Mapper接口: Service和实现类: 测试1: 运行后: 测试2:无此型号时 测试3:库存不足时 解决方案1:事务声明管理器 测试:…...
selenium IDE 接入jenkins-转载
Selenium-IDE脚本录制,selenium-side-runner自动化测试教程_51CTO博客_selenium ide录制脚本 备忘录...
云计算结合数据科学突破信息泛滥(下)
大家好,本文将继续讨论云计算结合数据科学突破信息泛滥的相关内容,讲述其余三个关键组成部分。 3.数据清理和预处理 收集数据并将其存储在云端之后,下一步是将数据进行转换。因为原始数据经常包含错误、不一致和缺失的值,这些都…...
蓝桥杯单片机第十二届国赛 真题+代码
iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求,进行代码调试和修改。 */ #include <STC1…...
MyBatis学习笔记之缓存
文章目录 一级缓存一级缓存失效 二级缓存二级缓存失效二级缓存相关配置 MyBatis集成EhCache 缓存:cache 缓存的作用:通过减少IO的方式,来提高程序的执行效率 mybatis的缓存:将select语句的查询结果放到缓存(内存&…...
小程序 WxValidate.js 再次封装
util.js // 合并验证规则和提示信息 const filterRules (objectItem) > {let rules {}, messages {};for (let key in objectItem) {rules[key] objectItem[key].rulesmessages[key] objectItem[key].message}return { rules, messages } }module.exports {filterRule…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
