import java.util.List;
import java.util.Optional;

import org.springframework.data.repository.Repository;

public interface StudentRepository extends Repository<Student, Long> {

    <S extends Student> S save(S student);

    Optional<Student> findById(Long id);

    List<Student> findAll();
}

其中

<S extends Student> S save(S student);

可以暂时理解为

Student save(Student student);

真正的语法含义是:

<S extends Student>

定义了一个方法级泛型 S。

意思是:

S 可以是 Student,也可以是 Student 的任何子类。

表示给我什么具体 S 类型,我保存以后还给你什么 S 类型。

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StudentService {

    private final StudentRepository repository;

    public StudentService(StudentRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public Student createStudent(String name, String grade, String notes) {
        String normalizedName = name == null ? null : name.strip();
        if (normalizedName == null || normalizedName.isEmpty()
                || normalizedName.length() > 20) {
            throw new IllegalArgumentException(
                    "Student name must contain 1 to 20 characters");
        }
        if (notes != null && notes.length() > 200) {
            throw new IllegalArgumentException(
                    "Student notes must not exceed 200 characters");
        }

        String normalizedGrade =
                grade == null || grade.isBlank() ? null : grade.strip();
        return repository.save(
                new Student(normalizedName, normalizedGrade, notes));
    }

    @Transactional(readOnly = true)
    public List<Student> listStudents() {
        return repository.findAll();
    }
}

@Transactional是事务注解

@Transactional(readOnly = true)
public List<Student> listStudents() {
    return repository.findAll();
}

则表示:

这个事务主要用于读取数据,不打算修改数据库。

readOnly = true 是给 Spring/JPA 一个提示:

这是查询操作
↓
不需要按照写事务处理

某些情况下可以帮助 ORM 和数据库做优化,也能让代码意图更清楚。

@Transactional

通常用于:

新增
修改
删除

而:

@Transactional(readOnly = true)

通常用于:

查询

这不是绝对语法规则,而是一种常见设计方式。


strip()是String的方法,用来去掉字符串首尾的空白字符。trim()也是这个功能,只不过方法比较老,新版本的java使用strip().


public interface StudentRepository
        extends Repository<Student, Long>
private final StudentRepository repository;

public StudentService(StudentRepository repository) {
    this.repository = repository;
}

StudentService 通过构造器依赖 StudentRepository 接口。Spring Data JPA 会为 Repository 接口生成实现,Spring 再通过依赖注入把这个实现传给 StudentService。

Spring 并不会给“每一个类”都生成对象。只有被 Spring 管理的类才会成为 Bean,添加了注解@Service、@Component、@Controller、@RestController\@Repository的类。扫描这些类后回生成对象放入Sping容器,当Spring需要某一个类的对象时,会直接从容器中取。

Spring 也不会因为“某个类依赖了一个接口”,就自动给任意接口生成实现类。在以上的案例中,虽然没有接口的StudentRepository的实现类,但是因为它属于 Spring Data Repository 体系,所以 Spring Data JPA 知道怎么为它创建运行时实现。

因此整个过程更准确地说是:

1. Spring 发现 @Service
   ↓
准备创建 StudentService Bean

2. 查看 StudentService 构造器
   ↓
发现它需要 StudentRepository

3. Spring 容器里必须找到一个
   StudentRepository 类型的 Bean

4. Spring Data JPA 恰好已经为
   StudentRepository 创建了实现对象

5. Spring 把这个对象传给构造器

6. StudentService 创建完成

Spring 在创建 Bean 时,需要先满足这个 Bean 的依赖。

如果一个接口没有实现类,也没有任何框架负责生成它的实现,那会在项目启动时报错:找不到Baen。

如果想要改正:3种方法

第一种:自己手动完成接口的实现类并让Spring管理它

最直接的是:你自己写一个实现类,并让 Spring 管理它。

public interface TestRepository {
    void save();
}

然后:

@Repository
public class TestRepositoryImpl implements TestRepository {

    @Override
    public void save() {
        System.out.println("保存数据");
    }
}

Spring启动时发现@Repository,就会创建一个TestRepositoryImpl Bean放入容器中,以便注入。

第二种:项目里的 Spring Data JPA 自动实现。

public interface TestRepository
        extends Repository<Student, Long> {
}

接口继承Repository。

第三种:用@Bean手动告诉 Spring 怎么创建。

例如:

public class TestRepositoryImpl implements TestRepository {

    @Override
    public void save() {
    }
}

这里我甚至可以不写:

@Repository

然后单独写配置类:

@Configuration
public class AppConfig {

    @Bean
    public TestRepository testRepository() {
        return new TestRepositoryImpl();
    }
}

意思是:

Spring,这个 TestRepository 对象你就按照我的方法创建。

Spring 一样可以注入:

public StudentService(TestRepository repository)

这种方式以后配置第三方组件时会经常见到。











以上。不过紧接着问题就又来了,如果我写了一个接口的两个实现类,那么Spring应该注入哪一个呢。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐