目录
第八章 JdbcTemplate
8.1 什么是Jdbc
Java数据库连接(Java Database Connectivity,简称JDBC),jdbc是java访问数据库的一种规范,它规定了访问数据库的标准,必须遵循它的标准才能对数据进行增删改查操作,如果不做统一的规定,那么不同的数据库厂商就要有不同的访问数据库规范,及其不容易管理。所以为了统一数据访问的方式,就有了jdbc的规范。
8.2 什么是JdbcTemplate
JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分,JdbcTemplate处理了资源的建立和释放,他帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。
在JdbcTemplate中执行SQL语句的方法大致分为3类:
execute
:可以执行所有SQL语句,但是该方法没有返回值,一般用于执行DDL语句。
update
:用于执行INSERT、UPDATE、DELETE等DML语句。
queryXxx
:用于DQL数据查询语句。
8.3 快速入门
导入pom
<!-- spring容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- spring 测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!-- spring JdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- 数据库连接工具-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 使用log4j2 日志-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
创建数据库连接信息
jdbc.dev.driverClass=com.mysql.jdbc.Driver
jdbc.dev.url=jdbc:mysql://localhost:3306/test
jdbc.dev.username=root
jdbc.dev.password=root
数据库对象
@Configuration
@PropertySource("classpath:jdbc.properties")
public class MyJdbcTemplate {
@Autowired
private Environment env;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.dev.driverClass"));
dataSource.setUrl(env.getProperty("jdbc.dev.url"));
dataSource.setUsername(env.getProperty("jdbc.dev.username"));
dataSource.setPassword(env.getProperty("jdbc.dev.password"));
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
}
容器配置类
@Configuration
@ComponentScan("com.lyf.jdbcTemplate")
public class JdbcAppConfig {
}
单元测试5 测试数据库连接
@SpringJUnitConfig(JdbcAppConfig.class)
public class MyJdbcTemplate {
private static final Logger log = LoggerFactory.getLogger(MyJdbcTemplate.class);
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void jdbc() throws SQLException {
Connection connection = jdbcTemplate.getDataSource().getConnection();
log.info("连接状态:{}",connection.getCatalog());
}
}
8.4 对数据库进行操作
8.4.1 基本增删改查
package jdbcTemplate.MyJdbcTemplate;
import com.lyf.jdbcTemplate.config.JdbcAppConfig;
import com.lyf.jdbcTemplate.config.User;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@SpringJUnitConfig(JdbcAppConfig.class)
public class MyJdbcTemplate {
private static final Logger log = LoggerFactory.getLogger(MyJdbcTemplate.class);
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void jdbc() throws SQLException {
Connection connection = jdbcTemplate.getDataSource().getConnection();
log.info("连接状态:{}",connection.getCatalog());
}
/**
* 创建表
* @throws SQLException
*/
@Test
public void test() throws SQLException {
String sql = "create table sys_user(id int not null primary key auto_increment, " +
"username varchar(20), password varchar , status int(20))";
jdbcTemplate.execute(sql);
log.info("数据表创建成功");
}
/**
* 删除表
*/
@Test
public void test1(){
String sql = "drop table test";
jdbcTemplate.execute(sql);
log.info("数据表删除成功");
}
/**
* 对sys_user进行插入
*/
@Test
public void test2() {
String sql ="INSERT INTO `test`.`sys_user`(`id`, `username`, `password`, `status`) VALUES (null , 'xiaoqi', '1e191d851b3b49a248f4ea62f6b06410', 4)";
int update = jdbcTemplate.update(sql);
if (update > 1) {
log.info("数据表成功");
}
}
/**
* 修改数据
*/
@Test
public void test3(){
String sql = "UPDATE `test`.`sys_user` SET `username` = 'xiaoqi', `password` = '1e191d851b3b49a248f4ea62f6b06410', `status` = 4 WHERE `id` = 5;";
int update = jdbcTemplate.update(sql);
if (update>1){
log.info("修改成功");
}
}
/**
* 查询所有数据
*/
@Test
public void test4(){
String sql = "select * from sys_user";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
for (User user :query) {
log.info("{}",user);
}
}
/**
* 查询一个
*/
@Test
public void test5(){
Integer id = 1;
String sql = "select * from sys_user where id ="+id;
User query = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class));
System.out.println(query);
}
/**
* 统计个数
*/
@Test
public void test6(){
String sql = "select count(*) from sys_user";
Integer query = jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println(query);
}
}
第九章 spring事务
9.1 什么是事务
Transactions代表事务,简写为tx,它是用户定义的一个操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
应用场景:银行转账。操作必须按顺序执行,如果中途遇到异常失败,则之前执行的操作必须回滚。
9.2 事务特性
原子性(Atomicity):强调事务的不可分割
一致性(Consistency):事务的执行的前后数据的完整性保持一致
隔离性(Isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(Durability):一个事务一旦结束,数据就会持久到数据库
9.3 事务隔离级别
如果不考虑事务的隔离性,可能会引发读安全性问题:
脏读
:一个事务读到了另一个事务未提交的数据
不可重复读
:一个事务读到了另一个事务已经提交的 update 的数据,导致多次查询结果不一致
幻读 / 虚读
:一个事务读到了另一个事务已经提交的 insert 的数据,导致多次查询结果不一致
事务隔离级别
要想解决读安全性问题,我们就需要给事务设置隔离级别,Spring定义了4种隔离级别:
隔离级别 | 中文说明 | 说明 |
---|---|---|
READ UNCOMMITTED | 读未提交 | 不能解决以上所有读问题,效率最高,安全性最低,一般不用 |
READ COMMITTED | 都已提交 | 避免脏读,不可重复读和幻读有可能发生,Oracle默认的隔离级别 |
REPEATABLE READ | 可重复读 | 避免脏读、不可重复读,幻读有可能发生,MySQL默认的隔离级别 |
SERIALIZABLE | 串行化 | 可以解决以上所有读问题,效率最差,安全性最高,一般不用 |
事务传播行为
事务传播行为(Propagation Behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何执行。
事务传播行为既然是传播,那么至少有两个东西,才可以发生传播,单体不存在传播这个行为。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
通常事务都是加在 Service 层,事务的传播行为是为了解决特别复杂的业务,业务层方法互相调用的问题,Spring定义了7种传播行为:
注意:使用REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚,两个事务互不影响,两个事务不是一个真正的嵌套事务。使用NESTED时,外层事务的回滚可以引起内层事务的回滚,而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
只读事务概念
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性。
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。
9.4 事务配置(xml)
DROP DATABASE IF EXISTS `test`;
CREATE DATABASE `test`;
USE `test`;
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
`aid` INT(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`aname` VARCHAR(20) DEFAULT NULL COMMENT '用户名称',
`amoney` INT(11) DEFAULT NULL COMMENT '用户余额',
PRIMARY KEY (`aid`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `t_account`(`aid`,`aname`,`amoney`) VALUES (1,'张三',1000);
INSERT INTO `t_account`(`aid`,`aname`,`amoney`) VALUES (2,'李四',1000);
导入pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springStudy</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.lyf</groupId>
<artifactId>transactions</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring 容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- jdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- spring 测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- mysql 驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 单元测试5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<!-- log4j2 日志-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
</project>
配置dao层
package com.lyf.tx.dao;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
public interface AccountDao {
/**
* 减钱
* @param id
* @param money
*/
public void reduceMoney(Integer id, Integer money);
/**
* 加钱
* @param id
* @param money
*/
public void addMoney(Integer id ,Integer money);
}
package com.lyf.tx.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
//@Repository
public class AccountDaoImpl implements AccountDao{
private static final Logger log = LoggerFactory.getLogger(AccountDaoImpl.class);
// @Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 减钱
*
* @param id
* @param money
*/
public void reduceMoney(Integer id, Integer money) {
String sql ="update t_account set amoney = amoney-? where aid =? ";
jdbcTemplate.update(sql,money,id);
log.info("{}少钱",id);
}
/**
* 加钱
*
* @param id
* @param money
*/
public void addMoney(Integer id, Integer money) {
String sql ="update t_account set amoney = amoney+? where aid =? ";
jdbcTemplate.update(sql,money,id);
log.info("{}多钱",id);
}
}
配置service层
package com.lyf.tx.service;
public interface AccountService {
/**
* 转转操作
* @param formId
* @param toId
* @param money
*/
void transfer(Integer formId,Integer toId ,Integer money);
}
package com.lyf.tx.service;
import com.lyf.tx.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class AccountServiceImpl implements AccountService{
@Autowired
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Integer formId, Integer toId, Integer money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//减钱
accountDao.reduceMoney(formId,money);
//模拟异常
int i = 1/0;
//加钱
accountDao.addMoney(toId,money);
}
});
}
}
数据库连接信息
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
spring配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
p:driverClassName="${jdbc.driverClass}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="accountDao" class="com.lyf.tx.dao.AccountDaoImpl"
p:jdbcTemplate-ref="jdbcTemplate"
/>
<bean class="com.lyf.tx.service.AccountServiceImpl"
p:accountDao-ref="accountDao"
p:transactionTemplate-ref="transactionTemplate"
/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"
p:transactionManager-ref="transactionManager"
p:isolationLevelName="ISOLATION_REPEATABLE_READ"
p:propagationBehaviorName="PROPAGATION_REQUIRED"
/>
</beans>
日志配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
</Console>
</Appenders>
<Loggers>
<!--默认使用rootLogger-->
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
测试代码
import com.lyf.tx.config.TxAppConfig;
import com.lyf.tx.dao.AccountDao;
import com.lyf.tx.service.AccountService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.sql.Connection;
import java.sql.SQLException;
//@SpringJUnitConfig(TxAppConfig.class)
@SpringJUnitConfig(locations = "classpath:context.xml")
public class TransactionsTest {
@Autowired
AccountService accountService;
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 测试数据库连接
* @throws SQLException
*/
@Test
public void test() throws SQLException {
Connection connection = jdbcTemplate.getDataSource().getConnection();
System.out.println(connection.getCatalog());
}
@Test
public void test1(){
accountService.transfer(1,2,100);
}
}
运行结果
9.5 事务配置(aop)
pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springStudy</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.lyf</groupId>
<artifactId>transactions</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring 容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- jdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- spring 测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- mysql 驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 单元测试5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<!-- log4j2 日志-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
</dependencies>
</project>
保持 AccountDao 、AccountDaoImpl 、AccountService 、测试代码类不变 log4j2.xml 不变。
修改AccountServiceImpl类
package com.lyf.tx.service;
import com.lyf.tx.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@Service
public class AccountServiceImpl implements AccountService{
@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Integer formId, Integer toId, Integer money) {
//减钱
accountDao.reduceMoney(formId,money);
//模拟异常
int i = 1/0;
//加钱
accountDao.addMoney(toId,money);
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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
http://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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!-- 引入外部属性文件-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
p:driverClassName="${jdbc.driverClass}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"
/>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<bean id="accountDao" class="com.lyf.tx.dao.AccountDaoImpl"
p:jdbcTemplate-ref="jdbcTemplate"
/>
<bean class="com.lyf.tx.service.AccountServiceImpl"
p:accountDao-ref="accountDao"
/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<!--事务-->
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面-->
<aop:config>
<!-- 配置通知点-->
<aop:pointcut id="transfer" expression="execution(* com.lyf.tx.service.AccountServiceImpl.transfer(..))"/>
<!-- 通知-->
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="transfer"/>
</aop:config>
</beans>
9.5 事务配置 (注解)
pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springStudy</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.lyf</groupId>
<artifactId>transactions</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring 容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- jdbcTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- spring 测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!-- mysql 驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 单元测试5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<!-- log4j2 日志-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
</project>
AccountDao
package com.lyf.tx.dao;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
public interface AccountDao {
/**
* 减钱
* @param id
* @param money
*/
public void reduceMoney(Integer id, Integer money);
/**
* 加钱
* @param id
* @param money
*/
public void addMoney(Integer id ,Integer money);
}
package com.lyf.tx.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
@Repository
public class AccountDaoImpl implements AccountDao{
private static final Logger log = LoggerFactory.getLogger(AccountDaoImpl.class);
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 减钱
*
* @param id
* @param money
*/
public void reduceMoney(Integer id, Integer money) {
String sql ="update t_account set amoney = amoney-? where aid =? ";
jdbcTemplate.update(sql,money,id);
log.info("{}少钱",id);
}
/**
* 加钱
*
* @param id
* @param money
*/
public void addMoney(Integer id, Integer money) {
String sql ="update t_account set amoney = amoney+? where aid =? ";
jdbcTemplate.update(sql,money,id);
log.info("{}多钱",id);
}
}
package com.lyf.tx.service;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
public interface AccountService {
/**
* 转转操作
* @param formId
* @param toId
* @param money
*/
void transfer(Integer formId,Integer toId ,Integer money);
}
package com.lyf.tx.service;
import com.lyf.tx.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
@Service
public class AccountServiceImpl implements AccountService{
@Autowired
private AccountDao accountDao;
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
@Override
public void transfer(Integer formId, Integer toId, Integer money) {
//减钱
accountDao.reduceMoney(formId,money);
//模拟异常
int i = 1/0;
//加钱
accountDao.addMoney(toId,money);
}
}
数据库配置类
package com.lyf.tx.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
@Configuration
@PropertySource("classpath:jdbc.properties")
public class MyDataSource {
@Autowired
private Environment env;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClass"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
spriing配置
@Configuration
@ComponentScan("com.lyf.tx")
@EnableTransactionManagement
public class TxAppConfig {
}
测试
import com.lyf.tx.config.TxAppConfig;
import com.lyf.tx.dao.AccountDao;
import com.lyf.tx.service.AccountService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author: lyf
* @CreateTime: 2022-11-28
* @description:
*/
@SpringJUnitConfig(TxAppConfig.class)
//@SpringJUnitConfig(locations = "classpath:context.xml")
public class TransactionsTest {
@Autowired
AccountService accountService;
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 测试数据库连接
* @throws SQLException
*/
@Test
public void test() throws SQLException {
Connection connection = jdbcTemplate.getDataSource().getConnection();
System.out.println(connection.getCatalog());
}
@Test
public void test1(){
accountService.transfer(1,2,100);
}
}
数据库数据
运行结果 异常回滚事务
数据库的值不变。