sping-tx

以xml配置形式

  • 導包,spring jar包下載地址

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
        </dependencies>
    
  • 目錄結構
    在這裏插入圖片描述

  • 業務邏輯代碼(以 xml 配置形式注入)
    service 調用 DAO層代碼,dao 操作數據庫。。。此處僅展示 dao 實現層代碼

    public class AccountDaoImpl implements AccountDao {
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
        /**
         * 要麼沒有,要麼只有唯一,根據Id查詢
         **/
        @Override
        public Account findAccountById(int id) {
            List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id);
            return accountList.isEmpty()?null:accountList.get(0);
        }
        @Override
        public List<Account> findAllCount() {
            List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
            return list.isEmpty()?null:list;
        }
        @Override
        public Account findByName(Account account) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), account.getName());
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 0)
                throw new RuntimeException("結果集不唯一...");
            return accounts.get(0);
        }
    }
    
  • spring 配置文件

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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">
    
        <bean id="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate" />
        </bean>
        <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDaoImpl"/>
        </bean>
        <!--配置數據源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    
  • 測試代碼

    public class Test {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            List<Account> allCount = accountService.findAllCount();
            allCount.forEach(account -> System.out.println(account));
        }
    }
    

以純註解形式

  • pom.xml ,新增測試 jar 包

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    
  • 業務層數據訪問層全部使用註解形式,並去掉 set 方法,其它代碼不變
    在這裏插入圖片描述

  • 新建一個叫 config 的包,此包下新建 SpringConfig和JdbcConfig文件,分別用來代替 applicationContext.xml 和 JdbcTemplate 配置數據源部分

    /**
     * spring的配置類,相當於applicationContext.xml
     */
    @Configuration
    @ComponentScan("com.lhg.spring.*") //掃描包
    @Import(JdbcConfig.class)//引入其它的xml配置
    @PropertySource("db.properties")//讀取數據庫配置 properties文件註解
    public class SpringConfig {
    }
    
    
    /**
     * 和連接數據庫相關的配置類
     */
    public class JdbcConfig {
        @Value("${jdbc.driver}")
        private String driver;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        /**
         * 創建jdbctemplate對象,但此時沒有進容器,要想進容器,需要一個bean註解
         */
        @Bean(name="jdbcTemplate")//是name不是value
        public JdbcTemplate createJdbcTemplate(DataSource dataSource){
            return new JdbcTemplate(dataSource);
        }
        @Bean(name="dataSource")
        public DataSource createDataSource(){
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    }
    
    
  • test 註解測試

    /**
     * 使用 Junit 單元測試,測試我們的配置
     * Srping 整合 junit 的配置
     * 1、導入 jar 包
     * 2、使用 Junit 提供的註解把 main 方法替換了,替換成 spring 提供的 @RunWith
     * 3、告知 spring 運行器,spring 和 IOC 創建時基於 xml 還是 註解,並且說明位置
     *  @ContextConfiguration
     *   locations:指定 xml 文件的位置,加上 classpath關鍵字表示在類路徑下
     *   classes:指定註解類所在位置
     */
    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java擁有IOC環境
    @ContextConfiguration(classes = SpringConfig.class)
    public class Test {
        @Autowired
        private AccountService accountService;
        @org.junit.Test
        public void test(){
            List<Account> allCount = accountService.findAllCount();
            allCount.forEach(account -> System.out.println("純註解:"+account));
        }
    }
    

spring AOP(基於 xml 配置)

什麼是 AOP:在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
說人話:簡單的說就是把程序重複的代碼抽取出來,在需要執行的時候,使用動態代理技術,在不修改源碼的基礎上對原有方法進行增強
優勢:減少重複代碼、提高開發效率、維護方便

  • AOP 實現步驟
    在這裏插入圖片描述

  • 導入 jar 包

    <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.13</version>
            </dependency>
        </dependencies>
    
  • 工程結構
    通用的 MVC 三層架構工程,爲了簡單,此工程併爲用到 DAO 層代碼,即沒有查數據庫

  • 需要增強的業務方法

    public class AccountServiceImpl implements AccountService {
        //對accountSave方法aop
        @Override
        public void accountSave() {
            System.out.println("accoutSave...");
            int i = 1 / 0;//有異常
        }
    }
    
  • 利用 LoggerUtil對原有方法增強

    public class LoggerUtil {
        public void beforeAdvice(){
            System.out.println("前置通知。。。開始打印日誌....");
        }
        public void afterAdvice(){
            System.out.println("後置通知。。。打印完了日誌....");
        }
        public void throwingrintAdvice(){
            System.out.println("異常通知。。打印了異常通知....");
        }
        public void finalAdvice(){
            System.out.println("最終通知打印了異常通知....");
        }
    }
    
  • applicationContext.xml (配置前置、後置、異常、最終四個通知)

    <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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="accountService" class="com.lhg.spring.service.impl.AccountServiceImpl" />
        <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" />
        <aop:config>
            <!--配置切入點表達式,簡化通知配置
            如果不寫aop:pointcut 切入點表達式標籤,在每個通知標籤都要寫 pointcut 屬性,繁瑣
            Id屬性用於指定表達式的唯一標識,expression用於指定表達式內容
            此標籤寫在aop:aspect>標籤內部只能當前切面使用
            還可以寫在aop:aspect>外面,此時就變成所有切面可用
            -->
            <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/>
            <aop:aspect id="logAdvice" ref="logger">
                <aop:before method="beforeAdvice" pointcut-ref="mypoint"/>
                <!--後置通知-->
                <aop:after-returning method="afterAdvice" pointcut-ref="mypoint"/>
                <!--異常通知-->
                <aop:after-throwing method="throwingrintAdvice" pointcut-ref="mypoint" />
                <!--最終通知-->
                <aop:after method="finalAdvice" pointcut-ref="mypoint"/>
    	        </aop:aspect>
        </aop:config>
    </beans>
    
  • test 進行測試

    public class AOPTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            accountService.accountSave();
        }
    }
    

    效果如下
    在這裏插入圖片描述

  • 測試 spring 環繞通知

    • 在 LoggerUtil 新增環繞通知方法

      /**
           * spring 框架爲我們提供了一個接口:ProceedingJoinPoint,該接口有一個方法proceed(),
           * 此方法就相當於明確調用切入點方法,該接口可以作爲環繞通知的方法參數,在程序執行時,
           * spring 框架會提供該接口的實現類供我們使用
           **/
          public Object aroundPringLog(ProceedingJoinPoint pjp){
              try {
                  Object res ;
                  Object[] args = pjp.getArgs();//這個參數就是實際方法執行所需的參數
                  System.out.println("寫在proceed之前是前置通知--->環繞..前置");
                  res = pjp.proceed(args);//明確調用業務層方法(切入點方法)
                  System.out.println("寫在proceed之後是後置通知--->環繞..後置");
                  return res;
              }catch (Throwable t){//這個異常必須寫Throwable,Exception攔不住的
                  System.out.println("寫在proceed之後catch裏面--->環繞..異常");
                  throw new RuntimeException(t);
              } finally {
                  System.out.println("寫在proceed之後finally裏面--->環繞..最終");
              }
          }
      
    • applicationContext.xml 新增環繞通知 AOP 配置

      <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"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
          <bean id="accountService" class="com.lhg.spring.service.impl.AccountServiceImpl" />
          <bean id="logger" class="com.lhg.spring.utils.LoggerUtil" />
          <aop:config>
              <!--配置切入點表達式,簡化通知配置
              Id屬性用於指定表達式的唯一標識,expression用於指定表達式內容
              此標籤寫在aop:aspect>標籤內部只能當前切面使用
              還可以寫在aop:aspect>外面,此時就變成所有切面可用
              -->
              <aop:pointcut id="mypoint" expression="execution(* com.lhg.spring.service.impl.*.*())"/>
              <aop:aspect id="logAdvice" ref="logger">
                  <!--配置環繞通知-->
                  <aop:around method="aroundPringLog" pointcut-ref="mypoint" />
              </aop:aspect>
          </aop:config>
      </beans>
      
    • 測試效果
      在這裏插入圖片描述

spring AOP(基於 註解配置)

  • service 層改動

    @Service
    public class AccountServiceImpl implements AccountService {
        //對accountSave方法aop
        @Override
        public void accountSave() {
            System.out.println("accoutSave...");
            int i = 1 / 0;
        }
    }
    
  • LogggerUtils 改動(注意通知括號內必須帶上"pointCut()")

    @Component
    @Aspect//表示當前類是一個切面
    public class LoggerUtil {
        //指定切入點表達式
        @Pointcut("execution(* com.lhg.spring.service.impl.*.*())")
        public void pointCut(){}
        @Before("pointCut()")
        public void beforeAdvice(){
            System.out.println("前置通知。。。開始打印日誌....");
        }
        @AfterReturning("pointCut()")
        public void afterAdvice(){
            System.out.println("後置通知。。。打印完了日誌....");
        }
        @AfterThrowing("pointCut()")
        public void throwingrintAdvice(){
            System.out.println("異常通知。。打印了異常通知....");
        }
        @After("pointCut()")
        public void finalAdvice(){
            System.out.println("最終通知打印了異常通知....");
        }
     }
    
  • applicationContext.xml 改動(注意新增context約束)

    <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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        <!--配置 spring 創建容器要掃描的包-->
        <context:component-scan base-package="com.lhg.spring" />
        <!--配置 spring 開啓AOP  註解支持-->
        <aop:aspectj-autoproxy />
    </beans>
    
  • 測試代碼及效果

    public class AOPTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
            accountService.accountSave();
        }
    }
    

    在這裏插入圖片描述

    可以看到異常通知和最終通知輸出結果並沒有按預期那樣的順序,這個。。。是改變不了的。spring基於註解的 AOP 這四種通知確實有這種順序的問題,所以在實際開發中要有個慎重的考慮,但是如果是基於註解的環繞通知是不會有這種順序問題的,如下:

    @Component
    @Aspect//表示當前類是一個切面
    public class LoggerUtil {
        //指定切入點表達式
        @Pointcut("execution(* com.lhg.spring.service.impl.*.*())")
        public void pointCut(){}
        /**
         * spring 框架爲我們提供了一個接口:ProceedingJoinPoint,該接口有一個方法proceed(),
         * 此方法就相當於明確調用切入點方法,該接口可以作爲環繞通知的方法參數,在程序執行時,
         * spring 框架會提供該接口的實現類供我們使用
         **/
        @Around("pointCut()")
        public Object aroundPringLog(ProceedingJoinPoint pjp){
            try {
                Object res ;
                Object[] args = pjp.getArgs();//這個參數就是實際方法執行所需的參數
                System.out.println("寫在proceed之前是前置通知--->環繞..前置");
                res = pjp.proceed(args);//明確調用業務層方法(切入點方法)
                System.out.println("寫在proceed之後是後置通知--->環繞..後置");
                return res;
            }catch (Throwable t){//這個異常必須寫Throwable,Exception攔不住的
                System.out.println("寫在proceed之後catch裏面--->環繞..異常");
                throw new RuntimeException(t);
            } finally {
                System.out.println("寫在proceed之後finally裏面--->環繞..最終");
            }
        }
    }
    

    測試效果如下
    在這裏插入圖片描述
    一個更加好理解的圖示
    在這裏插入圖片描述

實戰–基於 xml 的AOP 實現事務控制(spring內置聲明式事務)

轉賬問題:如下,每次操作數據庫時都獲取一個連接,當出現異常時,前面正確執行的成功提交了,而異常後面的語句則沒有提交,導致一方賬戶錢減少而另一方賬戶錢並沒有增加。
解決辦法:應該共用一個連接,用 ThreadLocal 把 Connection 和當前線程綁定,從而使一個線程中只有一個能控制事務的對象,事務控制應該都是放在業務層
在這裏插入圖片描述
在這裏插入圖片描述

  • 依賴 jar 包

    <?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">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.lhg</groupId>
        <artifactId>spring_day03</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.1.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.13</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    </project>
    
  • AccountDaoImpl 代碼

    public class AccountDaoImpl  implements AccountDao {
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        /**
         * 要麼沒有,要麼只有唯一,根據Id查詢
         **/
        @Override
        public Account findAccountById(int id) {
            List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<>(Account.class), id);
            return accountList.isEmpty()?null:accountList.get(0);
        }
        @Override
        public List<Account> findAllCount() {
            List<Account> list = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
            return list.isEmpty()?null:list;
        }
    
        @Override
        public Account findByName(String name) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name);
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 1)
                throw new RuntimeException("結果集不唯一...");
            return accounts.get(0);
        }
        @Override
        public int updateAccount(Account account) {
            return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId());
        }
    }
    
  • AccountServiceImpl 代碼

    public class AccountServiceImpl implements AccountService {
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public Account findAccountById(int id) {
            return accountDao.findAccountById(id);
        }
    
        @Override
        public List<Account> findAllCount() {
            return accountDao.findAllCount();
        }
    
        @Override
        public int updateAccount(Account account) {
            return 0;
        }
    
        @Override
        public void transfer(String sourceName, String targetName, int money) {
            //根據名稱查詢轉入轉出賬戶
            Account source = accountDao.findByName(sourceName);
            Account target = accountDao.findByName(targetName);
            //轉出賬戶減錢 轉入賬戶加錢
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            //更新轉入轉出賬戶
            accountDao.updateAccount(source);
            int i = 1/0;
            accountDao.updateAccount(target);
        }
    }
    
  • applicationContext.xml 配置

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean id="accountDaoImpl" class="com.lhg.spring.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate" />
        </bean>
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
            <property name="dataSource" ref="dataSource" />
        </bean>
        <bean id="accountServiceImpl" class="com.lhg.spring.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDaoImpl"/>
        </bean>
        <!--配置數據源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <!--配置事務管理器-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--配置事務通知-->
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <!--配置事務的屬性:
                isolation:用於指定事務的隔離級別,默認值是DEFAULT,表示使用數據庫的默認隔離級別
                propagation:用於指定事務的傳播行爲,默認值是REQUERD,表示一定會有事務,增刪改的選擇,查詢方法可以使用SUPPORTS
                read-only:用於指定事務是否只讀,只有查詢方法才能設置爲true,默認值是false,表示讀寫
                rollback-for:用於指定一個異常,當該異常產生時,事務回滾,產生其它異常時事務不回滾,沒有默認值,表示任何異常都回滾
                no-rollback-for:用於指定一個異常,當該異常產生時事務不回滾,產生其它異常時事務回滾,沒有默認值,表示任何異常都回滾
                -->
                <tx:method name="*"  propagation="REQUIRED" read-only="false"/><!--通用匹配-->
                <tx:method name="find*" propagation="SUPPORTS" read-only="true" /><!--匹配以find開頭的方法,優先級更高-->
            </tx:attributes>
        </tx:advice>
       <aop:config>
           <!--配置切入點表達式-->
          <aop:pointcut id="pt" expression="execution(* com.lhg.spring.service.impl.*.*(..))"/>
           <!--建立切入點表達式與事務通知的對應關係-->
           <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
       </aop:config>
    </beans>
    
  • 測試

    public class XMLTest {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountServiceImpl = (AccountService) ac.getBean("accountServiceImpl");
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
  • 效果
    在這裏插入圖片描述

實戰–基於註解的聲明式事務控制

  • 導入相關依賴,測試 jar 包…略

  • service 層

    @Service
    @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只讀型事務的配置,而我們的轉賬需要的是讀寫型事務的配置
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountDao accountDao;
    
        @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
        @Override
        public void transfer(String sourceName, String targetName, int money) {
            System.out.println("start transfer...");
            //根據名稱查詢轉入轉出賬戶
            Account source = accountDao.findByName(sourceName);
            Account target = accountDao.findByName(targetName);
            //轉出賬戶減錢 轉入賬戶加錢
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            //更新轉入轉出賬戶
            accountDao.updateAccount(source);
            int i = 1/0;
            accountDao.updateAccount(target);
        }
    }
    
  • dao 層

    @Repository
    public class AccountDaoImpl  implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        @Override
        public Account findByName(String name) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<>(Account.class), name);
            if (accounts.isEmpty())
                return null;
            if (accounts.size() > 1)
                throw new RuntimeException("結果集不唯一...");
            return accounts.get(0);
        }
    
        @Override
        public int updateAccount(Account account) {
            return jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId());
        }
    
    }
    
  • applicationContext.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <context:component-scan base-package="com.lhg.spring" />
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--配置數據源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://192.168.25.132:3306/ssm" />
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>
        <!--配置事務管理器-->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        <!--開啓spring對註解事務的支持-->
       <tx:annotation-driven transaction-manager="txManager" />
    </beans>
    
  • 單元測試

    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java擁有IOC環境
    //@ContextConfiguration(classes = SpringConfig.class)//純註解
    @ContextConfiguration(locations = "classpath:applicationContext.xml")//注意寫法
    public class Test {
        @Autowired
        private AccountService accountServiceImpl;
        @org.junit.Test
        public void test(){
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
  • 效果:轉賬出現異常時,事務成功回滾

實戰–基於純註解的聲明式事務控制

  • service 層和 dao 層 不變,使用註解注入

  • 新建一個 config 的包,在裏面分別建三個 java 文件

    • SpringConfig.java

      /**
       * spring的配置類,相當於applicationContext.xml
       */
      @Configuration
      @ComponentScan("com.lhg.spring.*")
      @Import({JdbcConfig.class, TransactionConfig.class})//引入其它的配置
      @PropertySource("db.properties")//讀取數據庫配置文件註解
      @EnableTransactionManagement//開啓對註解的支持,替換xml中<tx:annotation-driven transaction-manager="txManager" />
      public class SpringConfig {
      }
      
    • JdbcConfig.java

      /**
       * 和連接數據庫相關的配置類
       */
      public class JdbcConfig {
          @Value("${jdbc.driver}")
          private String driver;
          @Value("${jdbc.url}")
          private String url;
          @Value("${jdbc.username}")
          private String username;
          @Value("${jdbc.password}")
          private String password;
          /**
           * 創建jdbctemplate對象,但此時沒有進容器,要想進容器,需要一個bean註解
           */
          @Bean(name="jdbcTemplate")//是name不是value
          public JdbcTemplate createJdbcTemplate(DataSource dataSource){
              return new JdbcTemplate(dataSource);
          }
          @Bean(name="dataSource")
          public DataSource createDataSource(){
              DriverManagerDataSource dataSource = new DriverManagerDataSource();
              dataSource.setDriverClassName(driver);
              dataSource.setUrl(url);
              dataSource.setUsername(username);
              dataSource.setPassword(password);
              return dataSource;
          }
      }
      
      
    • TransactionConfig.java

      /**
       * 和事務相關的配置類,替換xml中
       *     <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       *         <property name="dataSource" ref="dataSource" />
       *     </bean>
       */
      public class TransactionConfig {
          @Bean(name="transactionManager")
          public PlatformTransactionManager createTransactionManager(DataSource dataSource){
              return new DataSourceTransactionManager(dataSource);
          }
      }
      
  • 測試結果:有異常時事務回滾,沒異常時轉賬成功

    /**
     * 使用 Junit 單元測試,測試我們的配置
     * Srping 整合 junit 的配置
     * 1、導入 jar 包
     * 2、使用 Junit 提供的註解把 main 方法替換了,替換成 spring 提供的 @RunWith
     * 3、告知 spring 運行器,spring 和 IOC 創建時基於 xml 還是 註解,並且說明位置
     *  @ContextConfiguration
     *   locations:指定 xml 文件的位置,加上 classpath關鍵字表示在類路徑下
     *   classes:指定註解類所在位置
     */
    @RunWith(SpringJUnit4ClassRunner.class)//使得Test.java擁有IOC環境
    @ContextConfiguration(classes = SpringConfig.class)//純註解
    //@ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class Test {
        @Autowired
        private AccountService accountServiceImpl;
        @org.junit.Test
        public void test(){
            accountServiceImpl.transfer("a","b",500);
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章