Spring - 事务篇
目录
1. 指路 - Spring AOP 篇
2. 指路 - Spring IoC 篇
1. Spring整合JDBC
1.1 使用spring-jdbc操作数据库
- 主要内容:学习使用
JdbcTemplate API
和如何使用Spring管理JdbcTemplate
- 创建项目引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
- 测试连接
@Test
public void test() throws PropertyVetoException {
// 创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/数据库名?serverTimezone=Asia/Shanghai&" +
"characterEncoding=utf8&useUnicode=true&useSSL=false");
dataSource.setUser("用户名");
dataSource.setPassword("密码");
// 使用JDBCTemplate
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "INSERT INTO 表名(字段名1,字段名2) VALUES (?,?)";
int update = jdbcTemplate.update(sql,"字段值1","字段值2");
System.out.println("插入数据的结果:" + update);
}
1.2 Spring 管理 JdbcTemplate
- Spring整合JDBC的时候我们都是直接让我的
Dao
继承Spring提供的JdbcDaoSupport
类,该类中提供了JdbcTemplate
模板可用。
示例 :
/**
* Dao
*
* @author murphy
*/
public class UserDao extends JdbcDaoSupport {
/**
* 增加
* @param user
* @return
*/
public int insert(User user) {
String sql = "INSERT INTO USER(UNAME,LOCATION) VALUES (?,?)";
return this.getJdbcTemplate().update(sql,user.getUname(),user.getLocation());
}
/**
* 更新
* @param user
* @return
*/
public int update(User user) {
String sql = "UPDATE User SET uname = ?, location=? WHERE uid = ?";
return this.getJdbcTemplate().update(sql,user.getUname(),user.getLocation(),user.getUid());
}
/**
* 删除
* @param id
* @return
*/
public int del(int id) {
String sql = "Delete from User where uid = ?";
return this.getJdbcTemplate().update(sql,id);
}
/**
* 处理结果的方法
* @param resultSet
* @return
* @throws SQLException
*/
public User handleResult(ResultSet resultSet) throws SQLException {
User user = new User();
user.setUid(resultSet.getInt(1));
user.setUname(resultSet.getString(2));
user.setLocation(resultSet.getString(3));
return user;
}
/**
* 根据ID查询
* @param id
* @return
*/
public User findById(int id) {
String sql = "SELECT * FROM USER WHERE UID = ?";
return this.getJdbcTemplate().queryForObject(sql, new Object[]{id}, new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
// User user = new User();
// user.setUid(resultSet.getInt(1));
// user.setUname(resultSet.getString(2));
// user.setLocation(resultSet.getString(3));
return handleResult(resultSet);
}
});
}
/**
* 查询所有用户信息
* @return
*/
public List<User> findAll() {
String sql = "SELECT * FROM User";
return this.getJdbcTemplate().query(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
// User user = new User();
// user.setUid(resultSet.getInt(1));
// user.setUname(resultSet.getString(2));
// user.setLocation(resultSet.getString(3));
return handleResult(resultSet);
}
});
}
/**
* 查询总行数
* @return
*/
public int getCount() {
String sql = "select count(uid) from User";
return this.getJdbcTemplate().queryForObject(sql,Integer.class);
}
/**
* 查询最大值 + 最小值
* @return
*/
public Map<String, Object> getMaxAndMin() {
String sql = "select max(uid), min(uid) from User";
return this.getJdbcTemplate().queryForMap(sql);
}
}
- Spring的配置文件
spring.xml
中需要创建数据源和给TeamDao中的jdbcTemplate
赋值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置JdbcTemplate的数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/数据库名?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false"></property>
<property name="user" value="数据库账号"></property>
<property name="password" value="数据库密码"></property>
</bean>
<!-- 配置JdbcTemplate模版 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDao" class="com.murphy.dao.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
测试:
/**
* 测试类
*
* @author murphy
*/
public class ConnectTest {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 新增测试
@Test
public void testInsert() {
UserDao userDao = (UserDao) context.getBean("userDao");
User user = new User();
user.setUname("Adan");
user.setLocation("Hangzhou");
int res = userDao.insert(user);
System.out.println("插入数据的结果:" + res);
}
// 修改测试
@Test
public void testUpdate() {
UserDao userDao = (UserDao) context.getBean("userDao");
User user = new User();
user.setUname("Tik");
user.setLocation("Beijing");
user.setUid(5);
int res = userDao.update(user);
System.out.println("更新数据的结果:" + res);
}
// 删除测试
@Test
public void testDel() {
UserDao userDao = (UserDao) context.getBean("userDao");
int res = userDao.del(6);
System.out.println("删除数据的结果:" + res);
}
// 查询ID测试
@Test
public void testFindById() {
UserDao userDao = (UserDao) context.getBean("userDao");
User user = userDao.findById(1);
System.out.println(user);
}
// 查询所有测试
@Test
public void testFindAll() {
UserDao userDao = (UserDao) context.getBean("userDao");
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
// 总行数获取测试
@Test
public void testGetCount() {
UserDao userDao = (UserDao) context.getBean("userDao");
int count = userDao.getCount();
System.out.println("总行数:" + count);
}
// 最大和最小测试
@Test
public void testGetMaxAndMin() {
UserDao userDao = (UserDao) context.getBean("userDao");
Map<String, Object> many = userDao.getMaxAndMin();
Set<Map.Entry<String, Object>> entries = many.entrySet();
for (Map.Entry<String, Object> entry : entries) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
}
}
2. Spring事务管理
- 事务原本是数据库中的概念,在Dao层。但在实际开发中,一般将事务提升到业务层,即Service层。
- 这样做是为了能够使用事务的特性来管理具体的业务。
2.1 Spring事务管理API
- Spring的事务管理,主要用到两个事务相关的接口。
2.1.1 事务管理器接口
- 事务管理器是
PlatformTransactionManager
接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口常用的实现类 :
DataSourceTransactionManager
:使用 JDBC 或 MyBatis 进行数据库操作时使用。
Spring的回滚方式
- Spring 事务的默认回滚方式是:发生运行时异常和
error
时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
2.1.2 事务定义接口
- 事务定义接口
TransactionDefinition
中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
2.1.2.1 事务隔离级别常量
这些常量均是以ISOLATION_
开头。即形如ISOLATION_XXX
。
DEFAULT
:采用DB默认的事务隔离级别。MySql的默认为REPEATABLE_READ
; Oracle默认为READ_COMMITTED
。READ_UNCOMMITTED
:读未提交。未解决任何并发问题。READ_COMMITTED
:读已提交。解决脏读,存在不可重复读与幻读。REPEATABLE_READ
:可重复读。解决脏读、不可重复读,存在幻读SERIALIZABLE
:串行化。不存在并发问题。
2.1.2.2 事务传播行为常量
- 所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法
doSome()
调用B事务中的方法doOther()
,在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 - 事务传播行为常量都是以
PROPAGATION_
开头,形如PROPAGATION_XXX
。
Propagation.REQUIRED
- 当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务,比较常用的设置
Propagation.SUPPORTS
- 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行
Propagation.MANDATORY
- 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常
Propagation.REQUIRES_NEW
- 创建新事务,无论当前是否有事务都会创建新的
PROPAGATION_NESTED
- 如果当前存在事务,则以嵌入式事务方式去执行,和
PROPAGATION_REQUIRED
行为类似
PROPAGATION_NEVER
- 以非事务方式执行,如果当前存在事务则抛出异常
PROPAGATION_NOT_SUPPORTED
- 以非事务方式执行,如果当前存在事务则挂起当前事务
2.1.2.3 默认事务超时时限
- 常量
TIMEOUT_DEFAULT
定义了事务底层默认的超时时限,sql语句的执行时长。 - 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。
2.2 声明式事务控制
- Spring提供的对事务的管理,就叫做声明式事务管理。
- 如果用户需要使用Spring的声明式事务管理,在配置文件中配置即可:不想使用的时候直接移除配置。
- 这种方式实现了对事务控制的最大程度的解耦。
- 声明式事务管理,核心实现就是基于AOP。
Spring中提供了对事务的管理。开发者只需要按照Spring的方式去做就行。事务必须在service层统一控制。
事务的粗细粒度:
- 细粒度:对方法中的某几行的代码进行开启提交回滚;
- 粗粒度:对整个方法进行开启提交回滚;
Spring中的aop只能对方法进行拦截,所有我们也就针对方法进行事务的控制。
如果只有单条的查询语句,可以省略事务;如果一次执行的是多条查询语句,例如统计结果、报表查询。必须开启事务。
2.3 基于注解的事务
/**
* 事务实现
*
* @author murphy
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
/**
* @Transactional 属性 说明:
* readOnly:是否只读 *
* rollbackFor={Exception.class}: 遇到什么异常会回滚 *
* propagation事务的传播:
* Propagation.REQUIRED:当前没有事务的时候,就会创建一个新的事务;如果当前有事务,就直接加入该事务,比较常用的设置
* Propagation.SUPPORTS:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行
* Propagation.MANDATORY:支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常
* Propagation.REQUIRES_NEW:创建新事务,无论当前是否有事务都会创建新的 *
* isolation=Isolation.DEFAULT:事务的隔离级别:默认是数据库的隔离级别 *
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public int insert(User user) {
int num1 = userDao.insert(user);
System.out.println("第一条执行结果:num1 = " + num1);
System.out.println(10/0);
int num2 = userDao.insert(user);
return num1 + num2;
}
}
<!-- 配置文件中添加 context/ tx约束 -->
<!-- 开启事务 -->
<context:component-scan base-package="com.murphy.service,com.murphy.dao"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
// 测试类
@Test
public void test() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) ac.getBean("userService");
int num = userService.insert(new User(9, "Aki", "Wuhan"));
System.out.println(num);
}
2.4 基于XML的事务
添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
spring.xml
配置
<!-- 事务管理器 -->
<!-- 开启事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.murphy.service..*.*(..))"/>
<aop:advisor pointcut-ref="pt" advice-ref="txAdvice"/>
</aop:config>