Codex VibeCoing | 泛型的理解 | @Transactional | strip() | IOC&DI
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应该注入哪一个呢。
更多推荐



所有评论(0)