一文搞懂 Spring 資料庫事務操作
CSDN · 程式 ·

一文搞懂 Spring 資料庫事務操作

ing通過一個名為spring-tx-436-RELEASE的J

作者 | 阿文,責編 | 郭芮

頭圖 | CSDN 下載自東方IC

出品 | CSDN(ID:CSDNnews)

今天我們一起了解下Spring的資料庫事務操作。在操作資料庫時,我們經常會使用到事務,為此Spring 提供了專門的用於處理事務的API方便開發者調用,那麼本文就著重來講解下Spring 對於事務的相關功能。

關功能。Spring事務的核心接口Spr

Spring 事務的核心接口

Spring 通過一個名為spring-tx-4.3.6-RELEASE 的JAR包來管理事務,在這個JAR包中的org.Springframework.transaction 包中包含了三個接口文件:

  • PlatformTramsactionManager 主要用於管理事務,包括獲取事務的狀態、提交事務和回滾事務;

  • TramsactionDefinition 該接口是事務定義的對象,包括了獲取事務的名稱、隔離級別、事務的傳播行為、超時時間、事務是否只讀等;

  • TramsactionStatus 該接口是事務的狀態,描述了某一個時間點事務狀態信息,包括刷新事務、獲取是否存在保存點、是否是新事務、是否回滾、設置事務回滾。

的API方便開發者調用,那麼本文就著重來講解下Spring對於事務的相

實例講解

接下來我們將通過實例的方式來講解如何使用註解的方式來通過Spring 進行事務的處理,手續我們在maven的pom.xml 中增加事務的JAR包:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>

我們首先準備一個資料庫:

CREATE TABLE IF NOT EXISTS `user`(
`id` INT UNSIGNED AUTO_INCREMENT,
`username` VARCHAR(100) NOT ,
`password` VARCHAR(40) NOT ,
`jifen` int(10) NOT ,
PRIMARY KEY ( `id` ))ENGINE=InnoDB DEFAULT CHARSET=utf8;

然後向資料庫中寫入一些數據,包括了用戶名、密碼和積分,如下所示:

MariaDB [spring_db]> select * from user;
+----+----------+----------+-------+
| id | username | password | jifen |
+----+----------+----------+-------+
| 1 | zhangsan | 123 | 1000 |
| 2 | lisi | 1234 | 1000 |
| 3 | wangwu | 1234 | 1000 |
+----+----------+----------+-------+
3 rows in set (0.000 sec)

我們要做的事情就是把張三的積分轉給李四。

我們需要創建一個 User 類,如下:

package com.SpringDemo;

public class User {
private Integer id;
private String username;
private String password;
private Integer jifen;
public Integer getId {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername {
return username;
}
public void setJifen(Integer jifen){
this.jifen = jifen;
}
public Integer getjifen {
return jifen;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String toString {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}

然後創建一個接口 UserDao:

package com.SpringDemo;

import java.util.List;

public interface UserDao {
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(int id);
//通過id查詢用戶
public User findUserById(int id);
//查詢所有用戶
public List<User> findAllUser;
public void transfer(String outUser,String inUser,Integer jifen);
}

在UserDao 接口中我們定義了一個transfer 的方法,它包含了三個參數分別是outUser、inUser、jifen。

接來下我們定義實現類 UserDAOImpl:

package com.SpringDemo;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public int addUser(User user) {
String sql="insert into user(username,password) value(?,?)";
Object obj=new Object{
user.getUsername,
user.getPassword
};
int num=this.jdbcTemplate.update(sql,obj);
return num;
}

@Override
public int updateUser(User user) {
String sql="update user set username=?,password=? where id=?";
Object params=new Object{
user.getUsername,
user.getPassword,
user.getId
};
int num=this.jdbcTemplate.update(sql,params);
return num;
}

@Override
public int deleteUser(int id) {
String sql="delete from user where id=?";
int num=this.jdbcTemplate.update(sql,id);
return num;
}

@Override
public User findUserById(int id) {
String sql="select * from user where id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.queryForObject(sql,rowMapper,id);
}

@Override
public List<User> findAllUser {
String sql="select * from user";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.query(sql,rowMapper);
}

@Override

public void transfer(String outUser, String inUser, Integer jifen) {
// 贈送積分
this.jdbcTemplate.update("update user set jifen=jifen+? where username=?",jifen,inUser);
// 模擬系統運行時的突發性問題
int i =1/0;
//贈送出積分
this.jdbcTemplate.update("update user set jifen=jifen-? where username=?",jifen,outUser);

}
}

接下來我們定義一個applicationContext.xml:

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--1.配置數據源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--資料庫驅動 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--連接資料庫的ur1 -->
<property name="url" value="jdbc:mysql://192.168.10.128:3306/spring_db" />
<!--連接資料庫的用戶名 -->
<property name="username" value="root" />
<!--連接資料庫的密碼 -->
<property name="password" value="123456" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--默認必須使用數據源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定義id為userDao的Bean -->
<bean id="userDao" class="com.SpringDemo.UserDaoImpl">
<!--將 jdbcTemplate注入到 userDao實例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事務管理器,依賴於數據源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.註冊事務管理驅動 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

Spring 的事務管理方式有2種,一種是傳統的編程序事務管理,即通過代碼來管理事務的開始、執行和異常以及回滾,一種是聲明式管理,即通過配置文件的方式,原理是通過AOP技術實現,我們在實際開發過程中推薦使用聲明式事務管理,效率會大大提升,因為只需要通過配置即可。

在該接口中我們我們重寫transfer的方法,更新資料庫將inUser 的積分進行增加,而對應的outUser 積分要進行減少,但是在這裡我們要模擬系統運行的一些突然性問題。之後我們加了一個@Transactionl 註解,並設置了propagation、Isolation、readOnly 三個參數。

@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,
readOnly = false)
public void transfer(String outUser, String inUser, Integer jifen) {
// 贈送積分
this.jdbcTemplate.update("update user set jifen=jifen+? where username=?",jifen,inUser);
// 模擬系統運行時的突發性問題
int i =1/0;
//贈送出積分
this.jdbcTemplate.update("update user set jifen=jifen-? where username=?",jifen,outUser);

}

註解 @Transactional 的參數含義如下:

料庫時,我們經常會使用到事務,為此Spring提供了專門的用於處理事務

@Transactional 除了 DEFAULT,還有其他屬性,我們可以在Isolation 這個類中看到相對於的定位@Transactional 註解也可以添加到類級別上。當把@Transactional 註解放在類級別時,表示所有該類的公共方法都配置相同的事務屬性信息。
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);

private final int value;

private Isolation(int value) {
this.value = value;
}

public int value {
return this.value;
}
}

Propagation 的屬性如下:

public enum Propagation {
REQUIRED(0),//表示當前方法必須運行在一個事務環境中,如果存在就直接使用,否則開啟一個新的事務執行該方法
SUPPORTS(1),//如果當前方法處於事務環境中則使用,否則不使用事務
MANDATORY(2),//表示該方法的線程必須在事務中否則拋出異常
REQUIRES_NEW(3), //要求在新事務中執行,如果已經在事務中了則先暫停然後啟動新事務執行,如果不在則啟動一個新事務後執行
NOT_SUPPORTED(4), //不支持當前事務,總是以非事務狀態執行,如果調用該方法的線程處於事務中澤先暫停然後執行
NEVER(5), //不支持當前執行的方法在事務中,如果在拋出異常
NESTED(6); //即便當前執行的方法在事務中也會啟動一個新事務,然後執行該方法

private final int value;

private Propagation(int value) {
this.value = value;
}

public int value {
return this.value;
}
}

此外使用@Transactional 必須保證是在public 級別的方法中使用,@Transactional 只能應用到 public 方法才有效,這是因為在使用 Spring AOP 代理時,Spring 在調用 TransactionInterceptor 在目標方法執行前後進行攔截之前,DynamicAdvisedInterceptor(CglibAopProxy 的內部類)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource(Spring 通過這個類獲取 @Transactional 註解的事務屬性配置屬性信息)的 computeTransactionAttribute 方法。

接下來我們創建一個測試類來進行測試:

package com.SpringDemo;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TransactionTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.transfer("zhangsan","lisi",100);
System.out.println("贈送積分成功");

}

}

我們執行上述程序,可以發現報錯了。程序報:

Exception in thread "main" java.lang.ArithmeticException: / by zero

如圖所示:

ws)今天我們一起了解下Spring的資料庫事務操作。在操作資

此時,我們查看資料庫中的數據沒有發生任何變化:

MariaDB [spring_db]> select * from user;
+----+----------+----------+-------+
| id | username | password | jifen |
+----+----------+----------+-------+
| 1 | zhangsan | 123 | 1000 |
| 2 | lisi | 1234 | 1000 |
| 3 | wangwu | 1234 | 1000 |
+----+----------+----------+-------+
3 rows in set (0.000 sec)

而當我們把 int i =1/0; 注釋掉再次執行就會發現程序執行沒有報錯了,並且數據發生了變化:

MariaDB [spring_db]> select * from user;
+----+----------+----------+-------+
| id | username | password | jifen |
+----+----------+----------+-------+
| 1 | zhangsan | 123 | 900 |
| 2 | lisi | 1234 | 1100 |
| 3 | wangwu | 1234 | 1000 |
+----+----------+----------+-------+
3 rows in set (0.000 sec)

好了,以上就是關於Spring的事務管理介紹。

SDN下載自東方IC出品|CSDN(ID:CSDNne
作者|阿文,責編|郭芮頭圖|C

☞斬獲GitHub 2000+ Star,阿里雲開源的 Alink 機器學習平台如何跑贏雙11數據「博弈」? | AI 技術生態論

☞2020 年,AI 晶片內存哪家強?

☞拜託,別再問我什麼是 B+ 樹了

☞程式設計師為什麼應該旗幟鮮明地反對「最佳實踐」?

☞半小時訓練億級規模知識圖譜,亞馬遜AI開源知識圖譜嵌入表示框架DGL-KE

☞「出道」 5 年採用率達 78%,Kubernetes 的成功秘訣是什麼?

☞警惕!新騙術出現:這些虛假二維碼生成器已成功盜取 4.6 萬美元!

今日福利:評論區留言入選,可獲得價值299元的「2020 AI開發者萬人大會」在線直播門票一張。 快來動動手指,寫下你想說的話吧。

聲明:文章觀點僅代表作者本人,PTTZH僅提供信息發布平台存儲空間服務。
喔!快樂的時光竟然這麼快就過⋯
繼續其他精彩內容吧!
more