JavaEE企业级应用开发教程——第九章 Spring的数据库编程(黑马程序员第二版)(SSM)

从第九章 Spring的数据库编程

9.1 Spring JDBC

传统的JDBC在操作数据库时,需要手动管理数据库连接等资源,这样频繁的数据库操作会产生大量重复代码,导致代码冗余。这使得开发人员需要处理低层级的细节,从而分散了他们的注意力和精力。但是,Spring 的 JDBC 模块可以负责数据库资源管理和错误处理,大大简化了开发人员对数据库的操作。

Spring JDBC 提供了一种更为高效、简洁的方式来操作数据库。使用 Spring JDBC 模块,开发人员可以不必处理与数据库连接、事务管理、异常处理等相关的底层细节,从而将更多的精力投入到业务逻辑的编写中。


9.1.1 JdbcTemplate概述

Spring 框架提供了 JdbcTemplate 类来简化数据库操作。JdbcTemplate 是一个模板类,Spring JDBC 中的更高层次的抽象类都是基于 JdbcTemplate 创建的。同时,JdbcTemplate 实现了 JdbcOperations 接口。

  • JdbcTemplate 类的继承关系比较简单,它继承自抽象类 JdbcAccessor,而 JdbcAccessor 为其子类提供了一些访问数据库时使用的公共属性。其中,DataSource 是 JdbcAccessor 的主要功能,它用于获取数据库连接。在具体的数据操作中,DataSource 还可以提供对数据库连接的缓冲池和分布式事务的支持。

  • SQLExceptionTranslator 是另一个 JdbcAccessor 的属性,它是一个接口,全称为 org.springframework.jdbc.support.SQLExceptionTranslator。SQLExceptionTranslator 接口负责对 SQLException 异常进行转译工作。通过必要的设置或者调用 SQLExceptionTranslator 接口中的方法,JdbcTemplate 可以将 SQLException 的转译工作委托给 SQLExceptionTranslator 的实现类来完成。

总之,JdbcTemplate 是 Spring JDBC 模块中的一个核心类,它可以简化数据库操作。通过继承自 JdbcAccessor,JdbcTemplate 可以使用其提供的公共属性,如 DataSource 和 SQLExceptionTranslator 等,来方便地访问数据库和处理异常。


9.1.2 Spring JDBC的配置

包名描述
org.springframework.jdbc.core这个包是 Spring JDBC 模块的核心包,包含了 JdbcTemplate、SimpleJdbcTemplate、NamedParameterJdbcTemplate 等重要的类和接口,以及一些辅助类和异常类。这些类和接口提供了一组简单易用的方法,可以帮助开发人员更加方便地进行数据访问操作。
org.springframework.jdbc.datasource这个包包含了一些数据源相关的类和接口,例如 DataSource、DataSourceTransactionManager 等。这些类和接口可以帮助开发人员更加方便地管理数据库连接和事务。
org.springframework.jdbc.object这个包包含了一些对象化的 JDBC 操作类,例如 SqlUpdate、SqlQuery 等。这些类可以帮助开发人员将 SQL 操作封装成一个对象,并提供一些便利的方法来执行这些操作。
org.springframework.jdbc.support这个包包含了一些支持类和接口,例如 SQLExceptionTranslator、SqlValue、SqlLobValue 等。这些类和接口可以帮助开发人员更加方便地处理 JDBC 相关的异常、值和 LOB 数据。

下面是一个完整的 Spring JDBC 配置文件 applicationContext.xml 的代码,包含了数据源配置、JdbcTemplate 配置、事务管理配置和 DAO 类配置:

<?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">

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 设置数据库驱动类名 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!-- 设置数据库连接 URL -->
        <property name="url" value="jdbc:mysql://localhost:3306/test" />
        <!-- 设置数据库用户名 -->
        <property name="username" value="root" />
        <!-- 设置数据库密码 -->
        <property name="password" value="password" />
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 设置数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置 DAO 类 -->
    <bean id="userDao" class="com.example.dao.UserDaoImpl">
        <!-- 注入 JdbcTemplate 实例 -->
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>

    <!-- 配置 Service 类 -->
    <bean id="userService" class="com.example.service.UserService">
        <!-- 注入 UserDaoImpl 实例 -->
        <property name="userDao" ref="userDao" />
    </bean>

</beans>

数据源的配置中,我们使用 DriverManagerDataSource 类配置了一个简单的数据源,包括数据库驱动类名、数据库连接 URL、用户名和密码等。在每个 property 元素中,我们设置了相应的属性值。

在 JdbcTemplate 的配置中,我们创建了一个 JdbcTemplate 实例,并将它的数据源设置为上面配置的数据源。这里我们使用了 ref 属性,它的值是一个引用其他 bean 的 ID,在这里我们引用了上面的数据源 bean 的 ID。

在 UserDaoImpl 和 UserService 的配置中,我们使用 property 元素将它们所依赖的 JdbcTemplate 和 UserDaoImpl 实例注入进来。这里我们同样使用了 ref 属性来引用其他 bean 的 ID。


9.2 Spring JdbcTemplate 的常用方法

JdbcTemplate 提供了一系列常用的方法。


9.2.1 execute()

Spring JdbcTemplate 中的 execute 方法用于执行任意的 SQL 语句,不返回任何结果。它的签名如下:

public void execute(String sql) throws DataAccessException;

其中,sql 参数是要执行的 SQL 语句。

使用 execute 方法可以执行任意的 SQL 语句,包括创建表、删除表、更新表等操作。例如:

jdbcTemplate.execute("CREATE TABLE users (id INT, name VARCHAR(50))");

这个例子中,我们使用 execute 方法创建了一个名为 users 的表,包括 id 和 name 两个字段。

需要注意的是,execute 方法不会返回任何结果,因此它适合用于执行没有返回值的 SQL 语句。如果要执行有返回值的 SQL 查询语句,应该使用 query 或 queryForObject 方法,而不是 execute 方法。


9.2.2 update()

Spring JdbcTemplate 中的 update 方法用于执行 INSERT、UPDATE 和 DELETE 等操作,返回受影响的行数。它的签名如下:

public int update(String sql, Object... args) throws DataAccessException;

其中,sql 参数是要执行的 SQL 语句,args 参数是 SQL 语句中的占位符所对应的值。

使用 update 方法可以执行任意的 INSERT、UPDATE 和 DELETE 操作。例如:

jdbcTemplate.update("INSERT INTO users (id, name) VALUES (?, ?)", 1, "Alice");

这个例子中,我们使用 update 方法向名为 users 的表中插入一条数据,其中 id 为 1,name 为 Alice。

如果要执行批量的 INSERT、UPDATE 和 DELETE 操作,可以使用 batchUpdate 方法。例如:

List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[]{1, "Alice"});
batchArgs.add(new Object[]{2, "Bob"});
int[] rows = jdbcTemplate.batchUpdate("INSERT INTO users (id, name) VALUES (?, ?)", batchArgs);

这个例子中,我们使用 batchArgs 参数指定了两次 INSERT 操作的参数,最终使用 batchUpdate 方法执行这两个操作,并返回每个操作受影响的行数。

需要注意的是,update 方法返回的是受影响的行数,而不是查询结果。如果要执行 SELECT 查询操作,应该使用 query 或 queryForObject 方法。


9.2.3 query()

Spring JdbcTemplate 中的 query 方法用于执行 SELECT 查询操作,返回查询结果。它的签名如下:

public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;

其中,sql 参数是要执行的 SQL 语句,rowMapper 参数是一个 RowMapper 对象,用于将 ResultSet 中的行映射为 Java 对象,args 参数是 SQL 语句中的占位符所对应的值。

使用 query 方法可以执行任意的 SELECT 查询操作。例如:

List<User> users = jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());

这个例子中,我们使用 query 方法查询名为 users 的表中的所有数据,并使用 UserRowMapper 将每行数据映射为 User 对象。

需要注意的是,query 方法返回的是一个 List,其中每个元素都是查询结果中的一行数据,使用 RowMapper 将 ResultSet 中的行映射为 Java 对象。如果查询结果为空,query 方法返回一个空的 List,而不是 null。

如果要查询单个结果,可以使用 queryForObject 方法。如果要查询分页结果,可以使用 query 方法的重载版本,指定查询结果的起始位置和数量。


9.3 Spring 事物管理概述

Spring 事务管理是 Spring 框架的一个核心特性,它提供了一种简单而强大的方法来管理数据库事务。Spring 事务管理框架可以用于任何基于 Java 的应用程序,包括 Web 应用程序和独立的 Java 应用程序。


9.3.1 事务管理的核心接口

spring-tx-4.3.6.RELEASE 是 Spring 框架提供的用于事务管理的依赖包,其中包含了 PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口。

  • PlatformTransactionManager 接口定义了事务管理器的基本操作,例如开启事务、提交事务、回滚事务等。PlatformTransactionManager 有多个实现类,用于不同的事务管理场景,例如 JpaTransactionManager、DataSourceTransactionManager 等。

  • TransactionDefinition 接口定义了事务的属性,例如事务传播行为、隔离级别、超时时间等。TransactionDefinition 常用于在 @Transactional 注解中声明事务属性。

  • TransactionStatus 接口定义了事务的状态信息,例如是否是新事务、是否已经完成、是否已经回滚等。TransactionStatus 可以用于检查事务状态、控制事务提交和回滚等。

这三个接口是 Spring 事务管理的核心接口,它们提供了事务管理的基本操作、事务属性的声明和事务状态的检查和控制等功能。使用这些接口,可以方便地管理事务,提高代码的可维护性和可靠性。


1. PlatformTransactionManager

PlatformTransactionManager 是 Spring 框架提供的事务管理器接口,定义了事务管理的基本操作,例如开启事务、提交事务、回滚事务等。它是 Spring 事务管理的核心接口之一,用于管理事务的生命周期。

PlatformTransactionManager 接口定义了以下方法:

  • TransactionStatus getTransaction(TransactionDefinition definition):开启一个新事务,并返回该事务的状态信息。

  • void commit(TransactionStatus status):提交事务,并释放该事务占用的资源。

  • void rollback(TransactionStatus status):回滚事务,并释放该事务占用的资源。

其中,getTransaction 方法用于开启新事务,并返回该事务的状态信息。TransactionDefinition 参数用于指定事务的属性,例如事务传播行为、隔离级别、超时时间等。

commit 方法用于提交事务,将事务的修改操作持久化到数据库中,并释放该事务占用的资源。

rollback 方法用于回滚事务,将事务的修改操作撤销,并释放该事务占用的资源。


2. TransactionDefinition

TransactionDefinition 是 Spring 框架提供的事务定义接口,用于定义事务的属性,例如事务传播行为、隔离级别、超时时间等。它是 Spring 事务管理的核心接口之一,用于声明事务的属性。


(1)事务的隔离级别
隔离级别常量描述
ISOLATION_DEFAULT使用数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED读未提交:一个事务可以读取另一个事务未提交的数据。这是最低的隔离级别,它允许脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED读已提交:一个事务只能读取另一个事务已经提交的数据。这种隔离级别避免了脏读,但不可避免地会出现不可重复读和幻读。
ISOLATION_REPEATABLE_READ可重复读:在同一个事务中,多次读取同一个数据,得到的结果始终相同。这种隔离级别避免了脏读和不可重复读,但仍然可能出现幻读。
ISOLATION_SERIALIZABLE序列化:所有事务按顺序依次执行,每个事务都会等待前一个事务执行完毕才能执行。这是最高的隔离级别,避免了脏读、不可重复读和幻读,但是对于系统的性能和并发度有很大的影响。

在数据库中,脏读(Dirty Read)、幻读(Phantom Read)和不可重复读(Non-repeatable Read)是三种事务并发控制问题,它们分别表示以下情况:

脏读:一个事务读取了另一个事务未提交的数据。也就是说,一个事务读取了另一个事务修改但还未提交的数据,如果这个事务最终回滚,那么读取到的数据就是无效的,这就是脏读。

幻读:一个事务读取了另一个事务已提交的数据,但是后来又发现了新的数据。也就是说,一个事务在某个时间点读取了一组数据,但是在之后的时间点,另一个事务插入了一些新的数据,这时候第一个事务再次读取同一组数据,就会发现多了一些之前没有的数据,这就是幻读。

不可重复读:一个事务在同一个时间点内多次读取同一组数据,但是得到的结果不一样。也就是说,一个事务在某个时间点读取了一组数据,但是在之后的时间点,另一个事务修改了这组数据并提交,这时候第一个事务再次读取同一组数据,就会发现数据已经发生了变化,这就是不可重复读。


(2)事务的传播行为

在 Spring 中,事务的传播行为(Transaction Propagation)是指在一个方法调用另一个方法时,如何处理事务的传播。Spring 提供了以下七种事务传播行为:

传播行为常量描述
PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;否则创建一个新的事务。这是默认的传播行为。
PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务;否则以非事务的方式执行。
PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;否则抛出异常。
PROPAGATION_REQUIRES_NEW创建一个新的事务,并且如果当前存在事务,则挂起该事务。
PROPAGATION_NOT_SUPPORTED以非事务的方式执行,并且如果当前存在事务,则挂起该事务。
PROPAGATION_NEVER以非事务的方式执行,并且如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务中执行;否则创建一个新的事务。嵌套的事务可以独立地提交或回滚,并且只对外部事务的提交产生影响。

(3)事务的超时时间

在 Spring 中,事务的超时时间(Transaction Timeout)是指事务的最大执行时间,如果事务在规定的时间内没有完成,则会被自动回滚。事务的超时时间可以通过设置 @Transactional 注解的 timeout 属性来指定,单位是秒。例如,@Transactional(timeout = 30) 表示该事务的最大执行时间为 30 秒。


(4)是否为只读事务

当事务被标记为只读时,事务管理器会将该事务优化为只读事务,即只允许查询操作,不允许进行修改操作。这样可以减少事务的锁定时间,提高并发性能,同时也可以避免一些潜在的数据覆盖问题。

如果在只读事务中进行修改数据的操作,事务管理器会检测到该操作,然后抛出异常,即只读事务不支持修改数据,只能进行查询操作。


TransactionDefnition 接口中除了提供事务的隔离级别、事务的传播行为、事务的超时时间和是否为读事务的常量外,还提供了一系列方法来获取事务的属性。

方法名描述
getPropagationBehavior获取事务的传播行为。
getIsolationLevel获取事务的隔离级别。
getTimeout获取事务的超时时间,单位为秒。
isReadOnly判断事务是否为只读事务。
getName获取事务的名称。
isTimeoutSet判断事务是否设置了超时时间。
rollbackOn获取指定异常类是否会导致事务回滚。
isRollbackOn判断指定异常类是否会导致事务回滚。
setRollbackOn设置指定异常类是否会导致事务回滚。
getRollbackRules获取所有异常类与回滚规则之间的映射关系。
hasTimeout判断事务是否设置了超时时间。
equals比较两个事务定义对象是否相等。
hashCode获取事务定义对象的哈希码。
toString返回事务定义对象的字符串表示。

3.TransactionStatus

TransactionStatus 是 Spring 框架中用于表示事务状态的接口。它定义了许多方法,用于查询和修改事务的状态。

下面是 TransactionStatus 接口中常用的方法:

  • boolean isNewTransaction(): 判断当前事务是否为新事务。
  • boolean hasSavepoint(): 判断当前事务中是否有保存点。
  • void setRollbackOnly(): 标记当前事务为回滚状态。
  • boolean isRollbackOnly(): 判断当前事务是否标记为回滚状态。
  • void flush(): 提交事务。
  • boolean isCompleted(): 判断当前事务是否已经完成。

除了上述方法外,TransactionStatus 接口还有其他一些方法,用于获取和设置事务状态的属性,例如超时时间、隔离级别等。


9.3.2 事务管理的方式

在 Spring 框架中,事务管理的方式有以下几种:

  • 编程式事务管理:通过编写代码实现事务管理。在这种方式下,开发人员需要手动控制事务的开启、提交、回滚等操作。可以使用 TransactionTemplate 或 PlatformTransactionManager 等类来进行编程式事务管理。

  • 声明式事务管理:通过声明式配置实现事务管理。在这种方式下,开发人员只需要在配置文件中声明事务管理的方式和条件,Spring 框架会自动根据这些配置来实现事务管理。可以使用 @Transactional 注解或 AOP 等方式来进行声明式事务管理。

  • 注解式事务管理:这是声明式事务管理的一种方式,使用 @Transactional 注解来声明事务管理的方式和条件。

  • XML 配置事务管理:这是声明式事务管理的一种方式,使用 XML 配置文件来声明事务管理的方式和条件。可以使用 tx:advice 和 tx:attributes 等标签来进行 XML 配置事务管理。

以上四种方式各有优缺点,可以根据自己的实际需求和开发经验选择适合的方式。一般来说,声明式事务管理更加简洁和方便,而编程式事务管理更加灵活和精细。


9.4 声明式事务管理

Spring 声明式事务管理可以使用两种方式实现:

  • 基于注解的方式:使用 @Transactional 注解来标记需要进行事务管理的方法,通过注解的方式配置事务管理的属性,如传播行为、隔离级别、回滚条件等。这种方式简单便捷,适合于对事务管理要求较简单的场景。

  • 基于XML的方式:使用 XML 配置文件来定义事务管理的属性,通过配置文件的方式来实现事务管理。可以使用 tx:advice 和 tx:attributes 等标签来配置事务管理的属性,也可以使用 AOP 切面来切入需要进行事务管理的方法。这种方式相对更加灵活,适合于对事务管理要求较为严格的场景。


9.4.1 基于XML方式的声明式事务

基于 XML 方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的

在 Spring 2.0 以后,可以使用 tx 命名空间来配置事务。tx 命名空间下提供了 <advice> 元素,可用于配置事务的通知(增强处理)。配置 <advice> 元素后,可以通过编写的 AOP 配置让 Spring 自动对目标生成代理。

配置 <advice> 元素时,通常需要指定 id 和 transaction-manager 属性。其中,id 属性是该元素在配置文件中的唯一标识,transaction-manager 属性用于指定事务管理器。除此之外,还需要配置一个 attributes 子元素,该子元素可通过配置多个 method 子元素来配置执行事务的细节

在每个 method子元素中,可以指定需要进行事务管理的方法及其对应的事务属性,如传播行为、隔离级别、超时等等。最后,需要在 AOP 配置中将事务增强处理和切入点进行绑定,以便 Spring 自动为目标对象生成代理,实现事务管理。

声明式事务就是通过AOP实现的。

tx:method元素的常用属性如表所示:

属性名称描述
name指定要应用事务管理的方法名。
propagation指定事务传播行为。
isolation指定事务隔离级别。
timeout指定事务超时时间。
read-only指定事务是否为只读。
rollback-for指定需要回滚的异常类型。
no-rollback-for指定不需要回滚的异常类型。

案例

以下是一个通过XML方式实现Spring声明式事务管理的案例,用于模拟银行转账的程序。

以下是一个通过XML方式实现Spring声明式事务管理的案例,用于模拟银行转账的程序。

1. AccountDao接口

首先,我们需要定义一个AccountDao接口,用于操作银行账户信息。

public interface AccountDao {
    /**
     * 获取指定账户的余额
     * @param account 账户名
     * @return 余额
     */
    double getBalance(String account);

    /**
     * 更新指定账户的余额
     * @param account 账户名
     * @param amount 金额
     */
    void updateBalance(String account, double amount);
}
2. AccountDao实现类

然后,我们需要定义一个AccountDao的实现类,用于实现银行账户的转账操作。

public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;

    // 设置JdbcTemplate实例
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public double getBalance(String account) {
        String sql = "SELECT balance FROM account WHERE account_name = ?";
        return jdbcTemplate.queryForObject(sql, Double.class, account);
    }

    @Override
    public void updateBalance(String account, double amount) {
        String sql = "UPDATE account SET balance = balance + ? WHERE account_name = ?";
        jdbcTemplate.update(sql, amount, account);
    }

    // 转账操作
    public void transfer(String fromAccount, String toAccount, double amount) {
        // 从fromAccount账户中扣除amount金额
        updateBalance(fromAccount, -amount);
        // 向toAccount账户中增加amount金额
        updateBalance(toAccount, amount);
    }
}

在这个实现类中,我们使用了Spring的JdbcTemplate来操作数据库,并实现了银行转账的功能。

3. Spring配置文件

接下来,我们需要在Spring配置文件中声明事务管理器事务通知器,以便在转账操作时自动开启和提交事务。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/bank"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!-- 配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <!-- 配置事务通知器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置AOP切面 -->
    <aop:config>
        <aop:pointcut id="accountDaoPointcut" expression="execution(* com.example.dao.AccountDao.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="accountDaoPointcut"/>
    </aop:config>

    <!-- 配置AccountDao实例 -->
    <bean id="accountDao" class="com.example.dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

在这个配置文件中,我们首先配置了数据源和JdbcTemplate,然后配置了事务管理器和事务通知器,最后配置了AccountDao的实现类。

4. 测试类

最后,我们编写一个测试类来测试银行转账的功能是否正常。

public class TestAccountDao {

    @Test
    public void testTransfer() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountDao accountDao = context.getBean(AccountDao.class);

        // 账户初始余额
        double balance1 = accountDao.getBalance("account1");
        double balance2 = accountDao.getBalance("account2");

        // 转账操作
        accountDao.transfer("account1", "account2", 100);

        // 转账后余额
        double newBalance1 = accountDao.getBalance("account1");
        double newBalance2 = accountDao.getBalance("account2");

        // 验证转账操作是否正确
        Assert.assertEquals(balance1 - 100, newBalance1, 0.01);
        Assert.assertEquals(balance2 + 100, newBalance2, 0.01);
    }
}

在这个测试类中,我们首先创建了一个ApplicationContext对象,然后从中获取AccountDao实例。接着,我们获取了账户的初始余额,并进行了转账操作。最后,我们验证了转账操作后账户余额是否正确。

通过以上代码,我们成功地实现了一个通过XML方式实现Spring声明式事务管理的案例,用于模拟银行转账的程序。


9.4.2 基于注解方式的声明式事务

基于注解方式的声明式事务,是一种使用注解来声明事务的方式,它可以简化代码,提高开发效率。

在基于注解方式的声明式事务中,我们可以使用**@Transactional**注解来声明事务。这个注解可以被应用到类或方法上,用于指示哪些方法应该被包装在事务内。当一个被@Transactional注解标记的方法被调用时,Spring会自动创建一个事务,并在方法执行完成后自动提交或回滚事务。


@Transactional注解的属性:

属性名类型默认值描述
valueString“”事务管理器的名称
propagationPropagationPropagation.REQUIRED事务的传播行为
isolationIsolationIsolation.DEFAULT事务的隔离级别
readOnlybooleanfalse是否只读
timeoutint-1事务超时时间(单位为秒)
rollbackForClass<? extends Throwable>[]{}触发回滚的异常类型
noRollbackForClass<? extends Throwable>[]{}不触发回滚的异常类型
rollbackForClassNameString[]{}触发回滚的异常类名
noRollbackForClassNameString[]{}不触发回滚的异常类名

上面是@Transactional注解的全部属性,它们的含义和使用方法如下:

  • value:指定事务管理器的名称,如果不指定则使用默认的事务管理器。
  • propagation:指定事务的传播行为,即事务方法被另一个事务方法调用时,当前事务如何传播。默认值为Propagation.REQUIRED,表示如果当前存在事务,则加入该事务,否则创建一个新的事务。
  • isolation:指定事务的隔离级别,即多个事务同时执行时,它们之间的隔离程度。默认值为Isolation.DEFAULT,表示使用数据库默认的隔离级别。
  • readOnly:指定事务是否只读。如果设置为true,则表示事务只读取数据,不会修改数据,可以优化事务的性能。默认值为false,表示事务既可以读取数据,也可以修改数据。
  • timeout:指定事务超时时间,即事务在多长时间内必须完成,否则自动回滚。默认值为-1,表示不设置超时时间。
  • rollbackFor:指定哪些异常类型会触发事务回滚。如果不指定,默认只有RuntimeException及其子类会触发回滚。
  • noRollbackFor:指定哪些异常类型不会触发事务回滚。
  • rollbackForClassName:指定哪些异常类名会触发事务回滚。如果不指定,默认只有RuntimeException及其子类会触发回滚。
  • noRollbackForClassName:指定哪些异常类名不会触发事务回滚。

需要注意的是,@Transactional注解只能用于public方法上,因为只有public方法才能被外部调用。如果将它应用到private、protected或默认访问级别的方法上,会被忽略,事务不会生效。


案例

以下是一个通过注解方式实现 Spring 的声明式事务管理的案例,模拟银行转账的程序:

1. 导入依赖

在pom.xml文件中添加以下依赖:

<dependencies>
    <!--Spring核心依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <!--H2数据库依赖-->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>${h2.version}</version>
    </dependency>
    
    <!--MySQL数据库依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
</dependencies>
2. 定义dao层方法

在AccountDao接口中定义以下方法:

public interface AccountDao {
    
    /**
     * 获取账户余额
     * @param accountId 账户ID
     * @return 账户余额
     */
    public double getBalance(long accountId);
    
    /**
     * 取款
     * @param accountId 账户ID
     * @param amount 取款金额
     */
    public void withdraw(long accountId, double amount);
    
    /**
     * 存款
     * @param accountId 账户ID
     * @param amount 存款金额
     */
    public void deposit(long accountId, double amount);
}

3. 实现dao层方法

在AccountDaoImpl类中实现AccountDao接口中的方法:

@Repository
public class AccountDaoImpl implements AccountDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public double getBalance(long accountId) {
        String sql = "select balance from account where id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{accountId}, Double.class);
    }
    
    @Override
    @Transactional  // 声明事务
    public void withdraw(long accountId, double amount) {
        String sql = "update account set balance = balance - ? where id = ?";
        jdbcTemplate.update(sql, amount, accountId);
    }
    
    @Override
    @Transactional  // 声明事务
    public void deposit(long accountId, double amount) {
        String sql = "update account set balance = balance + ? where id = ?";
        jdbcTemplate.update(sql, amount, accountId);
    }
}

4. 修改配置文件

在applicationContext.xml文件中添加以下配置:

<!--开启Spring注解驱动-->
<context:annotation-config/>

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

<!--开启注解声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置AccountDao-->
<bean id="accountDao" class="com.example.dao.AccountDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
5. 测试系统

在Main类中添加以下测试代码:

// 获取ApplicationContext对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

// 获取AccountDao对象
AccountDao accountDao = context.getBean(AccountDao.class);

// 账户1的余额
double balance1 = accountDao.getBalance(1);
System.out.println("账户1的余额:" + balance1);

// 账户2的余额
double balance2 = accountDao.getBalance(2);
System.out.println("账户2的余额:" + balance2);

// 账户1向账户2转账100元
accountDao.withdraw(1, 100);
accountDao.deposit(2, 100);

// 获取转账后账户1的余额
double balance1AfterTransfer = accountDao.getBalance(1);
System.out.println("转账后账户1的余额:" + balance1AfterTransfer);

// 获取转账后账户2的余额
double balance2AfterTransfer = accountDao.getBalance(2);
System.out.println("转账后账户2的余额:" + balance2AfterTransfer);
6. 运行测试

运行Main类中的测试代码,控制台输出如下:

账户1的余额:1000.0
账户2的余额:0.0
转账后账户1的余额:900.0
转账后账户2的余额:100.0

可以看到,通过注解方式实现了 Spring 的声明式事务管理,确保了转账过程中的数据一致性和完整性。


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值