Spring JDBC和 事务控制
1.主要内容
2.Spring整合 JDBC环境
Spring 框架除了提供 IOC 与 AOP 核⼼功能外,同样提供了基于JDBC 的数据访问功能,使得访问持
久层数据更加⽅便。使⽤ Spring JDBC 环境,⾸先需要⼀套 Spring 整合 JDBC 的环境。
2.1.添加依赖坐标
<!-- 添加相关的依赖坐标 -->
<!-- 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>
2.2.添加jdbc配置⽂件
在src/main/resources⽬录下新建jdbc.properties配置⽂件,并设置对应的配置信息
# 驱动名
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
2.3. 修改 spring 配置⽂件
spring.xml
2.4. 配置数据源
由于建⽴数据库连接是⼀个⾮常耗时耗资源的⾏为,所以通过连接池预先同数据库建⽴⼀些连接,放
在内存中,应⽤程序需要建⽴数据库连接时直接到连接池中申请⼀个就⾏,⽤完后再放回去。
C3P0 与 DBCP ⼆选⼀即可
DBCP(DataBase connection pool),数据库连接池。是 apache 上的⼀个 java 连接池项⽬,也是
tomcat 使⽤的连接池组件。单独使⽤dbcp需要2个包:commons-dbcp.jar,commons-pool.jar
dbcp,没有⾃动回收空闲连接的功能。
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
2.3.修改spring配置⽂件
<!-- 加载properties 配置⽂件,⽤来读取jdbc.properties⽂件中的数据 -->
<context:property-placeholder location="jdbc.properties" />
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"
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">
<!-- Spring扫描注解的配置 -->
<context:component-scan base-package="com.xxx" />
<!-- 加载properties 配置⽂件,可以读取jdbc.properties配置文件中的数据 -->
<context:property-placeholder location="jdbc.properties" />
</beans>
2.4.配置数据源
由于建⽴数据库连接是⼀个⾮常耗时耗资源的⾏为,所以通过连接池预先同数据库建⽴⼀些连接,放
在内存中,应⽤程序需要建⽴数据库连接时直接到连接池中申请⼀个就⾏,⽤完后再放回去。
C3P0 与 DBCP ⼆选⼀即可
DBCP(DataBase connection pool),数据库连接池。是 apache 上的⼀个 java 连接池项⽬,也是
tomcat 使⽤的连接池组件。单独使⽤dbcp需要2个包:commons-dbcp.jar,commons-pool.jar
dbcp,没有⾃动回收空闲连接的功能。
C3P0是⼀个开源的JDBC连接池,它实现了数据源,⽀持JDBC3规范和JDBC2的标准扩展。⽬前使⽤它
的开源项⽬有Hibernate,Spring等。c3p0有⾃动回收空闲连接功能。
2.4.1. C3P0数据源配置
<!--配置C3P0数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--通过property标签配置对应的的值,value的属性值对应的是properties配置的值-->
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
C3P0 其他额外配置(对应的值在jdbc.properties⽂件中指定)
<!-- 指定连接池中保留的最⼤连接数。 Default:15-->
<property name="maxPoolSize" value="${maxPoolSize}"/>
<!-- 指定连接池中保留的最⼩连接数。-->
<property name="minPoolSize" value="${minPoolSize}"/>
<!-- 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3-->
<property name="initialPoolSize" value="${initialPoolSize}"/>
<!-- 最⼤空闲时间,60秒内未使⽤则连接被丢弃。若为0则永不丢弃。 Default:0-->
<property name="maxIdleTime" value="${maxIdleTime}"/>
<!-- 当连接池中的连接耗尽的时候c3p0⼀次同时获取的连接数。 Default:3-->
<property name="acquireIncrement" value="${acquireIncrement}"/>
<!-- JDBC的标准,⽤以控制数据源内加载的PreparedStatements数ᰁ。
但由于预缓存的statements属于单个connection,⽽不是整个连接池所以设置这个参数需要考虑到多⽅
⾯的因数。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。
Default:0-->
<property name="maxStatements" value="${maxStatements}"/>
<!-- 每60秒检查所有连接池中的空闲连接。Default:0 -->
<property name="idleConnectionTestPeriod"
value="${idleConnectionTestPeriod}"/>
2.4.2. DBCP数据源配置
<!-- 配置dbcp数据源-->
<bean id="myDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="1"/>
<!-- 最⼤空闲值.当经过⼀个⾼峰时间后,连接池可以将已经⽤不到的连接慢慢释放⼀部分,⼀直
减少到maxIdle为⽌ -->
<property name="maxIdle" value="2"/>
<!-- 最⼩空闲值.当空闲的连接数少于阀值时,连接池就会预申请⼀些连接,以避免洪峰来时再申
请⽽造成的性能开销 -->
<property name="minIdle" value="1"/>
</bean>
2.5.模板类配置
Spring把 JDBC 中重复的操作建⽴成了⼀个模板类:org.springframework.jdbc.core.JdbcTemplate.
<!--配置jdbc Template模板对象,并注入一个数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.6. JDBC测试
2.6.1.创建指定数据库
选择连接,右键选择"新建数据库",设置数据库的名称和编码格式
2.6.2.创建数据表
2.6.3.使⽤JUnit测试
通过 junit 测试 jdbcTemplate bean 是否获取到
2.6.3.1. JUnit测试
package com.xxx.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class SpringJdbcTest01 {
@Test
public void testJdbc(){
//获取Spring的上下文环境
ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
//得到模板类JdbcTemPlate
JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
//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(){
//获取Spring的上下文环境
ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
//得到模板类JdbcTemPlate
JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
//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);
}
}
2.6.3.2.简单封装
package com.xxx.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class SpringJdbcTest02 {
private JdbcTemplate jdbcTemplate;
@Before
public void init(){
//获取Spring的上下文环境
ApplicationContext ac =new ClassPathXmlApplicationContext("spring.xml");
//得到模板类JdbcTemPlate
jdbcTemplate = (JdbcTemplate) ac.getBean("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);
}
}
2.6.3.3.注解封装
@RunWith
就是⼀个运⾏器
@RunWith(JUnit4.class) 就是指⽤JUnit4来运⾏
@RunWith(SpringJUnit4ClassRunner.class) 让测试运⾏于Spring测试环境
@ContextConfiguration
Spring整合JUnit4测试时,使⽤注解引⼊多个配置⽂件
@ContextConfiguration(Locations="classpath:applicationContext.xml")
@ContextConfiguration(locations = {"classpath:spring.xml",
"classpath:bean.xml"})
package com.xxx.test;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class) //将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"}) //设置要加载的配置文件
public class SpringJdbcTest03 {
@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);
}
}
2.6.3.4.通⽤封装
1.定义⼀个⽗类,设置通⽤的配置信息
package com.xxx.test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) //将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"}) //设置要加载的配置文件
public class BaseTest {
}
2.继承通⽤的测试类
package com.xxx.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
public class SpringJdbcTest04 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);
}
}
3.持久层账户模块操作
当完成 Spring Jdbc 环境集成后,这⾥使⽤spring jdbc 完成账户单表crud 操作.
3.1.账户接口方法定义
3.1.1. 定义实体类
Account.java
package com.xxx.po;
import java.util.Date;
/**
* 用户账户类
*/
public class Account {
private Integer accountId; //账户ID,主键
private String accountName; //账户名称
private String accountType; //账户类型
private Double money; //账户金额
private String remark; //账户备注
private Date createTime; //创建时间
private Date updateTime; //修改时间
private Integer userId; //用户id,账户所属用户
public Account() {
}
public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
this.accountName = accountName;
this.accountType = accountType;
this.money = money;
this.remark = remark;
this.userId = userId;
}
@Override
public String toString() {
return "Account{" +
"accountId=" + accountId +
", accountName='" + accountName + '\'' +
", accountType='" + accountType + '\'' +
", money=" + money +
", remark='" + remark + '\'' +
", createTime=" + createTime +
", updateTime=" + updateTime +
", userId=" + userId +
'}';
}
public Integer getAccountId() {
return accountId;
}
public void setAccountId(Integer accountId) {
this.accountId = accountId;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
3.1.2.定义接⼝类
IAccountDao.java
package com.xxx.dao;
import com.xxx.po.Account;
import java.util.List;
/**
* 账户模块的接口定义
* 1.添加账户
* 添加账户记录,返回受影响的行数
* 添加账户记录,返回主键
* 批量添加账户记录,返回受影响的行数
* 2.修改账户
* 修改账户记录,返回受影响的行数
* 批量修改账户记录,返回受影响的行数
* 3.删除账户
* 删除账户记录,返回受影响的行数
* * 批量删除账户记录,返回受影响的行数
* 4.查询账户
* 查询指定用户的账户的总记录数,返回总记录数
* 查询指定账户的账户详情,返回账户对象
* 多条件查询指定用户的账户列表,返回账户集合
*/
public interface IAccountDao {
/**
* 添加账户
* 添加账户记录,返回受影响的行数
* @param account
* @return
*/
public int addAccount(Account account);
/**
* 添加账户
* 添加账户记录,返回主键
* @param account
*/
public int addAccountHasKey(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);
}
3.1.3.定义接⼝实现类
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
@Override
public int addAccount(Account account) {
return 0;
}
@Override
public int addAccountHasKey(Account account) {
return 0;
}
@Override
public int addAccountBatch(List<Account> accounts) {
return 0;
}
@Override
public int queryAccountCount(int userId) {
return 0;
}
@Override
public Account queryAccountById(int accountId) {
return null;
}
@Override
public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
return null;
}
@Override
public int updateAccount(Account account) {
return 0;
}
@Override
public int updateAccountBatch(List<Account> accounts) {
return 0;
}
@Override
public int deleteAccount(int accountId) {
return 0;
}
@Override
public int deleteAccountBatch(Integer[] ids) {
return 0;
}
}
3.2.账户记录添加实现
在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记
录等情况。这里对于账户记录添加方式分为三种⽅式:添加单条记录返回受影响行数、添加单条记录返
回主键、批量添加多条记录。
3.2.1.添加账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@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 row;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;
import javax.annotation.Resource;
import java.sql.SQLOutput;
/**
* 账户模块添加操作测试类
*/
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);
}
}
3.2.2.添加记录返回主键
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 添加账户记录,返回主键
* @param account
* @return
*/
@Override
public int addAccountHasKey(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;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;
import javax.annotation.Resource;
import java.sql.SQLOutput;
/**
* 账户模块添加操作测试类
*/
public class SpringJdbcAddTest extends BaseTest {
// 注入
@Resource
private IAccountDao accountDao;
/**
* 添加账户记录,返回主键
*/
@Test
public void testAddAccountHasKey(){
// 准备要添加的数据
Account account = new Account("账户4","中国银行",600.0,"绩效",2);
// 调用对象中的添加方法,返回受影响的行数主键
int key = accountDao.addAccountHasKey(account);
System.out.println("添加账户,返回主键:"+key);
}
}
3.2.3.批量添加账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 批量添加账户记录,返回受影响的行数
* @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;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import com.xxx.test.BaseTest;
import org.junit.Test;
import javax.annotation.Resource;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块添加操作测试类
*/
public class SpringJdbcAddTest extends BaseTest {
// 注入
@Resource
private IAccountDao accountDao;
/**
* 批量添加账户记录,返回受影响的行数
*/
@Test
public void testBatchAddAccount(){
// 准备要添加的数据
Account account = new Account("账户4","农业银行",700.0,"奖金",3);
Account account2 = new Account("账户5","工商银行",400.0,"早餐",3);
Account account3 = new Account("账户6","中国银行",2000.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);
}
}
3.3.账户记录查询实现
账户记录查询这⾥提供了三种查询⽅式,查询指定⽤户所有账户记录数,查询单条账户记录详情,多
条件查询指定⽤户账户记录。
3.3.1.查询⽤户的账户总记录数
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 查询指定用户的账户总记录数,返回总数量
* @param userId
* @return
*/
@Override
public int queryAccountCount(int userId) {
//定义sql语句
String sql = "select count(1) from tb_account where user_id=?";
//查询方法
int count = jdbcTemplate.queryForObject(sql,Integer.class,userId);
return count;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
public class SpringJdbcQueryTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 查询指定用户的账户总记录数,返回总数量
*/
@Test
public void testQueryAccount(){
int total = accountDao.queryAccountCount(1);
System.out.println("查询指定用户的账户总记录数,返回总数量:"+total);
}
}
3.3.2.查询指定账户记录详情
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 查询指定账户记录详情,返回对象
* @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 acc =new Account();
acc.setAccountId(accountId);
acc.setAccountName(rs.getString("account_name"));
acc.setAccountType(rs.getString("account_type"));
acc.setMoney(rs.getDouble("money"));
acc.setRemark(rs.getString("remark"));
acc.setUserId(rs.getInt("user_id"));
acc.setCreateTime(rs.getDate("create_time"));
acc.setUpdateTime(rs.getDate("update_time"));
return acc;
},accountId);
return account;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
public class SpringJdbcQueryTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 查询指定账户记录详情,返回账户对象
*/
@Test
public void testQueryAccountById(){
Account account = accountDao.queryAccountById(1);
System.out.println("账户详情:"+account.toString());
}
}
3.3.3.多条件查询用户账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 多条件查询指定用户的账户记录列表,返回账户集合
* @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 acc =new Account();
acc.setAccountId(rs.getInt("account_id"));
acc.setAccountName(rs.getString("account_name"));
acc.setAccountType(rs.getString("account_type"));
acc.setMoney(rs.getDouble("money"));
acc.setRemark(rs.getString("remark"));
acc.setUserId(rs.getInt("user_id"));
acc.setCreateTime(rs.getTimestamp("create_time"));
acc.setUpdateTime(rs.getTimestamp("update_time"));
return acc;
});
return accountList;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
import java.util.List;
public class SpringJdbcQueryTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 多条件查询,返回账号列表
*/
@Test
public void testQueryAccountList(){
//List<Account> accountList =accountDao.queryAccountByParams(3,null,null,null);
//System.out.println(accountList.toString());
//List<Account> accountList2 =accountDao.queryAccountByParams(3,"5",null,null);
//System.out.println(accountList2.toString());
List<Account> accountList3 =accountDao.queryAccountByParams(3,"5","中国银行",null);
System.out.println(accountList3.toString());
}
}
3.4.账户记录更新实现
3.4.1.更新账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 修改账户,返回受影响的行数
* @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;
}
}
测试⽅法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;
import javax.annotation.Resource;
public class SpringJdbcUpdateTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 修改账户记录,返回受影响的行数
*/
@Test
public void testUpdateAccount(){
Account account = new Account("账户1","农业银行",700.0,"奖金",1);
account.setAccountId(1);
int row = accountDao.updateAccount(account);
System.out.println("修改账户记录,返回受影响的行数:"+row);
}
}
3.4.2.批量更新账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 批量修改账户记录,返回受影响的行数
* @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;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.junit.Test;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
public class SpringJdbcUpdateTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 批量修改账户记录,返回受影响的行数
*/
@Test
public void testBatchUpdateAccount(){
// 准备要添加的数据
Account account = new Account("账户55","农业银行",555.0,"奖金",3);
account.setAccountId(5);
Account account2 = new Account("账户66","工商银行",666.0,"早餐",3);
account2.setAccountId(6);
Account account3 = new Account("账户77","中国银行",777.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);
}
}
3.5.账户记录删除实现
3.5.1.删除账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 删除账户记录,返回受影响的行数
*/
@Override
public int deleteAccount(int accountId) {
//定义sql语句
String sql = "delete from tb_account where account_id=?";
//设置参数
int row = jdbcTemplate.update(sql,accountId);
return row;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import org.junit.Test;
import javax.annotation.Resource;
public class SpringJdbcDeleteTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 删除账户记录,返回受影响的行数
*/
@Test
public void testDeleteAccount(){
int row = accountDao.deleteAccount(1);
System.out.println("删除账户记录:"+row);
}
}
3.5.2.批量删除账户记录
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// 注入JdbcTemplate模板类
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 批量删除账户记录,返回受影响的行数
* @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;
}
}
测试方法
package com.xxx.test;
import com.xxx.dao.IAccountDao;
import org.junit.Test;
import javax.annotation.Resource;
public class SpringJdbcDeleteTest extends BaseTest {
@Resource
private IAccountDao accountDao;
/**
* 批量删除账户记录,返回受影响的行数
*/
@Test
public void testDeleteBatch(){
Integer[] ids ={2,3,4};
int rows =accountDao.deleteAccountBatch(ids);
System.out.println("批量删除:"+rows);
}
}
4.Spring事务控制
4.1.转账场景模拟实现
4.1.1.接口方法定义
package com.xxx.dao;
import com.xxx.po.Account;
import java.util.List;
public interface IAccountDao {
/**
* 支出
* @param accountId
* @param money
* @return
*/
public int outAccount(Integer accountId,Double money);
/**
* 收入
* @param accountId
* @param money
* @return
*/
public int inAccount(Integer accountId,Double money);
}
4.1.2.实现对应接口
对于转账涉及到双方账户以及对应转账⾦额,所以有⼊账和出账两个方法。
package com.xxx.dao.impl;
import com.xxx.dao.IAccountDao;
import com.xxx.po.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 账户模块接口的实现类
*/
@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);
}
}
4.1.3.转账方法实现
package com.xxx.service;
import com.xxx.dao.IAccountDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class AccountService {
@Resource
private IAccountDao accountDao;
/**
* 转账的业务实现
* @param outId 支出账户
* @param inId 收入账户
* @param money 金额
* @return 成功或失败 1=成功,0=失败
*/
public int updateAccountByTranfer(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);
//账户B 收入,修改账户金额,返回受影响的行数
int inRow =accountDao.inAccount(inId,money);
//如果支出和收入两个操作都执行成功,表示转账成功
if(outRow==1&&inRow==1){
code=1; //成功
}
return code;
}
}
仔细思考代码会发现,在程序运行中无法保证 service 层业务代码不发生异常,如果通过 jdbc 的方式
处理事务,此时需要手动方式控制事务,这样的话凡是涉及到事务控制的业务方法均需要开发⼈员手动
来进行事务处理,无法满足生产的需要。
4.2. Spring事务概念
4.2.1. 事务的四大特性(ACID)
原子性(Atomicity)
共生死,要么全部成功,要么全部失败!
一致性(Consistency)
事务在执行前后,数据库中数据要保持⼀致性状态。(如转账的过程 账户操作后数据必须保持
⼀致)
隔离性(Isolation)
事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对统⼀记录进行操作必须保证没
有任何干扰),当然没有影响是不可能的,为了让影响级别降到最低,通过隔离级别加以限制:
1.READ_UNCOMMITTED (读未提交)
隔离级别最低的⼀种事务级别。在这种隔离级别下,会引发脏读、不可重复读和幻读。
2.READ_COMMITTED (读已提交)
读到的都是别⼈提交后的值。这种隔离级别下,会引发不可重复读和幻读,但避免了脏读
3.REPEATABLE_READ (可重复读)
这种隔离级别下,会引发幻读,但避免了脏读、不可重复读。
4.SERIALIZABLE (串行化)
最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行。脏读、不可重复读、幻读都不会出现。
持久性(Durability)
事务提交完毕后,数据库中的数据的改变是永久的。
4.2.2. Spring事务核心接⼝
Spring 事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,
下面通过讲解 Spring 的事务接楼来了解 Spring 实现事务的具体策略。
Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate
或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。
Spring 事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通
过这个接口,Spring 为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,但是具体的实现
就是各个平台自己的事情了。此接口的内容如下:
public interface PlatformTransactionManager(){
// 由 TransactionDefinition 得到 TransactionStatus 对象
TransactionStatus getTransaction(TransactionDefinition definition) throws
TransactionException;
// 提交
void commit(TransactionStatus status) throws TransactionException;
// 回滚
void rollback(TransactionStatus status) throws TransactionException;
}
从这⾥可知具体的具体的事务管理机制对 Spring 来说是透明的,它并不关心那些,那些是对应各个
平台需要关心的,所以 Spring 事务管理的⼀个优点就是为不同的事务 API 提供⼀致的编程模型,如
JTA、JDBC、Hibernate、JPA。下⾯分别介绍各个平台框架实现事务管理的机制。
4.2.2.1. JDBC事务
如果应用程序中直接使⽤用JDBC 来进行持久化,此时使用 DataSourceTransactionManager 来处理事
务边界。为了使用DataSourceTransactionManager,需要使用如下的 XML 将其装配到应用程序的上
下文定义中:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager 是通过调用 java.sql.Connection 来管理事务,而后者是
通过 DataSource 获取到的。通过调用连接的 commit() 方法来提交事务,同样,事务失败则通过调用
rollback() 方法进行回滚。
4.2.2.2. Hibernate事务
如果应用程序的持久化是通过 Hibernate 实现的,那么你需要使用
HibernateTransactionManager。对于 Hibernate3,需要在 Spring 上下文定义中添加如下的声明:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory 属性需要装配⼀个 Hibernate 的 session 工厂,HibernateTransactionManager 的
实现细节是它将事务管理的职责委托给 org.hibernate.Transaction 对象,而后者是从 Hibernate
Session 中获取到的。当事务成功完成时,HibernateTransactionManager 将会调用 Transaction 对象
的 commit() 方法,反之,将会调用 rollback() 方法。
4.2.2.3. Java持久化API事务(JPA)
Hibernate 多年来⼀直是 Java 持久化标准,但是现在 Java 持久化 API 作为真正的 Java 持久化标准进
⼊⼤家的视野。如果你计划使用 JPA 的话,那你需要使用Spring 的 JpaTransactionManager 来处理事
务。你需要在 Spring 中这样配置 JpaTransactionManager:
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager 只需要装配⼀个 JPA 实体管理工厂
(javax.persistence.EntityManagerFactory 接口的任意实现)。 JpaTransactionManager 将与由工厂
所产生的 JPA EntityManager 合作来构建事务。
4.2.2.4. Java原生API事务
如果应用程序没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不
同的数据源),此时需要使用 JtaTransactionManager:
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager 将事务管理的责任委托给 javax.transaction.UserTransaction 和
javax.transaction.TransactionManager 对象,其中事务成功完成通过 UserTransaction.commit() 方
法提交,事务失败通过 UserTransaction.rollback() 方法回滚。
4.3. Spring事务控制配置
通过 jdbc 持久化事务,对于事务配置实现由两种方式即:Xml 配置,注解配置。
4.3.1. XML配置
4.3.1.1.添加命名空间
在spring.xml配置文件的添加事务和aop的命名空间
事务
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
AOP
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
配置如下
<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">
</beans>
4.3.1.2.设置aop代理
<!--开启Aop代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.3.1.3.配置事务管理器
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--数据源 ref代表的是C3P0数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4.3.1.4.配置事务相关通知
⼀般来说增删改方法 propagation=Required,对于查询方法使用 read-only="true"
<!--配置事务通知 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>
事务传播⾏为介绍:
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个⽅法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在⼀个已有的事务中执⾏,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在⼀个没有的事务中执⾏,否则抛出异常(与 Propagation.MANDATORY 相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他 bean 调⽤这个⽅法,在其他 bean 中声明事务,那就⽤事务.
如果其他 bean 没有声明事务,那就不⽤事务.
@Transactional(propagation=Propagation.NESTED)
⽀持当前事务,如果当前事务存在,则执⾏⼀个嵌套事务,如果当前没有事务,就新建⼀个事务
4.3.1.5.配置aop
<!--配置Aop:定义Aop切面(切入点和通知)-->
<aop:config>
<!--设置切入点,设置需要被拦截的方法-->
<aop:pointcut id="cut" expression="execution(* com.xxx.service..*.*(..))"/>
<!--设置通知 事务通知-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor>
</aop:config>
4.3.2.注解配置
4.3.2.1.配置事务管理器
<!-- spring 注解式事务声明 -->
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.3.2.2.配置注解⽀持
<tx:annotation-driven transaction-manager="txManager"/>
4.3.2.3.方法上加⼊事务注解
Service 方法上在需要添加事务的方法上加入事务注解
package com.xxx.service;
import com.xxx.dao.IAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class AccountService {
@Resource
private IAccountDao accountDao;
/**
* 转账的业务实现
* @param outId 支出账户
* @param inId 收入账户
* @param money 金额
* @return 成功或失败 1=成功,0=失败
*/
@Transactional(propagation = Propagation.REQUIRED)
public int toupdateAccountByTranfer(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;
}
}
备注:默认 spring 事务只在发生未被捕获的 runtimeexcetpion 时才回滚。
spring aop 异常捕获原理:
被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到⽅法的异常,才能进行回
滚,默认情况下 aop 只捕获 runtimeexception 的异常,但可以通过配置来捕获特定的异常并回滚换
句话说在 service 的方法中不使用 try catch 或者在 catch 中最后加上 throw new
RunTimeexcetpion(),这样程序异常时才能被 aop 捕获进而回滚.
标签:事务,JDBC,Spring,sql,account,springframework,import,org,public From: https://www.cnblogs.com/MeltSky/p/17206677.html