Spring8-事务
目录
JdbcTemplate
声明式事务
事务
概述
特性(ACID)
编程式事务
声明式事务
基于注解的声明式事务
@Transactional注解标识的位置
事务属性:只读
事务属性:超时
事务属性:隔离级别
事务属性:传播行为
全注解配置事务
基于xml的声明式事务
JdbcTemplate
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
搭建子模块:spring-jdbc
加入依赖:
<!--spring jdbc Spring 持久化层支持jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.15.RELEASE</version></dependency>
创建jdbc.properties:
jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><!--创建jdbcTemplate对象,注入数据源--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"></property></bean>
</beans>
准备数据库与测试表:
CREATE DATABASE `spring`;use `spring`;CREATE TABLE `t_emp` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) DEFAULT NULL COMMENT '姓名',`age` int(11) DEFAULT NULL COMMENT '年龄',`sex` varchar(2) DEFAULT NULL COMMENT '性别',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实体类:
package com.qcby;public class Emp {private Integer id;private String name;private Integer age;private String sex;public Emp() {}public Emp(Integer id, String name, Integer age, String sex) {this.id = id;this.name = name;this.age = age;this.sex = sex;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}
}
实现CURD:
package com.qcby;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;import java.util.List;@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestJdbcTemplate {@Autowiredprivate JdbcTemplate jdbcTemplate;//添加@Testpublic void testInsert(){//1.编写SQL语句String sql = "insert into t_emp values(NULL,?,?,?)";//2.调用jdbcTemplate的方法,传入相关参数int rows = jdbcTemplate.update(sql, "张三", 20, "男");System.out.println(rows);}//修改@Testpublic void testUpdate(){//1.编写SQL语句String sql = "update t_emp set name=? where id=?";//2.调用jdbcTemplate的方法,传入相关参数int rows = jdbcTemplate.update(sql, "李四",1);System.out.println(rows);}//删除@Testpublic void testDelete(){//1.编写SQL语句String sql = "delete from t_emp where id=?";//2.调用jdbcTemplate的方法,传入相关参数int rows = jdbcTemplate.update(sql, 1);System.out.println(rows);}//查询:返回单个@Testpublic void testSelectObject(){String sql = "select * from t_emp where id=?";Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),2);System.out.println(emp);}//查询:返回所有@Testpublic void testSelectList(){String sql = "select * from t_emp";List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));System.out.println(list);}//查询:返回记录总数@Testpublic void testSelectValue(){String sql = "select count(*) from t_emp";Integer count = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);}
}
声明式事务
事务
概述
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成
特性(ACID)
A:原子性(Atomicity)
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
C:一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态
I:隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据
D:持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态
编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;try {// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();}catch(Exception e){// 回滚事务conn.rollBack();}finally{// 释放数据库连接conn.close();}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用
声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作
好处:
- 提高开发效率
- 消除了冗余的代码
- 框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
所以,我们可以总结下面两个概念:
-
编程式:自己写代码实现功能
-
声明式:通过配置让框架实现功能
基于注解的声明式事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--组件扫描--><context:component-scan base-package="com.qcby.tx"></context:component-scan><!--引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><!--创建jdbcTemplate对象,注入数据源--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"></property></bean>
</beans>
jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
创建表:
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);
package com.qcby.tx.controller;import com.qcby.tx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class BookController {@Autowiredprivate BookService bookService;public void buyBook(Integer bookId,Integer userId){bookService.buyBook(bookId,userId);}
}
package com.qcby.tx.service;public interface BookService {void buyBook(Integer bookId, Integer userId);
}
package com.qcby.tx.service;import com.qcby.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookServiceImpl implements BookService{@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {//1.根据图书id查询图书价格Integer price = bookDao.getBookPriceByBookId(bookId);//2.更新图书库存-1bookDao.updateStock(bookId);//3.更新用户余额:-图书价格bookDao.updateUserBalance(userId,price);}
}
package com.qcby.tx.dao;public interface BookDao {Integer getBookPriceByBookId(Integer bookId);void updateStock(Integer bookId);void updateUserBalance(Integer userId, Integer price);
}
package com.qcby.tx.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;@Repository
public class BookDaoImpl implements BookDao{@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic Integer getBookPriceByBookId(Integer bookId) {String sql = "select price from t_book where book_id=?";Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);return price;}@Overridepublic void updateStock(Integer bookId) {String sql = "update t_book set stock=stock-1 where book_id=?";jdbcTemplate.update(sql,bookId);}@Overridepublic void updateUserBalance(Integer userId, Integer price) {String sql = "update t_user set balance=balance-? where user_id=?";jdbcTemplate.update(sql,price,userId);}
}
测试无事务:
package com.qcby;import com.qcby.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestBook {@Autowiredprivate BookController bookController;@Testpublic void testBuyBook(){bookController.buyBook(1,1);}
}
用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额
假设用户id为1的用户,购买id为1的图书
用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段
此时执行sql语句会抛出SQLException
加入事务:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!--组件扫描--><context:component-scan base-package="com.qcby.tx"></context:component-scan><!--引入外部属性文件,创建数据源对象--><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"></property><property name="driverClassName" value="${jdbc.driver}"></property><property name="username" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><!--创建jdbcTemplate对象,注入数据源--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource"></property></bean><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="druidDataSource"></property></bean><!--开启事务的注解驱动通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
--><!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 --><tx:annotation-driven transaction-manager="transactionManager" /></beans>
在BookServiceImpl添加注解@Transactional:
package com.qcby.tx.service;import com.qcby.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Transactional
@Service
public class BookServiceImpl implements BookService{@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {//1.根据图书id查询图书价格Integer price = bookDao.getBookPriceByBookId(bookId);//2.更新图书库存-1bookDao.updateStock(bookId);//3.更新用户余额:-图书价格bookDao.updateUserBalance(userId,price);}
}
@Transactional注解标识的位置
- @Transactional标识在方法上,则只会影响该方法
- @Transactional标识的类上,则会影响类中所有的方法
事务属性:只读
设置只读,只能查询,不能修改、添加、删除
@Transactional(readOnly = true)
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
事务属性:超时
超时回滚,释放资源
//超时时间单位秒
@Transactional(timeout = 3)
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out
事务属性:回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚
可以通过@Transactional中相关属性设置回滚策略
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符串类型的全类名
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
事务属性:隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
隔离级别一共有四种:
-
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改
-
读已提交:READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改
-
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新
-
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 有 | 有 | 有 |
READ COMMITTED | 无 | 有 | 有 |
REPEATABLE READ | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
隔离级别 | Oracle | MySQL |
---|---|---|
READ UNCOMMITTED | × | √ |
READ COMMITTED | √(默认) | √ |
REPEATABLE READ | × | √(默认) |
SERIALIZABLE | √ | √ |
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
事务属性:传播行为
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为
一共有七种传播行为:
-
REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
-
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管】
-
MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
-
REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
-
NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
-
NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
-
NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样】
演示:
package com.qcby.tx.service;public interface CheckoutService {void checkout(Integer[] bookIds, Integer userId);
}
package com.qcby.tx.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Transactional
@Service
public class CheckoutServiceImpl implements CheckoutService{@Autowiredprivate BookService bookService;//买多本书@Overridepublic void checkout(Integer[] bookIds, Integer userId) {for (Integer bookId : bookIds) {bookService.buyBook(bookId,userId);}}
}
package com.qcby.tx.controller;import com.qcby.tx.service.BookService;
import com.qcby.tx.service.CheckoutService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class BookController {@Autowiredprivate BookService bookService;@Autowiredprivate CheckoutService checkoutService;// public void buyBook(Integer bookId,Integer userId){
// bookService.buyBook(bookId,userId);
// }public void checkout(Integer[] bookIds,Integer userId){checkoutService.checkout(bookIds,userId);}
}
在数据库中将用户的余额修改为100元
可以通过@Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本
全注解配置事务
用配置类代替配置文件
package com.qcby.tx.config;import com.alibaba.druid.pool.DruidDataSource;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@Configuration //配置类
@ComponentScan("com.qcby.tx")
@EnableTransactionManagement //开启事务管理
public class SpringConfig {@Beanpublic DataSource getDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");return dataSource;}@Bean(name = "jdbcTemplate")public JdbcTemplate getJdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager getTransactionManager(DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}}
测试:
package com.qcby;import com.qcby.tx.config.SpringConfig;
import com.qcby.tx.controller.BookController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class TestAnno {@Testpublic void testTxAllAnnotation(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);BookController accountService = applicationContext.getBean("bookController", BookController.class);accountService.buyBook(1, 1);}
}
基于xml的声明式事务
参考基于注解的声明式事务
注意:基于xml实现的声明式事务,必须引入以下依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version>
</dependency>
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
<aop:config><!-- 配置事务通知和切入点表达式 --><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.qcby.tx.service.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- tx:method标签:配置具体的事务方法 --><!-- name属性:指定方法名,可以使用星号代表多个字符 --><tx:method name="get*" read-only="true"/><tx:method name="query*" read-only="true"/><tx:method name="find*" read-only="true"/><tx:method name="buy*" read-only="true"/><!-- read-only属性:设置只读属性 --><!-- rollback-for属性:设置回滚的异常 --><!-- no-rollback-for属性:设置不回滚的异常 --><!-- isolation属性:设置事务的隔离级别 --><!-- timeout属性:设置事务的超时属性 --><!-- propagation属性:设置事务的传播行为 --><tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/><tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/><tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/></tx:attributes>
</tx:advice>
相关文章:
Spring8-事务
目录 JdbcTemplate 声明式事务 事务 概述 特性(ACID) 编程式事务 声明式事务 基于注解的声明式事务 Transactional注解标识的位置 事务属性:只读 事务属性:超时 事务属性:隔离级别 事务属性:传…...
在Python中,类是用于定义对象的蓝图或模板,而对象则是根据类创建的具体实例
当然,我可以为您演示类与对象的基本概念和它们之间的关系。在Python中,类是用于定义对象的蓝图或模板,而对象则是根据类创建的具体实例。 下面是一个简单的Python程序,它定义了一个Car类,该类具有一些属性和方法&…...
【小波去噪】【matlab】基于小波分析的一维信号滤波(对照组:中值滤波、均值滤波、高斯滤波)
链接1-傅里叶变换 链接2-傅立叶分析和小波分析间的关系 链接3-小波变换(wavelet transform)的通俗解释 链接4-小波基的选择 1.示例代码 function main_wavelet clc clear close all warning off %% 1.信号生成 time_length 10;%总时长,秒 …...
CentOS 7官方源停服,配置本机光盘yum源
1、挂载系统光盘 mkdir /mnt/iso mount -o loop /tools/CentOS-7-x86_64-DVD-1810.iso /mnt/iso cd /mnt/iso/Packages/ rpm -ivh /mnt/iso/Packages/yum-utils-1.1.31-50.el7.noarch.rpm(图形界面安装,默契已安装) 如安装yum-utils依赖错误&#x…...

2024年汉字小达人区级自由报名备考冲刺:2024官方模拟题练一练(续)
2024年第十一届汉字小达人的区级活动的时间9月25-30日正式开赛,满打满算还有9天时间。 今天继续回答一些问题关于汉字小达人的常见问题,再做几道2024年官方模拟题,帮助大家直观地了解汉字小达人的比赛题型和那你程度。 本专题在比赛前持续更…...
实战Redis与MySQL双写一致性的缓存模式
Redis和MySQL都是常用的数据存储系统,它们各自有自己的优缺点。在实际应用中,我们可能需要将它们结合起来使用,比如将Redis作为缓存,MySQL作为持久化存储。 在这种情况下,我们需要保证Redis和MySQL的数据一致性&…...

KVM环境下制作ubuntu qcow2格式镜像
如果是Ubuntu KVM环境是VMware虚拟机,需要CPU开启虚拟化 1、配置镜像源 wget -O /etc/apt/sources.list https://www.qingtongqing.cc/ubuntu/sources.list2、安装kvm qemu-img libvirt kvm虚拟化所需环境组件 apt -y install qemu-kvm virt-manager libvirt-da…...

基于SpringBoot+Vue的高校竞赛管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…...

PHP发邮件教程:配置SMTP服务器发送邮件?
PHP发邮件的几种方式?如何使用PHP通过SMTP协议发信? PHP作为一种广泛使用的服务器端脚本语言,提供了多种方式来发送邮件。AokSend将详细介绍如何通过配置SMTP服务器来实现PHP发邮件教程的核心内容。 PHP发邮件教程:设置参数 这…...

SpringBootWeb增删改查入门案例
前言 为了快速入门一个SpringBootWeb项目,这里就将基础的增删改查的案例进行总结,作为对SpringBootMybatis的基础用法的一个巩固。 准备工作 需求说明 对员工表进行增删改查操作环境搭建 准备数据表 -- 员工管理(带约束) create table emp (id int …...

pytorch实现RNN网络
目录 1.导包 2. 加载本地文本数据 3.构建循环神经网络层 4.初始化隐藏状态state 5.创建随机的数据,检测一下代码是否能正常运行 6. 构建一个完整的循环神经网络 7.模型训练 8.个人知识点理解 1.导包 import torch from torch import nn from torch.nn imp…...
智能工厂的软件设计 “程序program”表达式,即 接口模型的代理模式表达式
Q1、前面将“智能工厂的软件设计”中绝无仅有的“程序”视为 专注于 给定的某个单一面(语言面/逻辑面/数学面)中的 问题,专注于分析问题和解决问题的程序活动的组织,每一面都是一个“组织者”就像一个“独角兽”,并提出…...
leetcode 难度【简单模式】标签【数据库】题型整理大全
文章目录 175. 组合两个表181. 超过经理收入的员工182. 查找重复的电子邮箱COUNT(*)COUNT(*) 与 COUNT(column) 的区别 where和vaing之间的区别用法 183.从不订购的客户196.删除重复的电子邮箱197.上升的温度511.游戏玩法分析I512.游戏玩法分析II577.员工奖金584.寻找用户推荐人…...

利士策分享,自我和解:通往赚钱与内心富足的和谐之道
利士策分享,自我和解:通往赚钱与内心富足的和谐之道 在这个快节奏、高压力的时代,我们往往在追求物质财富的同时,忽略了内心世界的和谐与平衡。 赚钱,作为现代生活中不可或缺的一部分,它不仅仅是生存的手段…...
【物联网】深入解析时序数据库TDengine及其Java应用实践
文章目录 一、什么是时序数据库?二、TDengine简介三、TDengine的Java应用实践(1)环境准备(2)数据插入(3)数据查询 一、什么是时序数据库? 时序数据库(Time-Series Datab…...

2023北华大学程序设计新生赛部分题解
时光如流水般逝去,我已在校园中奋战大二!(≧▽≦) 今天,静静回顾去年的新生赛,心中涌起无尽感慨,仿佛那段青春岁月如烟花般绚烂。✧。(≧▽≦)。✧ 青春就像一场燃烧的盛宴,激情澎湃&…...

PPP的配置
概述:PPP模式,即公私合作模式(Public-Private Partnership),是一种公共部门与私营部门合作的模式。 一、实验拓扑 实验一:PPP基本功能 实验步骤: (1)配置AR1的接口IP地…...
回溯算法总结篇
组合问题:N个数里面按一定规则找出k个数的集合 如果题目要求的是组合的具体信息,则只能使用回溯算法,如果题目只是要求组合的某些最值,个数等信息,则使用动态规划(比如求组合中元素最少的组合,…...

机器学习-点击率预估-论文速读-20240916
1. [经典文章] 特征交叉: Factorization Machines, ICDM, 2010 分解机(Factorization Machines) 摘要 本文介绍了一种新的模型类——分解机(FM),它结合了支持向量机(SVM)和分解模型的优点。与…...
【leetcode】堆习题
215.数组中的第K个最大元素 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输…...

工作邮箱收到钓鱼邮件,点了链接进去无法访问,会有什么问题吗?
没事的,很可能是被安全网关拦截了。最近做勒索实验,有感而发,不要乱点击邮箱中的附件。 最初我们采用钓鱼邮件投递恶意载荷,发现邮件网关把我们的 exe/bat 程序直接拦截了,换成压缩包也一样拦截了,载荷始终…...

【系统架构设计师】绪论-系统架构概述
目录 绪论 系统架构概述 单选题 绪论 系统架构概述 单选题 1、软件方法学是以软件开发方法为研究对象的学科。其中,()是先对最高居次中的问题进行定义、设计、编程和测试,而将其中未解决的问题作为一个子任务放到下一层次中去…...
GO 基础语法和数据类型面试题及参考答案(上)
目录 Go 中变量定义方式有哪些?各有什么适用场景? 使用 : 定义变量的限制是什么? 全局变量可以使用 : 声明吗?为什么? Go 中如何声明一个多变量赋值?有哪些注意事项? 常量能否通过表达式赋值…...
Linux安全机制:从SELinux到Intel SGX的堡垒
Linux安全机制:从SELinux到Intel SGX的堡垒 数字世界的钢铁长城 引言:操作系统的"防御工事" 当服务器每天承受数百万次攻击尝试时,Linux内核的安全机制如同精密的防御系统,在纳秒级时间内做出响应。现代Linux安全架构已…...
CSS高级技巧及新增属性
CSS高级技巧及新增属性 jarringslee 文章目录 CSS高级技巧及新增属性精灵图 Sprite字体图标 iconfontCSS几何图形的写法更改鼠标样式更改表单轮廓取消文本域的拖拽行内块元素的垂直居中对齐溢出文字处理 CSS布局技巧CSS5新增内容及其他属性新增选择器新增基础属性及其他属性ca…...

记录一个用了很久的git提交到github和gitee比较方便的方法
在当前git init后,在隐藏的git文件夹中找到config文件 [user]name thels [remote "github"]url your github repository urlfetch refs/heads/*:refs/remotes/origin/* [remote "gitee"]url your gitee repository urlfetch refs/heads/*:…...
JavaScript ES6 解构:优雅提取数据的艺术
JavaScript ES6 解构:优雅提取数据的艺术 在 JavaScript 的世界中,ES6(ECMAScript 2015)的推出为开发者带来了许多革命性的特性,其中“解构赋值”(Destructuring Assignment)无疑是最受欢迎的功…...

14-Oracle 23ai Vector Search 向量索引和混合索引-实操
一、Oracle 23ai支持的2种主要的向量索引类型: 1.1 内存中的邻居图向量索引 (In-Memory Neighbor Graph Vector Index) HNSW(Hierarchical Navigable Small World :分层可导航小世界)索引 是 Oracle AI Vector Search 中唯一支持的内存邻居图向量索引类…...

Deepseek基座:Deepseek-v2核心内容解析
DeepSeek原创文章1 DeepSeek-v3:基于MLA的高效kv缓存压缩与位置编码优化技术 2 Deepseek基座:DeepSeek LLM核心内容解析 3 Deepseek基座:Deepseek MOE核心内容解析 4 Deepseek基座:Deepseek-v2核心内容解析 5Deepseek基座…...

WebRTC通话原理与入门难度实战指南
波煮的实习公司主要是音视频业务,所以最近在补习WebRTC的相关内容,会不定期给大家分享学习心得和笔记。 文章目录 WebRTC通话原理进行媒体协商:彼此要了解对方支持的媒体格式网络协商:彼此要了解对方的网络情况,这样才…...