Spring - 事务篇

本文介绍了Spring如何整合JDBC,特别是管理JdbcTemplate的方式。接着深入探讨Spring的事务管理,包括事务管理API,如PlatformTransactionManager接口及其常用实现,以及TransactionDefinition接口中的事务隔离级别和传播行为。还讨论了声明式事务控制,特别是基于注解和XML的事务配置方式,强调了事务在Service层的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring - 事务篇



1. 指路 - Spring AOP 篇
2. 指路 - Spring IoC 篇


1. Spring整合JDBC

1.1 使用spring-jdbc操作数据库

  • 主要内容:学习使用JdbcTemplate API和如何使用Spring管理JdbcTemplate
  1. 创建项目引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.13.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.13.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!--编译插件-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>
  1. 测试连接
@Test
public void test() throws PropertyVetoException {
    // 创建数据源
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/数据库名?serverTimezone=Asia/Shanghai&" +
            "characterEncoding=utf8&useUnicode=true&useSSL=false");
    dataSource.setUser("用户名");
    dataSource.setPassword("密码");
    // 使用JDBCTemplate
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    String sql = "INSERT INTO 表名(字段名1,字段名2) VALUES (?,?)";
    int update = jdbcTemplate.update(sql,"字段值1","字段值2");
    System.out.println("插入数据的结果:" + update);
}

1.2 Spring 管理 JdbcTemplate

  • Spring整合JDBC的时候我们都是直接让我的Dao继承Spring提供的JdbcDaoSupport类,该类中提供了JdbcTemplate模板可用。

示例 :

/**
 * Dao
 *
 * @author murphy
 */
public class UserDao extends JdbcDaoSupport {
    /**
     * 增加
     * @param user
     * @return
     */
    public int insert(User user) {
        String sql = "INSERT INTO USER(UNAME,LOCATION) VALUES (?,?)";
        return this.getJdbcTemplate().update(sql,user.getUname(),user.getLocation());
    }

    /**
     * 更新
     * @param user
     * @return
     */
    public int update(User user) {
        String sql = "UPDATE User SET uname = ?, location=? WHERE uid = ?";
        return this.getJdbcTemplate().update(sql,user.getUname(),user.getLocation(),user.getUid());
    }

    /**
     * 删除
     * @param id
     * @return
     */
    public int del(int id) {
        String sql = "Delete from User where uid = ?";
        return this.getJdbcTemplate().update(sql,id);
    }

    /**
     * 处理结果的方法
     * @param resultSet
     * @return
     * @throws SQLException
     */
    public User handleResult(ResultSet resultSet) throws SQLException {
        User user = new User();
        user.setUid(resultSet.getInt(1));
        user.setUname(resultSet.getString(2));
        user.setLocation(resultSet.getString(3));
        return user;
    }

    /**
     * 根据ID查询
     * @param id
     * @return
     */
    public User findById(int id) {
        String sql = "SELECT * FROM USER WHERE UID = ?";
        return this.getJdbcTemplate().queryForObject(sql, new Object[]{id}, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
//                User user = new User();
//                user.setUid(resultSet.getInt(1));
//                user.setUname(resultSet.getString(2));
//                user.setLocation(resultSet.getString(3));
                return handleResult(resultSet);
            }
        });
    }

    /**
     * 查询所有用户信息
     * @return
     */
    public List<User> findAll() {
        String sql = "SELECT * FROM User";
        return this.getJdbcTemplate().query(sql, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
//                User user = new User();
//                user.setUid(resultSet.getInt(1));
//                user.setUname(resultSet.getString(2));
//                user.setLocation(resultSet.getString(3));
                return handleResult(resultSet);
            }
        });
    }

    /**
     * 查询总行数
     * @return
     */
    public int getCount() {
        String sql = "select count(uid) from User";
        return this.getJdbcTemplate().queryForObject(sql,Integer.class);
    }

    /**
     * 查询最大值 + 最小值
     * @return
     */
    public Map<String, Object> getMaxAndMin() {
        String sql = "select max(uid), min(uid) from User";
        return this.getJdbcTemplate().queryForMap(sql);
    }
}
  • Spring的配置文件spring.xml中需要创建数据源和给TeamDao中的jdbcTemplate赋值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置JdbcTemplate的数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/数据库名?serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"></property>
        <property name="user" value="数据库账号"></property>
        <property name="password" value="数据库密码"></property>
    </bean>
    <!-- 配置JdbcTemplate模版 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="userDao" class="com.murphy.dao.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

测试:

/**
 * 测试类
 *
 * @author murphy
 */
public class ConnectTest {

    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

  	// 新增测试
    @Test
    public void testInsert() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        User user = new User();
        user.setUname("Adan");
        user.setLocation("Hangzhou");
        int res = userDao.insert(user);
        System.out.println("插入数据的结果:" + res);
    }

    // 修改测试
    @Test
    public void testUpdate() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        User user = new User();
        user.setUname("Tik");
        user.setLocation("Beijing");
        user.setUid(5);
        int res = userDao.update(user);
        System.out.println("更新数据的结果:" + res);
    }

    // 删除测试
    @Test
    public void testDel() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        int res = userDao.del(6);
        System.out.println("删除数据的结果:" + res);
    }

    // 查询ID测试
    @Test
    public void testFindById() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        User user = userDao.findById(1);
        System.out.println(user);
    }

    // 查询所有测试
    @Test
    public void testFindAll() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    // 总行数获取测试
    @Test
    public void testGetCount() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        int count = userDao.getCount();
        System.out.println("总行数:" + count);
    }

    // 最大和最小测试
    @Test
    public void testGetMaxAndMin() {
        UserDao userDao = (UserDao) context.getBean("userDao");
        Map<String, Object> many = userDao.getMaxAndMin();
        Set<Map.Entry<String, Object>> entries = many.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            System.out.println(entry.getKey() + " - " + entry.getValue());
        }
    }
}

2. Spring事务管理

  • 事务原本是数据库中的概念,在Dao层。但在实际开发中,一般将事务提升到业务层,即Service层。
  • 这样做是为了能够使用事务的特性来管理具体的业务。

2.1 Spring事务管理API

  • Spring的事务管理,主要用到两个事务相关的接口。
2.1.1 事务管理器接口
  • 事务管理器PlatformTransactionManager接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息

在这里插入图片描述

PlatformTransactionManager 接口常用的实现类

  • DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。

Spring的回滚方式

  • Spring 事务的默认回滚方式是:发生运行时异常和error时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
2.1.2 事务定义接口
  • 事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。

在这里插入图片描述

2.1.2.1 事务隔离级别常量

这些常量均是以ISOLATION_开头。即形如ISOLATION_XXX

  • DEFAULT:采用DB默认的事务隔离级别。MySql的默认为REPEATABLE_READ; Oracle默认为READ_COMMITTED
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。
2.1.2.2 事务传播行为常量
  • 所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法doSome()调用B事务中的方法doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
  • 事务传播行为常量都是以PROPAGATION_开头,形如PROPAGATION_XXX

Propagation.REQUIRED

  • 当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务,比较常用的设置

Propagation.SUPPORTS

  • 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行

Propagation.MANDATORY

  • 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常

Propagation.REQUIRES_NEW

  • 创建新事务,无论当前是否有事务都会创建新的

PROPAGATION_NESTED

  • 如果当前存在事务,则以嵌入式事务方式去执行,和PROPAGATION_REQUIRED行为类似

PROPAGATION_NEVER

  • 以非事务方式执行,如果当前存在事务则抛出异常

PROPAGATION_NOT_SUPPORTED

  • 以非事务方式执行,如果当前存在事务则挂起当前事务
2.1.2.3 默认事务超时时限
  • 常量TIMEOUT_DEFAULT定义了事务底层默认的超时时限,sql语句的执行时长。
  • 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

2.2 声明式事务控制

  • Spring提供的对事务的管理,就叫做声明式事务管理
  • 如果用户需要使用Spring的声明式事务管理,在配置文件中配置即可:不想使用的时候直接移除配置。
  • 这种方式实现了对事务控制的最大程度的解耦。
  • 声明式事务管理,核心实现就是基于AOP。

Spring中提供了对事务的管理。开发者只需要按照Spring的方式去做就行。事务必须在service层统一控制。

事务的粗细粒度:

  • 细粒度:对方法中的某几行的代码进行开启提交回滚;
  • 粗粒度:对整个方法进行开启提交回滚;

Spring中的aop只能对方法进行拦截,所有我们也就针对方法进行事务的控制。

如果只有单条的查询语句,可以省略事务;如果一次执行的是多条查询语句,例如统计结果、报表查询。必须开启事务。

2.3 基于注解的事务

/**
 * 事务实现
 *
 * @author murphy
 */
@Service
public class UserService {

    @Autowired
    private UserDao userDao;
    /**
      * @Transactional 属性 说明:
      * readOnly:是否只读 *
      * rollbackFor={Exception.class}: 遇到什么异常会回滚 *
      * propagation事务的传播:
      * 	Propagation.REQUIRED:当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务,比较常用的设置
      * 	Propagation.SUPPORTS:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行
      * 	Propagation.MANDATORY:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常
      * 	Propagation.REQUIRES_NEW:创建新事务,无论当前是否有事务都会创建新的 *
      * isolation=Isolation.DEFAULT:事务的隔离级别:默认是数据库的隔离级别 *
      */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
    public int insert(User user) {
        int num1 = userDao.insert(user);
        System.out.println("第一条执行结果:num1 = " + num1);
        System.out.println(10/0);
        int num2 = userDao.insert(user);
        return num1 + num2;
    }
}
<!-- 配置文件中添加 context/ tx约束 -->
<!-- 开启事务 -->
<context:component-scan base-package="com.murphy.service,com.murphy.dao"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
// 测试类
@Test
public void test() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    UserService userService = (UserService) ac.getBean("userService");
    int num = userService.insert(new User(9, "Aki", "Wuhan"));
    System.out.println(num);
}

2.4 基于XML的事务

添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>

spring.xml配置

<!-- 事务管理器 -->
<!-- 开启事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="pt" expression="execution(* com.murphy.service..*.*(..))"/>
    <aop:advisor pointcut-ref="pt" advice-ref="txAdvice"/>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xmurphymurphy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值