面向Java技术栈较为落后的读者,比如我,很久以前大三大四的时候写过Java Web项目,现在过去快十年了,语法和架构进步了很多,各种注解满天飞。作为开发备忘,供以后参考。
1、工程搭建
使用IDEA 2025进行开发,ide自带Spring Boot项目类型(生成器),需要注意:
- 语言选择Java。现在新的语言如Kotlin和Groovy好像都很火,但是老人还是减少学习成本,就用Java。
- 类型选择Maven。Gradle是一种新的项目构建工具,同上,我还是就用Maven吧。需要注意的是,IDEA是自带Maven的,当然也可以配置路径为自己安装的Maven。
- JDK和Java版本选择相同。此处我没有仔细求证版本能否选择不同,如有了解请告诉我。我以为JDK是编译的环境,JAVA可能是运行的环境。对于新版本Spring 3.x,需要JDK 17以上,我选择了更新一点的21版本。
- 打包选择Jar。Jar就是自带tomcat可以独立运行的,War是只有服务,需要放在服务器路径下。
点击下一步,需要选择该工程引入的依赖库,对应的是pom文件的dependencies。作为一个需要连接数据库(Postgre)、提供Rest api接口的标准后台服务,我最终选择如下:
- Spring Boot DevTools,这是Spring Boot的基础依赖。
- Spring Web,这是支持网络服务的依赖,如果不添加那就只是一个本地运行的后台小程序。
- Spring Data JPA,这是一种数据访问封装框架,我以前只用过JDBC直接连,这次想体验基于实体的数据库连接方法。
- PostgreSQL Driver,数据库连接驱动。
- Lombok,基于注解的开发绕不过去的依赖库,不是很了解,本来没想用,但是写着写着发现真得用。
其他的,JDBC可能是我误添加,CycloneDX我也不知道怎么添加进去了。。总之,后面还手动添加了hibernate-spatial空间依赖,完整的pom依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-spatial</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2、工程结构
对于新创建的工程,Maven会先进行依赖管理,下载相关库。
这一步如果很慢可能需要进行Maven配置,如设置国内镜像等。下图中,虽然我用的配置文件和路径是我本机安装的Maven,但是实际IDEA运行时还是用的自带的Maven,仅仅是从我设置的路径读取配置罢了。
然后出于开发习惯,需要对新的工程做两个准备:
- 将项目配置文件改为yml格式。在src/main/resources文件夹下,默认的项目配置文件为 application.properties,建议改为application.yml,可读性和可操作性都更强。
- 准备好目录结构。根据经典的MVC开发架构,需要在src/main/java/com.xx.xx文件夹下设置好几个空的目录和文件,如下图所示。
其中:
- entity/MyTestEntity是实体Java类,与数据库中的表相互对应;
- dao/MyTestRepository是一个数据访问的接口,用于获取数据,可以理解为与SQL查询语句对应;
- service/MyTestService是使用数据的核心业务类,对数据进行操作处理;
- controller/MyTestController是用户接口类,与具体的网络服务对应。
下面贴出我的application.yml配置以供参考
最后,我的数据库测试表结构如下图所示:
3、代码实现
Entity
在类外面,@Entity注解表明该实体类与数据表对应,@Table注解指定了表名(如果不指定就会按类名去寻找),@NoArgsConstructor声明该类不用写一个无参构造函数,否则Entity默认需要一个 public 或者 protected 的无参构造函数。最后使用了lombok的@Getter和@Setter注解,为了给所有的类属性(也就是数据表的列)自动添加get和set方法。
在类里面,用到了@Id和@GeneratedValue表明自增长的主键对应的属性。
需要说明的是,属性的变量类型需要和数据表中的对应,否则会赋值不成功(比如用一个bool型属性去接表里的数值型字段)。
@Entity
@NoArgsConstructor
@Getter
@Setter
@Table(name = "test")
public class MyTestEntity {
@Id
@GeneratedValue
private Integer id;
private String name;
private String date;
private Double score;
}
Dao
Dao层的实现是一个接口,命名为MyTestRepository,而不是一个类,这是为了能够继承JpaRepository<Entity, Integer>这个通用的数据接口。这是为了使用Query注解,括号内是JPQL语言,可以看到from后面的是Entity而不是表。当然,也可以通过指定nativeQuery = true来使用原始SQL语言查询,这更多用于多表关联查询的情况。
public interface MyTestRepository extends JpaRepository<MyTestEntity, Integer> {
@Query("select t from MyTestEntity t where t.name=:name")
List<MyTestEntity> findByName(@Param("name") String name);
@Query("select t from MyTestEntity t where t.date=:date")
List<MyTestEntity> findByDate(@Param("date") String date);
@Query("select t from MyTestEntity t where t.name=:name and t.date=:date")
List<MyTestEntity> findByNameAndDate(@Param("name") String name, @Param("date") String date);
}
Service
Service层是业务逻辑层,调用数据库查询,实现某种具体业务计算。具体地,首先服务类通过@Service进行注解,然后内部有一个Dao层的用于进行数据获取的属性。
由于MyTestRepository是一个接口,没有类的实现代码,因此用到了@Autowired注解,表明我不管类的实现,要代码自动帮忙完成这个流程(所谓 自动注入)。有了这个属性,就可以很自然的调用相关数据查询方法了。
@Service
public class MyTestService {
@Autowired
private MyTestRepository myTestRepository;
public List<MyTestEntity> findByName(String name){
return myTestRepository.findByName(name);
}
public List<MyTestEntity> findByDate(String date){
return myTestRepository.findByDate(date);
}
public List<MyTestEntity> findByNameAndDate(String name,String date){
return myTestRepository.findByNameAndDate(date,name);
}
}
Controller
最后的最后,在用户接口层,首先用@RestController指定这是个Rest接口类,然后同样的用@Autowired完成属性的注入(避免写构造函数,避免在构造函数里new,简化代码)。
通过@GetMapping注解将接口url和具体函数对应,同时用json包装service层的计算结果。
@RestController
public class MyTestController {
@Autowired
private MyTestService myTestService;
@GetMapping("/getbyname")
public String getByName(@RequestParam(value = "name") String name) throws JsonProcessingException {
try {
System.out.println("name: " + name);
List<MyTestEntity> list = myTestService.findByName(name);
System.out.println("查询结果数量 = " + list.size());
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(list);
return jsonString;
}catch (Exception e) {
e.printStackTrace();
return "发生异常:" + e.getMessage();
}
}
}
最后看一看运行结果: