AOP应用——事务管理

相信大家对Spring的事务管理并不陌生,再推荐一篇AOP切入点表达式的文章,我把重点放在AOP实现上。在项目开发过程中通常用Hibernate或MyBatis(iBatis)作为持久层,因此Demo基于Spring+MyBatis实现转账业务。时间很宝贵,先上代码。
spring-mybatis.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="db.mybatis" name-generator="extend.CustomBeanNameGenerator">
        <context:include-filter type="aspectj" expression="db.mybatis.service..*"/>
    context:component-scan>
    <context:property-placeholder location="jdbc.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    bean>

    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:db/mybatis/mappers/**/*.xml"/>
    bean>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"/>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="db.mybatis.dao"/>
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
    bean>
    
    <bean id="dbTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="dataSource"/>
    bean>

    <aop:config proxy-target-class="true">
        <aop:advisor advice-ref="transactionInterceptor" pointcut="execution(* db.mybatis.service..*Impl.*(..))"/>
    aop:config>

    <tx:advice id="transactionInterceptor" transaction-manager="dbTransactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="modify*" propagation="REQUIRED"/>
            <tx:method name="edit*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="remove*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="load*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="search*" read-only="true" propagation="SUPPORTS"/>
            <tx:method name="*" propagation="SUPPORTS"/>
        tx:attributes>
    tx:advice>
beans>

项目目录结构如下
AOP应用——事务管理_第1张图片
这一部分要完成的任务就是上面那些了,我们开始动手吧。
1、开发所需jar包
建议用maven或Gradle(eclipse暂不支持)管理。如果用maven管理,只要添加以下依赖。

<dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.4version>
        dependency>
        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>
                     <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.6version>
        dependency>
                      
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-coreartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-beansartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-expressionartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-txartifactId>
            <version>4.2.0.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-testartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aopartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aspectsartifactId>
            <version>4.2.1.RELEASEversion>
        dependency>
        

        
        <dependency>
            <groupId>c3p0groupId>
            <artifactId>c3p0artifactId>
            <version>0.9.1.2version>
        dependency>

2、配置spring-mybatis.xml
配置完之后和最开始放上去是一样的,这里主要对一些配置做说明:
(1)里的name-generator属性
默认情况下IOC容器是根据类名来命名的,不同模块相似的功能很多类名是一样的,这时候beanid不唯一,项目不能正常启动。
配置的类代码如下:

 public class CustomBeanNameGenerator implements BeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return definition.getBeanClassName().replaceAll("\\.", "").replace("Impl","");
    }
}

(2)
其type类型有五种,建议使用aspejct和regex。这样你不需要在每个类加类似@Service这样的注解;在不同的上下文中扫描目录明确。
(3)
推荐一篇文章,请点击标签。
3、开始开发
dao层的xml和接口



<mapper namespace="db.mybatis.dao.AccountDao">
    <resultMap id="BaseResultMap" type="db.mybatis.model.Account">
        <id column="id" property="id"/>
        <result column="user" property="user"/>
        <result column="money" property="money"/>
    resultMap>
    <select id="getRecords" resultMap="BaseResultMap">
    SELECT id,user,money FROM account
    select>

    <select id="getMoneyByUser" resultType="double">
        SELECT money FROM account where user = #{user}
    select>

    <update id="updateMoneyByUser">
        update account set money=${money}  where user = #{user}
    update>
mapper>
public interface AccountDao {
    List getRecords();
    double getMoneyByUser(String user);
    int updateMoneyByUser(@Param("money")double money,@Param("user")String user);
}

service的接口和实现类

public interface AccountService {
    /**
     * 查询所有的人员记录
     * @return
     */
    List getRecords();
    /**
     * 转账
     * @param user1 转钱的人
     * @param user2 收钱的人
     * @param money 转出去的钱
     * @return
     */
    int transAccount(String user1,String user2,double money);
}
public class AccountServiceImpl implements AccountService {
    @Resource
    private AccountDao accountDao;
    @Override
    public List getRecords() {
        return accountDao.getRecords();
    }
    @Override
    public int transAccount(String user1, String user2, double money) {
        int result = 0;
        double money1 = accountDao.getMoneyByUser(user1);
        if (money1 > money) {
            result += accountDao.updateMoneyByUser(money1 - money, user1);
            double money2 = accountDao.getMoneyByUser(user2);
            result += accountDao.updateMoneyByUser(money2 + money, user2);
        } else {
            System.out.println(user1 + "的余额不足");
        }
        return result;
    }
}

测试类

public class UnitTestBase {
    private ClassPathXmlApplicationContext context;

    private String springXmlPath;

    public UnitTestBase(String springXmlPath) {
        this.springXmlPath = springXmlPath;
    }

    @Before
    public void before() {
        if (StringUtils.isEmpty(springXmlPath)) {
            springXmlPath = "classpath:applicationContext.xml";
        }
        try {
            context = new ClassPathXmlApplicationContext(springXmlPath.split("[,\\s]+]"));
            context.start();
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }

    @After
    public void after() {
        if (context != null)
            context.destroy();
    }

    protected  T getBean(String beanId) {
        try {
            return (T) context.getBean(beanId);
        } catch (BeansException e) {
            e.printStackTrace();
            return null;
        }
    }

    protected  T getBean(Class clazz) {
        try {
            return context.getBean(clazz);
        } catch (BeansException e) {
            e.printStackTrace();
            return null;
        }
    }
}
public class TestDB extends UnitTestBase {
    public TestDB() {
        super("classpath*:spring-mybatis.xml");
    }

    @Test
    public void testDB() throws Exception {
        AccountService accountService = getBean("dbmybatisserviceimplAccountService");
        System.out.println("转账前:");
        List records = accountService.getRecords();
        printCollection(records);
        int transCount = accountService.transAccount("张三", "李四", 200);
        System.out.println("转账后:");
        records = accountService.getRecords();
        printCollection(records);
        System.out.println("更新记录总数:" + transCount);
    }


    private void printCollection(Collection collection) {
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
           System.out.println(iterator.next().toString());
        }
    }
}

第一次执行结果如下:
AOP应用——事务管理_第2张图片
现在将AccountServiceImpl中transAccount方法改为

 @Override
    public int transAccount(String user1, String user2, double money) {
        int result = 0;
            double money1 = accountDao.getMoneyByUser(user1);
            if (money1 > money) {
                result += accountDao.updateMoneyByUser(money1 - money, user1);
                result += 1 / 0;
                double money2 = accountDao.getMoneyByUser(user2);
                result += accountDao.updateMoneyByUser(money2 + money, user2);
            } else {
                System.out.println(user1 + "的余额不足");
            }
        return result;
    }

再把测试方法改为

AccountService accountService = getBean(AccountService.class);
        System.out.println("转账前:");
        List records = accountService.getRecords();
        printCollection(records);
        int transCount = 0;
        try {
            transCount = accountService.transAccount("张三", "李四", 200);
        } catch (Exception e) {
            System.out.println("转账失败:"+e.getClass().getName()+"    "+e.getCause());
        }
        System.out.println("转账后:");
        records = accountService.getRecords();
        printCollection(records);
        System.out.println("更新记录总数:" + transCount);

再测试下我们的程序,结果如下:
AOP应用——事务管理_第3张图片
这时候张三转出去了200,但李四没收到200
我们在spring-mybatis.xml配置文件中加上
再测试下我们的程序,结果如下:
AOP应用——事务管理_第4张图片
AOP通过配置XML在事务管理的应用到此结束。
4、事务管理注解实现
在spring-mybatis.xml文件中添加标签开启注解事务,只需在需要加事务的类或方法上加注解@Transactional即可。
说明:
注解跟AOP配置文件实现可共存,也可只使用一个;
建议只使用一个,而且使用注解,因为在开发过程中aspectj配置很容易无效,而注解的代理模式(默认模式)不会出现这个问题。关于这个问题请阅读,我测试只用一个文件还是遇到了这样的问题。

你可能感兴趣的:(Spring,AOP,事务管理)