传统的JDBC在操作数据库时,需要手动管理数据库连接等资源,这样频繁的数据库操作会产生大量重复代码,导致代码冗余。这使得开发人员需要处理低层级的细节,从而分散了他们的注意力和精力。但是,Spring 的 JDBC 模块可以负责数据库资源管理和错误处理,大大简化了开发人员对数据库的操作。
Spring JDBC 提供了一种更为高效、简洁的方式来操作数据库。使用 Spring JDBC 模块,开发人员可以不必处理与数据库连接、事务管理、异常处理等相关的底层细节,从而将更多的精力投入到业务逻辑的编写中。
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 等,来方便地访问数据库和处理异常。
包名 | 描述 |
---|---|
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 类配置:
<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" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
bean>
<bean id="userDao" class="com.example.dao.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
bean>
<bean id="userService" class="com.example.service.UserService">
<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。
JdbcTemplate 提供了一系列常用的方法。
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 方法。
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 方法。
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 方法的重载版本,指定查询结果的起始位置和数量。
Spring 事务管理是 Spring 框架的一个核心特性,它提供了一种简单而强大的方法来管理数据库事务。Spring 事务管理框架可以用于任何基于 Java 的应用程序,包括 Web 应用程序和独立的 Java 应用程序。
spring-tx-4.3.6.RELEASE 是 Spring 框架提供的用于事务管理的依赖包,其中包含了 PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口。
PlatformTransactionManager 接口定义了事务管理器的基本操作,例如开启事务、提交事务、回滚事务等。PlatformTransactionManager 有多个实现类,用于不同的事务管理场景,例如 JpaTransactionManager、DataSourceTransactionManager 等。
TransactionDefinition 接口定义了事务的属性,例如事务传播行为、隔离级别、超时时间等。TransactionDefinition 常用于在 @Transactional 注解中声明事务属性。
TransactionStatus 接口定义了事务的状态信息,例如是否是新事务、是否已经完成、是否已经回滚等。TransactionStatus 可以用于检查事务状态、控制事务提交和回滚等。
这三个接口是 Spring 事务管理的核心接口,它们提供了事务管理的基本操作、事务属性的声明和事务状态的检查和控制等功能。使用这些接口,可以方便地管理事务,提高代码的可维护性和可靠性。
PlatformTransactionManager 是 Spring 框架提供的事务管理器接口,定义了事务管理的基本操作,例如开启事务、提交事务、回滚事务等。它是 Spring 事务管理的核心接口之一,用于管理事务的生命周期。
PlatformTransactionManager 接口定义了以下方法:
TransactionStatus getTransaction(TransactionDefinition definition):开启一个新事务,并返回该事务的状态信息。
void commit(TransactionStatus status):提交事务,并释放该事务占用的资源。
void rollback(TransactionStatus status):回滚事务,并释放该事务占用的资源。
其中,getTransaction 方法用于开启新事务,并返回该事务的状态信息。TransactionDefinition 参数用于指定事务的属性,例如事务传播行为、隔离级别、超时时间等。
commit 方法用于提交事务,将事务的修改操作持久化到数据库中,并释放该事务占用的资源。
rollback 方法用于回滚事务,将事务的修改操作撤销,并释放该事务占用的资源。
TransactionDefinition 是 Spring 框架提供的事务定义接口,用于定义事务的属性,例如事务传播行为、隔离级别、超时时间等。它是 Spring 事务管理的核心接口之一,用于声明事务的属性。
隔离级别常量 | 描述 |
---|---|
ISOLATION_DEFAULT | 使用数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 读未提交:一个事务可以读取另一个事务未提交的数据。这是最低的隔离级别,它允许脏读,不可重复读和幻读。 |
ISOLATION_READ_COMMITTED | 读已提交:一个事务只能读取另一个事务已经提交的数据。这种隔离级别避免了脏读,但不可避免地会出现不可重复读和幻读。 |
ISOLATION_REPEATABLE_READ | 可重复读:在同一个事务中,多次读取同一个数据,得到的结果始终相同。这种隔离级别避免了脏读和不可重复读,但仍然可能出现幻读。 |
ISOLATION_SERIALIZABLE | 序列化:所有事务按顺序依次执行,每个事务都会等待前一个事务执行完毕才能执行。这是最高的隔离级别,避免了脏读、不可重复读和幻读,但是对于系统的性能和并发度有很大的影响。 |
在数据库中,脏读(Dirty Read)、幻读(Phantom Read)和不可重复读(Non-repeatable Read)是三种事务并发控制问题,它们分别表示以下情况:
脏读:一个事务读取了另一个事务未提交的数据。也就是说,一个事务读取了另一个事务修改但还未提交的数据,如果这个事务最终回滚,那么读取到的数据就是无效的,这就是脏读。
幻读:一个事务读取了另一个事务已提交的数据,但是后来又发现了新的数据。也就是说,一个事务在某个时间点读取了一组数据,但是在之后的时间点,另一个事务插入了一些新的数据,这时候第一个事务再次读取同一组数据,就会发现多了一些之前没有的数据,这就是幻读。
不可重复读:一个事务在同一个时间点内多次读取同一组数据,但是得到的结果不一样。也就是说,一个事务在某个时间点读取了一组数据,但是在之后的时间点,另一个事务修改了这组数据并提交,这时候第一个事务再次读取同一组数据,就会发现数据已经发生了变化,这就是不可重复读。
在 Spring 中,事务的传播行为(Transaction Propagation)是指在一个方法调用另一个方法时,如何处理事务的传播。Spring 提供了以下七种事务传播行为:
传播行为常量 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;否则创建一个新的事务。这是默认的传播行为。 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;否则以非事务的方式执行。 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;否则抛出异常。 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,并且如果当前存在事务,则挂起该事务。 |
PROPAGATION_NOT_SUPPORTED | 以非事务的方式执行,并且如果当前存在事务,则挂起该事务。 |
PROPAGATION_NEVER | 以非事务的方式执行,并且如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务中执行;否则创建一个新的事务。嵌套的事务可以独立地提交或回滚,并且只对外部事务的提交产生影响。 |
在 Spring 中,事务的超时时间(Transaction Timeout)是指事务的最大执行时间,如果事务在规定的时间内没有完成,则会被自动回滚。事务的超时时间可以通过设置 @Transactional 注解的 timeout 属性来指定,单位是秒。例如,@Transactional(timeout = 30) 表示该事务的最大执行时间为 30 秒。
当事务被标记为只读时,事务管理器会将该事务优化为只读事务,即只允许查询操作,不允许进行修改操作。这样可以减少事务的锁定时间,提高并发性能,同时也可以避免一些潜在的数据覆盖问题。
如果在只读事务中进行修改数据的操作,事务管理器会检测到该操作,然后抛出异常,即只读事务不支持修改数据,只能进行查询操作。
TransactionDefnition 接口中除了提供事务的隔离级别、事务的传播行为、事务的超时时间和是否为读事务的常量外,还提供了一系列方法来获取事务的属性。
方法名 | 描述 |
---|---|
getPropagationBehavior | 获取事务的传播行为。 |
getIsolationLevel | 获取事务的隔离级别。 |
getTimeout | 获取事务的超时时间,单位为秒。 |
isReadOnly | 判断事务是否为只读事务。 |
getName | 获取事务的名称。 |
isTimeoutSet | 判断事务是否设置了超时时间。 |
rollbackOn | 获取指定异常类是否会导致事务回滚。 |
isRollbackOn | 判断指定异常类是否会导致事务回滚。 |
setRollbackOn | 设置指定异常类是否会导致事务回滚。 |
getRollbackRules | 获取所有异常类与回滚规则之间的映射关系。 |
hasTimeout | 判断事务是否设置了超时时间。 |
equals | 比较两个事务定义对象是否相等。 |
hashCode | 获取事务定义对象的哈希码。 |
toString | 返回事务定义对象的字符串表示。 |
TransactionStatus 是 Spring 框架中用于表示事务状态的接口。它定义了许多方法,用于查询和修改事务的状态。
下面是 TransactionStatus 接口中常用的方法:
除了上述方法外,TransactionStatus 接口还有其他一些方法,用于获取和设置事务状态的属性,例如超时时间、隔离级别等。
在 Spring 框架中,事务管理的方式有以下几种:
编程式事务管理:通过编写代码实现事务管理。在这种方式下,开发人员需要手动控制事务的开启、提交、回滚等操作。可以使用 TransactionTemplate 或 PlatformTransactionManager 等类来进行编程式事务管理。
声明式事务管理:通过声明式配置实现事务管理。在这种方式下,开发人员只需要在配置文件中声明事务管理的方式和条件,Spring 框架会自动根据这些配置来实现事务管理。可以使用 @Transactional 注解或 AOP 等方式来进行声明式事务管理。
注解式事务管理:这是声明式事务管理的一种方式,使用 @Transactional 注解来声明事务管理的方式和条件。
XML 配置事务管理:这是声明式事务管理的一种方式,使用 XML 配置文件来声明事务管理的方式和条件。可以使用 tx:advice 和 tx:attributes 等标签来进行 XML 配置事务管理。
以上四种方式各有优缺点,可以根据自己的实际需求和开发经验选择适合的方式。一般来说,声明式事务管理更加简洁和方便,而编程式事务管理更加灵活和精细。
Spring 声明式事务管理可以使用两种方式实现:
基于注解的方式:使用 @Transactional 注解来标记需要进行事务管理的方法,通过注解的方式配置事务管理的属性,如传播行为、隔离级别、回滚条件等。这种方式简单便捷,适合于对事务管理要求较简单的场景。
基于XML的方式:使用 XML 配置文件来定义事务管理的属性,通过配置文件的方式来实现事务管理。可以使用 tx:advice 和 tx:attributes 等标签来配置事务管理的属性,也可以使用 AOP 切面来切入需要进行事务管理的方法。这种方式相对更加灵活,适合于对事务管理要求较为严格的场景。
基于 XML 方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。
在 Spring 2.0 以后,可以使用 tx 命名空间来配置事务。tx 命名空间下提供了
配置
在每个 method子元素中,可以指定需要进行事务管理的方法及其对应的事务属性,如传播行为、隔离级别、超时等等。最后,需要在 AOP 配置中将事务增强处理和切入点进行绑定,以便 Spring 自动为目标对象生成代理,实现事务管理。
声明式事务就是通过AOP实现的。
tx:method元素的常用属性如表所示:
属性名称 | 描述 |
---|---|
name | 指定要应用事务管理的方法名。 |
propagation | 指定事务传播行为。 |
isolation | 指定事务隔离级别。 |
timeout | 指定事务超时时间。 |
read-only | 指定事务是否为只读。 |
rollback-for | 指定需要回滚的异常类型。 |
no-rollback-for | 指定不需要回滚的异常类型。 |
以下是一个通过XML方式实现Spring声明式事务管理的案例,用于模拟银行转账的程序。
以下是一个通过XML方式实现Spring声明式事务管理的案例,用于模拟银行转账的程序。
首先,我们需要定义一个AccountDao接口,用于操作银行账户信息。
public interface AccountDao {
/**
* 获取指定账户的余额
* @param account 账户名
* @return 余额
*/
double getBalance(String account);
/**
* 更新指定账户的余额
* @param account 账户名
* @param amount 金额
*/
void updateBalance(String account, double amount);
}
然后,我们需要定义一个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来操作数据库,并实现了银行转账的功能。
接下来,我们需要在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>
<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:config>
<aop:pointcut id="accountDaoPointcut" expression="execution(* com.example.dao.AccountDao.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="accountDaoPointcut"/>
aop:config>
<bean id="accountDao" class="com.example.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
bean>
beans>
在这个配置文件中,我们首先配置了数据源和JdbcTemplate,然后配置了事务管理器和事务通知器,最后配置了AccountDao的实现类。
最后,我们编写一个测试类来测试银行转账的功能是否正常。
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声明式事务管理的案例,用于模拟银行转账的程序。
基于注解方式的声明式事务,是一种使用注解来声明事务的方式,它可以简化代码,提高开发效率。
在基于注解方式的声明式事务中,我们可以使用**@Transactional**注解来声明事务。这个注解可以被应用到类或方法上,用于指示哪些方法应该被包装在事务内。当一个被@Transactional注解标记的方法被调用时,Spring会自动创建一个事务,并在方法执行完成后自动提交或回滚事务。
@Transactional注解的属性:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
value | String | “” | 事务管理器的名称 |
propagation | Propagation | Propagation.REQUIRED | 事务的传播行为 |
isolation | Isolation | Isolation.DEFAULT | 事务的隔离级别 |
readOnly | boolean | false | 是否只读 |
timeout | int | -1 | 事务超时时间(单位为秒) |
rollbackFor | Class[] | {} | 触发回滚的异常类型 |
noRollbackFor | Class[] | {} | 不触发回滚的异常类型 |
rollbackForClassName | String[] | {} | 触发回滚的异常类名 |
noRollbackForClassName | String[] | {} | 不触发回滚的异常类名 |
上面是@Transactional注解的全部属性,它们的含义和使用方法如下:
需要注意的是,@Transactional注解只能用于public方法上,因为只有public方法才能被外部调用。如果将它应用到private、protected或默认访问级别的方法上,会被忽略,事务不会生效。
以下是一个通过注解方式实现 Spring 的声明式事务管理的案例,模拟银行转账的程序:
在pom.xml文件中添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<version>${h2.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
dependencies>
在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);
}
在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);
}
}
在applicationContext.xml文件中添加以下配置:
<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>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="accountDao" class="com.example.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
bean>
在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);
运行Main类中的测试代码,控制台输出如下:
账户1的余额:1000.0
账户2的余额:0.0
转账后账户1的余额:900.0
转账后账户2的余额:100.0
可以看到,通过注解方式实现了 Spring 的声明式事务管理,确保了转账过程中的数据一致性和完整性。