一.Spring Data Jpa 简介
JPA
JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338,这些接口所在包为javax.persistence)。JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。总的来说,JPA包括以下3方面的技术:
- ORM映射元数据: 支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系
- API: 操作实体对象来执行CRUD操作
- 查询语言: 通过面向对象而非面向数据库的查询语言(JPQL)查询数据,避免程序的SQL语句紧密耦合
Jpa、Hibernate、Spring Data Jpa三者之间的关系
JPA是ORM规范,Hibernate、TopLink等是JPA规范的具体实现,这样的好处是开发者可以面向JPA规范进行持久层的开发,而底层的实现则是可以切换的。Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现),极大地简化持久层开发及ORM框架切换的成本。
二.使用
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
- 相关配置
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: mysql123
jpa:
database: MySQL
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
ddl-auto: update
ddl-auto upadte:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新
3. 实体类
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Entity
@Table(name = "tb_user")
@Data
public class User {
@Id
@GeneratedValue
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", length = 64)
private String email;
}
@Table 使用一个特定的数据库表格来保存主键
@GeneratedValue表明是逐渐自增
@Column 数据库中的列和实体类对应
- Dao层--
extends JpaRepository
package com.example.springbootjpa.repository;
import com.example.springbootjpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, String> {
}
先省略service层
5.Controller层
package com.example.springbootjpa.controller;
import com.example.springbootjpa.entity.User;
import com.example.springbootjpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping()
public User saveUser(@RequestBody User user) {
return userRepository.save(user);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable("id") String userId) {
userRepository.deleteById(userId);
}
@PutMapping("/{id}")
public User updateUser(@PathVariable("id") String userId, @RequestBody User user) {
user.setId(userId);
return userRepository.saveAndFlush(user);
}
@GetMapping("/{id}")
public User getUserInfo(@PathVariable("id") String userId) {
Optional<User> optional = userRepository.findById(userId);
return optional.orElseGet(User::new);
}
@GetMapping("/list")
public Page<User> pageQuery(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return userRepository.findAll(PageRequest.of(pageNum - 1, pageSize));
}
}
在这里使用RestFul风格,浏览器必须安装插件。普通浏览器不能发送更新,删除请求。如下,火狐浏览器中安装RESTClient插件
6.就可以测试啦
package com.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.bean.Gender;
import com.example.bean.User;
import com.example.dao.UserDao;
@SpringBootTest
class DemoJpaApplicationTests {
@Autowired
private UserDao dao;
@Test
void contextLoads() {
dao.save(new User("lyt",20,Gender.WOMAN));
}
}
Spring Data查询方法
Spring Data JPA中提供了强⼤的查询功能,我们只需要在dao层接⼝中按规则,定出对应的查询⽅法,
那么Spring Data JPA会根据我们所定义的⽅法名,⾃动⽣成对应的sql语句,例如:
public interface UserDao extends JpaRepository<User, Long>{
User findByName(String name);
List<User> findByAge(int age);
List<User> findByNameOrAge(String name, int age);
List<User> findByNameLike(String name);
User findByNameIgnoreCase(String name);
List<User> findByAgeOrderByNameDesc(int age);
//first和top的效果是⼀样的,默认去第⼀条数据
User findFirstByOrderByAgeAsc();
User findFirstByOrderByAgeDesc();
User findTopByOrderByAgeAsc();
User findTopByOrderByAgeDesc();
//first和top后⾯都可以跟数字,表示取查询出来数据的前N条数据
List<User> findFirst2ByGender(Gender gender);
}
当在测试类中调⽤我们⾃⼰在接⼝中定义的 findByName ⽅法的时候,会⾃动⽣成对应的sql语句:
@Test
public void test_findByName() throws Exception {
User user = userDao.findByName("briup");
System.out.println(user);
}
当在测试类中调⽤我们⾃⼰在接⼝中定义的 findFirst2ByGender ⽅法的时候,会⾃动⽣成对应的sql
语句:
@Test
public void test_findFirst2ByGender() throws Exception {
List<User> list = userDao.findFirst2ByGender(Gender.MAN);
list.forEach(System.out::println);
}
4. 排序
Spring Data JPA中可以使⽤ Sort 对查询的结果进⾏排序,让 Sort 作为查询⽅法的参数即可。
⽗接⼝中已经有定好的⽅法的参数含有 Sort ,例如 JpaRepository 中的 List findAll(Sort
sort);
也可以⾃⼰在接⼝中⾃定义⽅法的参数中添加 Sort 参数:
public interface UserDao extends JpaRepository<User, Long>{
List<User> findByGender(Gender gender,Sort sort);
List<User> findFirst25ByGender(Gender gender, Sort sort);
}
@Test
public void test_sort1() throws Exception {
List<User> list = userDao.findAll(Sort.by(Direction.DESC, "age"));
list.forEach(System.out::println);
}
5.分页
Spring Data JPA中分⻚功能和排序的使⽤⽅式类似,只需要在查询⽅法的参数列表中添加⼀
个 Pageable 类型的参数即可。
⽗接⼝中已经有定好的⽅法的参数含有 Pageable ,例如 PagingAndSortingRepository 中的
Page findAll(Pageable pageable);
也可以⾃⼰在接⼝中⾃定义⽅法的参数中添加 Pageable 参数:
public interface UserDao extends JpaRepository<User, Long>{
Page<User> findByGender(Gender gender,Pageable pageable);
Page<User> findFirst25ByGender(Gender gender, Pageable pageable);
List<User> findTop25ByGender(Gender gender, Pageable pageable);
}
⽅法的返回类型是可以是Page类型的,也可以是List集合类型的,只是Page类型的返回值可以提
供更多的分⻚相关信息的获取。
@Test
public void test_pageable1() throws Exception {
int page = 0;//当前⻚数--第⼀⻚从0开始计算
int size = 2;//每⻚指定多少个元素
Pageable pageable = PageRequest.of(page,size);
Page<User> pageObj = userDao.findAll(pageable);
System.out.println("总⻚数:\t\t"+pageObj.getTotalPages());
System.out.println("总数据量:\t\t"+pageObj.getTotalElements());
System.out.println("当前的⻚码:\t\t"+pageObj.getNumber());
System.out.println("每⻚条数:\t\t"+pageObj.getSize());
System.out.println("当前⻚实际条数:\t\t"+pageObj.getNumberOfElements());
System.out.println("当前⻚是否有数据:\t\t"+pageObj.hasContent());
System.out.println("当前⻚内容:\t\t"+pageObj.getContent());
System.out.println("分⻚查询的排序规则为:\t\t"+pageObj.getSort());
System.out.println("当前是否为第⼀⻚:\t\t"+pageObj.isFirst());
System.out.println("当前是否为最后⼀⻚:\t\t"+pageObj.isLast());
System.out.println("当前是否有上⼀⻚:\t\t"+pageObj.hasPrevious());
System.out.println("当前是否有下⼀⻚:\t\t"+pageObj.hasNext());
如果返回值类型使⽤List⽽不是Page类型的 ,那么就⽆法直接获取到这些分⻚相关的数据信息。
6.注解
Spring Data JPA中可以在接⼝中⾃定义⽅法上⾯使⽤ @Query 注解,在该注解中可以使⽤JPQL或SQL来
指定此⽅法被调⽤的时候需要执⾏的sql语⾔是什么。
JPQL(JavaPersistence Query Language)是⼀种⾯向对象的查询语⾔,它在ORM框架中最终会翻译成为sql进⾏执⾏。在hibernate框架中,这种语句叫做HQL(Hibernate Query Language)。
- PQL是⾯向对象的查询语⾔,在查询语句中,出现的不是表的名字、字段的名字,⽽是类的名字和类中
属性的名字,因为在ORM框架中,类和表,属性和字段都做好了映射关系,所以JPQL最后是可以根据映
射关系转换sql语句的。 - JPQL的特点:
语句中不能出现表名,列名,只能出现java的类名,属性名,并且区分⼤⼩写
语句中出现的关键字和sql语句中的是⼀样的意思,不区分⼤⼩写
语句中不能写select * ⽽是要写select 类的别名,或者写select 具体的属性名
@Query("select u from User u where u.name = ?1")
User findByUserName(String name);
该语句中的 ?1 代表第⼀个参数,?2 代表第⼆个参数
7.关联
Spring Data JPA中除了单表操作外,还可以⽀持多表级联操作,但是这需要表和表之间有关联关系,同
时还需要在实体类中把这种关联关系给配置映射出来(Object-Relationl Mapping)。
1对1关系:
Address
package com.briup.bean;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "t_address")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String city;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
Student
package com.briup.bean;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name = "t_studnet")
public class Student {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
/**
* @OneToOne 表示当前studnet实体类和Address实现类之间是1对1关系
* cascade=CascadeType.ALL 表示student和address之间的级联类型为ALL
*
* @JoinColumn 该注解专⻔⽤来指定外键列的名字
* 在这⾥表示:将来student表中将会有⼀个外键列为 address_id,
* 该外键列默认引⽤Address表中的主键列(id)的值
*/
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name = "address_id")
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
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 interface StudentDao extends JpaRepository<Student, Long> {
}
@Autowired
private StudentDao studentDao;
@Test
public void test_one2one() throws Exception {
}
运⾏测试⽅法后,可以看到建表语句,根据我们配置的映射信息,之间建表
- 如果需要,可以进⾏⾃动建表
- create可以每次启动测试先进⾏删除表,然后再建表
- update可以每次启动测试时先检查数据库中是否有表,没对应的表则会⾃动建表,有表单话就不再创建了
spring.jpa.hibernate.ddl-auto=update
测试:
@Test
public void test_one2one_insert() throws Exception {
Student stu = new Student();
Address address = new Address();
address.setCity("上海");
stu.setName("tom");
//学⽣对象和地址对象在内存中建⽴关系,最后会映射成数据库中俩个表中数据的关系
stu.setAddress(address);
studentDao.save(stu);
}
@Test
public void test_one2one_select() throws Exception {
Student stu = studentDao.findById(1L).orElse(null);
System.out.println(stu);
System.out.println(stu.getAddress());
}
1对N关系:
假设⼀个⽼师有多辆⻋,⼀辆⻋只属于⼀个⽼师,那么⽼师和汽⻋的关系就是1对N
- 注意,1对N关系中,数据库表中的外键列需要设置在N的⼀⽅,否则会有数据冗余。
package com.briup.bean;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "t_teacher")
public class Teacher {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
private Double salary;
/**
* @OneToMany 表示我(teacher)和对⽅(car)之间是⼀对多关系
*
* mappedBy="teacher"
* 表示我们之间⼀多对关系的外键列
* 是由对⽅(car)的teacher属性类映射维护的
*
*/
@OneToMany(mappedBy="teacher",cascade=CascadeType.ALL,fetch =
FetchType.EAGER)
private List<Car> cars = new ArrayList<>();
public List<Car> getCars() {
return cars;
}
public void setCars(List<Car> cars) {
this.cars = cars;
}
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 Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
}
Car
package com.briup.bean;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "t_car")
public class Car {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String type;
private Double price;
/**
* @ManyToOne 表示我(car)和对⽅(teacher)之间的关系是多对⼀
* @JoinColumn 表示当前属性为外键列属性,并且指定外键列的名字
*/
@ManyToOne
@JoinColumn(name="teacher_id")
private Teacher teacher;
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
} }
package com.briup.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.briup.bean.Teacher;
public interface TeacherDao extends JpaRepository<Teacher, Long> {
}
package com.briup.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.briup.bean.Car;
public interface CarDao extends JpaRepository<Car, Long> {
}
级联保存
@Autowired
private TeacherDao teacherDao;
@Test
public void test_one2many_insert() throws Exception {
Teacher t = new Teacher();
t.setName("tom");
t.setSalary(10000D);
Car c1 = new Car();
c1.setType("BMW");
c1.setPrice(300000D);
Car c2 = new Car();
c2.setType("BENZ");
c2.setPrice(400000D);
//很重要的步骤,建⽴起⽼师对象和汽⻋对象之间的关联关系
//这个关系会映射到数据库中
c1.setTeacher(t);
c2.setTeacher(t);
List<Car> list = new ArrayList<>();
list.add(c1);
list.add(c2);
t.setCars(list);
teacherDao.save(t);
}
@Test
public void test_one2many_select() throws Exception{
Teacher t = teacherDao.findById(1L).orElse(null);
System.out.println(t);
System.out.println(t.getCars());
}
N对N关系:
注意,1对N关系中,数据库中需要有第三张桥表来关联另外俩张表。
player
package com.example.bean;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "t_player")
public class Player {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
/**
* mappedBy属性,在双向关联中,必定会出现
*
* mappedBy表示 外键的配置/维护 由对⽅来处理
* 如果不配置mappedBy,则默认表示 外键的配置/维护 由我⽅来处理
*
* mappedBy="gameList" 表示 我⽅(Game)和对⽅(Player)之间关系的维护
* 是由对⽅(Player)的属性(gameList)来进⾏配置/维护的。
* 其实是由对⽅(Player)中的属性(gameList)上的注解进⾏配置/维护的
*
*/
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name="player_game",
joinColumns=@JoinColumn(name="player_id"),
inverseJoinColumns=@JoinColumn(name = "game_id")
)
private List<Game> gameList = new ArrayList<>();
public Player() {}
public Player(String name) {
this.name = name;
}
public List<Game> getGameList() {
return gameList;
}
public void setGameList(List<Game> gameList) {
this.gameList = gameList;
}
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;
}
@Override
public String toString() {
return "Player [id=" + id + ", name=" + name + ", gameList=" + "]";
}
}
game
package com.example.bean;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "t_game")
public class Game {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy="gameList",fetch = FetchType.EAGER)
private List<Player> playerList = new ArrayList<>();
public Game() {}
public Game(String name) {
this.name = name;
}
public List<Player> getPlayerList() {
return playerList;
}
public void setPlayerList(List<Player> playerList) {
this.playerList = playerList;
}
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;
}
@Override
public String toString() {
return "Game [id=" + id + ", name=" + name + "]";
}
}
package com.example.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.bean.Game;
public interface GameDao extends JpaRepository<Game, Long>{
}
package com.example.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.bean.Player;
public interface PlayerDao extends JpaRepository<Player,Long>{
}
@Test
public void test02() {
/*
* Player p1 = new Player(); p1.setName("tom1"); Player p2 = new Player();
* p2.setName("tom2"); Game g1 = new Game(); g1.setName("疯狂篮球"); Game g2 = new
* Game(); g2.setName("疯狂⾜球"); Game g3 = new Game(); g3.setName("疯狂排球");
* List<Game> gameList1 = new ArrayList<>(); gameList1.add(g1);
* gameList1.add(g2); gameList1.add(g3); List<Game> gameList2 = new
* ArrayList<>(); gameList2.add(g1); gameList2.add(g3); //建⽴对象之间的关联关系
* p1.setGameList(gameList1); p2.setGameList(gameList2); //注意,这样要记得先保存game对象的数据
* //然后再保存player对象的数据,因为是让player关联的game gameDao.save(g1); gameDao.save(g2);
* gameDao.save(g3); playerDao.save(p1); playerDao.save(p2);
*/
Player p = playerDao.findById(1L).orElse(null);
System.out.println(p);
System.out.println(p.getGameList());
}