1. 程式人生 > 其它 >【mq】從零開始實現 mq-13-註冊鑑權 auth

【mq】從零開始實現 mq-13-註冊鑑權 auth

spring 事務

介紹

事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操作都失敗

事務四個特性(ACID)

(1)原子性
(2)一致性
(3)隔離性
(4)永續性

事務的四大特性詳解 https://blog.csdn.net/weixin_45730866/article/details/123654710

搭建事務操作環境

建立資料庫表,新增記錄,各有1000

引入 jar 包

建立 service,搭建 dao,完成物件建立和注入關係

dao介面

public interface UserDao {

    /**
     * 少錢
     */
    void reduceMoney();

    /**
     * 多錢
     */
    void addMoney();
}

dao介面實現類

@Repository
public class UserDaoImpl implements UserDao{

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * lucy 轉賬 100 給 mary
     * 少錢
     */
    @Override
    public void reduceMoney() {
        String sql = "update account set money = money - ? where user_name = ?";
        jdbcTemplate.update(sql, 100, "lucy");
    }

    /**
     * 多錢
     */
    @Override
    public void addMoney() {
        String sql = "update account set money = money + ? where user_name = ?";
        jdbcTemplate.update(sql,100, "mary");
    }
}

service類

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    /**
     * 轉賬的方法
     */
    public void accountMoney() {
        // lucy少100
        userDao.reduceMoney();

        // mary多100
        userDao.addMoney();
    }
}

配置檔案

<!--直接配置連線池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/user_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

<!-- JdbcTemplate 物件 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入 dataSource-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 元件掃描 -->
<context:component-scan base-package="com.zjh.tx"/>

測試方法

@Test
public void txTest() {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean.xml");

    UserService userService = context.getBean("userService", UserService.class);
    userService.accountMoney();
}

結果

上面方法測試,是沒有問題的。但是如果程式碼執行過程中出現異常,就會有問題

新增異常程式碼

測試結果為

可以看到已經出現異常了,但是lucy 的錢卻扣減成功了,而mary的錢卻沒有變化這就是異常導致的問題。


這就需要使用到事務了,具體看下方內容

Spring 事務管理介紹

在 Spring 進行事務管理操作

(1)程式設計式事務管理
(2)和宣告式事務管理(使用)

宣告式事務管理

(1)基於註解方式(使用)
(2)基於 xml 配置檔案方式

在 Spring 進行宣告式事務管理,底層使用 AOP 原理

Spring 事務管理 API

(1)提供一個介面,代表事務管理器,這個介面針對不同的框架提供不同的實現類

註解宣告式事務管理

在 spring 配置檔案配置事務管理器

<!--建立事務管理器-->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入資料來源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

在 spring 配置檔案,開啟事務註解

(1)在 spring 配置檔案引入名稱空間 tx、aop

xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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

(2)開啟事務註解

<!--開啟事務註解-->
<tx:annotation-driven transaction-manager="transactionManager"/>

在 service 類上面(或者 service 類裡面方法上面)新增事務註解

(1)@Transactional,這個註解新增到類上面,也可以新增方法上面
(2)如果把這個註解新增類上面,這個類裡面所有的方法都新增事務
(3)如果把這個註解新增方法上面,為這個方法新增事務

測試結果

雖然有異常但是結果是我們理想的,出現異常就回滾,不進行增減的操作。

宣告式事務管理引數配置

在 service 類上面添加註解@Transactional,在這個註解裡面可以配置事務相關引數

propagation:事務傳播行為

REQUIRE:如果有事務在執行,當前的方法就在這個事務內執行,否則,就啟動一個新的事務,並在自己的事務內執行

REQUIRED NEW:當前的方法必須啟動新事務,並在它自己的事務內執行。如果有事務正在執行,應該將它掛起

SUPPORTS:如果有事務在執行,當前的方法就在這個事務內執行.否則它可以不執行在事務中

NOT SUPPORTE:當前的方法不應該執行在事務中,如果有執行的事務,將它掛起

MANDATORY:當前的方法必須執行在事務內部,如果沒有正在執行的事務,就丟擲異常

NEVER:當前的方法不應該執行在事務中,如果有執行的事務,就丟擲異常

NESTED:如果有事務在執行,當前的方法就應該在這個事務的巢狀事務內執行。否則,就啟動一個新的事務,並在它自己的事務內執行

設定方法

@Transactional(propagation = Propagation.REQUIRED)

ioslation:事務隔離級別

  1. 事務有特性稱為隔離性,多事務操作之間不會產生影響。不考慮隔離性產生很多問題

  2. 有三個讀問題:髒讀、不可重複讀、虛(幻)讀

    • 髒讀:一個未提交事務讀取到另一個未提交事務的資料

    • 不可重複讀:一個未提交事務讀取到另一提交事務修改資料

    • 虛讀:一個未提交事務讀取到另一提交事務新增資料

通過設定事務隔離級別,解決讀問題

READ UNCOMMIT TED:讀未提交,包含的問題:髒度、不可重複讀、幻讀

READ COMMITTED:讀已提交,包含的問題:不可重複讀、幻讀

REPEATABLE READ:可重複讀,包含的問題:幻讀

SERIALIZABLE:序列化

設定方法

@Transactional(isolation = Isolation.REPEATABLE_READ)

timeout:超時時間

(1)事務需要在一定時間內進行提交,如果不提交進行回滾

(2)預設值是 -1 ,設定時間以秒單位進行計算

readOnly:是否只讀

(1)readOnly 預設值 false,表示可以查詢,可以新增修改刪除操作

(2)設定 readOnly 值是 true,設定成 true 之後,只能查詢

rollbackFor:回滾

設定出現哪些異常進行事務回滾

noRollbackFor:不回滾

設定出現哪些異常不進行事務回滾

XML 宣告式事務管理

使用 xml 檔案的方式就不用加上註解了

在 spring 配置檔案中進行配置

(1)配置事務管理器

<!--建立事務管理器-->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入資料來源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

(2)配置通知

<!-- 配置通知-->
<tx:advice id="txAdvice">
    <!--配置事務引數-->
    <tx:attributes>
        <!--指定哪種規則的方法上面新增事務-->
        <tx:method name="accountMoney" propagation="REQUIRED"/>
        <!--<tx:method name="account*"/>-->
    </tx:attributes>
</tx:advice>

(3)配置切入點和切面

<!-- 配置切入點和切面-->
<aop:config>
    <!--配置切入點-->
    <aop:pointcut id="pt" expression="execution(* com.zjh.spring.service.UserService.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>

完全註解宣告式事務管理

建立配置類,使用配置類替代 xml 配置檔案

/**
 * @author zjh
 * 
 * EnableTransactionManagement:開啟事務
 */
@Configuration
@ComponentScan(basePackages = "com.zjh.tx")
@EnableTransactionManagement
public class TxConfig {

    /**
     * 建立資料庫連線池
     * @return DruidDataSource
     */
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    /**
     * 建立 JdbcTemplate 物件
     * @param dataSource 資料來源
     * @return JdbcTemplate
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        //到 ioc 容器中根據型別找到 dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /**
     * 建立事務管理器
     * @param dataSource 資料來源
     * @return DataSourceTransactionManager
     */
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new
                DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}