在国内,使用jpa的人可谓是少的可怜。甚至已经出现了hibernate被mybatis替代的说法。翻翻各大社区,基本都是hibernate已死的论调。但在欧美地区,压根就没有mybatis这种sql模板工具的市场。jpa中文资料极度缺乏。网上搜的各种资料也都是浅尝辄止。一旦遇到一点复杂的需求,就没有下文。
我用jpa许多年了。也看了很多人们对于jpa的吐槽。就我碰到的问题和在各大论坛遇到的问题,做个总结,希望人们少点对jpa的偏见。
1. 简介
1.1 JPA和hibernate的关系
JPA是Java 官方的 ORM 标准,Hibernate是JPA规范的一个实现。他们的关系类似于。servlet和tomcat,jdbc和MySQL Connector/J。
它定义了:
- 如何用注解(如 @Entity, @Id, @OneToMany)描述对象与数据库表的映射;这只是一堆注解,没有解析器它就是个摆设。
- 如何通过 EntityManager 进行增删改查;这玩意是个接口。就行jdbc的Connection一样,里面只有一堆方法定义。jpa不提供任何实现。
- JPQL(Java Persistence Query Language)语法等。这个不是sql,他翻译过来是java持久化对象查询语言,它查询的是对象。而不是数据库。
除了hibernate之外,还有几个Jpa的实现:EclipseLink, OpenJPA。但它们在实现成熟度上都没有hibernate高,而spring默认的jpa实现就是Hibernate。
1.2. 使用jpa的优势
- 完整 ORM(Object-Relational Mapping),将数据库表映射为 Java 对象,支持继承、关联、生命周期回调等 OOP 特性;
- 编译期类型安全 + IDE 自动补全;代码没有文本字符串性的内容,错误,重构IDE都能自动解决。
- 数据库方言,使用配置就可切换数据库实现,不需要改xml文件里面的特定位置(这个在某些情况下会失效)
- Spring的官方支持,我认为这个是非常重要的。面向Spring编程的javaer们,Spring的选择值得信赖。
- 对graalvm-native的支持完美,在Spring的框架体系下,想用native镜像,最好选择JPA,graalvm-native是质的飞跃。
1.3. 使用JPA的劣势
- 学习成本高,注解确实多,从数据库反向的结果质量低。建议只从代码构建数据库字段。
- 复杂的嵌套关联查询有点难搞,需要借助第三方工具实现。
2. 起步
下面通过一个简单的例子来 User,Role模型,来实现一个jpa完整工程
2.1 依赖引入
本项目的示例基于spring boot 4.0,所有有些依赖的写法和spring boot 3.x不一致。
要在spring boot项目中使用spring data jpa,只需要添加spring boot data jpa相关的依赖即可。
dependencies {
// 添加spring boot data jpa依赖
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
// 添加h2数据库控制台依赖
implementation("org.springframework.boot:spring-boot-h2console")
// 添加h2数据库依赖
runtimeOnly("com.h2database:h2")
}
其它依赖说明
- spring-boot-h2console 本示例使用h2作为测试数据库,以方便大家下载即用,这个依赖提供了一个网页上查看h2数据库的控制台。
- com.h2database:h2 这个是h2的数据库驱动
2.2 添加配置文件
在application.yml加入相关的数据库连接配置
spring:
datasource:
url: "jdbc:h2:~/test"
username: sa
password: ""
jpa:
hibernate:
ddl-auto: update
show-sql: true
h2:
console:
enabled: true
基于以上配置,可以实现如下效果
- 项目启动时,连接到本地h2数据库。数据库文件持久化在 ~/test.mv.db 中
- 数据库初始用户名,以及后续连接用户名为sa,密码为空
- 项目启动时,会根据实体配置,自动更新表结构。
- 运行时,会在控制台打印sql语句。
2.3 构建User模型
在model包中,构建User模型。这个模型是和数据库结构一致的。
@Entity
@Table(name = "t_user_")
class User {
@Id
@Column(name = "id_")
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
@Column(name = "username_", length = 50)
var username: String = ""
@Column(name = "password_", length = 64)
var password: String = ""
@Column(name = "created_time_")
val cratedTime: ZonedDateTime = ZonedDateTime.now()
@Column(name = "last_modified_time_")
var lastModifiedTime: ZonedDateTime = ZonedDateTime.now()
}
这个模型就是一个简单的java类,在上面添加了一些注解。这些注解都位于jakarta.persistence包下,这些就是jpa的核心。它们是:
- @Entity 必需,这是JPA实体的核心注解,它标识了这个类是一个jpa数据实体,通过它和数据库进行交互
- @Id 必需,标识实体的主键属性。每个实体必须有一主键属性。
- @Table 可选,它用于标识实体在数据库中的相关配置,包括名称,索引,约束。
- @Column 可选,它标识了这个属性在数据库中对应的字段配置,包括长度,精度
- @GeneratedValue 可选 标识这个属性在保存时通过某种方式自动生成的。我们在代码中不需要对他进行赋值
- 更多的注解会在后面引入,有了现在这些就可以实现简单的增删改查了。
我们不需要在@Column中指定数据类型,JPA引擎会自动做好java基础类型到数据库类型的映射。
2.4 创建repository接口
接下来,创建一个接口,继承自JpaRepository,然后就可以在项目中注入这个接口,并通过它来实现增删改查
interface UserRepository : JpaRepository<User, Long>
2.5 创建相应的Service
我们一般在service中定义业务方法。在service中注入repository实现对应的业务逻辑。
- UserService.kt
interface UserService {
fun save(request: UserRequest): UserDto
fun update(id: Long, request: UserRequest): UserDto
fun delete(id: Long)
fun findById(id: Long): UserDto?
fun find(pageable: Pageable): PagedModel<UserDto>
}
- UserServiceImpl.kt
@Service
class UserServiceImpl(
private val userRepository: UserRepository
) : UserService {
@Transactional
override fun save(request: UserRequest): UserDto {
val user = User().apply {
username = request.username
password = request.password
lastModifiedTime = ZonedDateTime.now()
}
val savedUser = userRepository.save(user)
return UserDto(savedUser)
}
@Transactional
override fun update(id: Long, request: UserRequest): UserDto {
val user = userRepository.findByIdOrNull(id) ?: throw IllegalArgumentException("用户不存在")
user.apply {
username = request.username
password = request.password
lastModifiedTime = ZonedDateTime.now()
}
val updatedUser = userRepository.save(user)
return UserDto(updatedUser)
}
@Transactional
override fun delete(id: Long) {
userRepository.deleteById(id)
}
@Transactional(readOnly = true)
override fun findById(id: Long): UserDto? {
return userRepository.findByIdOrNull(id)?.let { UserDto(it) }
}
@Transactional(readOnly = true)
override fun find(pageable: Pageable): PagedModel<UserDto> {
return userRepository.findAll(pageable).map {
UserDto.Companion(it)
}.let {
PagedModel(it)
}
}
}
整体的逻辑非常简单。构建对象,赋值,调用repository的save方法将数据库持久化到数据库。
接下来就可以在controller中调用service方法了。
@RestController
@RequestMapping("/users")
class UserController(
private val userService: UserService
) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun save(@RequestBody request: UserRequest): UserDto {
return userService.save(request)
}
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.CREATED)
fun update(@PathVariable id: Long, @RequestBody request: UserRequest): UserDto {
return userService.update(id, request)
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun delete(@PathVariable id: Long) {
userService.delete(id)
}
@GetMapping("/{id}")
fun findById(@PathVariable id: Long): UserDto? {
return userService.findById(id)
}
@GetMapping
fun find(
pageable: Pageable
): PagedModel<UserDto> {
return userService.find(pageable)
}
}
后续会持续的更新其它内容。直到jpa的进阶应用,欢迎关注。 项目地址:github.com/ldwqh0/jpa-…