ssm框架之spring:浅聊事务--JdbcTemplate
简介
JdbcTemplate 是 Spring 对 JDBC 的封装,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放,它帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果即可。
如果直接使用JDBC的话,需要我们加载数据库驱动、创建连接、释放连接、异常处理等一系列的动作,繁琐且代码看起来不直观。而使用 jdbctemplate 则无需关注加载驱动、释放资源、异常处理等一系列操作,我们只需要提供 sql 语句并且提取最终结果即可,大大方便我们编程开发。此外,Spring提供的JdbcTempate能直接数据对象映射成实体类,不再需要获取ResultSet去获取值、赋值等操作,提高开发效率;
Spring为了简化数据库访问,主要做了以下几点工作:
- 提供了简化的访问JDBC的模板类,不必手动释放资源;
- 提供了一个统一的 DAO 类以实现 Data Access Object 模式;
- 把
SQLException
封装为DataAccessException
,这个异常是一个RuntimeException
,并且让我们能区分SQL异常的原因,例如,DuplicateKeyException
表示违反了一个唯一约束; - 能方便地集成Hibernate、JPA和MyBatis这些数据库访问框架。
环境搭建
导入依赖jar
如果同maven导入依赖的话,将spring几个核心的jar引入后,还需要引入:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.18</version>
</dependency>
或者将spring-orm-*.*.*.jar 将其导入到环境中。
其它依赖
因为需要连接数据库,因为使用你的阿里的druid,所以在maven中配置
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version>
</dependency>
如果不通过maven导入依赖环境的花,还是需要手动导入第三方包。
完整的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"><parent><artifactId>spring_test3</artifactId><groupId>com.xzd</groupId><version>1.0-SNAPSHOT</version></parent><packaging>jar</packaging><modelVersion>4.0.0</modelVersion><artifactId>spring_jdbctemplate</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- https://mvnrepository.com/artifact/org.springframework/spring-core --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.20</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.20</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.20</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.20</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-expression--><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>5.3.20</version></dependency>
<!-- 这里使用spring整合的测试包,和junit的优势 演示会体现--><!-- https://mvnrepository.com/artifact/org.springframework/spring-test --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.23</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.18</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>RELEASE</version><scope>test</scope></dependency></dependencies>
</project>
环境配置
现在进行环境的搭建:
首先有数据:
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test_jdbtemplate` /*!40100 DEFAULT CHARACTER SET utf8mb3 */ /*!80016 DEFAULT ENCRYPTION='N' */;USE `test_jdbtemplate`;/*Table structure for table `t_categories` */DROP TABLE IF EXISTS `t_categories`;CREATE TABLE `t_categories` (`categories_flag` int DEFAULT NULL,`categories_name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;/*Data for the table `t_categories` */insert into `t_categories`(`categories_flag`,`categories_name`) values (1,'射击'),(2,'动作');/*Table structure for table `t_customer` */DROP TABLE IF EXISTS `t_customer`;CREATE TABLE `t_customer` (`customer_id` int DEFAULT NULL,`customer_name` varchar(20) DEFAULT NULL,`customer_phone` int DEFAULT NULL,`customer_money` double(5,2) unsigned DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;/*Data for the table `t_customer` */insert into `t_customer`(`customer_id`,`customer_name`,`customer_phone`,`customer_money`) values (1,'貂蝉',1311111111,100.00),(2,'吕布',1312222222,200.00);/*Table structure for table `t_game` */DROP TABLE IF EXISTS `t_game`;CREATE TABLE `t_game` (`categories_id` int DEFAULT NULL,`game_id` int DEFAULT NULL,`game_name` varchar(20) DEFAULT NULL,`game_details` varchar(30) DEFAULT NULL,`game_price` double(5,2) unsigned DEFAULT NULL,`game_count` int unsigned DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;/*Data for the table `t_game` */insert into `t_game`(`categories_id`,`game_id`,`game_name`,`game_details`,`game_price`,`game_count`) values (1,1,'使命召唤','最经典的暴雪射击游戏',111.00,30),(2,2,'艾尔登法环','宫崎老贼的善意开局,大树守卫',198.00,20),(2,3,'战神','又是那个光头肌肉男',169.00,15);
首先配置一个,mysql的配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/test_jdbtemplate?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
现在在spring配置文件中进行配置数据连接池的信息:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 导入配置文件的 如果是普通Java项目 可以写location="jdbc.properties" 但是最后都带classpath: --><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><context:component-scan base-package="com.xzd"></context:component-scan><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>
<!-- 这个是之间连接 spring中的JdbcTemplate类 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
</beans>
通过spring整合的junit和jdbctemplate
说的话可能很乱,直接演示:
如果使用之前的导入测试类应该如下写
public class test {@Testpublic void testMethod(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_jdbc.xml");JdbcTemplate jdbcTemplate= (JdbcTemplate) applicationContext.getBean("jdbcTemplate");System.out.println(jdbcTemplate);}
}
现象看一下整合的方式写法:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_jdbc.xml")
public class test {@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testMethod(){System.out.println(jdbcTemplate);}
}
可以看出可以通过注解实现ICO容器的注入。
其中@RunWith可以通过SpringJUnit4ClassRunner.class进行启动IOC容器。
代码演示
常规数据操作
JdbcTemplate主要提供以下五类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
先来一个插入
@Testpublic void insertMethod(){String sql="INSERT INTO `test_jdbtemplate`.`t_game` VALUES (?,?,?,?,?,?)";
// 其实插入和删除都是使用update方法jdbcTemplate.update(sql,1,4,"寂静岭","那可是护士啊",82,10);}
然后再来要给删除:
@Testpublic void deleteMethod(){String sql="DELETE FROM `test_jdbtemplate`.`t_game` WHERE `t_game`.`game_id`=?";jdbcTemplate.update(sql,4 );}
插入和删除相对来说简单一些,主要是查询:
先创建一个游戏类:
public class Game {int categoriesId;int gameId;String gamName;String gameDetails;double gamePrice;int gameCount;// 具体有参无参构造方法,set get 方法 toString 方法就不再黏贴了不然太长,用IDE快捷键很快创建出来}
现在得到一个数据的集合:
@Testpublic void selectMethod(){String sql="SELECT * FROM `test_jdbtemplate`.`t_game` ";List<Game> gameList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Game>(Game.class));for (Game game: gameList) {System.out.println(game);}
如果只返回一个对象数据:
@Testpublic void selectMethod1() {String sql = "SELECT * FROM `test_jdbtemplate`.`t_game` WHERE `t_game`.`game_id`=? ";Game game = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Game>(Game.class), 2);System.out.println(game);}
@Testpublic void selectMethod2() {String sql = "SELECT count(*) FROM `test_jdbtemplate`.`t_game`";Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(integer);}
@Testpublic void selectMethod2() {String sql = "SELECT game_name,COUNT(*) FROM `test_jdbtemplate`.`t_game` WHERE game_id =? GROUP BY game_name";Map map = jdbcTemplate.queryForMap(sql,1);System.out.println(map);}
所以常用方法如下:
-
执行简单的增删改
int update(String sql,Object[] args) int update(String sql,Objcet... args)
-
batchUpdate批量增删改
int[] batchUpdate(String[] sql) int[] batchUpdate(String sql,List<Object[]>)
-
单个简单查询
T queryForObjcet(String sql,Class<T> type) T queryForObjcet(String sql,Object[] args,Class<T> type) T queryForObjcet(String sql,Class<T> type,Object... arg)
-
获取多个
//API List<T> queryForList(String sql,Class<T> type) List<T> queryForList(String sql,Object[] args,Class<T> type) List<T> queryForList(String sql,Class<T> type,Object... arg)
-
查询复杂对象(封装为Map)
-
获取单个
Map queryForMap(String sql) Map queryForMap(String sql,Objcet[] args) Map queryForMap(String sql,Object... arg)
-
获取多个
List<Map<String,Object>> queryForList(String sql) List<Map<String,Object>> queryForList(String sql,Obgject[] args) List<Map<String,Object>> queryForList(String sql,Obgject... arg)
-
-
查询复杂对象(封装为实体对象)
Spring jdbcTemplate是通过实现
org.springframework.jdbc.core.RowMapper
这个接口来完成对entity对象映射。不过一般是通过其实现类BeanPropertyRowMapper
。-
获取单个
T queryForObject(String sql,RowMapper<T> mapper) T queryForObject(String sql,object[] args,RowMapper<T> mapper) T queryForObject(String sql,RowMapper<T> mapper,Object... arg)
-
获取多个
List<T> query(String sql,RowMapper<T> mapper) List<T> query(String sql,Object[] args,RowMapper<T> mapper) List<T> query(String sql,RowMapper<T> mapper,Object... arg)
-
事务操作
概念
事务其实又分两种:编程式事务和声明式事务。
-
编程式
编程式事务是通过编写程序来管理事务,程序员需要在程序中显式的指定事务的处理过程,由程序员负责事务的开启、提交和回滚等操作。
就是自己开发的时候,写事务提交,事务启动,根据不同的行为异常进行回滚等。简单的说就是自己写代码实现功能。
-
声明式
Spring声明式事务是通过使用Spring中的AOP技术,将事务管理逻辑从业务逻辑代码中剥离出来,使用注解等形式来声明事务,实现事务管理的功能。
简单的说就是通过配置让框架实现功能。
现象网上进行复制一些两者的区别,以及优缺点:
- 区别:
- 管理性:Spring声明式事务的管理性较强,而编程式事务的管理性较弱;
- 使用性:Spring声明式事务在实现事务管理时不需要写业务代码,使用起来更加简单,而编程式事务需要编写程序来管理事务,使用起来比较复杂;
- 配置性:Spring声明式事务只需要在spring配置文件中配置就可以实现事务管理,而编程式事务需要在代码中配置才能实现事务管理。
- 优缺点
- 声明式事务
- 优点
- 简化开发:将事务管理从业务逻辑代码中剥离,减少了开发的工作量;
- 提高效率:使用Spring声明式事务,可以提高事务处理的效率;
- 提高可维护性:使用Spring声明式事务,可以更加方便的进行维护。
- 缺点
- 不能进行复杂的操作:Spring声明式事务只能实现简单的事务处理,不能进行复杂的操作;
- 不能实现完全的事务处理:Spring声明式事务不能实现完全的事务处理,只能实现最基本的事务处理。
- 优点
- 编程式事务
- 优点
- 灵活性高:编程式事务的灵活性高,可以进行复杂的操作;
- 可以实现完全的事务处理:编程式事务可以实现完全的事务处理。
- 缺点
- 管理性差:编程式事务的管理性较差,需要在代码中手动管理事务;
- 使用性差:编程式事务需要编写程序来管理事务,使用起来比较复杂。
- 优点
- 声明式事务
至于数据库的事务有涉及道的,事务时什么,以及事务的隔离级别等概念,可以看另一篇文章:传送阵
代码演示
注解演示
无事务案例
先不开启事务的话,来一个报错的:
首先看一下我的结构:
然后创建service接口,以及实现的类
// 接口public interface GameService {
// Game getGameById();void BuyGame(int customerId,int gameId);
}// 实现接口
@Service
public class GameServiceImpl implements GameService {@Qualifierprivate GameDao gameDao;public GameServiceImpl(GameDao gameDao) {this.gameDao = gameDao;}@Overridepublic void BuyGame(int customerId,int gameId) {Double price =gameDao.findGamePrice(gameId);gameDao.updateGamecount(gameId);gameDao.updateCustomerMoney(customerId,price);}
}
然后创建实现dao接口以及实现类:
// 接口
public interface GameDao {double findGamePrice(int game_id);void updateGamecount(int gameId);void updateCustomerMoney(int customerId,double price);
}// 接口实现类@Repository
public class GameDaoImpl implements GameDao {@ResourceJdbcTemplate jdbcTemplate;@Overridepublic double findGamePrice(int game_id) {String sql="SELECT game_price FROM `test_jdbtemplate`.`t_game` WHERE `game_id`=?";Double price=jdbcTemplate.queryForObject(sql,Double.class,game_id);System.out.println(price);return price;}@Override
// 这里直接默认只会购买一个游戏 毕竟主要时演示事务的效果public void updateGamecount(int gameId) {String sql="UPDATE `test_jdbtemplate`.`t_game` SET `game_count` = game_count - 1 WHERE `game_id` = ?";System.out.println("更新一些游戏表种某款被卖了游戏库存");jdbcTemplate.update(sql,gameId);}@Overridepublic void updateCustomerMoney(int customerId, double price) {String sql="UPDATE `test_jdbtemplate`.`t_customer` SET `customer_money` = customer_money-? WHERE `customer_id` = ?";System.out.println("更新一些会员的余额");jdbcTemplate.update(sql,price,customerId);}
}
首先看一下数据库会员的信息:
然后看下游戏仓库的数据:
然后调用测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_jdbc.xml")
public class test {
// @Resource(name = "jdbcTemplate")private JdbcTemplate jdbcTemplate;
//@Resource(name = "gameServiceImpl")@Autowiredprivate GameService gameService;@Testpublic void test(){gameService.BuyGame(1,1);}
}
其中数据库种会员的余额,是不可以是带符合的数,所以不够买书会报错,这个还算是可以理解,然后看一下数据库:
游戏数量是减少了,这个就是没有开启事务的一个弊端,这个过程没有执行完毕,应该游戏的数量不会变的,但是变了。还是那句话事务概念不懂的,可以看另一篇文章:传送阵。而本篇主要是演示在Spring种的JdbcTemplate中如何启动事务。
带事务案例
因为需要在bean中添加tx标签:所以需要如下配置:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
其中添加了:
xmlns:tx="http://www.springframework.org/schema/tx"以及在 xsi:schemaLocation中添加了 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
最后看一下实际的配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 导入配置文件的 如果是普通Java项目 可以写location="jdbc.properties" 但是最后都带classpath: --><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><context:component-scan base-package="com.xzd"></context:component-scan><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>
<!-- 这个是之间连接 spring中的JdbcTemplate类 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
<!-- 将框架中已经定义好的事务切面类,创建道容器当中--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean>
<!-- 启动事务注解驱动,等于输入事务注解即可实现 --><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven><!-- <bean id="kk" class=""></bean>-->
</beans>
如果在是事务的时候不写切面类id,一般建议写上,毕竟好理解,如下:
<!-- 将框架中已经定义好的事务切面类,创建道容器当中--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean>
<!-- 启动事务注解驱动,等于输入事务注解即可实现 --><tx:annotation-driven ></tx:annotation-driven>
然后使用注解:@Transactional,其可以ansactional 可以作用在接口、类、类方法。
-
作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
-
作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
-
作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
所以说一般常用的情况是在类上或者方法上。现象还是上面的例子,至少在其中的服务实现类上添加这个事务注解:
@Service
@Transactional
public class GameServiceImpl implements GameService {@Qualifierprivate GameDao gameDao;public GameServiceImpl(GameDao gameDao) {this.gameDao = gameDao;}@Overridepublic void BuyGame(int customerId,int gameId) {
// System.out.println("-------"+gameDao);Double price =gameDao.findGamePrice(gameId);gameDao.updateGamecount(gameId);gameDao.updateCustomerMoney(customerId,price);}
}
然后运行测试代码类,也是报如下错:
然后看下数据库中,是否数据变化。
可以看出这个启动的是一个事务,那就是一个报错,然后整个事务进行回滚。
@Transactional注有哪些属性
属性 | 描述 |
---|---|
propagation | 代表事务的传播行为,默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为 Isolation.DEFAULT。 |
timeout | 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。一般是编译时不回滚,运行时异常回滚。 |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型也只在运行时其效果。 |
然后说一下其中的某几个属性(因为都是理论直接复制黏贴了):
-
propagation属性:代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下
- Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
- Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
- Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
- Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
- Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
- Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
- Propagation.NESTED :和 Propagation.REQUIRED 效果一样。
-
isolation 属性:事务的隔离级别,默认值为 Isolation.DEFAULT。其它如下:
- Isolation.DEFAULT:使用底层数据库默认的隔离级别。
- Isolation.READ_UNCOMMITTED:未授权读取级别。以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他写事务(但允许其他读事务)。此隔离级别可以防止更新丢失,但不能防止脏读、不可重复读、幻读。此隔离级别可以通过“排他写锁”实现。
- iIsolation.READ_COMMITTED:授权读取级别。以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读,但不能防止不可重复读、幻读。此隔离级别可以通过“瞬间共享读锁”和“排他写锁”实现。
- iIsolation.REPEATABLE_READ:可重复读取级别。以操作同一行数据为前提,读事务禁止其他写事务(但允许其他读事务),未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。此隔离级别可以通过“共享读锁”和“排他写锁”实现。
- iIsolation.SERIALIZABLE:序列化级别。提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
具体隔离解决了什么问题,可以看另一篇文章:传送阵。
不过说实话说了这样多的属性,以及分别,不过一般的时候使用的是默认
比如将服务类变成:
@Transactional(readOnly = true)
还有就是前面启动事务,然后更新用户钱的时候报错,然后事务整体回滚,然后可以如下:
@Service
// DataIntegrityViolationException是之前事务混滚的时候报,然后使用如果出现这个异常不回滚,然后运行看一下结果:noRollbackFor值为数组,如果只有一个也可以如下写:noRollbackFor = DataIntegrityViolationException.class。
@Transactional(noRollbackFor = {DataIntegrityViolationException.class})
public class GameServiceImpl implements GameService {@Qualifierprivate GameDao gameDao;public GameServiceImpl(GameDao gameDao) {this.gameDao = gameDao;}@Overridepublic void BuyGame(int customerId,int gameId) {
// System.out.println("-------"+gameDao);Double price =gameDao.findGamePrice(gameId);gameDao.updateGamecount(gameId);gameDao.updateCustomerMoney(customerId,price);}
}
运行的时候依然报错,让事务没有回滚。
其它的也就不再一一演示了
但是涉及到一个事务的传播,这个需要演示一下:
然后创建一个新的需求:
public interface CleanMoney {void cleanmoney(Integer[] gameIds, Integer customerId);
}@Service
@Transactional
public class CleanMoneyImpl implements CleanMoney {@AutowiredGameService gameService;@Overridepublic void cleanmoney(Integer[] gameIds, Integer customerId) {for (Integer gameId : gameIds) {gameService.BuyGame( customerId,gameId);}}
}
另一个服务的代码如下:
@Service
@Transactional
public class GameServiceImpl implements GameService {@Qualifierprivate GameDao gameDao;public GameServiceImpl(GameDao gameDao) {this.gameDao = gameDao;}@Overridepublic void BuyGame(int customerId,int gameId) {
// System.out.println("-------"+gameDao);Double price =gameDao.findGamePrice(gameId);gameDao.updateGamecount(gameId);gameDao.updateCustomerMoney(customerId,price);}
}
然后调用方法:
public class test {private JdbcTemplate jdbcTemplate;@Autowiredprivate CleanMoney cleanMoney;@Test
// 这里吕布有200块 其它游戏都是100多,所以至少可以购买一个游戏public void test(){Integer[] gameIds={1,2};cleanMoney.cleanmoney(gameIds,2);}
}
可见虽然下面也启动的事务,虽然两个服务都有开启了事务,但是还是以CleanMoney上的事务为准的。
但是如果如下设置:
@Service
@Transactional( propagation = Propagation.REQUIRES_NEW)
public class GameServiceImpl implements GameService {@Qualifierprivate GameDao gameDao;public GameServiceImpl(GameDao gameDao) {this.gameDao = gameDao;}@Overridepublic void BuyGame(int customerId,int gameId) {
// System.out.println("-------"+gameDao);Double price =gameDao.findGamePrice(gameId);gameDao.updateGamecount(gameId);gameDao.updateCustomerMoney(customerId,price);}
}
虽然报错,但是可以购买一本书。
@Transactional失效场景(直接复制了)
-
@Transactional 应用在非 public 修饰的方法上,就会失效
-
@Transactional 注解属性 propagation 设置错误,这种失效是由于配置错误。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。 -
@Transactional 注解属性 rollbackFor 设置错误。一般不会设置这个属性,而是使用默认值。
-
同一个类中方法调用,导致@Transactional失效。其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
-
异常被你的 catch“吃了”导致@Transactional失效。
-
算是一种会影响导致@Transactional失效的可能性那就是数据库引擎不支持事务。
xml演示事务
这个就不演示了,而是直接用一个xml配置文件聊了:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 导入配置文件的 如果是普通Java项目 可以写location="jdbc.properties" 但是最后都带classpath: --><context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder><context:component-scan base-package="com.xzd"></context:component-scan><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>
<!-- 这个是之间连接 spring中的JdbcTemplate类 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
<aop:config><!-- 配置事务通知和切入点表达式 --><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.xzd.*.*.*.*(..))"></aop:advisor></aop:config><!-- tx:advice标签:配置事务通知 --><!-- id属性:给事务通知标签设置唯一标识,便于引用 --><!-- transaction-manager属性:关联事务管理器 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- tx:method标签:配置具体的事务方法 --><!-- name属性:指定方法名,可以使用星号代表多个字符 --><tx:method name="buyBook"/>
<!-- <tx:method name="*"/>--><tx:method name="get*" read-only="true"/><tx:method name="query*" read-only="true"/><tx:method name="find*" 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>
</beans>
不过一般的时候使用注解进行开发的情况会多一些,xml目前了解即可,至少在需要使用的时候会用。
相关文章:

ssm框架之spring:浅聊事务--JdbcTemplate
简介 JdbcTemplate 是 Spring 对 JDBC 的封装,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放,它帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流&…...

盘点Python那些简单实用的第三方库
文章目录前言关于本文使用 pip 命令下载第三方库1、phone 库(获取手机号码信息)2、geoip2 库(IP 检测功能)3、freegames 库(免费小游戏)4、jionlp 库(解析地址信息)5、pyqrcode 库&a…...

leetCode热题21-26 解题代码,调试代码和思路
前言 本文属于特定的六道题目题解和调试代码。 1 ✔ [160]相交链表 Easy 2023-03-17 171 2 ✔ [54]螺旋矩阵 Medium 2023-03-17 169 3 ✔ [23]合并K个排序链表 Hard 2022-12-08 158 4 ✔ [92]反转链表 II Medium 2023-03-01 155 5 ✔ [415]字符串相加 Easy 2023-03-14 150 6 …...

ChatGPT推出第四代GPT-4!不仅能聊天,还可以图片创作!
3月15日凌晨,OpenAI震撼发布了多模态预训练大模型 GPT-4。 根据官网发布的通告可以知道,GPT-4 实现了以下几个方面的飞跃式提升:强大的AI创作识图能力;文字输入限制提升至 2.5 万字;回答准确性显著提高;能够…...

二叉搜索树:AVL平衡
文章目录一、 二叉搜索树1.1 概念1.2 操作1.3 代码实现二、二叉搜索树的应用K模型和KV模型三、二叉搜索树的性能分析四、AVL树4.1 AVL树的概念4.2 AVL树的实现原理4.3 旋转4.4 AVL树最终代码一、 二叉搜索树 1.1 概念 二叉搜索树( Binary Search Tree,…...

数据结构和算法(1):数组
目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…...

python+django+vue全家桶鲜花商城售卖系统
重点: (1) 网上花店网站中各模块功能之间的的串联。 (2) 网上花店网站前台与后台的连接与同步。 (3) 鲜花信息管理模块中鲜花的发布、更新与删除。 (4) 订单…...

一文带你领略 WPA3-SAE 的 “安全感”
引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP (authenticator) 和 (supplicant)进行四次信息交互,生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …...
Python解题 - CSDN周赛第38期
又来拯救公主了。。。本期四道题还是都考过,而且后面两道问哥在以前写的题解里给出了详细的代码(当然是python版),直接复制粘贴就可以过了——尽管这样显得有失公允,考虑到以后还会出现重复的考题,所以现在…...

Android绘制——自定义view之onLayout
简介 在自定义view的时候,其实很简单,只需要知道3步骤: 测量——onMeasure():决定View的大小,关于此请阅读《Android自定义控件之onMeasure》布局——onLayout():决定View在ViewGroup中的位置绘制——onD…...

用Qt画一个温度计
示例1 以下是用Qt绘制一个简单的温度计的示例代码: #include <QPainter> #include <QWidget> #include <QApplication> class Thermometer : public QWidget { public:Thermometer(QWidget *parent 0); protected:void paintEvent(QPaintEvent …...

Java设计模式 04-建造者模式
建造者模式 一、 盖房项目需求 1)需要建房子:这一过程为打桩、砌墙、封顶 2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的. 3)请编写程序,完成需求. …...

安语未公告于2023年3月20日发布
因一些特殊原因,凡事都是有开始,高潮和结束三大过程,做出以下决定: 所有对 安语未文章 为之热爱、鞭策、奉献,和支持过的开发者: 注:所有资源以及资料都会正常下载和查看 如需联系࿱…...

进销存是什么?如何选择进销存系统?
什么是进销存?进销存软件概念起源于上世纪80年代,由于电算化的普及,计算机管理的推广,不少企业对于仓库货品的进货,存货,出货管理,有了强烈的需求,进销存软件的发展从此开始。 进入…...

基于BP神经网络的图像跟踪,基于BP神经网络的细胞追踪识别
目录 摘要 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 基于BP神经网络的细胞识别追踪 matab编程代码 效果 结果分析 展望 摘要 智能驾驶,智能出行是现代社会发展的趋势之一,其中,客量预测对智能出行至关重要,…...

Java面试总结篇
引用介绍 1.线程安全不安全的概念 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…...

100天精通Python(可视化篇)——第80天:matplotlib绘制不同种类炫酷柱状图代码实战(簇状、堆积、横向、百分比、3D柱状图)
文章目录0. 专栏导读1. 普通柱状图2. 簇状柱形图3. 堆积柱形图4. 横向柱状图5. 横向双向柱状图6. 百分比堆积柱形图7. 3D柱形图8. 3D堆积柱形图9. 3D百分比堆积柱形图0. 专栏导读 🏆🏆作者介绍:Python领域优质创作者、CSDN/华为云/阿里云/掘金…...

【Java】UDP网络编程
文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。 UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对…...

Springboot源代码总结
前言 编写微服务,巩固知识 文章目录 前言springboot原理springboot启动流程SpringBoot自动配置底层源码解析自动配置到底配了什么?自动配置类条件注解Starter机制@ConditionalOnMissingBeanSpringBoot启动过程源码解析构造SpringApplication对象SpringBoot完整的配置优先级s…...

JVM监控搭建
文章目录JVM监控搭建整体架构JolokiaTelegrafInfluxdbGrafanaJVM监控搭建 整体架构 JVM 的各种内存信息,会通过 JMX 接口进行暴露。 Jolokia 组件负责把 JMX 信息翻译成容易读取的 HTTP 请求。Telegraf 组件作为一个通用的监控 agent,和 JVM 进程部署在…...

java中如何优化大量的if...else...
目录 策略模式(Strategy Pattern) 工厂模式(Factory Pattern) 映射表(Map) 数据驱动设计(Data-Driven Design) 策略模式(Strategy Pattern) 将每个条件分…...

24. linux系统基础
两个进程间想通讯,必须要通过内核,今天讲的信号其实本质也是讲进程间通讯的意思,那么你为什么可以在shell环境下,可以和一个进程发kill-9啊? shell是不是相当于一个进程?你自己运行的那个进程是不是也相当于…...

【C++】面试101,二叉搜索树的最近公共祖先,在二叉树中找到两个节点的最近公共祖先,序列化二叉树,重建二叉树,输出二叉树的右视图,组队竞赛,删除公共字符
目录 1.二叉搜索树的最近公共祖先 2.在二叉树中找到两个节点的最近公共祖先 3.序列化二叉树 4.重建二叉树 5.输出二叉树的右视图 6.组队竞赛 7.删除公共字符 1.二叉搜索树的最近公共祖先 这是一个简单的问题,因为是二叉搜索树(有序)&am…...

Java常见面试题及解答
Java常见面试题及解答1 面向对象的三个特征2 this,super关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载(overload)、重写(override)8 final、finalize、final…...

【Docker】镜像的原理定制化镜像
文章目录镜像是什么UnionFS(联合文件系统)Docker镜像加载原理制作本地镜像 docker commit -m"提交的描述信息" -a"作者" 容器ID 要创建的目标镜像名:[标签名]案例演示ubuntu安装vim本地镜像发布到阿里云本地镜像发布到阿里云流程将本…...

国内版的ChatGPT弯道超车的机会在哪里?
前言 从去年11月最后一天ChatGPT诞生,截至目前,ChatGPT的热度可谓是爆了。众所周知,ChatGPT是美国“开放人工智能研究中心”研发的聊天机器人程序,它是一个人工智能技术驱动的自然语言处理工具,它能够通过学习和理解人…...

【字符串】
string1.char str[]类型fgets(s,10000,stdin) cin.getline(cin,10000) strlen(str)sizeof 求静态数组长度2.string类型getline(cin,a) cin.getline(cin,10000) str.lenth()str.size()cin 遇到空格就停止3.gets 函数char str[20];gets(str);4.puts 函数puts(str) 相当于 cout<…...

加载驱动之后无法在/dev/下生成vedio0
前言 环境介绍: 1.编译环境 Ubuntu 18.04.5 LTS 2.SDK orangepi Linux 5.4 SDK 3.uboot v2020.04 4.gcc gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 5.单板 orangepi pc plus 一、问题 继上一篇成功加载gc2035.ko文件之后,理论上…...

Java之类与对象(图文结合)
目录 一、面向对象的初步认知 1、什么是面向对象 2、面向对象与面向过程 二、类定义和使用 1、简单认识类 2、类的定义格式 3、练习 (1)定义一个狗类 (2)定义一个学生类 三、类的实例化 1、什么是实例化 2、类和对象的…...

基于 VCS-NLP 的动态低功耗仿真验证介绍
🔥点击查看精选 IC 技能树系列文章🔥 🔥点击进入【芯片设计验证】社区,查看更多精彩内容🔥 📢 声明: 🥭 作者主页:【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#…...