以一个例子学习:
银行转账(用烂的萌新例子),当出错时,如何用Spring控制Mysql的事务。
前期准备
创建数据表
创建一个名字为account
的数据表,列名有:
id
: 主键自增user_name
: 用户名money
: 该用户有多少钱
CREATE TABLE account(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(20),
money INTEGER
);
数据表插入数据
往表里插入两条数据
INSERT INTO account (user_name,money) VALUES
("chengyunlai",1000),
("yunlaicheng",1000);
Spring连接数据库需要依赖
这里使用的是Spring对JDBC操作封装的spring-jdbc
<!--Spring对JDBC的封装-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<!--java连接mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
数据源的配置
配置数据源,获得数据库的访问。在Spring中,我们需要配置的数据源在导入包spring-jdbc
中,类名为:DriverManagerDataSource
,按照IOC的思想就是,我们需要让Spring管理这个Bean。
在DriverManagerDataSource
类中我们可以发现一个set
方法,即可以通过依赖注入的方式,注入DriverClassName
驱动,因为我们是使用Java连接Mysql,所以这个驱动实现是mysql-connector-java
,也就是导入的mysql-connector-java
Jar包。
为什么填上com.mysql.jdbc.Driver
就可以注入Driver
类给setDriverClassName
了呢?我们看setDriverClassName
中有这样一句代码:
Class.forName(driverClassNameToUse, true, ClassUtils.getDefaultClassLoader());
是不是非常熟悉呢?通过放射的方式就可以实现了。
那么其他的url
,username
,password
呢?我们可以发现DriverManagerDataSource
继承了一个抽象类,而抽象类其中一个作用就是做一些子类重复做的事。
public class DriverManagerDataSource extends AbstractDriverBasedDataSource
果然在抽象类中定义了,那么就表示spring-jdbc
可以通过driverClassName
的不同操作其他不同的数据库了,这个大家可以按照我的思路去试一试其他的数据,这里还是以Mysql
为主。
XML
回到正题,我们通过XML的方式将数据源进行配置,至于为什么这样做已经在上面展开。这里我们需要做的就是将DriverManagerDataSource
交给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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/juejin_sql"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
注解
就是将New的对象交给Spring管理,也很简单。
@Configuration
public class JdbcAnnotationConfig {
@Bean
public DriverManagerDataSource driverManagerDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/juejin_sql");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
测试数据源
以注解的方式为例子,拿到一个connection
即可
public class JdbcAnnotationApplication {
public static void main(String[] args) throws SQLException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JdbcAnnotationConfig.class);
DriverManagerDataSource bean = ctx.getBean(DriverManagerDataSource.class);
Connection connection = bean.getConnection();
System.out.println(connection);
}
}
配置Template
我们是使用Template
去执行SQL
语句的,而Template
需要指定数据源,这一步操作就不详细说啦,直接以XML为例:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
编写Dao
- Dao需要注入
Template
- Dao需要完成两个动作,一个是加钱,一个是减钱
写一个BaseDao
为什么突然要写一个BaseDao
?我们上面阅读的过程中发现抽象类可以完成重复动作的抽取,因为实际中我们需要写大量注入Template
,所以我们将这个动作可以抽出来形成一个抽象类。
抽象类
@Component
public abstract class BaseDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
定义接口
public interface AccountDao {
// 加钱
public void addMoney(Integer num);
// 减钱
public void reduceMoney(Integer num);
}
实现类
写了两个SQL,一个是用来加money的,一个是用来减money的。
@Repository
public class AccountDaoImpl extends BaseDao implements AccountDao {
@Override
public void addMoney(Integer id,Integer num) {
this.getJdbcTemplate().update("UPDATE account SET money = money + ? WHERE id = ?",num,id);
}
@Override
public void reduceMoney(Integer id,Integer num) {
this.getJdbcTemplate().update("UPDATE account SET money = money - ? WHERE id = ?",num,id);
}
}
编写Service
也就是一个普通的模拟一个账户加钱,一个账户减钱的操作。
@Service
public class AccountService {
@Autowired
AccountDao accountDao;
// 一个交易方法
public void deal(){
accountDao.addMoney(1,100);
accountDao.reduceMoney(2,100);
}
}
直接通过启动类执行当然没问题,ok现在模拟一个出错。在两个方法之间加上int i = 1 / 0
发现问题
Exception in thread "main" java.lang.ArithmeticException: / by zero
首先会报这个错误,然后我们看数据库。
很好,事务的问题出现了。这两个SQL执行并未按照事务的原则执行。