JdbcTemplate 是 Spring 对 JDBC 的封装,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放,它帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果即可。
如果直接使用JDBC的话,需要我们加载数据库驱动、创建连接、释放连接、异常处理等一系列的动作,繁琐且代码看起来不直观。而使用 jdbctemplate 则无需关注加载驱动、释放资源、异常处理等一系列操作,我们只需要提供 sql 语句并且提取最终结果即可,大大方便我们编程开发。此外,Spring提供的JdbcTempate能直接数据对象映射成实体类,不再需要获取ResultSet去获取值、赋值等操作,提高开发效率;
Spring为了简化数据库访问,主要做了以下几点工作:
SQLException
封装为DataAccessException
,这个异常是一个RuntimeException
,并且让我们能区分SQL异常的原因,例如,DuplicateKeyException
表示违反了一个唯一约束;如果同maven导入依赖的话,将spring几个核心的jar引入后,还需要引入:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.18version>
dependency>
或者将spring-orm-*.*.*.jar 将其导入到环境中。
因为需要连接数据库,因为使用你的阿里的druid,所以在maven中配置
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.16version>
dependency>
如果不通过maven导入依赖环境的花,还是需要手动导入第三方包。
完整的xml是:
<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_test3artifactId>
<groupId>com.xzdgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<packaging>jarpackaging>
<modelVersion>4.0.0modelVersion>
<artifactId>spring_jdbctemplateartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-expressionartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.23version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.18version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.29version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.16version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>RELEASEversion>
<scope>testscope>
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">
<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>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
beans>
说的话可能很乱,直接演示:
如果使用之前的导入测试类应该如下写
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主要提供以下五类方法:
先来一个插入
@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 mapper)
T queryForObject(String sql,object[] args,RowMapper mapper)
T queryForObject(String sql,RowMapper mapper,Object... arg)
获取多个
List query(String sql,RowMapper mapper)
List query(String sql,Object[] args,RowMapper mapper)
List query(String sql,RowMapper mapper,Object... arg)
事务其实又分两种:编程式事务和声明式事务。
编程式
编程式事务是通过编写程序来管理事务,程序员需要在程序中显式的指定事务的处理过程,由程序员负责事务的开启、提交和回滚等操作。
就是自己开发的时候,写事务提交,事务启动,根据不同的行为异常进行回滚等。简单的说就是自己写代码实现功能。
声明式
Spring声明式事务是通过使用Spring中的AOP技术,将事务管理逻辑从业务逻辑代码中剥离出来,使用注解等形式来声明事务,实现事务管理的功能。
简单的说就是通过配置让框架实现功能。
现象网上进行复制一些两者的区别,以及优缺点:
至于数据库的事务有涉及道的,事务时什么,以及事务的隔离级别等概念,可以看另一篇文章:传送阵
先不开启事务的话,来一个报错的:
首先看一下我的结构:
然后创建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">
<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>
<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>
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);
}
}
然后运行测试代码类,也是报如下错:
然后看下数据库中,是否数据变化。
可以看出这个启动的是一个事务,那就是一个报错,然后整个事务进行回滚。
属性 | 描述 |
---|---|
propagation | 代表事务的传播行为,默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为 Isolation.DEFAULT。 |
timeout | 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。一般是编译时不回滚,运行时异常回滚。 |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型也只在运行时其效果。 |
然后说一下其中的某几个属性(因为都是理论直接复制黏贴了):
propagation属性:代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下
isolation 属性:事务的隔离级别,默认值为 Isolation.DEFAULT。其它如下:
具体隔离解决了什么问题,可以看另一篇文章:传送阵。
不过说实话说了这样多的属性,以及分别,不过一般的时候使用的是默认
比如将服务类变成:
@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 应用在非 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配置文件聊了:
<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">
<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>
<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="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buyBook"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<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目前了解即可,至少在需要使用的时候会用。