Spring事务的学习(三)

        上一篇写了spring框架中自定义事务管理器的xml配置和注解配置的两种方式,这篇主要说一下spring框架中自己封装的事务管理器的使用以及xml和注解两种配置方式。下面所涉及的理解均是通过b站上的学习视频所获得,链接先附上:https://www.bilibili.com/video/BV1mE411X7yp。
        接下来,正式开始:
首先准备一下pom文件:



    springAnnoTx_withoutXML_demo1

    com.myself
    1.0-SNAPSHOT
    4.0.0
    jar

    
        
            org.springframework
            spring-context
            5.0.2.RELEASE
        
        
            org.springframework
            spring-tx
            5.0.2.RELEASE
        
        
            org.springframework
            spring-jdbc
            5.0.2.RELEASE
        
        
            org.springframework
            spring-test
            5.0.2.RELEASE
        
        
            mysql
            mysql-connector-java
            5.1.6
        
        
            junit
            junit
            4.12
        
        
            org.aspectj
            aspectjweaver
            1.8.7
        
    


在这里说明,前面也一直忘了说这件事,为了方便测试类方便测试,这里讲spring整合junit的jar包也导入了进来,在没有整合之前,junit中的main方法每次执行的时候,如果没有加载spring的bean,xml文件,是不会创建容器的,整合完之后,junit的main方法执行时会先去加载spring容器。
        话不多说,其他的环境也先附上:
实体类:

package com.myself.domain;

import java.io.Serializable;

/**
 * 账户实体类
 */
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

dao层:

package com.myself.dao;

import com.myself.domain.Account;

/**
 * 持久层接口
 */
public interface IAccountDao {
    Account findByName(String name);
    void update(Account account);
}

package com.myself.dao.impl;

import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class AccountDaoImpl implements IAccountDao {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Account findByName(String name) {
        List accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper(Account.class), name);
        if (accounts == null){
            return null;
        }
        if (accounts.size() > 1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

    @Override
    public void update(Account account) {
        jdbcTemplate.update("update account set name = ? ,money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
    }
}

service层:

package com.myself.service;

/**
 * 业务层接口
 */
public interface IAccountService {
    void transfer(String sourceName,String targetName,Float money);
}

package com.myself.service.impl;

import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import com.myself.service.IAccountService;

/**
 * 业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        accountDao.update(source);
//        int i=1/0;
        accountDao.update(target);
    }
}

        一切准备完毕,就到了最关键的bean.xml的配置:




    
        
    

    
        
    

    
        
        
        
        
    

    
        
    

    
    
        
    

    
    
        
            
            
        
    

    
    
        
        
    

-         接下来,重点说一下bean.xml文件中事务的配置方式。

1、首先,需要配置spring自己的事务管理器:DataSourceTransactionManager,并且注入数据源
2、然后配置事务的通知类型,使用 tx:advice标签已经将提交和回滚事务的通知类型给封装好了,不用再繁琐的配置前置、后置、异常、最终通知了。在tx:advice标签中还有一个子标签tx:attributes,该标签下可以配置多个tx:method标签,这里配置的是事务的传播行为。tx:method标签的name属性指明业务层中被增强方法的方法名,可以用*代表所有方法(一般不这么写)。接下来还有事务的几个重要属性:

isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认级别
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚,没有默认值,表示任何异常都回滚
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚,没有默认值,表示任何异常都回滚
propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS
read-only:用于指定事务是否只读只有查询方法才能设置为true,默认值是false,表示读写
timeout:用于指定事务的超时时间,默认值是-1.表示永不超时,以秒为单位

这里常用的两个属性就是read-only和propagation,用于区分是只读操作还是读写操作。
3、事务管理器有了,事务的通知类型有了,还需要切入点与事务的对应关系就可以啦。在aop:config标签下,首先配置切入点表达式: aop:pointcut;完事之后再讲切入点与事务关联上就可以了:aop:advisor标签中advice-ref属性指明用哪个通知类型的事务,pointcut-ref指明该事务与哪个切入点关联。
        到此,spring自己封装的事务管理器的xml配置方式就完事了,接下来使用测试类测试:

package com.itheima.test;

import com.itheima.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;

    @Test
    public void transfer(){
        accountService.transfer("aaa","bbb",100f);
    }
}

        以上就是spring种事务管理器xml的配置方式。

  • 接下来,说一下注解的方式:

dao层的依赖注入,这里就不再阐述了,上一篇也提到过。主要说一下service层实现类怎么使用注解的方式实现事务的控制。
service层实现类需要添加如下注解:

package com.myself.service.impl;

import com.myself.dao.IAccountDao;
import com.myself.domain.Account;
import com.myself.service.IAccountServiceDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读
public class AccountServiceImpl implements IAccountServiceDao {
    @Autowired
    private IAccountDao accountDao;
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)//读写
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        accountDao.update(source);
//        int i= 1/0;
        accountDao.update(target);
    }
}

首先,在类的上方加一个@Transactional(propagation注解,里面依然会有事务传播行为的几个属性,我这里配置了只读的几个属性
接下来,如果碰到有读写操作的方法,可以在方法上再加@Transactional(propagation注解,修改属性值。
这么配置完就剩最后一步了,在bean.xml文件中开启注解对事务的支持。,指定事务管理器。




    
    
    
    

    
        
    

    
    
        
        
        
        
    

    
    
        
    

    
    

这里我有两个疑问,1、开启注解对事务的支持后,就不用开启注解了:aop:aspectj-autoproxy/是不是tx:annotation-driven标签中已经开启了注解开关;2、使用注解的方式就不用配置切入点表达式了,我猜想是因为@Transactional(propagation注解加在了类上,是不是就代表将该类中的方法作为切入点了。
        到此,注解的方式就配置完了,总体一看注解的方式会简单很多,但是也存在一个问题,比如@Transactional(propagation注解的类中的方法只读操作和读写操作各占一半,这种情况就需要将一半的方法上重新加上@Transactional(propagation注解,并修改其属性,从这一点考虑,没有xml一劳永逸。

  •         最后,说一下纯注解的方式(没有bean.xml)
    首先,需要准备出一个配置类:
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * spring的配置类
 */
@Configuration
@ComponentScan("com.myself")//开启扫描
@Import({JdbcConfig.class,TransactionManager.class})//导入其他配置类
@PropertySource("jdbcConfig.properites")//加载资源文件
@EnableTransactionManagement//开启注解对事务的支持
public class SpringConfguration {
}

说明一下几个注解:
1、@Configuration:表明该类是一个配置类;
2、@ComponentScan(“com.myself”):开启扫描,扫描com.myself包下的所有注解;
3、@Import({JdbcConfig.class,TransactionManager.class}):@Import这个注解,说明该类是一个主配置类,里面可以有可变的其他配置类。比如:JdbcConfig、TransactionManager;
4、@PropertySource(“jdbcConfig.properites”)加载类路径下的配置文件,这里的配置文件写的是连接数据库的相关信息;
5、@EnableTransactionManagement:开启注解对事务的支持
        说完主配置类,重点关注一下JdbcConfig、TransactionManager以及jdbcConfig.properites资源文件
jdbcConfig.properites:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mystudy
jdbc.username=root
jdbc.password=root

JdbcConfig:

package config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**
 * 数据库配置相关
 */
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对象
     * @param dataSource
     * @return
     */
    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("dataSource")
    public DataSource createDriverManagerDataSource(){
        DriverManagerDataSource dms = new DriverManagerDataSource();
        dms.setDriverClassName(driver);
        dms.setUrl(url);
        dms.setUsername(username);
        dms.setPassword(password);
        return dms;
    }
}

这个配置类中,首先是创建jdbcTemplate对象,并用@Bean注解加入到spring容器中。创建数据源对象,并用@Bean注解加入到spring容器中,涉及到的四个变量通过 @Value注解从资源文件中获取。
TransactionManager:
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**

  • 配置事务相关类
    */
    public class TransactionManager {

    /**

    • 创建事务管理器对象
    • @param dataSource
    • @return
      */
      @Bean(“transactionManager”)
      public PlatformTransactionManager createTransactionManager(DataSource dataSource){
      return new DataSourceTransactionManager(dataSource);
      }
      }
      这个配置类中穿件事务管理器对象并用@Bean注解加入到spring容器中。
              至此,使用纯注解的方式也就完事啦,bean.xml也可以删除了。我的spring事务的学习也暂时告一段落了~
              再次说明,资源来自b站的学习视频,已在文章开头标明出处,有兴趣的同学可以去看看,如有理解有误的地方,望指出。

你可能感兴趣的:(Spring学习)