目录
一、导图
二、Spring 整合 JDBC 环境
添加依赖坐标
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.msb</groupId>
<artifactId>spring_jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring_jdbc</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 配置相关的依赖坐标 -->
<!-- spring 框架坐标依赖添加 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- spring 测试环境 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- spring事物 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- mysql 驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- c3p0 连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!-- commons-lang-->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
</dependencies>
</project>
添加 jdbc 配置文件
# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://localhost:3306/(数据库名称)?
useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)
# 以下为可选配置
# 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3
initialPoolSize=20
# 指定连接池中保留的最大连接数. Default:15
maxPoolSize=100
# 指定连接池中保留的最小连接数
minPoolSize=10
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0
maxIdleTime=600
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.
Default:3
acquireIncrement=5
# JDBC的标准,用以控制数据源内加载的PreparedStatements数量。
maxStatements=5
# 每60秒检查所有连接池中的空闲连接.Default:0
idleConnectionTestPeriod=60
修改 spring 配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启自动化扫描 -->
<context:component-scan base-package="com.msb" />
<!-- 加载properties配置,可以读取jdbc.properties配置文件中的数据 -->
<context:property-placeholder location="jdbc.properties"/>
</beans>
DBCP 数据源配置&模板类配置
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 通过property标签配置对应的值,value属性值对应的是properties配置文件中的值 -->
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置JdbcTemplate模板对象,并注入一个数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
使用 JUnit 测试-通用封装
BaseTest.java
@RunWith(SpringJUnit4ClassRunner.class) // 将测试运行在Spring测试环境中
//@ContextConfiguration(locations = {"classpath:spring.xml"}) // 设置要加载的配置文件
@ContextConfiguration(locations = {"classpath:spring02.xml"}) // 设置要加载的配置文件
public class BaseTest {
}
SpringJdbcTest.java
public class SpringJdbcTest extends BaseTest{
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void testJdbc(){
// crud 操作
// 定义sql语句
String sql = "select count(1) from tb_account";
// 执行查询操作 (无参数)
Integer total = jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println("总记录数:" + total);
}
@Test
public void testJdbc02(){
// crud 操作
// 定义sql语句
String sql = "select count(1) from tb_account where user_id = ?";
// 执行查询操作 (无参数)
Integer total = jdbcTemplate.queryForObject(sql,Integer.class,2);
System.out.println("总记录数:" + total);
}
}
三、持久层账户模块操作
账户模块 接口定义
1. 添加账户
添加账户记录,返回受影响的行数
添加账户记录,返回主键
批量添加账户记录,返回受影响的行数
2. 修改账户
修改账户记录,返回受影响的行数
批量修改账户记录,返回受影响的行数
3. 删除账户
删除账户记录,返回受影响的行数
批量删除账户记录,返回受影响的行数
4. 查询账户
查询指定账户的账户的总记录数,返回总记录数
查询指定账户的账户详情,返回账户对象
多条件查询指定用户的账户列表,返回账户集合
IAccountDao接口类:
public interface IAccountDao {
/**
* 添加账户
* 添加账户记录,返回受影响的行数
* @param account
* @return
*/
public int addAccount(Account account);
/**
* 添加账户
* 添加账户记录,返回主键
* @param account
*/
public int addAccountHashKey(Account account);
/**
* 添加账户
* 批量添加账户记录,返回受影响的行数
* @param accounts
* @return
*/
public int addAccountBatch(List<Account> accounts);
/**
* 查询账户
* 查询指定用户的账户的总记录数,返回总记录数
* @param userId
* @return
*/
public int queryAccountCount(int userId);
/**
* 查询账户
* 查询指定账户的账户详情,返回账户对象
* @param accountId
* @return
*/
public Account queryAccountById(int accountId);
/**
* 查询账户
* 多条件查询指定用户的账户列表,返回账户集合
* @param userId 指定用户的ID
* @param accountName 账户名称(模糊查询)
* @param accountType 账户类型
* @param createTime 创建时间(大于当前时间)
* @return
*/
public List<Account> queryAccountByParams(Integer userId, String accountName,String accountType, String createTime);
/**
* 修改账户
* 修改账户记录,返回受影响的行数
* @param account
* @return
*/
public int updateAccount(Account account);
/**
* 修改账户
* 批量修改账户记录,返回受影响的行数
* @param accounts
* @return
*/
public int updateAccountBatch(List<Account> accounts);
/**
* 删除账户
* 删除账户记录,返回受影响的行数
* @param accountId
* @return
*/
public int deleteAccount(int accountId);
/**
* 删除账户
* 批量删除账户记录,返回受影响的行数
* @param ids
* @return
*/
public int deleteAccountBatch(Integer[] ids);
}
实现类:
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
//注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 添加账户记录,返回受影响的行数
* @param account
* @return
*/
@Override
public int addAccount(Account account) {
// 定义sql语句
String sql = "insert into tb_account (account_name,account_type,money,remark," +
" create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";
// 设置参数
Object[] objs = {account.getAccountName(), account.getAccountType(), account.getMoney(),account.getRemark(),
account.getUserId()};
int row = jdbcTemplate.update(sql, objs);
return 0;
}
/**
* 添加账户记录,返回主键
* @param account
* @return
*/
@Override
public int addAccountHashKey(Account account) {
// 定义sql语句
String sql = "insert into tb_account (account_name,account_type,money,remark," +
" create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";
// 定义KeyHolder对象 获取记录的主键值
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
//预编译sql语句
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 设置参数
ps.setString(1, account.getAccountName());
ps.setString(2, account.getAccountType());
ps.setDouble(3, account.getMoney());
ps.setString(4, account.getRemark());
ps.setInt(5, account.getUserId());
//返回预编译对象
return ps;
}, keyHolder);
//得到返回的主键
int key = keyHolder.getKey().intValue();
return key;
}
/**
* 批量添加账户记录,返回受影响的行数
* @param accounts
* @return
*/
@Override
public int addAccountBatch(List<Account> accounts) {
// 定义sql语句
String sql = "insert into tb_account (account_name,account_type,money,remark," +
" create_time,update_time,user_id) values (?,?,?,?,now(),now(),?)";
int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Account account = accounts.get(i);
// 设置参数
ps.setString(1, account.getAccountName());
ps.setString(2, account.getAccountType());
ps.setDouble(3, account.getMoney());
ps.setString(4, account.getRemark());
ps.setInt(5, account.getUserId());
}
@Override
public int getBatchSize() {
return accounts.size();
}
}).length;
return rows;
}
/**
* 查询指定用户的账户总记录数,返回总数量
* @param userId
* @return
*/
@Override
public int queryAccountCount(int userId) {
//定义sql语句
String sql = "select count(1) from tb_account where user_id = ?";
//查询方法
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, userId);
return count;
}
/**
* 查询指定账户记录详情,返回账户对象
* @param accountId
* @return
*/
@Override
public Account queryAccountById(int accountId) {
// 定义sql语句
String sql = "select * from tb_account where account_id = ?";
//查询对象
Account account = jdbcTemplate.queryForObject(sql, (ResultSet rs, int i)->{
Account ac = new Account();
ac.setAccountId(accountId);
ac.setAccountName(rs.getString("account_name"));
ac.setAccountType(rs.getString("account_type"));
ac.setMoney(rs.getDouble("money"));
ac.setRemark(rs.getString("remark"));
ac.setUserId(rs.getInt("user_id"));
ac.setCreateTime(rs.getDate("create_time"));
ac.setUpdateTime(rs.getDate("update_time"));
return ac;
}, accountId);
return account;
}
/**
* 多条件查询指定用户的账户记录列表,返回账户集合
* @param userId 指定用户的ID
* @param accountName 账户名称(模糊查询)
* @param accountType 账户类型
* @param createTime 创建时间(大于当前时间)
* @return
*/
@Override
public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
// 定义sql语句
String sql = "select * from tb_account where user_id = ? ";
//定义参数列表
List<Object> params = new ArrayList<>();
params.add(userId);
// 判断参数是否为空,如果不为空,拼接sql语句及设置对应的参数
// 账户名称
if(StringUtils.isNotBlank(accountName)){
// 拼接sql语句
sql += " and account_name like concat('%', ?,'%')";
// 设置参数
params.add(accountName);
}
// 账户类型
if(StringUtils.isNotBlank(accountType)){
// 拼接sql语句
sql += " and account_type = ?";
//设置参数
params.add(accountType);
}
//创建时间
if(StringUtils.isNotBlank(createTime)){
//拼接sql语句
sql+=" and create_time < ? ";
//设置参数
params.add(createTime);
}
//将集合转换成数组
Object[] objs = params.toArray();
//查询集合
List<Account> accountList = jdbcTemplate.query(sql, objs, (ResultSet rs, int i) -> {
Account ac = new Account();
ac.setAccountId(rs.getInt("account_id"));
ac.setAccountName(rs.getString("account_name"));
ac.setAccountType(rs.getString("account_type"));
ac.setMoney(rs.getDouble("money"));
ac.setRemark(rs.getString("remark"));
ac.setUserId(rs.getInt("user_id"));
ac.setCreateTime(rs.getDate("create_time"));
ac.setUpdateTime(rs.getDate("update_time"));
return ac;
});
return accountList;
}
/**
* 修改账户,返回受影响的行数
* @param account
* @return
*/
@Override
public int updateAccount(Account account) {
// 定义sql
String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
" update_time = now(), user_id = ? where account_id = ?";
// 设置参数
Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(),
account.getRemark(),account.getUserId(),account.getAccountId()};
int row = jdbcTemplate.update(sql, objs);
return row;
}
/**
* 批量修改账户记录,返回受影响的行数
* @param accounts
* @return
*/
@Override
public int updateAccountBatch(List<Account> accounts) {
// 定义sql语句
String sql = "update tb_account set account_name = ? , account_type = ? , money = ? , remark = ? , " +
" update_time = now(), user_id = ? where account_id = ?";
int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Account account = accounts.get(i);
// 设置参数
ps.setString(1,account.getAccountName());
ps.setString(2,account.getAccountType());
ps.setDouble(3,account.getMoney());
ps.setString(4,account.getRemark());
ps.setInt(5,account.getUserId());
ps.setInt(6,account.getAccountId());
}
@Override
public int getBatchSize() {
return accounts.size();
}
}).length;
return rows;
}
/**
* 删除账户记录,返回受影响的行数
* @param accountId
* @return
*/
@Override
public int deleteAccount(int accountId) {
//准备sql
String sql = "delete from tb_account where account_id = ?";
int row = jdbcTemplate.update(sql, accountId);
return row;
}
/**
* 批量删除账户记录,返回受影响的行数
* @param ids
* @return
*/
@Override
public int deleteAccountBatch(Integer[] ids) {
// 定义SQL语句
String sql = "delete from tb_account where account_id = ?";
int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, ids[i]);
}
@Override
public int getBatchSize() {
return ids.length;
}
}).length;
return rows;
}
}
账户模块添加操作测试类:
/**
* 账户模块添加操作测试类
*/
public class SpringJdbcAddTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 添加账户记录,返回受影响的行数
*/
@Test
public void testAddAccount() {
// 准备要添加的数据
Account account = new Account("账户3","工商银行",200.0,"奖金",1);
// 调用对象中的添加方法,返回受影响的行数
int row = accountDao.addAccount(account);
System.out.println("添加账户,受影响的行数:" + row);
}
/**
* 添加账户记录,返回主键
*/
@Test
public void testAddAccountHasKey(){
// 准备要添加的数据
Account account = new Account("账户4","中国银行",300.0,"绩效奖",2);
// 调用对象中的添加方法,返回主键
int key = accountDao.addAccountHashKey(account);
System.out.println("添加账户,返回主键:" + key);
}
/**
* 批量添加账户记录,返回受影响的行数
*/
@Test
public void testAddAccountBatch(){
// 准备要添加的数据
Account account = new Account("账户5","农业银行",700.0,"奖金",3);
Account account2 = new Account("账户6","工商银行",890.0,"早餐",3);
Account account3 = new Account("账户7","中国银行",560.0,"绩效奖",3);
List<Account> accounts = new ArrayList<>();
accounts.add(account);
accounts.add(account2);
accounts.add(account3);
int rows = accountDao.addAccountBatch(accounts);
System.out.println("批量添加账户记录,返回受影响的行数:" + rows);
}
}
账户模块查询操作测试类:
public class SpringJdbcQueryTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 查询指定用户的账户总记录数,返回总数量
*/
@Test
public void testQueryAccountCount(){
int total = accountDao.queryAccountCount(3);
System.out.println("查询指定用户的账户总记录数:" + total);
}
/**
* 查询指定账户记录的详情,返回账户对象
*/
@Test
public void testQueryAccountById(){
Account account = accountDao.queryAccountById(3);
System.out.println("账户详情:"+account.toString());
}
/**
* 多条件查询,返回账户列表
*/
@Test
public void testQueryAccountByParams(){
List<Account> accounts = accountDao.queryAccountByParams(3, null, null, null);
System.out.println(accounts);
System.out.println("-------------------------");
List<Account> accounts1 = accountDao.queryAccountByParams(3, "5", null, null);
System.out.println(accounts1);
System.out.println("-------------------------");
List<Account> accounts2 = accountDao.queryAccountByParams(3, "7", "中国银行", null);
System.out.println(accounts2);
}
}
账户模块更新操作测试类:
public class SpringJdbcUpdateTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 修改账户记录,返回受影响的行数
*/
@Test
public void testUpdateAccount(){
Account account = new Account("账户11","农业银行",2000.0,"奖金",1);
account.setAccountId(1);
int row = accountDao.updateAccount(account);
System.out.println("修改账户记录,返回受影响的行数:" + row);
}
/**
* 批量修改账户记录,返回受影响的行数
*/
@Test
public void testUpdateAccountBatch(){
Account account = new Account("账户55","农业银行",7700.0,"奖金",3);
account.setAccountId(5);
Account account2 = new Account("账户66","工商银行",8890.0,"早餐",3);
account2.setAccountId(6);
Account account3 = new Account("账户77","中国银行",5560.0,"绩效奖",3);
account3.setAccountId(7);
List<Account> accounts = new ArrayList<>();
accounts.add(account);
accounts.add(account2);
accounts.add(account3);
int rows = accountDao.updateAccountBatch(accounts);
System.out.println("批量更新账户记录,返回受影响的行数:" + rows);
}
}
账户模块删除操作测试类:
public class SpringJdbcDeleteTest extends BaseTest{
@Resource
private IAccountDao accountDao;
/**
* 删除账户记录,返回受影响的行数
*/
@Test
public void testDeleteAccount(){
int row = accountDao.deleteAccount(1);
System.out.println("删除账户记录: " + row);
}
/**
* 批量删除账户记录,返回受影响的行数
*/
@Test
public void testDeleteBatch(){
Integer[] ids = {2,3,4};
int rows = accountDao.deleteAccountBatch(ids);
System.out.println("批量删除:" + rows);
}
}
四、Spring 事务控制
事务(Transaction)指的是一个操作序列,该操作序列中的多个操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
事务的四大特性(ACID)
事务的并发问题
脏读(Dirty read)
当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
不可重复读(Unrepeatableread):
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read):
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读
事务的隔离级别
事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题。
Spring 事务控制配置
XML 配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启自动化扫描 -->
<context:component-scan base-package="com.msb" />
<!-- 加载properties配置,可以读取jdbc.properties配置文件中的数据 -->
<context:property-placeholder location="jdbc.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 通过property标签配置对应的值,value属性值对应的是properties配置文件中的值 -->
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置JdbcTemplate模板对象,并注入一个数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
Spring 事务配置
1. 添加事务与AOP的命名空间
2. 开启AOP代理
3. 配置事务管理器
4. 配置事务通知
5. 配置AOP
-->
<!-- 开启AOP代理 -->
<aop:aspectj-autoproxy/>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 ref代表的是C3P0数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知 transaction-manager属性表示事务通知绑定的是哪个事务管理器 -->
<!--
tx:method的属性:
name
是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。
通配符(*)可以用来指定一批关联到相同的事务属性的方法。
如:'get*'、'handle*'、'on*Event'等等.
propagation
不是必须的,默认值是REQUIRED
表示事务传播行为, 包括:
REQUIRED,SUPPORTS,MANDATORY,NEVER
REQUIRES_NEW,NOT_SUPPORTED,NESTED
isolation
不是必须的,默认值DEFAULT
表示事务隔离级别(数据库的隔离级别)
timeout
不是必须的,默认值-1(永不超时)
表示事务超时的时间(以秒为单位)
read-only
不是必须的,默认值false不是只读的
表示事务是否只读
rollback-for
不是必须的
表示将被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
no-rollback-for
不是必须的
表示不被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
任何 RuntimeException 将触发事务回滚
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 定义什么方法需要使用事务处理:以add update delete query 开头的方法都使用事务 -->
<tx:attributes>
<!-- name属性代表的是方法名(或方法匹配) -->
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="query*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP:定义aop切面 (切入点和通知) -->
<aop:config>
<!-- 设置切入点 设置需要被拦截的方法 -->
<aop:pointcut id="cut" expression="execution(* com.msb.service..*.*(..))"/>
<!-- 设置通知 事务通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
</aop:config>
<!-- 配置注解支持 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
注解配置
<!-- 配置注解支持 -->
<tx:annotation-driven transaction-manager="txManager"/>
方法上配置:
@Transactional(propagation = Propagation.REQUIRED)
AccountDaoImpl.java
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
//注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 支出
* @param accountId
* @param money
* @return
*/
@Override
public int outAccount(Integer accountId, Double money){
String sql = "update tb_account set money = money - ? where account_id = ? ";
Object[] objs = {money, accountId };
return jdbcTemplate.update(sql, objs);
}
/**
* 收入
* @param accountId
* @param money
* @return
*/
@Override
public int inAccount(Integer accountId, Double money) {
String sql = "update tb_account set money = money + ? where account_id = ? ";
Object[] objs = {money, accountId};
return jdbcTemplate.update(sql, objs);
}
}
AccountService.java
@Service
public class AccountService {
@Resource
private IAccountDao accountDao;
/**
* 转账业务操作
* @param outId 支出账户
* @param inId 收入账户
* @param money 金额
* @return 1=成功 0=失败
*/
@Transactional(propagation = Propagation.REQUIRED)
public int updateAccountTranfer(Integer outId, Integer inId, Double money){
int code = 0; //成功或失败 1=成功 0=失败
/**
* 账户A->账户B 转100
* 账户A:金额-100
* 账户B:金额+100
*/
// 账户A 支出,修改账户金额,返回受影响的行数
int outRow = accountDao.outAccount(outId, money);
int i = 1/0;
// 账户B 收入,修改账户金额,返回受影响的行数
int inRow = accountDao.inAccount(inId, money);
// 如果支出和收入两个操作都执行成功,表示转账成功
if(outRow==1 && inRow==1){
code=1; //成功
}
return code;
}
}