java-web/docs/chapter05.md
2024-10-21 13:33:46 +08:00

28 KiB
Raw Blame History

5. 基于Spring框架的后端应用开发

5.1 Java基础增强(掌握)

5.1.1 Java异常处理

异常是在程序执行过程中发生的任何非正常情况或问题。它们打断了正常的程序流程,需要特别处理来确保程序的健壮性和可靠性。

java异常类的继承关系:

Throwable类是所有异常的超类,它不能被实例化。Throwable有两个重要的子类:ExceptionError

  • Throwable: 所有错误和异常的超类。
  • Error: 表示合理的应用不应该试图捕获的严重问题。例如,内存溢出等。
  • Exception: 表示合理的应用可能想要捕获的条件。异常通常由编程错误之外的情况引发。

非检查异常 (Unchecked Exceptions)

  • 包括Error和RuntimeException及其子类。
  • 这些异常通常是由编程错误引起的,如数组索引越界或空指针引用。
  • 编译器不会强制要求处理这些异常,因此称为“非检查”。

检查异常 (Checked Exceptions)

  • 除了Error和RuntimeException之外的所有其他异常。
  • 这些异常是程序应该能够预料到并需要处理的,如文件未找到异常。
  • 编译器要求要么在调用方法时处理这些异常使用try-catch块要么在方法声明中声明它们可能会抛出这些异常使用throws关键字

Java异常使用

关键词:

  • try: 用于标记可能抛出异常的代码块。
  • catch: 用于捕获并处理try块中抛出的异常。
  • finally: 不管是否有异常发生finally块中的代码总会被执行。
  • throw: 用于抛出一个新的异常实例。
  • throws: 用于声明一个方法可能会抛出的异常类型。

自定义异常类

创建自定义异常类时可以根据是否需要强制性地进行异常处理来选择继承Exception还是RuntimeException。

  • 继承Exception检查异常: 如果希望强制用户处理这个异常可以选择继承Exception。
  • 继承RuntimeException非检查异常: 如果异常是由于编程错误引起的或者不需要强制处理可以继承RuntimeException。

5.1.2 注解(掌握)

参考java注解是怎么实现的?

注解 (Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

——摘自百度百科

元数据: 说明数据的数据

注解定义的语法

public @interface 注解名称{
   属性列表;
}

类型:

  • 元注解: 用于注解其他注解的注解,如@Retention和@Target。
  • 内置注解: Java平台提供的注解,如@Override, @Deprecated, @SuppressWarnings等。
  • 自定义注解: 我们自己写的注解。 很多第三方框架通过提供自定义注解来简化实现, 如spring框架提供了@Autowired@RequestMapping等。 使用场景:
    • 为框架提供元数据例如Spring框架使用注解来标记组件。
    • 用于代码生成例如Lombok库使用注解来自动生成模板化的代码。
    • 用于编译时处理,例如通过注解来检查代码中的潜在错误。

使用位置

在实际开发中,注解可以出现在类、方法、成员变量、方法参数等多个位置。

作用

如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。例如:

  • @Test 标记一个方法为测试方法。
  • @Before 标记一个方法在每个测试方法之前执行。
  • @RequestMapping("/user/info") 提供 Controller 所对应的某个 URL 路径。

级别

注解与类、接口、枚举处于同一级别。注解的本质类似于 @interface与 interface 相似,但用于提供元数据。

注解三角

  • 定义注解: 创建注解。
  • 使用注解: 在代码中应用注解。
  • 读取注解: 通过反射或其他机制读取并处理注解。

5.1.3 Optional 的介绍和使用(了解)

定义

Optional 是 Java 8 引入的一个容器类,用于封装可能为 null 的值。它的主要目的是帮助开发者避免空指针异常,并提供一种更优雅的方式来处理可能不存在的值。

用途

  • 封装可能为 null 的对象: Optional 提供了一种安全的方式来处理可能为 null 的对象,从而避免了空指针异常。
  • 提供链式操作: Optional 支持方法链式调用,使得代码更加简洁和可读。

主要方法

  • Optional.of(T value): 如果 value 不为 null,则创建一个包含该值的 Optional 对象;如果 valuenull,则抛出 NullPointerException
  • Optional.ofNullable(T value): 如果 value 不为 null,则创建一个包含该值的 Optional 对象;如果 valuenull,则返回一个空的 Optional 对象。
  • Optional.empty(): 返回一个空的 Optional 对象。
  • Optional.isPresent(): 判断 Optional 是否包含值。
  • Optional.orElse(T other): 如果 Optional 包含值,则返回该值;否则返回 other
  • Optional.orElseGet(Supplier<? extends T> other): 如果 Optional 包含值,则返回该值;否则返回由 Supplier 提供的值。
  • Optional.orElseThrow(Supplier<? extends X> exceptionSupplier): 如果 Optional 包含值,则返回该值;否则抛出由 exceptionSupplier 生成的异常。
  • Optional.ifPresent(Consumer<? super T> action): 如果 Optional 包含值,则执行 action;否则不执行任何操作。

示例

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        // 使用Optional.ofNullable封装可能为null的值
        Optional<String> optional = Optional.ofNullable(getName());

        // 使用ifPresent方法处理Optional中的值
        optional.ifPresent(System.out::println);

        // 使用orElse处理Optional为空的情况
        String name = optional.orElse("Default Name");
        System.out.println("Name: " + name);

        // 使用orElseGet处理Optional为空的情况
        name = optional.orElseGet(() -> "Computed Default Name");
        System.out.println("Computed Name: " + name);

        // 使用orElseThrow处理Optional为空的情况
        try {
            name = optional.orElseThrow(() -> new IllegalArgumentException("Name is missing"));
            System.out.println("Name: " + name);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }

    }

    private static String getName() {
        return null; // 或者返回一个实际的值
    }
}

小结

  • 避免空指针异常: 使用 Optional 可以有效地避免空指针异常,并提供一种更优雅的方式来处理可能为 null 的值。
  • 链式操作: Optional 支持方法链式调用,使得处理逻辑更加简洁明了。
  • 提供多种处理方式: Optional 提供了多种方法来处理可能为空的情况,如 ifPresentorElseorElseGetorElseThrow 等。

通过使用 Optional,开发者可以写出更加健壮和易于维护的代码。

5.1.4 Stream API 的介绍和使用(了解)

定义

Stream API 是 Java 8 引入的新特性,用于处理集合数据。它提供了一种声明式的、函数式风格的数据处理方式,使得处理集合数据变得更加简洁和高效。

用途

  • 数据流处理: Stream API 提供了一种声明式的处理集合数据的方式,使得开发者可以更专注于描述“做什么”而不是“怎么做”。
  • 并行处理: Stream API 支持并行流处理,可以自动利用多核处理器的优势,提高数据处理的性能。

主要概念

  • Source: 数据的来源,通常是集合(如 List, Set)、数组或 InputStream 等。
  • 中间操作Intermediate Operations: 这些操作返回一个新的流,可以链接在一起形成流水线。常见的中间操作包括 filter, map, flatMap, sorted, peek 等。
  • 终止操作Terminal Operations: 这些操作产生结果或副作用,如计算总和、平均值、查找最大值或最小值等。常见的终止操作包括 forEach, reduce, collect, count, max, min, anyMatch, allMatch, noneMatch, findFirst, findAny 等。

示例

以下是一个使用 Stream API 处理集合数据的示例:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        // 创建一个整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 计算偶数之和
        int sum = numbers.stream()
                         .filter(n -> n % 2 == 0)
                         .mapToInt(Integer::intValue)
                         .sum();
        System.out.println("Sum of even numbers: " + sum); // 输出 6

        // 打印偶数
        System.out.println("Even numbers:");
        numbers.stream()
               .filter(n -> n % 2 == 0)
               .forEach(System.out::println); // 输出 2 和 4

        // 获取偶数的平方列表
        List<Integer> squaresOfEvens = numbers.stream()
                                              .filter(n -> n % 2 == 0)
                                              .map(n -> n * n)
                                              .collect(Collectors.toList());
        System.out.println("Squares of even numbers: " + squaresOfEvens); // 输出 [4, 16]

        // 计算偶数的最大值
        Integer maxEven = numbers.stream()
                                 .filter(n -> n % 2 == 0)
                                 .max(Integer::compare)
                                 .orElse(null);
        System.out.println("Maximum even number: " + maxEven); // 输出 4

        // 并行处理
        long parallelSum = numbers.parallelStream()
                                  .filter(n -> n % 2 == 0)
                                  .mapToInt(Integer::intValue)
                                  .sum();
        System.out.println("Parallel sum of even numbers: " + parallelSum); // 输出 6
    }
}

小结

  • 声明式编程: Stream API 采用声明式的编程风格,使得代码更加简洁易懂。
  • 函数式风格: Stream API 支持函数式编程特性,可以方便地组合多个操作。
  • 并行处理: Stream API 支持并行处理,可以充分利用多核处理器的优势来加速数据处理。
  • 链式操作: Stream API 支持链式调用,使得多个操作可以串联起来形成数据处理流水线。

通过使用 Stream API开发者可以写出更加高效、简洁且易于维护的代码。这对于处理大量数据集尤其有用同时也提高了代码的可读性和可维护性。

5.1.5 泛型(了解)

什么是泛型?

Java 中的泛型是一种通用的编程技术,它允许我们在编写代码时更加灵活、安全和高效。泛型的主要目的是解决 Java 中类型安全的问题。在 Java 的早期版本中,所有的集合类都是基于 Object 类型实现的,这意味着可以将任意类型的对象添加到集合中。然而,在取出元素时需要进行显式类型转换,如果转换错误,就会抛出 ClassCastException 异常。通过使用泛型,可以在编译阶段检查类型,从而避免这类异常的发生。

泛型的优点

  • 类型安全性: 泛型允许在编译时检查类型,避免了运行时出现类型转换异常等问题。
  • 代码重用性: 泛型可以让开发者编写通用的代码,提高代码的可重用性和可维护性。
  • 性能优化: 泛型可以避免不必要的类型转换,提高程序的运行效率。

泛型的语法

泛型主要包括三个部分:泛型类、泛型接口和泛型方法。

泛型类和泛型接口

泛型类和泛型接口是最常见的泛型形式。我们可以通过在类或接口名后面添加一对尖括号来声明一个或多个类型参数。

public class MyClass<T> {
    private T data;

    public MyClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

public interface MyInterface<K, V> {
    void add(K key, V value);
}

当我们使用泛型类或泛型接口时,可以使用具体的类型来替换类型参数。

MyClass<String> myClass = new MyClass<>("Hello, World!");
MyInterface<Integer, String> myInterface = new MyInterface<Integer, String>() {
    @Override
    public void add(Integer key, String value) {
        // 实现逻辑
    }
};
泛型方法

除了类和接口之外,我们还可以定义泛型方法,这些方法在方法签名前加上一对尖括号,表示该方法有一个或多个类型参数。

public class MyUtils {
    public static <T> T getLast(List<T> list) {
        if (list.isEmpty()) {
            return null;
        }
        return list.get(list.size() - 1);
    }
}

当我们使用泛型方法时,可以显式或隐式地为类型参数传入具体的类型。

  List<String> names = List.of("Alice", "Bob", "Charlie");
  String last = MyUtils.getLast(names); // 隐式类型推断
  String lastExplicit = MyUtils.<String>getLast(names); // 显式指定类型

泛型的应用

泛型是一种非常通用的编程技术,可以用它来编写各种各样的通用数据结构和工具类。

List 和 Map

Java 内置的 ListMap 类型都支持泛型,可以用来存储任意类型的对象。

  List<Integer> numbers = new ArrayList<>();
  numbers.add(1);
  numbers.add(2);
  numbers.add(3);
  
  Map<String, Integer> scoreMap = new HashMap<>();
  scoreMap.put("Alice", 90);
  scoreMap.put("Bob", 80);
  scoreMap.put("Charlie", 70);

小结

  • 类型安全: 泛型可以确保类型在编译时得到验证,避免运行时的类型转换错误。
  • 代码重用: 泛型允许编写通用的代码,提高代码的可重用性。
  • 性能提升: 泛型可以减少不必要的类型转换,提高程序的运行效率。

通过使用泛型,开发者可以写出更加健壮、可读性强和易于维护的代码。

5.2 Spring框架简介(了解)

5.2.1 Spring框架的历史和发展

  • 起源: Spring framework最初由Rod Johnson在2003年开始编写并于2004年发布了第一个版本1.0。
  • 目标: 创立Spring framework的主要目的是解决企业级Java应用中的复杂问题特别是解决依赖管理和面向切面编程AOP的问题。
  • 版本演进:
    • 1.x: 初期版本主要关注于依赖注入和面向切面编程。
    • 2.x: 添加了对Web应用程序的支持包括Spring MVC框架。
    • 3.x: 引入了对Java 5特性的支持如注解和泛型并且增强了对RESTful Web服务的支持。
    • 4.x: 增强了对Java 8的支持包括Lambda表达式和Stream API。
    • 5.x: 重点改进了对反应式编程模型的支持引入了Spring WebFlux框架。
  • 最新版本: 截至2024年Spring框架的最新版本为5.x系列持续增加了对现代Web开发趋势的支持如微服务架构、云原生应用等。

5.2.2 Spring框架的核心特性

  • 依赖注入 (DI):
    • 定义: 是一种设计模式,用于实现对象之间的解耦,让对象不需要直接创建依赖对象,而是由容器负责创建并注入。
    • 好处: 降低了组件之间的耦合度,提高了代码的可测试性和可维护性。
  • 面向切面编程 (AOP):
    • 定义: 一种编程范式,用于将横切关注点(如日志记录、事务管理)从业务逻辑中分离出来。
    • 好处: 通过切面编程,可以在不修改业务逻辑代码的情况下添加或修改功能。
  • 模块化: Spring框架被设计成高度模块化的用户可以根据需要选择使用不同的模块。
  • 可扩展性: Spring框架提供了许多扩展点允许开发者根据需要自定义行为。
  • 广泛的应用场景: 从简单的Web应用到复杂的企业级应用Spring都能提供解决方案。

5.2.3 Spring的家族框架

  • Spring Boot: 提供了一个快速搭建Spring应用的方式通过自动化配置、依赖管理和启动器简化了开发过程。
  • Spring Cloud: 用于构建分布式系统和服务网格,提供了一系列工具和服务,如服务注册与发现、配置中心、熔断器等。
  • Spring Data: 提供了一套数据访问抽象层,简化了数据访问层的开发工作,支持各种数据库技术。
  • Spring Security: 为Spring应用提供了全面的安全保护包括认证、授权等功能。
  • Spring Integration: 提供了基于消息驱动的企业集成模式的支持,适用于构建复杂的集成系统。
  • Spring Batch: 用于批量处理任务,支持大规模数据处理作业。
  • Spring WebFlux: 用于构建非阻塞的、事件驱动的Web应用和服务器端应用支持反应式编程模型。
  • Spring Web: 提供了构建Web应用程序所需的支持包括HTTP消息处理、多部分文件上传等功能以及与Spring MVC的紧密集成。

5.2.4 Spring的应用场景和优势

  • 应用场景:
    • Web应用: Spring MVC框架为构建高性能的Web应用提供了基础。
    • 企业级应用: Spring框架适合构建复杂的企业级应用包括业务流程管理、数据处理等。
    • 微服务: Spring Boot和Spring Cloud为构建微服务架构提供了便利。
    • 移动应用后端: Spring框架可以作为移动应用后端的服务端框架。
  • 优势:
    • 松耦合: 通过依赖注入实现了组件之间的松耦合。
    • 易于测试: Spring框架的设计使得单元测试变得简单。
    • 广泛的社区支持: 拥有庞大的开发者社区和丰富的文档资源。
    • 丰富的生态: Spring框架有一整套家族产品几乎覆盖了企业级应用的所有方面。
    • 灵活性: Spring框架非常灵活可以很容易地与其他技术栈集成。
    • 性能: Spring框架经过优化能够高效地运行大规模应用。
    • 社区活跃: Spring框架有一个活跃的社区不断更新以适应新的技术和标准。

5.2.5 Spring Web

  • 概述: Spring Web 是 Spring Framework 的一部分它为构建Web应用程序提供了必要的支持。Spring Web 包含了多个子模块旨在提供一套完整的Web开发解决方案。
  • 子模块:
    • Spring Web: 为Web开发提供了一些基本的支持比如HTTP消息转换、多部分文件上传、Web应用上下文等。
    • Spring Web MVC: 一个基于Model-View-Controller (MVC) 模式的Web框架用于构建动态Web应用程序。
    • Spring Web Flows: 一个用于构建复杂的Web应用程序的工作流框架。
    • Spring WebSocket: 提供了WebSocket的支持用于实现全双工通信。
    • Spring Web Services: 提供了构建和消费Web服务的支持。
  • 关键特性:
    • HTTP Message Converters: 提供了HTTP消息转换器用于序列化和反序列化HTTP请求和响应。
    • MultiPart File Upload: 支持多部分文件上传功能。
    • Web Application Context: 提供了Web应用程序上下文的支持包括初始化监听器等。
    • MVC Framework: Spring MVC 提供了一个基于MVC模式的Web框架用于构建动态Web应用程序。
    • File Upload Support: 支持多部分文件上传,用于处理文件上传请求。
    • Web Services Support: 支持构建和消费Web服务包括SOAP和RESTful服务。
    • WebSocket Support: 提供了WebSocket的支持用于实现实时双向通信。

5.3 Spring IoC(掌握)

5.3.1 Spring Bean的定义和生命周期

  • 定义: Spring Bean是Spring容器管理的对象。
  • 生命周期:
    • 初始化: 包括默认初始化、自定义初始化等。
    • 销毁: 包括默认销毁、自定义销毁等。
  • 示例:
    @Component
    public class MyBean {
        public MyBean() {
            System.out.println("MyBean created.");
        }
    
        @PostConstruct
        public void init() {
            System.out.println("MyBean initialized.");
        }
    
        @PreDestroy
        public void destroy() {
            System.out.println("MyBean destroyed.");
        }
    }
    

5.3.2 XML和注解方式的Bean配置

  • XML配置:
    • 定义Bean: <bean>元素定义一个Bean。
    • 依赖注入: 使用<property>元素注入依赖。
  • 注解配置:
    • 组件扫描: 使用@ComponentScan注解来自动发现和配置组件。
    • 依赖注入: 使用@Autowired@Qualifier等注解来注入依赖。
  • 示例:
    @Service
    public class MyService {
        private final MyRepository repository;
    
        @Autowired
        public MyService(MyRepository repository) {
            this.repository = repository;
        }
    
        public void doSomething() {
            // 使用repository
        }
    }
    

5.3.3 Bean的作用域和装配方式

  • 作用域:
    • Singleton: 单例模式每个Spring容器中只有一个实例。
    • Prototype: 原型模式,每次请求都会创建一个新的实例。
    • Request: 每次HTTP请求都会创建一个新的实例。
    • Session: 每个HTTP Session都会创建一个新的实例。
  • 装配方式:
    • 构造器注入: 通过构造器参数注入依赖。
    • setter注入: 通过setter方法注入依赖。
    • 属性注入: 通过属性直接注入依赖。

5.3.4 使用Spring管理依赖关系

  • 定义: Spring通过依赖注入来管理对象之间的依赖关系。
  • 目的: 降低组件之间的耦合度,提高代码的可测试性和可维护性。
  • 示例:
    @Service
    public class UserService {
        private final UserRepository userRepository;
    
        @Autowired
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        public User findUserById(Long id) {
            return userRepository.findById(id).orElse(null);
        }
    }
    

5.4 Spring MVC(掌握)

5.4.1 Spring MVC概述

  • 定义: Spring MVC是Spring web框架的一部分基于Model-View-Controller (MVC) 模式,用于构建Web应用。
  • 特点:
    • 请求处理: 通过@Controller注解来定义控制器。
    • 视图解析: 通过ViewResolver来解析视图。

5.4.2 使用@Controller定义控制器

  • 定义: 使用@Controller注解定义控制器类。
  • 示例:
    @Controller
    public class HelloController {
        @RequestMapping("/hello")
        public String hello(Model model) {
            model.addAttribute("message", "Hello, Spring MVC!");
            return "hello";
        }
    }
    

5.4.3 使用@RequestMapping处理请求

  • 定义: 使用@RequestMapping注解来映射请求路径。
  • 示例:
    @Controller
    public class HelloController {
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String hello(@RequestParam String name, Model model) {
            model.addAttribute("message", "Hello, " + name + "!");
            return "hello";
        }
    }
    

5.4.4 使用@ResponseBody返回JSON响应

  • 定义: 使用@ResponseBody注解来直接返回JSON响应。
  • 示例:
    @Controller
    public class HelloController {
        @RequestMapping(value = "/hello-json", method = RequestMethod.GET)
        @ResponseBody
        public Map<String, String> helloJson(@RequestParam String name) {
            Map<String, String> response = new HashMap<>();
            response.put("message", "Hello, " + name + "!");
            return response;
        }
    }
    

5.4.5 使用Spring Data JPA进行数据访问

  • 定义: Spring Data JPA是Spring Data项目的一部分用于简化JPA的数据访问。
  • 示例:
    @Repository
    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
    }
    

5.5 Spring AOP(了解)

5.5.1 AOP的基本概念

  • 定义: AOP是一种编程范式用于将横切关注点从业务逻辑中分离出来。
  • 用途:
    • 日志记录: 在方法执行前后记录日志。
    • 事务管理: 管理方法的事务边界。
    • 安全控制: 控制方法的访问权限。
  • 术语
    • 切点 (Pointcut): 定义了哪些连接点将被执行通知。
    • 连接点 (Joinpoint): 程序执行的某个特定点,如方法调用。
    • 通知 (Advice): 在连接点处执行的动作,如前置通知、后置通知等。
    • 切面 (Aspect): 包含切点和通知的组合。

5.5.2 使用AspectJ进行切面编程

  • 定义: AspectJ是一种常用的AOP框架提供了强大的切面编程能力。
  • 示例:
    @Aspect
    @Component
    public class LoggingAspect {
        @Before("execution(* com.example.service.*.*(..))")
        public void logBefore(JoinPoint joinPoint) {
            System.out.println("Executing: " + joinPoint.getSignature());
        }
    }
    

5.6 Spring事务管理(了解)

5.6.1 事务管理的基本概念

  • 定义: 事务是一组操作的集合,要么全部成功,要么全部失败。
  • ACID属性:
    • 原子性 (Atomicity): 事务中的所有操作要么全部完成,要么一个也不完成。
    • 一致性 (Consistency): 事务的执行不会破坏数据的一致性。
    • 隔离性 (Isolation): 事务之间是隔离的,不会相互影响。
    • 持久性 (Durability): 一旦事务提交,它的效果是持久的。

5.6.2 Spring事务管理器

  • 定义: Spring提供了多种事务管理器DataSourceTransactionManager
  • 示例:
    @Configuration
    @EnableTransactionManagement
    public class AppConfig {
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    

5.6.3 使用@Transactional进行声明式事务管理

  • 定义: 使用@Transactional注解来声明式地管理事务。
  • 示例:
    @Service
    @Transactional
    public class UserService {
        private final UserRepository userRepository;
    
        @Autowired
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        public void createUser(User user) {
            userRepository.save(user);
        }
    }
    

5.6.4 使用编程式事务管理

  • 定义: 通过编程的方式显式地管理事务。
  • 示例:
    @Service
    public class UserService {
        private final UserRepository userRepository;
        private final PlatformTransactionManager transactionManager;
    
        @Autowired
        public UserService(UserRepository userRepository, PlatformTransactionManager transactionManager) {
            this.userRepository = userRepository;
            this.transactionManager = transactionManager;
        }
    
        public void createUser(User user) {
            TransactionDefinition def = new DefaultTransactionDefinition();
            TransactionStatus status = transactionManager.getTransaction(def);
            try {
                userRepository.save(user);
                transactionManager.commit(status);
            } catch (Exception e) {
                transactionManager.rollback(status);
                throw e;
            }
        }
    }