文章目录

Spring JDBC与事务操作一、Spring整合JDBC的环境(一)添加依赖坐标(二)添加jdbc.properties的配置(三)创建Spring的配置文件(四)配置数据源(五)JDBC测试

二、持久层账户模块操作(例)(一)账户接口方法定义1. 定义账户实体类2.定义账户的接口类3.定义接口的实现类

(二)、账户记录添加实现1. 添加账户记录2.添加账户记录返回主键3.批量添加账户,返回受影响的行数

(三)账户记录查询的实现1. 查询用户的账户记录总数2.查询指定账户记录详情3. 多条件查询用户账户记录

(四)账户记录的更新操作1.更新账户记录2.批量更新账户记录

(五)账户记录删除实现1.删除账户记录2.批量删除账户记录

三、事务的概念(一)事务的四大特性(ACID)1. 原子性(Atomicity)2. 一致性(Consistency)3.隔离性(Isolation)4. 持久性(Durability)

(二)Spring事务核心接口1. JDBC事务2. Hibernate事务3. Java持久化API事务(JPA)4. Java原生API事务

(三)Spring事务控制的配置1. XML配置(1)添加命名空间(2)设置AOP代理(3)配置事务管理器(4)配置事务的相关通知(5)配置AOP

2. 注解配置(1)配置事务管理器(2)配置注解支持(3)加入事务注解

(四)Spring事务控制——模拟转账的实现1.接口方法定义2.接口实现3.业务层方法4.测试

Spring JDBC与事务操作

一、Spring整合JDBC的环境

​ Spring框架除了提供了IOC和AOP的核心功能之外,同样提供了基于JDBC的数据访问功能,使得访问持久层数据更加方便。使用Spring JDBC环境,首先需要一套Spring整合JDBC的环境。

(一)添加依赖坐标

junit

junit

4.12

org.springframework

spring-context

5.3.14

org.springframework

spring-test

5.3.14

org.aspectj

aspectjweaver

1.9.6

org.springframework

spring-jdbc

5.3.14

org.springframework

spring-tx

5.3.14

mysql

mysql-connector-java

8.0.16

com.mchange

c3p0

0.9.5.5

org.projectlombok

lombok

RELEASE

compile

(二)添加jdbc.properties的配置

jdbc.driver=com.mysql.cj.jdbc.Driver

jdbc.url=jdbc:mysql://localhost/spring_jdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false

jdbc.username = root

jdbc.password = root

jdbc.maxActive= 50

initialPoolSize=20

maxPoolSize=100

minPoolSize=10

maxIdleTime=600

acquireIncrement=5

maxStatements=5

idleConnectionTestPeriod=60

(三)创建Spring的配置文件

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

https://www.springframework.org/schema/context/spring-context.xsd

">

需要添加相应的命名空间,并且开启自动化扫描组件,使用context的property-placeholder属性,并指定location属性值为指定的配置文件。

(四)配置数据源

​ 由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时,直接到连接池中申请一个即可。用完再放回去。

​ C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3规范和JDBC2的标准扩展,目前使用它的开源项目有Hibernate,Spring等,C3P0具有自动回收空闲连接的功能。

C3P0数据源配置

模板类配置

Spring把JDBC中的重复操作组合成了一个模板类org.springframework.jdbc.core.JdbcTemplate

(五)JDBC测试

创建数据库表

创建tb_account表,并插入相应的数据支持。

USE MyBatis;

CREATE TABLE tb_account(

account_id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,

account_name varchar(20) NOT NULL,

account_type varchar(20) NOT NULL,

money double NOT NULL ,

remark varchar(50),

create_time datetime NOT NULL,

update_time datetime NOT NULL ,

user_id int(11) NOT NULL

);

INSERT INTO tb_account (account_id, account_name, account_type, money, remark, create_time, update_time, user_id)

VALUES (1,'账户1','建设银行',1000,'零花钱','2020-03-19 02:42:42','2020-03-19 02:42:56',1);

INSERT INTO tb_account(account_id, account_name, account_type, money, remark, create_time, update_time, user_id) VALUES

(2,'账户2','招商银行',500,'兼职费','2020-03-20 02:43:56','2020-03-20 02:46:57',1);

使用Junit测试

@Test

public void testTemplate1(){

//获取Spring的上下文环境

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

JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);

//定义sql语句

String totalUsers="SELECT count(1) FROM tb_account";

Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);

System.out.println("共有"+total+"条数据");

}

@Test

public void testJdbcTemplate2(){

//获取Spring的上下文环境

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

JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);

String sql="SELECT count(1) FROM tb_account WHERE user_id=?";

Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);

System.out.println("记录总数:"+integer);

}

junit测试改进

首先创建基类测试类

@RunWith(SpringJUnit4ClassRunner.class)//将测试运行在Spring的测试环境中

@ContextConfiguration(locations = {"classpath:spring.xml"})//设置要加载的配置文件,使用注解进行配置。

public class BaseTest {

}

需要注意的是基类测试类不需要书写任何内容,只需要在类上添加两个注解

@RunWith(SpringJUnit4ClassRunner.class):此处表示将原先的junit测试添加到Spring的测试环境之中去。

@ContextConfiguration(locations={xxxx,xxxx}):此处表示设置要加载的配置文件,locations表示配置文件的数组名,之后添加一个数组,数组的内容是需要使用到的xml配置文件。

创建基本的测试类

public class SpringJDBCTest extends BaseTest{

/**使用注解来注入*/

@Resource

private JdbcTemplate jdbcTemplate;

@Test

public void testTemplate1(){

//定义sql语句

String totalUsers="SELECT count(1) FROM tb_account";

Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);

System.out.println("共有"+total+"条数据");

}

@Test

public void testJdbcTemplate2(){

String sql="SELECT count(1) FROM tb_account WHERE user_id=?";

Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);

System.out.println("记录总数:"+integer);

}

}

此处需要注意的是将jdbcTemplate类对象作为属性注入到测试类中,并通过@Resource注解完成属性的注入。并且具体的测试类只需要继承父类BaseTest即可完成对配置文件的加载及junit环境添加到Spring环境之中去的操作。

二、持久层账户模块操作(例)

此处使用Spring JDBC完成增删改查操作

(一)账户接口方法定义

1. 定义账户实体类

Account.java类

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Account {

private Integer accountId;

private String accountName;

private String accountType;

private Double money;

private String remark;

private Date createTime;

private Date updateTime;

private Integer userId;

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;

}

}

2.定义账户的接口类

IAccountDao.java

/** * @author lambda * 添加、修改、删除、查询账户

* 1. 添加账户

* 添加账户记录,返回受影响的行数

* 添加账户记录,返回主键 *

* 批量添加账户记录,返回受影响的行数

* 2. 修改账户

* 修改账户记录,返回受影响的行数

* 批量修改账户记录,返回受影响的行数

*3.删除账户

* 删除账户记录,返回受影响的行数

*批量删除账户记录,返回受影响的行数

* 4. 查询账户

* 查询指定用户账户的总记录数

* 查询指定账户的账户详情,返回账户对象

* *多条件查询指定用户的账户列表,返回一个账户集合 *

* */public interface IAccountDao {

* /** * 添加账户记录,返回受影响的行数

* * @param account

* * @return

* */

* int addAccount(Account account);

* /**

* * 添加账户记录,返回主键

* * @param account

* * @return

* */

* int addAccountPrimaryKey(Account account);

* /**

* * 批量添加账户记录,返回受影响的行数

* * @param accounts

* * @return

* */

* int addAccountBatch(List accounts);

* /**

* * 查询指定用户账户的总记录数

* * @param userId

* * @return

* */

* int queryAccountCount(int userId);

* /**

* * 查询指定账户的账户详情,返回账户对象

* * @param accountId

* * @return

* */

* Account queryAccountByAccountId(int accountId);

* /**

* * 多条件查询指定用户的账户列表,返回一个账户集合

* * @param userId

* * @param accountName

* * @param accountType

* * @param createTime

* * @return

* */

* List queryAccountByParams(int userId,String accountName,String accountType,String createTime);

* /**

* * 修改账户记录,返回受影响的行数,传入整个账户对象

* * @param account

* * @return

* */

* int updateAccount(Account account);

* /**

* * 批量修改账户记录,返回受影响的行数

* * @param accounts

* * @return

* */

* int updateAccountBatch(List accounts);

* /**

* * 删除账户记录,返回受影响的行数

* * @param accountId

* * @return

* */

* int deleteAccount(int accountId);

* /**

* * 批量删除账户记录,返回受影响的行数

* * @param ids

* * @return

* */

* int deleteAccountBatch(int[] ids);}

3.定义接口的实现类

@Repository

public class AccountDaoImpl implements IAccountDao {

/**由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象, * 并完成注入到当前类中*/

@Resource

private JdbcTemplate jdbcTemplate;

@Override

public int addAccount(Account account) {

return 0;

}

@Override

public int addAccountPrimaryKey(Account account) {

return 0;

}

@Override

public int addAccountBatch(List accounts) {

return 0;

}

@Override

public int queryAccountCount(int userId) {

return 0;

}

@Override

public Account queryAccountByAccountId(int accountId) {

return null;

}

@Override

public List queryAccountByParams(int userId, String accountName, String accountType, String createTime) {

return null;

}

@Override

public int updateAccount(Account account) {

return 0;

}

@Override

public int updateAccountBatch(List accounts) {

return 0;

}

@Override

public int deleteAccount(int accountId) {

return 0;

}

@Override

public int deleteAccountBatch(int[] ids) {

return 0;

}

}

由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象

(二)、账户记录添加实现

在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况,这里对于账户记录添加方式为三种,添加单条记录,返回受影响的行数,添加单条记录,返回主键、批量添加多条记录。

1. 添加账户记录

添加账户记录返回首影响的行数

@Override

public int addAccount(Account account) {

String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" + ",update_time,user_id) VALUES(?,?,?,?,now(),now(),?)";

int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(), account.getMoney(), account.getRemark(), account.getUserId());

return update;

}

@Test

public void addAccount(){

int i = accountDao.addAccount(new Account("账户3", "工商银行", 200.0, "奖金", 1));

if (i>0){

System.out.println("插入成功!");

}

}

jdbcTemplate.update方法需要传入对应的sql语句,并传递相应的参数。参数以可变参数的形式添加到对应的表中。

2.添加账户记录返回主键

/**

* 添加记录返回主键

* @param account

* @return

*/

@Override

public int addAccountPrimaryKey(Account account) {

String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";

//定义该对象,用于获取我们记录的主键

GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();

//需要传入两个参数,一个是PreparedStatementCreator,一个是keyHolder

jdbcTemplate.update(con -> {

//此处需要利用lambda表达式去预编译sql语句,并设置返回主键(tatement.RETURN_GENERATED_KEYS)

PreparedStatement ps=con.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);

//执行完之后可以通过keyHolder获取主键

return keyHolder.getKey().intValue();

}

@Test

public void addAccountPrimaryKey(){

Account account = new Account("账户4", "中国银行", 300.0, "绩效奖", 4); int key = accountDao.addAccountPrimaryKey(account);

System.out.println("主键为:"+key);

}

GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();此处表示获取键值对象,后续可以用来获取主键 jdbcTemplate.update(PreparedStatementCreator pcs,KeyHolder keyHolder)此处表示执行update方法,并且传入的参数有经过处理的预处理语句(使用lambda表达式来进行设置sql语句的相关参数并返回预处理语句对象ps)和键对象最后通过keyHolder.getKey().intValue();来获取新添加的对象的主键值。

3.批量添加账户,返回受影响的行数

/**

* 批量添加账户,返回受影响的行数

* @param accounts

* @return

*/

@Override

public int addAccountBatch(List accounts) {

String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +

",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";

//调用batchUpdate批量添加,需要传入sql语句和BatchPreparedStatementSetter pss对象

int rows= jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

//此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)

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;

//jdbcTemplate.batchUpdate返回的是数组,通过length属性可以获取数组的大小,即受影响的行数

return rows;

}

@Test

public void addAccountBatch(){

List accounts=new ArrayList<>();

accounts.add(new Account("账户5", "广东发展银行", 400.0, "出勤奖", 2));

accounts.add(new Account("账户6", "浦发银行", 600.0, "年终奖", 2));

int i = accountDao.addAccountBatch(accounts);

System.out.println("添加了"+i+"条记录");

}

jdbcTemplate.batchUpdate表示执行批量添加操作,主要参数有要执行的sql语句和BatchPreparedStatementSetter pss对象。由于需要传入BatchPreparedStatementSetter对象。所以new一个对象,并实现其中的两个方法setValues和getBatchSizesetValues是使用预处理语句来对相应的sql语句进行预处理,并且通过循环取出集合中每一个值进行相应的赋值操作。getBatchSize最终返回的是经过预处理的集合的大小。最终返回数组的大小。

(三)账户记录查询的实现

账户记录查询提供了三种查询的方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。

1. 查询用户的账户记录总数

@Override

public int queryAccountCount(int userId) {

//定义sql语句(其中的count中的括号可以填写*表示全部,可以添加1,表示第一个字段的总数,也就是总数)

String sql="SELECT COUNT(1) FROM tb_account WHERE user_id=?";

//调用查询方法(其中的参数依次表示为sql语句,返回的类型,以及参数值)

Integer integer = jdbcTemplate.queryForObject(sql, Integer.class, userId);

return integer;

}

@Test

public void queryAccountCount(){

int i = accountDao.queryAccountCount(1);

System.out.println("1号用户的账户一共有:"+i+"条");

}

jdbcTemplate.queryForObject用来执行查询操作,参数为sql语句,返回值的类型,以及方法的参数值。最终将返回的结果返回即可获取指定用户的总账户数。

2.查询指定账户记录详情

/**

* 查询指定账户记录详情,返回账户对象

* @param accountId

* @return

*/

@Override

public Account queryAccountByAccountId(int accountId) {

String sql="SELECT * FROM tb_account WHERE account_id=?";

//调用查询对象的方法,此处方法的第二个参数是一个函数式接口对象,lambda表达式,返回account对象

Account account= jdbcTemplate.queryForObject(sql,(rs,rowNum)->{ //在内存中创建一个账户对象来设置相应的信息以便后续返回

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.setCreateTime(rs.getDate("create_time"));

acc.setUpdateTime(rs.getDate("update_time"));

acc.setUserId(rs.getInt("user_id"));

return acc;

},accountId);

return account;

}

@Test

public void queryAccountByAccountId(){

Account account = accountDao.queryAccountByAccountId(1);

System.out.println(account);

}

此处的queryForObject方法主要有3个参数,一个是sql,一个是RowMapper对象,一个是对应的可变参数RowMaper参数是一个函数式接口,可以使用lambda表达式,该接口中需要传入行集和行数,需要接收返回的Account(Account的各个信息由行集中获取并返回。)最终返回为一个Account对象

3. 多条件查询用户账户记录

@Override

public List queryAccountByParams(int userId, String accountName, String accountType, String createTime) {

//首先定义sql语句

String sql="SELECT * FROM tb_account WHERE user_id=?";

//定义一个参数集合,因为是多条件查询

List params=new ArrayList<>();

params.add(userId);

//判断字符串是否为空,如果不为空,则拼接sql语句

if (StringUtils.isNotBlank(accountName)){

//如果账户名称不为空,则进行拼接

sql+="and account_name Like contact('%',?,'%')";

params.add(accountName);

}

if (StringUtils.isNotBlank(accountType)){

//如果不为空

sql+="AND account_type=?";

params.add(accountType);

}

if (StringUtils.isNotBlank(createTime)){

//如果创建时间不为空

sql+="AND create_time

params.add(createTime);

}

//将集合转换为数组

Object[] objects = params.toArray();

List query = jdbcTemplate.query(sql, objects, (rs, 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.setCreateTime(rs.getDate("create_time"));

acc.setUpdateTime(rs.getDate("update_time"));

acc.setUserId(rs.getInt("user_id"));

return acc;

});

return query;

}

@Test

public void queryAccountByParams(){

List accounts = accountDao.queryAccountByParams(1, "账户1", "招商银行", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

System.out.println(accounts.toString());

}

此处多条件查询,需要涉及到AND条件的拼接,注意点在于AND关键字之间需要空格。jdbcTemplate.query方法需要传入三个参数,一个是sql、一个是参数数组,一个是RowMapper rowMapper对象。

sql语句需要查询出所有的所需要的数据参数数组:需要判断里面的参数是否为空,不为空,则添加到参数数组中,并且对sql语句进行相应的拼接操作。RowMapper对象需要使用lambda表达式依次对创建的对象进行相应的赋值, 最终返回查询到的对象在测试的时候需要使用SimpleDateFormat类来对此进行格式化时间。

(四)账户记录的更新操作

1.更新账户记录

/**

* 更新账户,返回受影响的行数

* @param account

* @return

*/

@Override

public int updateAccount(Account account) {

String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +

"update_time=now() ,user_id=? WHERE account_id=?";

int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(),

account.getMoney(), account.getRemark(), account.getUpdateTime(), account.getUserId()

, account.getAccountId());

return update;

}

@Test

public void updateAccount(){

int i = accountDao.updateAccount(new Account());

System.out.println("更新了"+i+"条数据.");

}

2.批量更新账户记录

/**

*批量更新账户记录

* @param accounts

* @return

*/

@Override

public int updateAccountBatch(List accounts) {

String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +

"update_time=now() ,user_id=? WHERE account_id=?";

int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

//此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)

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;

@Test

public void testUpdateBatch(){

Listaccounts=new ArrayList<>();

accounts.add(new Account());

accounts.add(new Account());

int i = accountDao.updateAccountBatch(accounts);

System.out.println("更新了"+i+"条数据");

}

(五)账户记录删除实现

1.删除账户记录

/**

*删除账户记录,返回受影响的行数

* @param accountId

* @return

*/

@Override

public int deleteAccount(int accountId) {

String sql="DELETE FROM tb_account WHERE account_id=?";

int update = jdbcTemplate.update(sql, accountId);

return update;

}

@Test

public void deleteAccountByAccountId(){

int i = accountDao.deleteAccount(1);

System.out.println("受影响的行数"+i);

}

对于jdbcTemplate而言,删除使用的是update语句

2.批量删除账户记录

/**

*批量删除用户记录,返回受影响的行数

* @param ids

* @return

*/

@Override

public int deleteAccountBatch(int[] ids) {

String sql="DELETE FROM tb_account WHERE account_id=?";

int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

ps.setInt(1, ids[i]);

//i表示行数

}

@Override

public int getBatchSize() {

return ids.length;

}

}).length;

return length;

}

}

@Test

public void deleteAccountBatch(){

int[] ids={1,2,3};

int i = accountDao.deleteAccountBatch(ids);

System.out.println("受影响的行数有:"+ids);

}

批量删除使用batchupdate语句,传入sql语句与BatchPreparedStatementSetter对象setValues是预处理语句循环赋值,因为是批处理,所以需要通过循环不断地为对应的处理语句赋值,最后返回参数数组的长度,即参数有多少个,赋了多少值。

三、事务的概念

(一)事务的四大特性(ACID)

1. 原子性(Atomicity)

要么全部成功,要么全部失败,指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着"同生共死"的感觉

2. 一致性(Consistency)

事务在执行前后,数据库中的数据要保持一致性状态(如转账过程前后需要保持数据的一致性),即只有改变前的状态和改变后的状态,没有所谓的中间状态。

3.隔离性(Isolation)

事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对于统一记录进行操作必须保证没有任何干扰)。没有任何影响显然是不可能的,为了让影响级别降到最低,通过隔离级别加以限制

READ_UNCOMMITED(读未提交),隔离级别最低的一种事务级别,在这种隔离级别下,会引发脏读、不可重复读和幻读READ_COMMITED(读已提交),读到的是别人提交后的值,在这种隔离级别下,会引发不可重复读和幻读,但是避免了脏读。REPEATABLE_READ(可重复读),在这种隔离级别下,会引发幻读,但是避免了脏读和不可重复读SERIALIZABLE(串行化),最严格的隔离级别,在serializable隔离级别下,所有事务按照次序依次进行,脏读不可重复读和幻读都不会出现。

4. 持久性(Durability)

事务提交完毕后,数据库中的数据的改变是永久的。

(二)Spring事务核心接口

Spring事务管理的实现有很多细节,如果对整个接口框架有个大体的了解会非常有利于我们理解事务。

Spring并不是直接管理事务,而是提供了多种事务管理器,他们将事务的管理职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现的。

Spring事务管理器的接口是org.springframework.transaction.PlatformTranscationManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都通过供了对应的事务管理器,但是具体的实现可以依据各个平台自己的实际来定。接口内容如下:

public interface PlatformTransactionManager extends TransactionManager {

//得到事务对象的方法

TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

//提交事务

void commit(TransactionStatus status) throws TransactionException;

//回滚事务

void rollback(TransactionStatus status) throws TransactionException;

}

从这里可知具体的事务管理机制对Spring来说是透明的,它并不关心具体的事务管理机制,哪些是对应各个平台需要关心的。所以Spring事务管理的一个优点就是为不同的事务API提供一个统一的编程模型,如JTA、JDBC、HIbernate、JPA等。

1. JDBC事务

如果应用程序中直接使用JDBC来进行持久化,此时使用DataSourceTransactionManager来处理事务边界,为了使用DataSourceTransactionManager,需要使用如下的xml信息将其装配到程序的上下文定义中去。(MyBatis也是使用DataSourceTransactionManager)

事实上,DataSourceTransactionManager是通过调用java.sql.connection来管理事务,后者是通过DataSource获取的,通过调用连接的commit方法来提交事务,同样,如果事务失败,则通过调用rollback方法进行回滚。

2. Hibernate事务

使用HIbernate事务需要首先导入Hibernate的依赖坐标

org.springframework

spring-hibernate3

2.0.8

如果应用程序的持久化是通过HIbernate来实现的话,需要使用HibernateTransactionManager,对于HIbernate3,需要在spring的上下文中定义如下声明:

sessionFactory属性需要装配一个HIbernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit方法,反之,则会调用rollback方法。

3. Java持久化API事务(JPA)

使用JPA需要导入坐标依赖

org.springframework.data

spring-data-jpa

2.6.1

Java持久化API作为真正的Java持久化标准进入我们的视野,使用JPA的话,需要使用Spring的JpaTransactionManager来处理事务,可以在xml文件中进入如下配置

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现),JpaTransactionManager将由与工厂所产生的JPA EntityManager合作构建事务。

4. Java原生API事务

如果应用程序跨越了多个事务管理器(比如两个或者多个不同的数据源)。此时需要使用JtaTransactionManager:

JtaTransactionManager将事务管理责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象。其中事务成功完成通过UserTransaction.commit()方法提交,事务失败,则通过UserTransaction.rollback()方法回滚。

(三)Spring事务控制的配置

通过jdbc持久化事务,对于事务配置实现由两种方式来实现,即xml配置和注解配置

1. XML配置

(1)添加命名空间

事务

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

配置如下:

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

https://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

">

(2)设置AOP代理

(3)配置事务管理器

(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\NESTEDisolation:不是必须的,默认值是DEFAULT,表示事务的隔离级别(数据库的隔离级别)timeout:不是必须的,默认值为-1(永不超时),表示事务的超时时间,以秒为单位。read-only:不是必须的,默认值false不是只读的,表示事务是否为只读。rollback-for:不是必须的,表示将被触发进行回滚的Exception(s),以逗号分开。no-rollback-for:不是必须的,表示不被触发进行回滚的Exception(s),以逗号分开。

事务传播行为的介绍

对应的属性值对应的意义@Transactional(propagation=Propagation.REQUIRED)如果有事务,那么加入事务,如果或没有事务,默认情况下新建一个。@Transactional(propagation=Propagation.NOT_SUPPORTED)容器不为这个方法开启事务@Transactional(propagation=Propagation.REQUIRES_NEW)不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,再继续执行老的事务。@Transactional(propagation=Propagation.MANDATORY)必须在一个已有的事务中执行,否则会抛出异常@Transactional(propagation=Propagation.NEVER)必须在一个没有的事务中执行,否则抛出异常(与MANDATORY相反)@Transactional(propagation=Propagation.SUPPORTS)如果其他的bean调用这个方法,在其他bean中声明事务,那就用事务。如果在其他bean中没有声明事务,那就不用事务。@Transactional(propagation=Propagation.NESTED)支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

(5)配置AOP

2. 注解配置

(1)配置事务管理器

(2)配置注解支持

(3)加入事务注解

在需要使用事务的方法上添加事务的注解

@Override

@Transactional(propagation=Propagation.REQUIRED)

public void saverUser(){

xxxxx

}

(四)Spring事务控制——模拟转账的实现

1.接口方法定义

/**

* 支出方法

* @param accountId

* @param money

* @return

*/

int outAccount(int accountId,Double money);

/**

* 表示收入方法

* @param accountId

* @param money

* @return

*/

int inAccount(int accountId,Double money);

2.接口实现

/**

* 表示支出

* @param accountId

* @param money

* @return

*/

@Override

public int outAccount(int accountId, Double money) {

String sql="UPDATE tb_account SET money=money-? WHERE account_id=?";

return jdbcTemplate.update(sql,money,accountId);

}

/**

*表示收入

* @param accountId

* @param money

* @return

*/

@Override

public int inAccount(int accountId, Double money) {

String sql="UPDATE tb_account SET money=money+? WHERE account_id=?";

return jdbcTemplate.update(sql,money,accountId);

}

3.业务层方法

@Service

@Transactional(propagation = Propagation.REQUIRED)

public class AccountService {

@Autowired

private IAccountDao accountDao;

/**

* 转账的业务操作

* @param outId

* @param inId

* @param money

* @return

*/

public int updateAccountByTransfer(Integer outId,Integer inId,Double money){

//code表示状态码,0表示失败,1表示成功

int code=0;

//支出方法

int outRow=accountDao.outAccount(outId,money);

//收入方法

int inRow=accountDao.inAccount(inId,money);

if (inRow==1 && outRow==1){

code=1;

}

return code;

}

}

@Transactional(propagation = Propagation.REQUIRED)表示用注解对该方法进行事务支持。

4.测试

public class TestTransfer extends BaseTest{

@Autowired

private AccountService accountService;

@Test

public void testTransfer(){

int i = accountService.updateAccountByTransfer(1, 2, 100.0);

if (i==1){

System.out.println("转账成功");

}

}

}

相关链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: 

发表评论

返回顶部暗黑模式