Spring框架使用详解(二)

目录

一、JdbcTemplate使用

1. JdbcTemplate介绍

2. JdbcTemplate使用

(1)配置JdbcTemplate

(2)编写查询语句

(3)测试

二、Spring AOP(spring切面编程)

1. AOP实现机制

(1)静态代理

(2)JDK动态代理

(3)CGLIB动态代理

(4)测试结果

2. Spring AOP介绍

(1)AOP的概念

3. Spring AOP使用场景

4. Spring AOP的使用(xml配置方式)

(1)导入相关依赖

(2)编写切面类

(3)配置xml文件

(4)测试

(5)测试结果 

5. Spring AOP纯注解编程

(1)编写aspect配置类

(2)配置spring配置类

(3)测试

(4)测试结果

(5)spring aop注解详解

三、Spring事务管理

1. Spring事务介绍

2. Spring事务的概念

3. Spring事务使用场景

3.Spring事务使用(xml配置方式)

(1)编写配置文件

(2)调用方法进行测试

4. Spring事务注解

(1)开启注解

(2)注解使用

5. 事务相关属性介绍

6. 隔离级别现象

(1)脏读(Dirty Read)

(2)不可重复读(Non-repeatable Read)

(3)幻读(Phantom Read)

(4)丢失更新(Lost Update)

四、总结


一、JdbcTemplate使用

1. JdbcTemplate介绍

        JdbcTemplate是Spring框架提供的一个核心类,用于简化数据库操作并提供对 JDBC(Java 数据库连接)的抽象和封装。它提供了一种更方便、更简洁的方式来执行 SQL 查询、更新和批处理操作,避免了繁琐的资源管理和异常处理。

2. JdbcTemplate使用

(1)配置JdbcTemplate

package com.jay.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;


import javax.sql.DataSource;

/**
 * @Author jay_mosu
 * @Date 2023/8/13 0013 22:00
 * @PackageName com.jay.config
 * @ClassName SpringConfig
 * @Description TODO
 * @Version 1.0
 */
@Configuration
@ComponentScan("com.jay")
@PropertySource(value = "classpath:jdbc.properties")
public class SpringConfig {

    @Value("${jdbc.driver}")
    private String driverName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

(2)编写查询语句

  • 编写service层接口
package com.jay.service;

import com.jay.entity.TLog;
import com.jay.entity.TUser;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author jay_mosu
 * @Date 2023/8/13 0013 22:09
 * @PackageName com.jay.com.jay.service
 * @ClassName UserService
 * @Description TODO
 * @Version 1.0
 */

public interface LogService {

    List<TLog> selectLogInfo();

    Integer insertLog(TLog tLog);

    Integer updateLog(TLog tLog);

    Integer deleteLog(int logId);
}
  • 编写service实现类
package com.jay.service.impl;

import com.jay.dao.LogDao;
import com.jay.entity.TLog;
import com.jay.entity.TUser;
import com.jay.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author jay_mosu
 * @Date 2023/8/13 0013 22:10
 * @PackageName com.jay.service.impl
 * @ClassName UserServiceImpl
 * @Description TODO
 * @Version 1.0
 */

@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;


    @Override
    public List<TLog> selectLogInfo() {
        return logDao.selectLogInfo();
    }

    @Override
    public Integer insertLog(TLog tLog) {
        return logDao.insertLog(tLog);
    }

    @Override
    public Integer updateLog(TLog tLog) {
        return logDao.updateLog(tLog);
    }

    @Override
    public Integer deleteLog(int logId) {
        return logDao.deleteLog(logId);
    }
}
  • 编写dao层接口
package com.jay.dao;

import com.jay.entity.TLog;
import com.jay.entity.TUser;

import java.util.List;

/**
 * @Author jay_mosu
 * @Date 2023/8/14 0014 9:46
 * @PackageName com.jay.dao
 * @ClassName UserDao
 * @Description TODO
 * @Version 1.0
 */
public interface LogDao {

    public List<TLog> selectLogInfo();

    Integer insertLog(TLog tLog);

    Integer updateLog(TLog tLog);

    Integer deleteLog(int logId);
}
  • 编写dao实现类
package com.jay.dao.impl;

import com.jay.dao.LogDao;
import com.jay.entity.TLog;
import com.jay.entity.TUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author jay_mosu
 * @Date 2023/8/14 0014 9:46
 * @PackageName com.jay.dao
 * @ClassName UserDaoImpl
 * @Description TODO
 * @Version 1.0
 */

@Repository
public class LogDaoImpl implements LogDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public List<TLog> selectLogInfo() {
        String sql = "SELECT * FROM t_log";
        List<TLog> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<TLog>(TLog.class));
        return query;

    }

    @Override
    public Integer insertLog(TLog tLog) {
        String sql = "INSERT INTO t_log values(null,?,?,?)";
        int update = jdbcTemplate.update(sql, tLog.getUserId(), tLog.getLogType(), tLog.getLogDate());
        return update;
    }

    @Override
    public Integer updateLog(TLog tLog) {
        String sql = "UPDATE t_log SET user_id = ?,log_type = ?, log_date = ? WHERE log_id = ?";
        int update = jdbcTemplate.update(sql, tLog.getUserId(), tLog.getLogType(), tLog.getLogDate(),tLog.getLogId());
        return update;
    }

    @Override
    public Integer deleteLog(int logId) {
        String sql = "DELETE FROM t_log WHERE log_id = ?";
        int update = jdbcTemplate.update(sql,logId);
        return update;
    }
}

(3)测试

package com.jay.test;
import java.time.LocalDateTime;

import com.jay.config.SpringConfig;
import com.jay.entity.TLog;
import com.jay.service.LogService;
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;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author jay_mosu
 * @Date 2023/8/13 0013 22:08
 * @PackageName com.jay.test
 * @ClassName ConfigTest
 * @Description TODO
 * @Version 1.0
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class LogTest {

    @Autowired
    private LogService logService;


    @Test
    public void test2() {
        List<TLog> tLogList = logService.selectLogInfo();
        System.out.println(tLogList);
    }

    @Test
    public void test3() {
        TLog tLog = new TLog();
        tLog.setUserId(9);
        tLog.setLogType("错误日志");
        tLog.setLogDate(LocalDateTime.now());

        int i = logService.insertLog(tLog);
        System.out.println(i);
    }

    @Test
    public void test4() {
        TLog tLog = new TLog();
        tLog.setLogId(3);
        tLog.setUserId(8);
        tLog.setLogType("修改日志");
        tLog.setLogDate(LocalDateTime.now());

        int i = logService.updateLog(tLog);
        System.out.println(i);
    }

    @Test
    public void test5() {
        int logId = 3;
        int i = logService.deleteLog(logId);
        System.out.println(i);
    }

}

二、Spring AOP(spring切面编程)

1. AOP实现机制

        代理(Proxy)是一种设计模式,它允许一个对象(代理对象)充当另一个对象(目标对象)的接口,以控制对目标对象的访问。代理模式在不改变目标对象的情况下,可以添加额外的行为,实现横切关注点的功能,如日志记录、权限控制、性能监控等。代理可以分为静态代理和动态代理,而Spring AOP 的实现就需要创建一个代理对象,根据代理对象的创建方式,又可以将AOP实现机制分为两种,一种是JDK动态代理,另一种是CGLib动态代理。

(1)静态代理

        静态代理是代理模式的一种实现方式,它在编译时就已经创建了代理类。在静态代理中,代理对象与目标对象实现相同的接口,通过代理对象来控制对目标对象的访问,并在访问前后添加额外的逻辑。以下是静态代理的使用介绍:

  • 优点:
    • 代码结构清晰:静态代理将核心业务逻辑和额外逻辑分离,使代码更易于理解和维护。
    • 控制访问:代理对象可以在目标对象的方法执行前后添加控制逻辑,如权限控制、事务管理等。
  • 缺点:
    • 重复代码:需要为每个目标对象创建一个代理类,可能会导致代码冗余。
    • 不灵活:如果目标对象接口发生变化,代理类也需要相应地修改。
    • 静态:代理类在编译时就已经创建,不适用于动态情况。
  • 示例

        新建一个UserService接口,并编写其实现类和代理类,然后进行测试。

        UserService接口

package com.jay.service;
/**
 * @Author jay_mosu
 * @Date 2023/8/15 0015 8:55
 * @PackageName com.jay.service
 * @ClassName UserService
 * @Description 
 */
public interface UserService {

    void doSomething();
}

        UserServiceImpl实现类

package com.jay.service.impl;

import com.jay.service.UserService;
import lombok.Data;
import org.springframework.stereotype.Service;

/**
 * @Author jay_mosu
 * @Date 2023/8/15 0015 8:56
 * @PackageName com.jay.service.impl
 * @ClassName UserServiceImpl
 * @Description 
 */
@Service
@Data
public class UserServiceImpl implements UserService {
    @Override
    public void doSomething() {

        System.out.println("------------doSomething------------");
    }
}

        UserServiceProxy代理类

package com.jay.proxy;

import com.jay.service.UserService;
import com.jay.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Author jay_mosu
 * @Date 2023/8/15 0015 8:57
 * @PackageName com.jay.proxy
 * @ClassName StaticProxy
 * @Description 
 */
@Component
public class UserServiceProxy implements UserService {

    private UserService userService;

    public UserServiceProxy(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void doSomething() {

        System.out.println("---------------开始之前-----------------");
        userService.doSomething();
        System.out.println("---------------开始之后-----------------");
    }
}

        测试输出

  @Test
    public void staticProxy() {
        UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());

        userServiceProxy.doSomething();
    }

(2)JDK动态代理

        JDK 动态代理是 Java 标准库中提供的一种代理模式实现方式,它允许在运行时生成代理对象,而不需要显式编写代理类。JDK 动态代理基于 Java 的反射机制,通过实现 java.lang.reflect.InvocationHandler 接口来创建代理对象,并在代理对象的方法中执行额外的逻辑。

以上述UserService接口和实现类为例继续测试。

  @Test
    public void dynamicProxy() {
        UserService userService = new UserServiceImpl();

        UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("---------------开始之前-----------------");
                Object invoke = method.invoke(userService, args);
                System.out.println("---------------开始之后-----------------");
                return invoke;
            }
        });

        proxy.doSomething();
    }

(3)CGLIB动态代理

        CGLIB(Code Generation Library)是一个强大的基于字节码的代码生成库,它可以用于在运行时生成代理类,从而实现动态代理。与 JDK 动态代理不同,CGLIB 动态代理不要求目标对象实现接口,而是通过继承目标对象的子类来创建代理对象。

  • 引入相关依赖
<!--    cglib动态代理-->
    <!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
      <version>2.2.2</version>
    </dependency>
  • 新建一个TUser用于代理测试
@Component
public class TUser {
    public void print(){
        System.out.println("打印");
    }
}
  • 测试输出
 @Test
    public void dynamicProxy2() {
        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(TUser.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("---------------开始之前-----------------");
                Object invoke = methodProxy.invokeSuper(o, objects);
                System.out.println("---------------开始之后-----------------");
                return invoke;
            }
        });
        TUser o = (TUser) enhancer.create();
        o.print();
    }

注意:使用cglib代理时,出现异常可能是版本不兼容导致,可以考虑降低版本,当前这个cglib无法在jdk8以上进行使用。  

(4)测试结果

        上述代理测试结果一致,如下图所示:

2. Spring AOP介绍

        Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个核心功能,用于将横切关注点(如事务、安全、日志等)从主要业务逻辑中分离出来,以模块化和更可维护的方式实现这些关注点。AOP 提供了一种跨越多个类和对象的方式来定义和管理横切关注点,从而提高代码的重用性和可读性。

(1)AOP的概念

  1. JoinPoint(连接点):目标对象中,所有<可以增强>的方法,叫做连接点
  2.  Pointcut(切入点):目标对象中,所有<已经增强>的方法,叫做切入点
  3. Advice(通知):
    1. <增强>的代码
    2. 所谓通知就是拦截到JoinPoint之后做的事情就是通知
    3. 通知又包括
      1. 前置通知(目标方法运行前通知)
      2. 返回通知(目标方法运行后通知)
      3. 异常通知(出现异常时会调用)
      4. 后置通知(无论是否异常都会调用,目标方法运行后调用)
      5. 环绕通知(目标方法运行前后通知)
  4. Target(目标对象):代理的对象(被代理的对象)
  5. Weaving(织入):将通知应用到切入点的过程
  6. Proxy(代理):将通知应用到目标对象后形成的代理对象
  7. Aspect(切面):通知和切入点的结合
  8. Introduction(引介):引介是一种特殊的通知,它可以为目标对象添加一些属性和方法。

3. Spring AOP使用场景

        Spring AOP 在许多不同的场景中都能发挥作用,它主要用于解决横切关注点的问题,从而提高代码的模块化和可维护性。以下是一些常见的 Spring AOP 使用场景:

  • 日志记录: 在方法执行前后记录日志,用于跟踪方法的调用和参数,或记录方法的执行时间。

  • 事务管理: 在方法执行前后控制事务的开始、提交或回滚,确保数据的一致性和完整性。

  • 安全性控制: 对方法的访问进行权限控制,确保只有授权用户可以执行特定方法。

  • 性能监控: 在方法执行前后记录性能指标,如响应时间、内存使用等,用于监控系统的性能。

  • 异常处理: 在方法抛出异常时执行额外的逻辑,如发送通知、记录错误信息等。

  • 缓存管理: 控制方法的结果是否被缓存,以提高系统性能。

  • 审计日志: 记录敏感操作的执行,用于追踪和审计系统的操作记录。

  • 参数校验: 在方法执行前校验方法参数的合法性,避免非法参数进入方法逻辑。

  • 数据转换: 在方法执行前后进行数据的转换,如格式化日期、金额等。

  • 国际化: 在方法执行前后进行国际化处理,实现多语言支持。

        需要注意的是,Spring AOP 主要适用于处理横切关注点,即那些分散在应用程序多处的通用性问题。对于单个方法的特定逻辑,更适合使用方法级别的注解(如 @Transactional@Cacheable 等)。另外,如果需要在非 Spring 托管的类上应用 AOP,可以使用 AspectJ 进行更高级的切面编程。

4. Spring AOP的使用(xml配置方式)

(1)导入相关依赖

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${spring-version}</version>
    </dependency>

(2)编写切面类

package com.jay.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @Author jay_mosu
 * @Date 2023/8/16 0016 15:34
 * @PackageName com.jay.aspect
 * @ClassName UserAspect
 * @Description 
 */
public class UserAspect {
    public void before(){
        System.out.println("-------------前置通知--------------");
    }

    public void afterReturning(){
        System.out.println("-------------后置通知--------------");
    }

    public void afterThrowing(){
        System.out.println("-------------异常通知--------------");
    }

    public void after(){
        System.out.println("-------------最终通知--------------");
    }

    public void around(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("-------------环绕通知前--------------");
            joinPoint.proceed();
            System.out.println("-------------环绕通知后--------------");
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        
    }
}

(3)配置xml文件

<?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: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 https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--将通知织入到目标对象中-->
    <!--1.配置目标对象-->
    <bean name="userService" class="com.jay.service.impl.UserServiceImpl"></bean>
    <!--2. 配置通知对象-->
    <bean name="userAspect" class="com.jay.aspect.UserAspect"></bean>
    <!--3.将对象织入目标对象中-->
    <aop:config>
        <!-- 3.1 配置切入点-->
        <!-- 通配符
               ”*“:表示匹配任意长度字符
               “..”:表示多级子包或者多个参数
        -->
        <aop:pointcut id="pt" expression="execution(* com.jay.service..*.*(..))"/>
        <!-- 3.2 指定通知-->
        <aop:aspect ref="userAspect">
            <aop:before method="before" pointcut-ref="pt"></aop:before>
            <aop:after-returning method="afterReturning" pointcut-ref="pt"></aop:after-returning>
            <aop:around method="around" pointcut-ref="pt"></aop:around>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"></aop:after-throwing>
            <aop:after method="after" pointcut-ref="pt"></aop:after>
        </aop:aspect>
    </aop:config>

    
</beans>

(4)测试

 @Test
    public void aop() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.doSomething();


    }

(5)测试结果 

5. Spring AOP纯注解编程

(1)编写aspect配置类

package com.jay.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author jay_mosu
 * @Date 2023/8/16 0016 15:34
 * @PackageName com.jay.aspect
 * @ClassName UserAspect
 * @Description 
 */


@Aspect
@Component
public class UserAspect_2 {

    @Pointcut("execution(* com.jay.service..*.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("-------------前置通知--------------");
    }

    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("-------------后置通知--------------");
    }
    
    @AfterThrowing(value = "pointcut()" ,throwing = "error")
    public void afterThrowing(Exception error){
        System.out.println("-------------异常通知--------------" + error.getMessage());
    }
    
    @After("pointcut()")
    public void after(){
        System.out.println("-------------最终通知--------------");
    }
    
    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("-------------环绕通知前--------------");
            joinPoint.proceed();
            System.out.println("-------------环绕通知后--------------");
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

    }
}

(2)配置spring配置类

package com.jay.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author jay_mosu
 * @Date 2023/8/15 0015 9:00
 * @PackageName com.jay.config
 * @ClassName SpringConfig
 * @Description 
 */
@Configuration
@ComponentScan("com.jay")
@EnableAspectJAutoProxy//启动aop中的AspectJ自动代理
public class SpringConfig {
}

(3)测试


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class ProxyTest {

    @Autowired
    private UserService userService;

    @Test
    public void aop2() {
        userService.doSomething();

    }
}

(4)测试结果

(5)spring aop注解详解

  • @Aspect: 声明一个类为切面类,该类包含通知和切入点的定义。
  • @Before: 在目标方法执行前执行通知。
  • @After: 在目标方法执行后执行通知,无论方法是否抛出异常。
  • @AfterReturning: 在目标方法成功执行后执行通知,通常用于获取方法的返回值。
  • @AfterThrowing: 在目标方法抛出异常后执行通知,可以用于处理异常或记录错误信息。
  • @Around: 环绕通知,包围目标方法的执行,可以在方法执行前后添加额外逻辑,甚至完全控制方法的执行。
  • @Pointcut: 声明一个切入点表达式,用于定义一组连接点,从而在切面中重复使用。
  • @DeclareParents: 引入通知,允许将新的接口和实现引入到现有的类中。
  • @EnableAspectJAutoProxy: 在配置类中启用 AspectJ 自动代理。

这些注解的使用场景主要包括:

  • 日志记录: 使用 @Before@After 来记录方法的调用和返回信息。
  • 事务管理: 使用 @Around@After 来控制方法的事务开始、提交或回滚。
  • 安全性控制: 使用 @Before@After 来控制对受保护方法的访问。
  • 性能监控: 使用 @Around 来记录方法的执行时间和性能指标。
  • 异常处理: 使用 @AfterThrowing 来处理方法抛出的异常。
  • 缓存管理: 使用 @Around@AfterReturning 来控制方法结果的缓存。
  • 审计日志: 使用 @After 来记录敏感操作的执行。
  • 参数校验: 使用 @Before 来校验方法参数的合法性。

        需要注意的是,这些注解主要用于声明切面和通知,用于解决横切关注点的问题。在使用注解时,需要在 Spring 配置类上添加 @EnableAspectJAutoProxy 来启用 AspectJ 自动代理。同时,为了正确使用切入点表达式,通常需要在切面类中定义切入点,然后在通知中引用该切入点。

三、Spring事务管理

1. Spring事务介绍

        Spring 事务是 Spring 框架提供的一种管理数据库事务的机制,它使得开发者能够在应用程序中声明式地管理事务,而不必显式地编写大量的事务管理代码。Spring 事务提供了一种统一的方式来处理数据库事务,无论是使用 JDBC、JPA、Hibernate 还是其他持久化框架。

2. Spring事务的概念

  • 事务管理器(Transaction Manager): Spring 事务管理器负责协调和控制事务的执行。它可以与不同的事务资源(如数据库、消息队列等)集成,确保事务的一致性和隔离性。常用的事务管理器包括 DataSourceTransactionManagerJtaTransactionManager 等。

  • 事务传播行为(Transaction Propagation): 事务传播行为定义了在方法调用期间如何处理事务。Spring 提供了多种传播行为选项,如 REQUIREDREQUIRES_NEWNOT_SUPPORTED 等,用于控制事务的传播方式。

  • 隔离级别(Isolation Level): 隔离级别定义了多个事务并发执行时彼此之间的可见性和影响范围。Spring 支持不同的隔离级别,如 DEFAULTREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE 等。

  • 事务回滚规则(Rollback Rules): 事务回滚规则定义了哪些异常触发事务的回滚操作。通过配置回滚规则,可以实现对特定异常的回滚操作。

  • 声明式事务(Declarative Transaction): Spring 允许通过注解或 XML 配置声明式地管理事务,将事务管理从业务逻辑中分离出来。使用 @Transactional 注解或 <tx:advice> 元素来声明事务属性。

  • 编程式事务(Programmatic Transaction): 除了声明式事务,Spring 也支持编程式事务,开发者可以在代码中使用编程式事务管理来实现更细粒度的事务控制。

3. Spring事务使用场景

  • 数据库操作: 确保数据库操作的一致性,如插入、更新、删除等。
  • 跨服务事务: 确保多个服务之间的操作在一个事务内完成,保持数据的完整性。
  • 业务流程: 在复杂的业务流程中,将多个步骤封装在一个事务中,保证整个流程的正确执行。
  • 异常处理: 在方法执行时捕获异常并执行事务回滚,确保数据的正确状态。
  • 性能优化: 使用事务来控制方法的执行和并发,以提高系统性能。

3.Spring事务使用(xml配置方式)

(1)编写配置文件

<?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: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 https://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 https://www.springframework.org/schema/aop/spring-aop.xsd">

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

    <!-- 1. 配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <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. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 3.定义事务切面-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.jay.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 使用通配符“*”来匹配需要配置事务的方法,并定义其相关属性-->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <!-- 更多方法和事务属性的定义 -->
        </tx:attributes>
    </tx:advice>

</beans>

(2)调用方法进行测试

        略

4. Spring事务注解

(1)开启注解

  • xml配置文件开启方式:<tx:annotation-driven/>
  • 配置类开启方式:@EnableTransactionManagement

(2)注解使用

可以通过@Transactional注解在方法上来标识需要添加事务的方法,并设置其规则,也可以直接放在类上,用来标识当前类中所有的方法,示例如下:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {SQLException.class})
public void methodA() {
    // ...
}

5. 事务相关属性介绍

  • propagation(传播行为):定义事务传播行为,即方法被调用时现有事务如何进行传播。

    • Propagation.REQUIRED默认值。如果当前没有事务,就创建一个新的事务;如果已存在事务,则加入当前事务。适用于需要多个方法共享同一个事务的情况,例如一个方法调用另一个方法,如果后者已经在事务中,前者将加入该事务,否则创建新的事务。
    • Propagation.REQUIRES_NEW始终创建一个新的事务,如果当前已存在事务,则将其挂起。适用于需要独立的事务,不受外部事务影响的方法。
    • Propagation.NOT_SUPPORTED不在事务中执行,如果已存在事务,则将其挂起。适用于只读操作或独立的非事务性方法。
    • Propagation.NESTED如果当前没有事务,就创建一个新的事务;如果已存在事务,则在嵌套事务中执行。适用于需要嵌套事务,独立控制嵌套事务的回滚。
  • isolation(隔离级别):

    • Isolation.DEFAULT默认值,由底层数据库决定隔离级别。
    • Isolation.READ_UNCOMMITTED允许读取未提交的数据更改,最低的隔离级别。适用于需要快速读取数据的情况,但可能会导致脏读、不可重复读和幻读等问题。
    • Isolation.READ_COMMITTED只能读取已提交的数据,常用于一般读取操作。适用于需要避免脏读的情况,保证读取的数据已提交。
    • Isolation.REPEATABLE_READ保证多次读取相同数据时的一致性,适用于需要稳定数据视图的场景,但仍可能出现幻读。
    • Isolation.SERIALIZABLE保证事务之间的完全隔离,适用于对数据一致性要求极高的场景,保证事务之间的完全隔离,但可能导致性能下降。
  • timeout(超时时间):

    • 指定以秒为单位的事务超时时间,当事务执行时间超过设定时间时,自动回滚事务。
    • 使用场景:处理长时间运行的事务,避免对系统造成阻塞。
    • 优点:避免长时间事务影响系统性能。
    • 缺点:可能导致正常操作因超时而回滚。
  • readOnly(只读事务):

    • true:标记事务为只读,只能进行读取操作。
    • false:默认值,事务可进行读写操作。
    • 使用场景:在只读操作频繁的方法上提高性能。
    • 优点:减少数据库锁竞争,提升并发性能。
    • 缺点:无法在只读事务中进行修改操作。
  • rollbackFor(回滚异常)和noRollbackFor(不回滚异常):

    • 指定异常类或类数组,控制事务回滚或不回滚的条件。
    • 使用场景:精确控制异常触发回滚的情况。
    • 优点:精确控制回滚,避免不必要的回滚。
    • 缺点:可能导致配置较为复杂。
  • rollbackForClassName(回滚异常类名)和noRollbackForClassName(不回滚异常类名):

    • rollbackFornoRollbackFor 类似,但使用异常类名字符串。
    • 使用场景:在 XML 配置中使用,或避免直接引用异常类。
    • 优点:灵活,避免直接依赖异常类。
    • 缺点:类名可能引入拼写错误。
  • value(别名为:transactionManager):

    • 指定事务管理器的名称,用于多数据源或多种事务管理器情况。
    • 使用场景:在多事务管理器环境中,明确指定使用哪个事务管理器。
    • 优点:适用于多数据源情况,可以精确控制事务管理器。

6. 隔离级别现象

(1)脏读(Dirty Read)

        指一个事务读取了另一个事务未提交的数据,可能导致不一致的数据状态。可能读取到无效或错误的数据,导致数据不一致。

(2)不可重复读(Non-repeatable Read)

        指一个事务内多次读取同一数据,但在事务间发生了数据修改。可能导致不一致的查询结果,降低数据准确性。

(3)幻读(Phantom Read)

        指一个事务内多次查询同一范围的数据,但在事务间插入了新数据。可能导致查询结果不一致,降低数据准确性。

(4)丢失更新(Lost Update)

        指多个事务并发更新同一数据,其中一个事务的修改被另一个事务的修改覆盖。可能导致部分数据更新丢失,降低数据准确性。

四、总结

        Spring AOP 和事务管理为应用程序提供了更高级别的代码抽象和控制,使开发者能够专注于核心业务逻辑,同时实现横切关注点的优雅处理,提高了代码质量、可维护性和可扩展性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值