1 概述
Spring Data JPA是一个框架,对 Java Persistence API 进行再次封装。 Java Persistence API 是Java EE中的规范,它的实现产品有最有名的如Hibernate、Eclipse Link等,都是ORM框架。而ORM的本质是对JDBC进行了封装。ORM可以让程序员以OO的思维来操作关系数据库。
使用Spring Data JPA有以下优点:
不再需要手动建表
不再需要 写插入SQL
不再需要 写修改SQL
不再需要 写删除SQL
简单的 查询SQL, 不要写(查找一条和查找所有记录),复杂的需要写
2 关键技术
如何使用Spring Data JPA呢?
第一步,在Spring boot工程中引入依赖。
org.springframework.boot spring-boot-starter-data-jpa 第二步,建立一个数据库,并建立数据库访问的用户名和密码,并设置数据库访问权限。过程略。第三步,设置数据库连接属性。在application.properties配置文件中添加以下配置:
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/liyongzhendb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=liyongzhendb
spring.datasource.password=liyongzhendb
#Spring Boot 2.0 includes HikariDataSource by default
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=12
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
spring.datasource.hikari.auto-commit=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.properties.hibernate.id.new_generator_mappings=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
spring.jpa.show-sql=true
配置很通俗易懂。仅介绍下spring.jpa.hibernate.ddl-auto,它的值是update,意思是实体有变动,会相应的修改表,如实体添加属性,表会相应添加字段。
更多application.properties配置见官方文档 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
第五步,创建实体。实体即是轻量级持久对象域。通常,实体表示关系数据库中的表,并且每个实体实例对应于该表中的一行。更多理论参数Java EE官方文档 https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html
创建的实体如下:
package com.wangshenghua.model;
import java.time.LocalDate;
import java.util.Arrays;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
@Entity
@Table(name = “user”)
public class User {
@NotNull
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotNull(message = "请输入姓名")
@Size(min = 2, max = 30, message = "姓名在2~30个字符之间")
private String name;
@NotNull(message = "请输入年龄")
@Min(message = "年龄至少15岁", value = 15)
private Integer age;
@NotNull(message = "请选择性别")
private String gender;
@NotNull
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate dateOfBirth;
@NotEmpty(message = "请输入邮箱")
@Email(message = "邮箱格式不对")
private String email;
@NotNull
@Size(min = 1, max = 5, message = "选择课程")
private String[] course;
@NotEmpty(message = "请选择故乡")
private String hometown;
@NotEmpty(message = "请选择兴趣爱好")
private String[] hobbies;
private String memo;
public User() {
}
public User(@NotNull long id,
@NotNull(message = "请输入姓名") @Size(min = 2, max = 30, message = "姓名在2~30个字符之间") String name,
@NotNull(message = "请输入年龄") @Min(message = "年龄至少15岁", value = 15) Integer age,
@NotNull(message = "请选择性别") String gender,
@NotEmpty(message = "请输入邮箱") @Email(message = "邮箱格式不对") String email) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.email = email;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String[] getCourse() {
return course;
}
public void setCourse(String[] course) {
this.course = course;
}
public String getHometown() {
return hometown;
}
public void setHometown(String hometown) {
this.hometown = hometown;
}
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
public String getMemo() {
return memo;
}
public void setMemo(String memo) {
this.memo = memo;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + ", dateOfBirth="
+ dateOfBirth + ", email=" + email + ", course=" + Arrays.toString(course) + ", hometown=" + hometown
+ ", hobbies=" + Arrays.toString(hobbies) + ", memo=" + memo + "]";
}
}
实体有3个关键点:其一注释@Entity指定此类是一个实体。其二注释@Table指定此实体在关系数据库中映射的表。其三注释@Id和@GeneratedValue用于指定主键和主键生成策略。
第六步,创建实现CrudRepository接口的接口。实现CrudRepository接口就具备了CURD的能力。
package com.wangshenghua.dao;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.wangshenghua.model.User;
@Repository
public interface UserRepository extends CrudRepository
}
Spring框架在分层上规划特别好。数据库访问层,使用注释@Repository。别使用@Controller或@Service哦。
CrudRepository接口是泛型,这里需要代入具体的实体名User,及实体的主键类型Long。
第七步,控制器层如何调用UserRepository?
package com.wangshenghua.controller;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import com.wangshenghua.dao.UserRepository;
import com.wangshenghua.model.User;
@Controller
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
final static Map RADIO_GENDER = Collections.unmodifiableMap(new LinkedHashMap() {
private static final long serialVersionUID = 1L;
{
put("男", "男");
put("女", "女");
}
});
final static Map CHECK_COURSE = Collections.unmodifiableMap(new LinkedHashMap() {
private static final long serialVersionUID = 1L;
{
put("Java程序设计", "java");
put("Spring", "spring");
put("MySQL", "mysql");
put("HTML", "html");
}
});
final static Map SELECT_HOMETOWN = Collections.unmodifiableMap(new LinkedHashMap() {
private static final long serialVersionUID = 1L;
{
put("郴州", "郴州");
put("耒阳", "耒阳");
put("广州", "广州");
}
});
final static Map SELECT_HOBBIES = Collections.unmodifiableMap(new LinkedHashMap() {
private static final long serialVersionUID = 1L;
{
put("打球", "打球");
put("听歌", "听歌");
put("玩游戏", "玩游戏");
put("写代码", "写代码");
put("吃烧烤", "吃烧烤");
}
});
@Autowired
private UserRepository userDao;
/** 控制器的方法 **/
@GetMapping("/")
public String index() {
return "redirect:/allUser";
}
@GetMapping("/allUser")
public String allUser(Model model) {
model.addAttribute("users", userDao.findAll());
return "list-user";
}
@GetMapping("/adduser")
public String showAddUserForm(Model model) {
User user = new User();
model.addAttribute("user", user);
model.addAttribute("radioItems", RADIO_GENDER);
model.addAttribute("checkItems", CHECK_COURSE);
model.addAttribute("selectItems", SELECT_HOMETOWN);
model.addAttribute("multipleItems", SELECT_HOBBIES);
return "add-user";
}
@PostMapping("/adduser")
public String addUser(@Valid User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("user", user);
model.addAttribute("radioItems", RADIO_GENDER);
model.addAttribute("checkItems", CHECK_COURSE);
model.addAttribute("selectItems", SELECT_HOMETOWN);
model.addAttribute("multipleItems", SELECT_HOBBIES);
return "add-user";
}
userDao.save(user);
return "redirect:/allUser";
}
@GetMapping("/edit/{id}") // {id}是占位符
public String showUpdateForm(@PathVariable("id") long id, Model model) { // @PathVariable 路径变量
Optional user = userDao.findById(id);
model.addAttribute("user", user);
return "update-user";
}
@PostMapping("/update/{id}")
public String updateUser(@PathVariable("id") long id, @Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "update-user";
}
userDao.save(user);
return "redirect:/allUser";
}
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") long id, Model model) {
userDao.deleteById(id);
return "redirect:/allUser";
}
}
别看代码多,关键代码仅几行:
@Autowired private UserRepository userDao;采用自动注入的方式注入 UserRepository 实例。
userDao.findAll()查找所有,实则调用CrudRepository接口的方法Iterable findAll();
userDao.findById(id)根据主键查找实体, 实则调用CrudRepository接口的方法Optional findById(ID id)
userDao.deleteById(id)根据主键删除实体, 实则调用CrudRepository接口的方法void deleteById(ID id);
第八步,在Spring boot工程的主程序@SpringBootApplication添加扫描实体所在包和Repository所在包的配置。如下:
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = “com.wangshenghua.dao”)
@EntityScan(basePackages = “com.wangshenghua.model”, basePackageClasses = { Application.class, Jsr310JpaConverters.class })
@EnableTransactionManagement表示启用事务管理
@EnableJpaRepositories表示启用 Repository 操作数据库
@EntityScan表示扫描实体
3 总结
本节课程我们学习Spring Data JPA操作关系数据库,来实现增删改查功能。使用 Spring Data JPA操作关系数据库非常简单(相对JDBC而言)。关键步骤仅8步。
越是使用简单的东东,底层代码越是复杂。不仅是Spring对底层进行抽象封装,Java EE组织对JDBC的抽象和封装从未止步。正是有这此组织和个人的除出,才有Java今天的地位。
本节课程源码已经上传到github,可以前往下载。