Spring5学习笔记(四、持久层整合与事务处理)

Spring与持久层

Spring框架为什么要与持久层技术进行整合?

  1. JavaEE开发需要持久层进行数据库的访问操作。
  2. JDBC、Hibernate、MyBatis进行持久开发过程存在大量的代码冗余。
  3. Spring基于模板设计模式对于上述的持久层技术进行了封装。

Spring可以与哪些持久层技术进行整合?

1. JDBC
	|-	JDBCTemplate

2. Hibernate(JPA)
	|-	HibernateTemplate

3. MyBatis
	|-	SqlSessionFactoryBean	MapperScannerConfigure

因为MyBatis是目前用得最多的持久层框架,所以这里只讲与MyBatis的整合。

Spring与MyBatis整合

MyBatis开发步骤的回顾

-------七步骤--------

  1. 实体
  2. 实体别名
  3. 创建DAO接口
  4. 实现Mapper文件
  5. 注册Mapper文件
  6. MyBatisAPI调用

引入mysql和mybatis的jar包,搭建开发环境

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.3</version>
</dependency>

创建mybatis配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--  设置实体类的别名  -->
    <typeAliases>
        
    </typeAliases>

    <!--  数据库连接  -->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/angenin?useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--  注册mapper文件  -->
    <mappers>
        
    </mappers>
</configuration>

七步骤

  1. 创建User实体类
    	public class User implements Serializable {
        private Integer id;
        private String name;
        private String password;
    
        //为了简洁,这里略写
    
        //有参无参
        //get/set
    }
    
  2. 在配置文件中设置User实体类的别名
    <!--  设置实体类的别名  -->
    <typeAliases>
        <typeAlias alias="user" type="com.angenin1.mybatis.User"/>
    </typeAliases>
    
  3. 创建表
    use angenin;
    
    create table t_users(
    	`id` int primary key auto_increment,
    	`name` varchar(12),
    	`password` varchar(12)
    );
    
    desc t_users;
    
  4. 创建UserDAO接口
    public interface UserDAO {
        void save(User user);
    }
    
  5. 实现UserMapper文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.angenin1.mybatis.UserDAO">
        <insert id="save" parameterType="user">
            insert into t_users(`name`,`password`)values(#{name},#{password});
        </insert>
    </mapper>
    
  6. 在配置文件中注册Mapper文件
    <!--  注册mapper文件  -->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
    
  7. MyBatisAPI调用(测试)
    public class TestMyBatis {
        public static void main(String[] args) throws IOException {
            // 读取主配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            // 获取sqlSession工厂对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            // 获取sqlSession对象
            SqlSession session = sqlSessionFactory.openSession();
            // 获取UserDAO对象
            UserDAO userDAO = session.getMapper(UserDAO.class);
    
            // 创建用户
            User user = new User();
            user.setName("angenin");
            user.setPassword("123456");
            // 保存用户
            userDAO.save(user);
    
            // 提交事务
            session.commit();
        }
    }
    
    运行结果:
    在这里插入图片描述
    在这里插入图片描述

MyBatis在开发过程中存在的问题

  1. 配置繁琐:第2步起别名和第6步注册mapper文件。(其实MyBatis也可以通过指定包来解决这个问题)
  2. 代码冗余:第7步API的调用。

Spring与MyBatis整合思路分析

在这里插入图片描述
在这里插入图片描述

Spring与MyBatis整合的开发步骤

搭建环境

<!--     mybatis     -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.3</version>
</dependency>
<!--除了上面导入的mybatis和mysql的jar包外,还需要导入三个jar包-->
<!--  spring整合mybatis   -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.18</version>
</dependency>
  • Spring配置文件的配置

    <!-- druid 连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/angenin?useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <!-- 通过 SqlSessionFactoryBean 创建SqlSessionFactory对象 -->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--设置连接池对象-->
        <property name="dataSource" ref="dataSource"/>
        <!--指定实体类所在的包-->
        <property name="typeAliasesPackage" value="com.angenin1.entity"/>
        <!--指定mapper文件所在的包-->
        <property name="mapperLocations">
            <list>
                <value>classpath:com.angenin1.mapper/*Mapper.xml</value>
            </list>
        </property>
    </bean>
    
    <!-- 通过 MapperScannerConfigurer 创建DAO对象 -->
    <bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--设置sqlsession工厂对象-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
        <!--指定DAO接口所在的包-->
        <property name="basePackage" value="com.angenin1.dao"/>
    </bean>
    
  • 编码

    -------七步骤--------

    1. 实体
    2. 实体别名
    3. 创建DAO接口
    4. 实现Mapper文件
    5. 注册Mapper文件
    6. MyBatisAPI调用

    七步变四步

    1. 实体
    2. 创建DAO接口
    3. 实现Mapper文件
  1. 创建entity包,在其包下创建User实体类
    public class User implements Serializable {
        private Integer id;
        private String name;
        private String password;
    	//get set
    }
    
  2. 创建t_users表
    # 上面已经创建了,继续用t_users表
    create table t_users(
    	`id` int primary key auto_increment,
    	`name` varchar(12),
    	`password` varchar(12)
    );
    
  3. 创建dao包,在其包下创建UserDAO接口
    public interface UserDAO {
        void save(User user);
    }
    
  4. 在resources目录下创建com.angenin1.mapper目录(注意:resources目录下多级目录用/分隔,这里用.,所以不是多级目录,只是一个目录),用于存放mapper文件,在这个目录下创建UserMapper.xml文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.angenin1.dao.UserDAO">
    
        <insert id="save" parameterType="User">
            insert into t_users(`name`,`password`)values(#{name},#{password});
        </insert>
    
    </mapper>
    
  5. 测试
    public class TestMyBatisSpring {
        @Test
        public void test() {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext5.xml");
            UserDAO userDAO = (UserDAO) ctx.getBean("userDAO");
    
            User user = new User();
            user.setName("angenin");
            user.setPassword("11");
    
            userDAO.save(user);
        }
    }
    
    运行结果:
    在这里插入图片描述
    在这里插入图片描述

Spring与Mybatis整合细节

问题:Spring与Mybatis整合后,为什么DAO不提交事务,但是数据能够插入数据库中?

谁创建的Connection,谁就控制着事务(tx)。

本质上控制连接对象(Connection)--> 连接池(DataSource)

之前由MyBatis提供的连接池对象 --> 创建Connection
	Connection.setAutoCommit(false)	需要我们手动控制事务,操作完成后手工调用commit进行提交。

而现在由Druid(C3P0、DBCP)作为连接池	--> 创建Connection
	Connection.setAutoCommit(true)	默认true,保持着自动控制事务,每条sql提交一次。

答案:因为Spring与MyBatis整合时,引入了外部连接池对象,保存自动的事务提交这个机制(Connection.setAutoCommit(true)),不需要手工进行事务的操作,也能进行事务的提交。

注意:未来实战中,还会手工控制事务(多条sql一起成功,一起失败),后续Spring通过事务控制解决这个问题。

Spring的事务处理

什么是事务?

保证业务操作完整性的一种数据库机制。

事务的4个特点:A C I D
A:原子性
C:一致性
I:隔离性
D:持久性
口诀:原子一致才能隔离持久。(个人口诀,不喜勿喷( ̄▽ ̄)")

如何控制事务?

JDBC:
	Connection.setAutoCommit(false);	//开启事务,关闭自动提交
	Connection.commit();	//提交
	Connection.rollback();	//回滚

MyBatis:
	MyBatis自动开启事务
	sqlSession.commit();	//提交
	sqlSession.rollback();	//回滚
	注意:sqlSession底层封装了Connection,所以还是调用了Connection。

结论:控制事务的底层都是Connection对象完成的

Spring控制事务的开发

Spring是通过AOP的方式进行事务开发的。

1. 原始对象

public class XxxServiceImpl() {
	private XxxDao xxxDao;
	//set get
	
	public 原始方法() {
		核心功能;
	}
}		

要注意的细节:

  1. 原始对象 --> 原始方法 --> 核心功能(业务处理+DAO调用)
  2. 因为Service依赖DAO对象,所以需要把DAO作为Service的成员变量,并提供get/set方法,用依赖注入(set注入)的方式进行赋值。

2. 额外功能

  1. 实现MethodInterceptor接口
    public class MyAround implements MethodInterceptor {
    	public Object invoke(MethodInvocation methodInvocation) {
    		Object ret = null;
    		try {
    			// 开启事务
    			Connection.setAutoCommit(false);
    			// 执行原始方法
    			ret = methodInvocation.proceed();
    			// 提交
    			Connection.commit();
    		} catch(Exception e) {
    			// 事务回滚
    			Connection.rollback();
    		}
    		return ret;
    	}
    }
    
    <!-- 额外功能类bean -->
    <bean id="around" class="com.angenin.dynamic.MyAround"/>
    
    <!--配置aop-->
    <aop:config>
        <!--配置切入点,id可任意起,expression:切入点表达式,这里代表所有的方法-->
        <aop:pointcut id="pc" expression="execution(* *(..)"/>
        <!--组装:把额外功能和切入点进行整合,advice-ref:额外功能,pointcut-ref:切入点-->
        <aop:advisor advice-ref="around" pointcut-ref="pc"/>
    </aop:config>
    
  2. 切面类,使用@Aspect@Around注解
    @Aspect	//切面类
    public class MyAspect {
    	
    	@Around("execution(* *(..))")
    	public Object myAround(ProceedingJoinPoint joinPoint) {
    		Object ret = null;
    		try {
    			// 开启事务
    			Connection.setAutoCommit(false);
    			// 执行原始方法
    			ret = joinPoint.proceed();
    			// 提交
    			Connection.commit();
    		} catch(Exception e) {
    			// 事务回滚
    			Connection.rollback();
    		}
    		return ret;
    	}
    }
    
    <!-- 切面类bean -->
    <bean id="around" class="com.angenin.aspect.MyAspect"/>
    <!-- 告知Spring基于注解进行AOP开发 -->
    <aop:aspectj-autoproxy />
    

而Spring在也帮我们封装了额外功能的这些内容,我们只要调用org.springframework.jdbc.datasource.DataSourceTransactionManager,而由于需要Connection对象,需要等于依赖,依赖就要注入,所以需要注入Connection对象,而又由于Connection对象来着连接池,所以注入连接池即可。

所以以后我们只需要:

  1. 调用org.springframework.jdbc.datasource.DataSourceTransactionManager
  2. 注入连接池
    <!-- druid 连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        ...
    </bean>
    
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

3. 切入点

使用DataSourceTransactionManager后,切入点由@Transactional注解决定。

@Transactional:事务的额外功能加入给哪些业务方法。

  1. 加在类上:这个类中的所有方法都会有额外功能,即加入事务。
  2. 加在方法上:这个方法会有额外功能,即加入事务。

4. 组装切面

切入点 + 额外功能

<!-- transaction-manager:组装额外功能bean -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

组装切入点:会自动扫描@Transactional注解。

Spring控制事务的编码

  • 搭建开发环境

    <!--引入Spring关于事务的jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.1.14.RELEASE</version>
    </dependency>
    
  • 原始对象
    创建service包,然后新建UserService和UserServiceImpl类

    public interface UserService {
        void register(User user);
    }
    
    public class UserServiceImpl implements UserService {
        private UserDAO userDAO;
    
        // 原始方法
        @Override
        public void register(User user) {
            // 核心功能
            userDAO.save(user);
        }
    
        public UserDAO getUserDAO() {
            return userDAO;
        }
    
        public void setUserDAO(UserDAO userDAO) {
            this.userDAO = userDAO;
        }
    }
    
        <bean id="userService" class="com.angenin.service.UserServiceImpl">
        <!-- 这里的userDAO是前面MapperScannerConfigurer创建的,创建的bean的id为首单词首字母小写 -->
        <property name="userDAO" ref="userDAO"/>
    </bean>
    
  • 额外功能

    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 切入点
    在UserServiceImpl类上加上@Transactional注解,把整个类作为切入点。

  • 组装切面
    在这里插入图片描述

    <!-- transaction-manager:组装额外功能bean -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    
  • 测试

    @Test
    public void test02() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext5.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        User user = new User();
        user.setName("angenin11");
        user.setPassword("123456");
        userService.register(user);
    }
    

    在这里插入图片描述

  • 细节

<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>

proxy-target-class属性:进行动态代理底层实现的切换
false:JDK代理(默认)
true:Cglib代理

Spring中的事务属性(Transaction Attribute)

属性:描述物体特征的一系列值。如一个人的属性有身高、体重、性别等。
事务属性:描述事务特征的一系列值,共5个。

  1. 隔离属性
  2. 传播属性
  3. 只读属性
  4. 超时属性
  5. 异常属性

添加事务属性:
@Transactional(isolation = xxx, propagation = xxx, readOnly = xxx, timeout = xxx, rollbackFor = xxx, noRollbackFor = xxx)

事务属性详解

1. 隔离属性(isolation)

隔离属性:描述了事务解决并发问题的特征。

  1. 什么是并发?
    多个事务(用户)在同一时间访问操作了相同的数据。
    同一时间:0.000几秒的差异,有 微小的前 和 微小的后 之分。

  2. 并发会产生哪些问题?

    1. 脏读
    2. 不可重复读
    3. 幻读
  3. 并发问题如何解决?
    通过隔离属性解决,隔离属性中设置不同的值,解决并发处理过程中的问题。

事务并发产生的问题
  1. 脏读
    一个事务,读取了另一个事务中还没提交的数据,会在本事务中产生数据不一致的问题。

    解决方案:@Transactional(isolation = Isolation.READ_COMMITTED)
    Isolation.READ_COMMITTED:只能读取已提交的数据。

  2. 不可重复读
    一个事务,多次读取相同的数据,但读取的结果不一样(可能是别的事务修改了这条数据),会在本事务中产生数据不一致的问题。
    注意:1. 不是脏读(读取的是已提交的数据);2. 一个事务中。

    解决方案:@Transactional(isolation = Isolation.REPEATABLE_READ
    Isolation.REPEATABLE_READ:数据库底层会对操作的数据加上行锁

  3. 幻读
    一个事务,多次整表进行查询统计(不是单条数据,是一个结果集),但结果不一样(可能是别的事务增加或删除了数据),会在本事务中产生数据不一致的问题。

    解决方案:@Transactional(isolation = Isolation.SERIALIZABLE
    Isolation.SERIALIZABLE:数据库底层会对操作的数据加上表锁

总结:

  • 并发安全:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED
  • 运行效率:READ_COMMITTED > REPEATABLE_READ > SERIALIZABLE

Isolation共有五个值:

  • DEFAULT (默认)
  • READ_UNCOMMITTED (读未提交)
  • READ_COMMITTED (读已提交)
  • REPEATABLE_READ(可重复读)
  • SERIALIZABLE(串行化)
    在这里插入图片描述
数据库对应隔离属性的支持
隔离属性MySQLOracle
READ_COMMITTED(解决脏读)
REPEATABLE_READ(解决不可重复读)×
SERIALIZABLE(解决幻读)

Oracle采用多版本比对的方式解决不可重复读的问题。

默认的隔离属性

默认的隔离属性:Isolation.ISOLATION_DEFAULT(会调用不同数据库所设置的默认隔离属性)

MySQL:REPEATABLE_READ(解决不可重复读,加行锁)
Oracle:READ_COMMITTED(解决脏读,不加锁)

查看数据库隔离级别:
MySQL5:select @@tx_isolation;
MySQL8:select @@transaction_isolation;
Oracle:
在这里插入图片描述
电脑没装Oracle,所以这里直接截图。

建议:在实战中,使用默认值即可。

未来的实战中,并发访问情况很少,因为需要海量的数据,如果真遇到并发问题,使用乐观锁来解决,不会太影响效率。
乐观锁:
Hibernate(JPA):支持乐观锁,使用Version
MyBatis:不支持乐观锁,所以需要通过拦截器自定义开发。
MyBatis-plus:支持乐观锁,使用version

2. 传播属性(propagation)

传播属性:描述了事务解决嵌套问题的特征。
事务嵌套:一个大事务中,包含多个小事务。(service调用service时会发生事务嵌套)
问题:大事务中融入了很多小的事务,他们彼此影响,最终会导致外部大的事务丧失原子性。
在这里插入图片描述

传播属性的值(7个)及其用法

在这里插入图片描述

传播属性的值外部不存在事务外部存在事务备注
REQUIRED(默认)开启新的事务融合到外部事务中增删改方法
SUPPORTS不开启新的事务融合到外部事务中查询方法
REQUIRES_NEW开启新的事务挂起(暂停)外部事务,创建新的事务日志记录方法
MANDATORY抛出异常融合到外部事务中极其不常用,知道即可
NOT_SUPPORTED不开启新的事务挂起(暂停)外部事务极其不常用,知道即可
NEVER不开启新的事务抛出异常极其不常用,知道即可
NESTED开启新的事务融合到外部事务中极其不常用,知道即可

3. 只读属性(readOnly)

只读属性:针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率(不会加锁)。

默认为false,查询操作改为true,只读。

4. 超时属性(timeout)

超时属性:指定了事务等待的最长时间(以秒为单位)。
访问的数据被其他事务加上锁,此时需要等待解锁。

默认为-1,最终由对应的数据库底层设置的超时时间来决定等待多久,一般不用去设置超时属性,使用默认值即可。

5. 异常属性(rollbackFor、noRollbackFor)

Spring事务处理过程中

  • 对应RuntimeException及其子类,默认采用回滚策略。
  • 对应Exception及其子类,默认采用提交策略。

设置成回滚:rollbackFor = {java.lang.Exception.class, xxx, xxx}
设置成不回滚(即提交):{java.lang.RuntimeException.class, xxx, xxx}

建议:实战中使用默认值即可,异常尽量使用RuntimeException及其子类。

事务属性常见配置总结

  1. 隔离属性,默认即可。
  2. 传播属性:
    1. REQUIRED(默认):增删改方法
    2. SUPPORTS:查询方法
    3. REQUIRES_NEW:日志记录方法
  3. 只读属性:查询改为true。
  4. 超时属性:默认(-1)即可。
  5. 异常属性:默认即可。

使用建议:

  • 增删改操作:@Transactional
  • 查询操作:@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

基于标签的事务配置方式(事务开发的第二种形式)

基于注解 @Transactional 的事务配置回顾

1. 原始类
<bean id="userService" class="com.angenin.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>

2. 额外功能
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

3. 切入点
@Transactional(...)	// 类切入点
public class UserServiceImpl implements UserService {
	@Transactional(...)	// 方法切入点
    public void register(User user) {
    	...
    }
    ...
}

4. 组装切面
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>

两种方法的差异在于第3步和第4步,第2步需要多配置一个事务属性。

基于标签的事务配置

1. 原始类
...

2. 额外功能与事务属性
...

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <tx:method name="register"/>
        <tx:method name="login" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>
<!-- 等效于
	@Transactional
	public void register(User user) {...}
	
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	public void login(String username, String password) {...} 
-->

<aop:config>
	3. 切入点
    <aop:pointcut id="pc" expression="execution(* com.angenin1.service.UserServiceImpl.*(..))"/>
    4. 组装切面
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

基于标签的事务配置在实战中的引用方式

事务属性配置

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <!--<tx:method name="register"/>-->
        <!--<tx:method name="login" propagation="SUPPORTS" read-only="true"/>-->
        
        <!--使用通配符解决tx:method标签过多的问题-->
        <!--编程时,service中负责增删改操作的方法都以modify开头即可-->
        <tx:method name="modify*"/>
        <!-- *:指除上面的其他方法(查询),需要把范围小的放前面,范围大的放后面 -->
        <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

切入点

<aop:config>
    <!--<aop:pointcut id="pc" expression="execution(* com.angenin1.service.UserServiceImpl.*(..))"/>-->
    <!--使用包切入点-->
	<aop:pointcut id="pc" expression="execution(* com.angenin1.service..*.*(..))"/>
	
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

下一篇:Spring5学习笔记(五、MVC框架整合)

学习视频(p108-p140):https://www.bilibili.com/video/BV185411477k?p=108

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值