1.搭建环境

1.在项目的pom.xml文件中添加c3p0数据源的依赖

  1. <dependency>
  2. <groupId>c3p0</groupId>
  3. <artifactId>c3p0</artifactId>
  4. <version>0.9.1.2</version>
  5. </dependency>
  1. 2.在项目的pom.xml文件中添加MySQL数据库驱动的依赖
  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>5.1.44</version>
  5. </dependency>
  1. 3.在项目的pom.xml文件中添加spring-jdbc模块的依赖
  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-jdbc</artifactId>
  4. <version>4.3.12.RELEASE</version>
  5. </dependency>

Spring简化了对数据库的操作,只要我们在项目中导入了以上spring-jdbc模块的依赖,那么它就可以简化对数据库的操作以及事务控制。我们在后面就可以用Spring提供的JDBC模板(即JdbcTemplate)来操作数据库。

2.配置数据源

向IOC容器中注册一个c3p0数据源,那么如何做到这一点呢?很简单,先新建一个配置类,例如TxConfig,再使用@Bean注解向IOC容器中注册一个c3p0数据源,如下所示:

  1. package com.sdehua.tx;
  2. import javax.sql.DataSource;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import com.mchange.v2.c3p0.ComboPooledDataSource;
  6. /**
  7. * @author coffee
  8. */
  9. @Configuration
  10. public class TxConfig {
  11. // 注册c3p0数据源
  12. @Bean
  13. public DataSource dataSource() throws Exception {
  14. ComboPooledDataSource dataSource = new ComboPooledDataSource();
  15. dataSource.setUser("root");
  16. dataSource.setPassword("123456");
  17. dataSource.setDriverClass("com.mysql.jdbc.Driver");
  18. dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
  19. return dataSource;
  20. }
  21. }

3.注册JdbcTemplate组件

向IOC容器中注册一个JdbcTemplate组件,它是Spring提供的一个简化数据库操作的工具,它能简化对数据库的增删改查操作。

  1. @Bean
  2. public JdbcTemplate jdbcTemplate() throws Exception {
  3. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
  4. return jdbcTemplate;
  5. }

注意,在创建JdbcTemplate对象的时候,得把数据源传入JdbcTemplate类的有参构造器中,因为需要从数据源里面获取数据库连接。

其实,将数据源传入JdbcTemplate类的有参构造器中,一共有两种方式。
第一种方式是将数据源作为一个参数传递到TxConfig配置类的jdbcTemplate()方法中。这样,JdbcTemplate类的有参构造器就可以使用到这个数据源了。

  1. @Bean
  2. public JdbcTemplate jdbcTemplate(DataSource dataSource) throws Exception {
  3. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  4. return jdbcTemplate;
  5. }

为什么可以这样做呢?因为@Bean注解标注的方法在创建对象的时候,方法参数的值是从IOC容器中获取的,并且标注在这个方法的参数上的@Autowired注解可以省略。

第二种方式就不用那么麻烦了,在JdbcTemplate类的有参构造器中调用一次dataSource()方法即可。可以看到,向IOC容器中注册一个JdbcTemplate组件时,使用的就是这种方式。

有些同学可能会有一些疑问,TxConfig配置类的dataSource()方法是向IOC容器中注册一个c3p0数据源的,该方法的逻辑也很简单,就是创建一个c3p0数据源并将其返回出去,而在向IOC容器中注册一个JdbcTemplate组件时,会在其有参构造器中调用一次dataSource()方法,那岂不是又会创建一个c3p0数据源呢?不知你会不会有这样一个疑问,反正我是有的。其实,并不会再创建一个c3p0数据源,因为对于Spring的配置类而言,只要某个方法是给IOC容器中注册组件的,那么我们第二次调用该方法,就相当于是从IOC容器中找组件,而不是说把该方法再运行一遍

总结一下,Spring对@Configuration注解标注的类会做特殊处理,多次调用给IOC容器中添加组件的方法,都只是从IOC容器中找组件而已。

4.创建表结构

首先在test数据库中临时创建一张表,例如tbl_user,建表语句如下:

  1. CREATE TABLE `tbl_user` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `username` varchar(50) DEFAULT NULL,
  4. `age` int(2) DEFAULT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表建好之后,我们就来说说开发需求,其实很简单,就是向tbl_user表中插入一条记录。接下来,我们就来编码实现这个需求。

5.开发dao层

新建一个UserDao类,代码如下所示:

  1. package com.sdehua.tx;
  2. import java.util.UUID;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.jdbc.core.JdbcTemplate;
  5. import org.springframework.stereotype.Repository;
  6. @Repository
  7. public class UserDao {
  8. @Autowired
  9. private JdbcTemplate jdbcTemplate;
  10. public void insert() {
  11. String sql = "insert into `tbl_user`(username, age) values(?, ?)";
  12. String username = UUID.randomUUID().toString().substring(0, 5);
  13. jdbcTemplate.update(sql, username, 19); // 增删改都来调用这个方法
  14. }
  15. }

注意,该类上标注了一个@Repository注解,因为待会我们要用到@ComponentScan注解来配置包扫描。

6.开发service层

新建一个UserService类,代码如下所示:

  1. package com.sdehua.tx;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class UserService {
  6. @Autowired
  7. private UserDao userDao;
  8. public void insertUser() {
  9. userDao.insert();
  10. System.out.println("插入完成...");
  11. }
  12. }

可以看到,现在默认insertUser()方法是没有任何事务特性的。如果这个方法上有事务,那么只要这个方法里面有任何一句代码出现了问题,该行代码之前执行的所有操作就都应该回滚。

此外,还应注意到,该类上标注了一个@Service注解。

接下来,我们就要在TxConfig配置类上添加@ComponentScan注解来配置包扫描了,如下所示:

  1. package com.sdehua.tx;
  2. import javax.sql.DataSource;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.ComponentScan;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import com.mchange.v2.c3p0.ComboPooledDataSource;
  8. /**
  9. * @author coffee
  10. */
  11. @ComponentScan("com.sdehua.tx")
  12. @Configuration
  13. public class TxConfig {
  14. // 注册c3p0数据源
  15. @Bean
  16. public DataSource dataSource() throws Exception {
  17. ComboPooledDataSource dataSource = new ComboPooledDataSource();
  18. dataSource.setUser("root");
  19. dataSource.setPassword("123456");
  20. dataSource.setDriverClass("com.mysql.jdbc.Driver");
  21. dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
  22. return dataSource;
  23. }
  24. @Bean
  25. public JdbcTemplate jdbcTemplate() throws Exception {
  26. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
  27. return jdbcTemplate;
  28. }
  29. }

至此,声明式事务的基本环境,我们就搭建好了,接下来就是来进行测试了。

7.测试环境

  1. package com.sdehua.tx;
  2. import org.junit.Test;
  3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  4. public class IOCTest_tx {
  5. @Test
  6. public void test01() {
  7. AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(TxConfig.class);
  8. UserService userService = app.getBean(UserService.class);
  9. userService.insertUser();
  10. app.close();
  11. }
  12. }

运行以上test01()方法,可以看到确实是向该表中插入了一条记录,如下图所示:
image.png

8.添加事务

接下来,我们就为UserService类中的insertUser()方法添加上事务,添加上事务以后,只要这个方法里面有任何一句代码出现了问题,那么该行代码之前执行的所有操作就都应该回滚。

如果要想为该方法添加上事务,那么就得使用@Transactional注解了。我们在该方法上标注这么一个注解,就是为了告诉Spring这个方法它是一个事务方法,这样,Spring在执行这个方法的时候,就会自动地进行事务控制。如果该方法正常执行,没出现任何问题,那么该方法中的所有操作都会生效,最终就会提交;如果该方法运行期间出现异常,那么该方法中的所有操作都会回滚。

  1. @Transactional
  2. public void insertUser() {
  3. userDao.insert();
  4. // otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
  5. System.out.println("插入完成...");
  6. int i = 10 / 0;
  7. }

光为insertUser()方法加一个@Transactional注解是不行的,那我们还得做什么呢?还得在TxConfig配置类上标注一个@EnableTransactionManagement注解,来开启基于注解的事务管理功能。

  1. package com.sdehua.tx;
  2. import javax.sql.DataSource;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.ComponentScan;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.transaction.annotation.EnableTransactionManagement;
  8. import com.mchange.v2.c3p0.ComboPooledDataSource;
  9. /**
  10. * @author coffee
  11. */
  12. @EnableTransactionManagement // 它是来开启基于注解的事务管理功能的
  13. @ComponentScan("com.sdehua.tx")
  14. @Configuration
  15. public class TxConfig {
  16. // 注册c3p0数据源
  17. @Bean
  18. public DataSource dataSource() throws Exception {
  19. ComboPooledDataSource dataSource = new ComboPooledDataSource();
  20. dataSource.setUser("root");
  21. dataSource.setPassword("123456");
  22. dataSource.setDriverClass("com.mysql.jdbc.Driver");
  23. dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
  24. return dataSource;
  25. }
  26. @Bean
  27. public JdbcTemplate jdbcTemplate() throws Exception {
  28. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
  29. return jdbcTemplate;
  30. }
  31. }

如果是像以前一样基于配置文件来开发,那么就得在配置文件中添加如下这样一行配置,来开启基于注解的事务管理功能。

  1. <tx:annotation-driven/>
  1. 最后配置基于平台的事务管理器来控制事务,像Springspring-jdbc模块,以及MyBatis框架等等这些想要进行事务控制,都需要用到这个DataSourceTransactionManager实现类。

接下来,我们就向IOC容器中注册事务管理器,即需要向TxConfig配置类中添加一个如下方法:

  1. // 注册事务管理器在容器中
  2. @Bean
  3. public PlatformTransactionManager platformTransactionManager() throws Exception {
  4. return new DataSourceTransactionManager(dataSource());
  5. }

注意,这个事务管理器有一个特别重要的地方,就是它要管理数据源,也就是说事务管理器一定要把数据源控制住。这样的话,它才会控制住数据源里面的每一个连接,这时该连接上的回滚以及事务的开启等操作,都将会由这个事务管理器来做。

好了,现在我们就要来测试一把了。运行IOCTest_tx类中的test01()方法,你会发现Eclipse控制台不仅打印出了插入完成…这样的消息,而且还抛出了一个除零的算术异常,最重要的是没有向tbl_user表中插入一条新的记录,这说明insertUser()方法现在就成了一个事务方法。