电商网站做导购,做网站前期创建文件夹,wordpress评论不了,做一个网站赚钱吗Java事务入门#xff1a;从基础概念到初步实践 引言1. Java事务基础概念1.1 什么是事务#xff1f;1.2 为什么需要事务#xff1f; 2. Java事务管理2.1 JDBC 的事务管理2.2 Spring 事务管理2.2.1 Spring JDBC2.2.1.1 添加 Spring 配置2.2.1.2 添加业务代码并测试验证 2.2.2… Java事务入门从基础概念到初步实践 引言1. Java事务基础概念1.1 什么是事务1.2 为什么需要事务 2. Java事务管理2.1 JDBC 的事务管理2.2 Spring 事务管理2.2.1 Spring JDBC2.2.1.1 添加 Spring 配置2.2.1.2 添加业务代码并测试验证 2.2.2 Spring JPA2.2.2.1 JPA相关依赖2.2.2.2 添加 JPA 配置2.2.2.3 添加 Spring 配置2.2.2.4 添加实体类2.2.2.5 添加业务代码并测试验证 2.2.3 Spring Mybatis Plus2.2.3.1 涉及依赖2.2.3.2 添加 Spring 配置2.2.3.3 添加实体类2.2.2.4 添加业务代码并测试验证 示例总结 引言
在 Java 语言相关的应用开发中事务Transaction是其中一个核心概念尤其是在涉及数据库操作时。理解并正确使用事务可以确保应用系统数据的完整性和一致性。本文 Huazie 将带您从 Java 事务的基础概念出发通过不同场景的事物管理实操帮助您快速入门 Java 事务。
1. Java事务基础概念
1.1 什么是事务
事务是数据库操作的基本执行单元它要么完全地执行要么完全地不执行。
事务作为一个逻辑工作单位其关键特性【数据的完整性和一致性】就体现在其 ACID 属性上如下
原子性Atomicity事务是一个原子操作单元其对数据的修改要么全都执行要么全都不执行。一致性Consistency事务必须使数据库从一个一致性状态变换到另一个一致性状态。隔离性Isolation事务的隔离性是指一个事务的执行不能被其他事务干扰即一个事务内部的操作及使用的数据对并发的其他事务是隔离的并发执行的各个事务之间不能互相干扰。持久性Durability持久性是指一个事务一旦提交它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。
1.2 为什么需要事务
我们先来想一下如下的场景
多个数据库操作之间有部分操作成功部分操作失败会咋样
显然这个时候应用系统的数据就会处于不一致的状态。这种不一致可能会带来非常严重的后果包括但不限于如下
数据错误由于某些操作成功而某些操作失败数据可能处于一个中间状态既不完全符合操作前的状态也不符合操作后预期的状态。业务逻辑错误当数据出现不一致时基于这些数据执行的业务逻辑可能会出现错误。例如在一个订单系统中如果订单的创建和库存的减少操作没有同时成功那么可能会导致订单状态与库存状态不匹配从而影响整个业务流程。系统可靠性降低一个不可靠的数据系统可能会导致更多的错误和故障需要更多的维护和支持从而增加运营成本。
因此为了避免这种情况在多个数据库操作之间我们就需要一种机制来确保这些操作要么全部成功要么全部失败。而事务正是为了解决这个问题而存在的。
2. Java事务管理
在 Java 中我们可以使用 JDBC 、Spring、JPA、MyBatisMyBatis Plus 等框架来管理数据库事务。这些框架提供了丰富的 API 和工具使我们能够轻松地管理事务。
2.1 JDBC 的事务管理
在 JDBC 中我们可以通过设置 Connection 对象的 autoCommit 属性来开启或关闭事务。
当 autoCommit 为 true 时每次执行 SQL 语句都会自动提交事务当 autoCommit 为 false 时我们需要手动调用 commit() 或 rollback() 方法来提交或回滚事务。
我们来看一个简单的 JDBC 事务管理示例 Testpublic void testJDBCTransaction() throws Exception {// flea-config.xml 中配置FleaJDBCConfig.init(DBSystemEnum.MySQL.getName(), fleajpatest);Connection conn null;PreparedStatement pstmt1 null;PreparedStatement pstmt2 null;try {conn FleaJDBCConfig.getConfig().getConnection();// 关闭自动提交conn.setAutoCommit(false);// 执行第一条SQL语句String sql1 UPDATE student SET stu_age stu_age-10 WHERE stu_namehuazie;pstmt1 conn.prepareStatement(sql1);pstmt1.executeUpdate();LOGGER.debug(执行第一条SQL语句);// 模拟异常throwEx();// 执行第二条SQL语句String sql2 UPDATE student SET stu_age stu_age12 WHERE stu_namehuazie;pstmt2 conn.prepareStatement(sql2);pstmt2.executeUpdate();LOGGER.debug(执行第二条SQL语句);// 提交事务conn.commit();LOGGER.debug(提交事物);} catch (SQLException e) {if (null ! conn) {try {// 回滚事务conn.rollback();LOGGER.debug(回滚事物);} catch (SQLException ex) {}}} finally {// 关闭资源if (null ! pstmt2) pstmt2.close();if (null ! pstmt1) pstmt1.close();if (null ! conn) conn.close();}}private void throwEx() throws SQLException {throw new SQLException(Test Exception);}在开始测试之前先来看看要操作的数据【如下红框所示】 上述示例中执行完第一条语句之后模拟抛出异常我们来运行一下可以看到如下结果 同时检查数据库中发现数据没变说明第一条 SQL 语句也没有执行成功可见已经被回滚了。
现在我们将模拟异常的代码去除然后断点调试下看下两条语句执行完但还没有提交的情况如下 从上述截图可知连接在提交之前数据还是原来的。
继续执行连接被提交我们可以看到 stu_age 已经被更改为 22可见示例中的两条 SQL 语句已经执行成功。 2.2 Spring 事务管理
Spring 提供了强大的声明式事务管理功能我们可以将事务管理与业务逻辑分离在不修改业务代码的情况下为业务方法添加事务支持。
Spring 事务管理的核心接口是 PlatformTransactionManager它是所有事务管理器的抽象。Spring 提供了多种事务管理器的实现。我们可以通过配置来选择合适的事务管理器以便于 Spring 结合 JDBC、JPA、MyBatisMybatis Plus 等框架来管理事物。
2.2.1 Spring JDBC
2.2.1.1 添加 Spring 配置
在 Spring 配置文件中我们需要配置数据源如下 bean idjdbcDataSource classorg.springframework.jdbc.datasource.DriverManagerDataSourceproperty namedriverClassName valuecom.mysql.jdbc.Driver /property nameurl valuejdbc:mysql://localhost:3306/fleajpatest?useUnicodetrueamp;characterEncodingUTF-8 /property nameusername value替换成你的MySQL用户名 /property namepassword value替换成你的MySQL用户密码 //bean配置 JdbcTemplate用于使用 JDBC 操作数据库如下 bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplateproperty namedataSource refjdbcDataSource//bean配置事物管理器 DataSourceTransactionManager如下 bean idjdbcTransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refjdbcDataSource //bean最后在 Spring 配置文件中我们需要开启对应的事务管理功能如下 tx:annotation-driven transaction-managerjdbcTransactionManager/2.2.1.2 添加业务代码并测试验证
下面让我们添加业务代码来演示下具体的事物管理如下
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service
public class StudentService {Autowiredprivate JdbcTemplate jdbcTemplate;Transactional(jdbcTransactionManager)public void service() throws SQLException {// 插入一条记录String sql insert into student(stu_name, stu_age, stu_sex, stu_state) values(?, ?, ?, ?);jdbcTemplate.update(sql, LGH, 18, 1, 1);sql update student set stu_state ? where stu_name ?;jdbcTemplate.update(sql, 0, LGH);throwEx();}private void throwEx() throws SQLException {throw new SQLException(Test Exception);}
}需要注意的是这里抛出的异常还是 SQLException它是 java.lang.Exception 的子类。
继续添加测试代码如下
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(locations {classpath:applicationContext.xml})
public class StudentSpringJDBCTest {private static final Logger LOGGER LoggerFactory.getLogger(StudentSpringJDBCTest.class);Resource(name studentService)private StudentService studentService;Testpublic void testSpringJDBCTransaction() throws Exception {studentService.service();}
}现在运行上述测试代码结果如下 从上图可知虽然这里抛出了异常但是实际数据还是提交成功了。
那这里是什么原因呢
这就要提到刚才抛出的异常了【即 SQLException】Spring 事物管理能处理的异常一定要是RuntimeException及其子类 或者 Error及其子类否则事物无法回滚。
可见如下截图【这块后续有时间展开讲解下】 我们重新来修改一下业务代码抛出 RuntimeException 异常 Transactional(jdbcTransactionManager)public void service() throws RuntimeException {// 省略。。。throwEx();}private void throwEx() throws RuntimeException {throw new RuntimeException(Test Exception);}继续运行测试代码执行结果如下 可以看到除了之前新增的本次要处理的 SQL 并没有执行到数据库。
现在让我们把抛出异常的代码注释掉运行结果如下 从上截图中可知要处理的 SQL 都已经成功执行。
2.2.2 Spring JPA
2.2.2.1 JPA相关依赖
Spring Data JPA 依赖
dependencygroupIdorg.springframework.data/groupIdartifactIdspring-data-jpa/artifactIdversion2.5.0/version
/dependencyEclipseLink 的 JPA 实现依赖
dependencygroupIdorg.eclipse.persistence/groupIdartifactIdeclipselink/artifactIdversion2.5.0/version
/dependency2.2.2.2 添加 JPA 配置
在资源文件夹的 META-INF 目录下添加 fleajpa-persistence.xml配置持久化单元的内容如下
?xml version1.0 encodingUTF-8?
persistence version2.0 xmlnshttp://java.sun.com/xml/ns/persistence xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsdpersistence-unit namefleajpa transaction-typeRESOURCE_LOCAL!-- provider --providerorg.eclipse.persistence.jpa.PersistenceProvider/provider!-- Connection JDBC --classcom.huazie.fleadbtest.jpa.common.entity.Student/classexclude-unlisted-classestrue/exclude-unlisted-classespropertiesproperty namejavax.persistence.jdbc.driver valuecom.mysql.jdbc.Driver /property namejavax.persistence.jdbc.urlvaluejdbc:mysql://localhost:3306/fleajpatest?useUnicodetrueamp;characterEncodingUTF-8 /property namejavax.persistence.jdbc.user value替换成你的MySQL用户名 /property namejavax.persistence.jdbc.password value替换成你的MySQL用户密码 //properties/persistence-unit/persistence
2.2.2.3 添加 Spring 配置
在 Spring 配置文件中添加 JPA 相关的默认 Bean用来初始化 LocalContainerEntityManagerFactoryBean如下 bean iddefaultPersistenceManagerclassorg.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManagerproperty namepersistenceXmlLocations!-- 可以配置多个持久单元 --listvalueclasspath:META-INF/fleajpa-persistence.xml/value/list/property/beanbean iddefaultPersistenceProvider classorg.eclipse.persistence.jpa.PersistenceProvider/!--bean iddefaultLoadTimeWeaver classorg.springframework.instrument.classloading.InstrumentationLoadTimeWeaver/--bean iddefaultVendorAdapter classorg.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapterproperty nameshowSql valuetrue//beanbean iddefaultJpaDialect classorg.springframework.orm.jpa.vendor.EclipseLinkJpaDialect/
配置 LocalContainerEntityManagerFactoryBean它是 Spring Data JPA 中用于创建和管理 EntityManagerFactory 的一个核心类如下所示 bean idfleaJpaEntityManagerFactoryclassorg.springframework.orm.jpa.LocalContainerEntityManagerFactoryBeanproperty namepersistenceUnitManager refdefaultPersistenceManager/property namepersistenceUnitName valuefleajpa/property namepersistenceProvider refdefaultPersistenceProvider/property namejpaVendorAdapter refdefaultVendorAdapter/property namejpaDialect refdefaultJpaDialect/property namejpaPropertyMapmapentry keyeclipselink.weaving valuefalse/entry keyeclipselink.logging.thread valuetrue//map/property/bean配置 JPA 事物管理器 JpaTransactionManager如下 bean idfleaJpaTransactionManager classorg.springframework.orm.jpa.JpaTransactionManagerproperty nameentityManagerFactory reffleaJpaEntityManagerFactory//bean启用 Spring Data JPA 仓库扫描如下 jpa:repositories base-packagecom.huazie.fleadbtest.jpa.repositoryentity-manager-factory-reffleaJpaEntityManagerFactorytransaction-manager-reffleaJpaTransactionManager/最后在 Spring 配置文件中还需要开启对应的事务管理功能如下 tx:annotation-driven transaction-managerfleaJpaTransactionManager/2.2.2.4 添加实体类
新建如下实体类 Student对应测试表 student
Entity
Table(name student)
public class Student implements FleaEntity {private static final long serialVersionUID 1267943552214677159L;IdGeneratedValue(strategy GenerationType.IDENTITY, generator STUDENT_SEQ)SequenceGenerator(name STUDENT_SEQ)Column(name stu_id, unique true, nullable false)private Long stuId; // 学生编号Column(name stu_name, nullable false)private String stuName; // 学生姓名Column(name stu_age, nullable false)private Integer stuAge; // 学生年龄Column(name stu_sex, nullable false)private Integer stuSex; // 学生性别1男 2女Column(name stu_state, nullable false)private Integer stuState; // 学生状态0删除 1在用// ... 省略get和set方法Overridepublic String toString() {return ToStringBuilder.reflectionToString(this);}
}2.2.2.5 添加业务代码并测试验证
首先添加 StudentRepository用于访问 Student 实体相关的数据库操作的接口
public interface StudentRepository extends JpaRepositoryStudent, Long {
}接着添加如下业务代码
Service
public class StudentService {Autowiredprivate StudentRepository studentRepository;Transactional(fleaJpaTransactionManager)public void service() throws RuntimeException {Student student new Student();student.setStuName(杜甫);student.setStuAge(35);student.setStuSex(1);student.setStuState(1);studentRepository.save(student);student.setStuState(0);studentRepository.save(student);throwEx();}private void throwEx() throws RuntimeException {throw new RuntimeException(Test Exception);}
}最后添加自测类如下
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(locations {classpath:applicationContext.xml})
public class StudentSpringJPATest {Resource(name studentService)private StudentService studentService;Testpublic void testSpringJDBCTransaction() throws Exception {studentService.service();}
}一切准备就绪我们来运行上述自测类结果如下 从上述截图可见业务代码抛出了异常相关的数据库操作并未执行成功。【注意 这里抛出的异常也一定要是RuntimeException及其子类 或者 Error及其子类】
将抛出异常的代码注释掉再来运行一下看看如下 从上图中我们可以看出业务代码的数据操作已经成功执行到了数据库中。
2.2.3 Spring Mybatis Plus
2.2.3.1 涉及依赖
alibaba 的数据库连接池 druid 依赖
dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.0/version
/dependencymybatis 依赖
dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.9/version
/dependencyMybatis Plus 依赖
dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus/artifactIdversion3.5.1/version
/dependency2.2.3.2 添加 Spring 配置
在 Spring 配置文件中添加数据源配置如下 bean iddataSource classcom.alibaba.druid.pool.DruidDataSource destroy-methodcloseproperty namedriverClassName valuecom.mysql.jdbc.Driver /property nameurl valuejdbc:mysql://localhost:3306/fleajpatest?useUnicodetrueamp;characterEncodingUTF-8 /property nameusername value替换成你的MySQL用户名 /property namepassword value替换成你的MySQL用户密码 //bean配置 SqlSessionFactory如下所示 bean idsqlSessionFactory classcom.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBeanproperty namedataSource refdataSource/property nametypeAliasesPackage valuecom.huazie.fleadbtest.mybatisplus.entity//bean配置 MapperScan如下所示 bean classorg.mybatis.spring.mapper.MapperScannerConfigurerproperty namebasePackage valuecom.huazie.fleadbtest.mybatisplus.mapper//bean配置事物管理器 DataSourceTransactionManager如下 bean iddataSourceTransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource//bean最后在 Spring 配置文件中还需要开启对应的事务管理功能如下 tx:annotation-driven transaction-managerdataSourceTransactionManager/2.2.3.3 添加实体类
新建如下实体类 Student对应测试表 student
Data
TableName(student)
public class Student {TableId(value stu_id, type IdType.AUTO)private Long stuId; // 学生编号TableField(value stu_name)private String stuName; // 学生姓名TableField(value stu_age)private Integer stuAge; // 学生年龄TableField(value stu_sex)private Integer stuSex; // 学生性别1男 2女TableField(value stu_state)private Integer stuState; // 学生状态0删除 1在用}2.2.2.4 添加业务代码并测试验证
添加学生服务层接口类如下
public interface IStudentService extends IServiceStudent {void service() throws RuntimeException;
}添加学生服务层实现类如下
Service(studentServiceImpl)
public class StudentServiceImpl extends ServiceImplStudentMapper, Student implements IStudentService {OverrideTransactional(dataSourceTransactionManager)public void service() throws RuntimeException {Student student new Student();student.setStuName(李白);student.setStuAge(25);student.setStuSex(1);student.setStuState(1);baseMapper.insert(student);student.setStuState(0);baseMapper.updateById(student);throwEx();}private void throwEx() throws RuntimeException {throw new RuntimeException(Test Exception);}}最后添加自测类如下
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(locations {classpath:applicationContext.xml})
public class StudentServiceSpringTest {Resource(name studentServiceImpl)private IStudentService studentService;Testpublic void testSpringMybatisPlusTransaction() throws RuntimeException {studentService.service();}
}现在我们可以运行上述自测类结果如下 从上述截图可见自定义异常已经被抛出并且数据库中也没有执行成功说明事物已经回滚了。
现在将抛出异常的代码注释掉再来运行看看如下 从上图可知相关数据库操作已经成功执行。
示例
相关演示示例请查看 GitHub 上的 flea-db-test 测试项目。
总结
本文 Huazie 从 Java 事物的基础概念出发带大家通过不同的事物管理方式进行实践进一步加深了对 Java 事物的理解。