(六)Spring-事务式声明

(六)Spring-事务式声明

一、回顾事务

1.1 事务特性:

  • ACID
  • 把一组业务当成一个业务来做,要么都成功,要么都失败!
  • 事务在项目开发中,十分的重要,涉及到数据的一致性问题。不能马虎!
  • 确保完整性和一致性!

1.2 事务ACID原则:

  • 原子性 A
  • 一致性 C
  • 隔离性 I
    • 多个业务可能操作同一个资源,如一条记录,防止数据损坏即确保完整性和一致性。
  • 持久性 D

二、spring中的事务管理

2.1 声明式事务:AOP

1 编写service层业务逻辑,包含事务

  • service层实现,通过容器注入属性mapper

  • 在业务逻辑中使用mapper即dao层

  • 编写事务,doTx()方法先添加后修改user,中间制造错误,如下:

package com.happy.service.user.impl;

import com.happy.mapper.user.UserMapper;
import com.happy.pojo.user.User;
import com.happy.service.user.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("userMapper")
    UserMapper userMapper;

    @Override
    public List<User> getUserList() {
        List<User> userList = userMapper.getUserList();
        return userList;


    }

    @Override
    public User getUserById(int id) {
        User user = userMapper.getUserById(id);
        return user;
    }

    @Override
    public int addUser(User user) {
        int result = userMapper.addUser(user);
        return result;
    }

    @Override
    public int deleteUser(User user) {
        int result = userMapper.deleteUser(user);
        return result;
    }

    @Override
    public int updateUser(User user) {
        int result = userMapper.updateUser(user);
        return result;
    }

    @Override
    public void doTx(){
        List<User> userList = userMapper.getUserList();
        System.out.println(userList);

        System.out.println("==========执行一组业务,先增后删==============");
        User user = new User(521, "gao518", "1987518");
        /*1 增加*/
        System.out.println("1.增加用户");
        userMapper.addUser(user);
        /*制造报错*/
        int error=1/0;
        /*2 删除*/
        User user2 = new User(520, "gao518518", null);
        System.out.println("2.删除用户");
        userMapper.updateUser(user2);
        userList = userMapper.getUserList();
        System.out.println(userList);
    }
}

2 编写mapper接口和mapper.xml文件

package com.happy.mapper.user;

import com.happy.pojo.user.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {

    @Select("select * from user")
    List<User> getUserList();

    User getUserById(@Param("id") int id);

    /*增*/
    int addUser(User user);

    /*删*/
    int deleteUser(User user);

    /*改*/
    int updateUser(User user);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.happy.mapper.user.UserMapper">
    <!--    <select id="getUserList" resultType="user">-->
    <!--        select * from user;-->
    <!--    </select>-->


    <select id="getUserById" resultType="user">
        select *
        from user
        where id = #{id};
    </select>

    <insert id="addUser" parameterType="user">
        insert into user(`id`, `name`, `pwd`)
        values (#{id}, #{name}, #{pwd})
    </insert>

    <delete id="deleteUser" parameterType="user">
        <if test="id!=null">
            delete from user where id=#{id};
        </if>
    </delete>

    <update id="updateUser" parameterType="user">
        <if test="id!=null">
            update user
            <set>
                <if test="name!=null">
                    name=#{name},
                </if>
                <if test="pwd!=null">
                    pwd=#{pwd},
                </if>
            </set>
            where id=#{id};
        </if>
    </update>
</mapper>

3 开启Spring的事务处理功能

在相关的spring xml中如spring-mybatis.xml,要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>

4 配置切面(通知)和切点

  • 配置切面

    • 结合aop实现事务的织入
    • 配置事务的类即:定义事务切面和通知即事务增强方法
      • 经过实测,方法名必须和严格一致或使用通配符,如method name="do*" propagation="REQUIRED"
    • 配置事务的传播特性
  • 配置切点

    • 即配置通知的作用点
    • 作用点一般定义在service层,请注意
    • service层,作用在接口和实现类均可。即可以是com.happy.service.user.UserService.,也可以是com.happy.service.user.impl.UserServiceImpl.
<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:property-placeholder location="db.properties"/>

    <!--注册bean-->

    <!--1.注册一个datasource,使用spring的datasource替换mybatis的配置,c3p0,dbcp,druid-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>


    <!--2.注册一个sqlSessionFactory 用于生成sqlSession-->
    <!-- String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <!--<property name="mapperLocations" value="classpath:com/happy/mapper/user/UserMapper.xml"></property>-->
    </bean>

    <!--3.注册一个MapperFactoryBean 用于获取mapper-->
    <!--需要关联UserMapper 相当于原来的sqlSession.getMapper(UserMapper.class)-->
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.happy.mapper.user.UserMapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>


    <!--spring开启事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
      <!--  <constructor-arg name="dataSource" ref="dataSource" />-->
    </bean>

    <!--结合aop实现事务的织入-->
    <!--1.配置事务的类即:定义事务切面和通知即事务增强方法-->
    <!--2.配置事务的传播特性-->
    <tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="addUser" propagation="REQUIRED"/>
            <tx:method name="deleteUser" propagation="REQUIRED"/>
            <tx:method name="updateUser" propagation="REQUIRED"/>
            <tx:method name="do*" propagation="REQUIRED"/>
            <!--get方法即查询不需要事务-->
            <tx:method name="getUserList" read-only="true"/>
            <tx:method name="getUserById" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--定义aop事务管理的切点-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.happy.service.*.*.*(..))"/>
        <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="txPointCut"></aop:advisor>
    </aop:config>

    <!--<tx:jta-transaction-manager />-->
</beans>

5 模拟controller调用service层方法,测试事务

  • 注意:必须从容器获取service层实例context.getBean("userService", UserService.class);,不能自己用构造器new
package com.happy.service.user;

import com.happy.pojo.user.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class UserServiceTest {


    @Test
    public void testGetUserList() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        List<User> userList = userService.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
    }

    @Test
    public void testGetUserById() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        User user = userService.getUserById(3);
        System.out.println(user);
    }

    @Test
    public void testTransaction() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
        UserService userService = context.getBean("userService", UserService.class);

        //注意:必须是被容器管理的对象,执行相关方法,才能被AOP拦截。
        userService.doTx();
    }

}

2.2 编程式事务:需要在代码中,进行事务的管理。

public class UserService {
  private final PlatformTransactionManager transactionManager;
  public UserService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  public void createUser() {
    TransactionStatus txStatus =
        transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
      userMapper.insertUser(user);
    } catch (Exception e) {
      transactionManager.rollback(txStatus);
      throw e;
    }
    transactionManager.commit(txStatus);
  }
}
  • 需要在原有业务逻辑上,通过手动代码实现事务,即try-catch+rollback

  • 要改变代码,现在一般不用了

2.3 小结

思考:

为什么需要事务

  • 如果不配置事务,可能存在数据提交不一致的情况。如银行转账绝对不允许。
  • 如果我们不在spring中去配置声明式事务,我们就需要在代码中手动配置事务
  • 事务在项目的开发中是否重要,涉及到数据的一致性和完整性问题,不容马虎。
  • 工作的时候,都建议配置上声明式事务,都是死的。
posted @ 2022-05-10 18:31  高兴518  阅读(62)  评论(0编辑  收藏  举报