简介
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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://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 {
@Test
public 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 {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testMethod(){
System.out.println(jdbcTemplate);
}
}
可以看出可以通过注解实现ICO容器的注入。
其中@RunWith可以通过SpringJUnit4ClassRunner.class进行启动IOC容器。
代码演示
常规数据操作
JdbcTemplate主要提供以下五类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
先来一个插入
@Test
public void insertMethod(){
String sql="INSERT INTO `test_jdbtemplate`.`t_game` VALUES (?,?,?,?,?,?)";
// 其实插入和删除都是使用update方法
jdbcTemplate.update(sql,1,4,"寂静岭","那可是护士啊",82,10);
}
然后再来要给删除:
@Test
public 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快捷键很快创建出来
}
现在得到一个数据的集合:
@Test
public 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);
}
如果只返回一个对象数据:
@Test
public 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);
}
@Test
public void selectMethod2() {
String sql = "SELECT count(*) FROM `test_jdbtemplate`.`t_game`";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(integer);
}
@Test
public 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 {
@Qualifier
private GameDao gameDao;
public GameServiceImpl(GameDao gameDao) {
this.gameDao = gameDao;
}
@Override
public 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 {
@Resource
JdbcTemplate jdbcTemplate;
@Override
public 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);
}
@Override
public 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")
@Autowired
private GameService gameService;
@Test
public 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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://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 {
@Qualifier
private GameDao gameDao;
public GameServiceImpl(GameDao gameDao) {
this.gameDao = gameDao;
}
@Override
public 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 {
@Qualifier
private GameDao gameDao;
public GameServiceImpl(GameDao gameDao) {
this.gameDao = gameDao;
}
@Override
public 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 {
@Autowired
GameService gameService;
@Override
public void cleanmoney(Integer[] gameIds, Integer customerId) {
for (Integer gameId : gameIds) {
gameService.BuyGame( customerId,gameId);
}
}
}
另一个服务的代码如下:
@Service
@Transactional
public class GameServiceImpl implements GameService {
@Qualifier
private GameDao gameDao;
public GameServiceImpl(GameDao gameDao) {
this.gameDao = gameDao;
}
@Override
public 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;
@Autowired
private 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 {
@Qualifier
private GameDao gameDao;
public GameServiceImpl(GameDao gameDao) {
this.gameDao = gameDao;
}
@Override
public 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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://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目前了解即可,至少在需要使用的时候会用。