目录
1 概述
Spring Data:spring提供的操作数据库的框架,Spring Data JPA是Spring Data框架下的一个基于JPA标准操作数据的模块。
Spring Data JPA:基于JPA标准对数据库进行操作,简化持久层的代码操作。只需要编写接口即可。
本章节主要学习Spring Data JPA 核心的五个接口,以及如何实现双向一对多,多对多关联。
2 Spring Data JPA整合
通过自动创建表并插入一条数据实例来演示Spring Data JPA的整合。
2.1 pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<groupId>com.liulg</groupId>
<artifactId>12-spring-boot-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring data jpa启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa </artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
2.2 配置文件
src/main/resources/application.properties
#数据源配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#如果程序启动出现时区异常,需要添加后面的serverTimezone参数
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=truespring.datasource.username=root
spring.datasource.password=123456
#数据库连接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#jpa配置
#ddl-auto:create----每次运行该程序,没有表格会新建表格,表内有数据会清空
#ddl-auto:create-drop----每次程序结束的时候会清空表
#ddl-auto:update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
#ddl-auto:validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错
spring.jpa.hibernate.ddl-auto=update
#显示sql
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans= true
2.3 实体类
package com.liulg.pojo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name="name")
private String name;
@Column(name="age")
private Integer age;
@Column(name="address")
private String address;
public Integer getId() {
return id;
}
public void setId(Integer 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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
2.4 Dao接口
package com.liulg.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.liulg.pojo.User;
/**
* 参数-T:当前需要映射的实体
* 参数-ID:当前映射实体中的ID(主键)的类型
*
*/
public interface UserRepository extends JpaRepository<User, Integer> {
}
2.5 启动类
package com.liulg;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.6 编写测试类
package com.liulg.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.liulg.Application;
import com.liulg.dao.JPARepository;
import com.liulg.pojo.User;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes= {Application.class })
public class JPARepositoryTest {
@Autowired
private JPARepository jpaRepository;
@Test
public void testSave() {
User user = new User();
user.setName("老大");
user.setAge(20);
user.setAddress("北京");
jpaRepository.save(user);
}
}
3 Spring Data JPA核心接口
3.1 Repository接口
3.1.1 基于方法名称命名方式查询
3.1.1.1 接口
package com.liulg.repository;
import java.util.List;
import org.springframework.data.repository.Repository;
import com.liulg.pojo.User;
public interface UserRepository extends Repository<User, Integer> {
// 方法的名称必须要遵循驼峰式命名规则。findBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写)
List<User> findByName(String name);
List<User> findByNameAndAge(String name, Integer age);
List<User> findByNameLike(String name);
// Like通配符在后面:"老%"
List<User> findByNameStartingWith(String name);
// Like通配符在前面:"%二"
List<User> findByNameEndingWith(String name);
}
3.1.1.2 测试代码
/***
* Repository接口--方法名称命名查询
*/
@Test
public void testFindByName() {
List<User> list = userRepository.findByName("老大哥");
for (User user : list) {
System.out.println("name属性查询:");
System.out.println(user);
}
List<User> list2 = userRepository.findByNameAndAge("老二哥", 20);
for (User user : list2) {
System.out.println("name属性和age属性查询:");
System.out.println(user);
}
List<User> list3 = userRepository.findByNameStartingWith("老");
for (User user : list3) {
System.out.println("StartingWith查询,名字以'老'开头的记录:");
System.out.println(user);
}
List<User> list4 = userRepository.findByNameLike("%二%");
for (User user : list4) {
System.out.println("通配符查询,名字中包含'二'的记录:");
System.out.println(user);
}
List<User> list5 = userRepository.findByNameEndingWith("弟");
for (User user : list5) {
System.out.println("EndingWith查询,名字以'弟'结尾的记录:");
System.out.println(user);
}
}
3.1.1.3 运行结果
3.1.2 基于@Query注解查询与更新
3.1.2.1 接口
package com.liulg.repository;
import java.util.List;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import com.liulg.pojo.User;
/***
* Repository接口使用注解方式进行查询和数据更新
* @author Administrator
*
*/
public interface UserRepositoryQueryAnnotation extends Repository<User, Integer > {
/**
* 使用HQL查询,jpa新版本必须在参数匹配符“?”后面添加参数索引,索引从1开始。
*/
@Query("from User where name=?1")
List<User> queryByNameUseHQL(String name);
/**
* nativeQuery=true,表示不使用hql查询,使用普通SQL查询
*/
@Query(value="select * from user where name=?",nativeQuery=true )
List<User> queryByName(String name);
/**
* 进行更新数据库的操作时,必须添加Modifying注解
*/
@Query("update User set name=?1 where id=?2")
@Modifying
void updateUserNameById(String name,Integer id);
}
3.1.2.2 测试代码
/**
* Repository接口注解方式测试--更新数据库操作时,必须添加事务注解。
*/
@Test
@Transactional
public void testRepositoryAnnotation() {
List<User> list = userRepositoryQueryAnnotation.queryByNameUseHQL("老大哥");
for (User user : list) {
System.out.println(user);
}
List<User> list2 = userRepositoryQueryAnnotation.queryByName("老大哥");
for (User user : list2) {
System.out.println(user);
}
userRepositoryQueryAnnotation.updateUserNameById("老大哥哥", 1);
}
3.1.2.3 运行结果
3.2 CrudRepository接口
CrudRepository接口,主要是完成一些增删改查的操作。
CrudRepository集成了Repository接口。
3.2.1 接口
package com.liulg.repository;
import org.springframework.data.repository.CrudRepository;
import com.liulg.pojo.User;
public interface UserCrudRepository extends CrudRepository<User, Integer> {
}
3.2.2 测试代码
/**
* CrudRepository接口测试,该接口提供的save方法用来实现保存和更新两个功能。
* 保存记录前,会通过主键ID判断记录是否存在,如果存在就执行更新操作,如果不存在就执行保存操作。
*/
@Test
public void testCrudRepository() {
//添加用户信息
User user = new User();
user.setName("老五弟");
user.setAge(20);
user.setAddress("山西");
userCrudRepository.save(user);
//通过主键查询
Optional<User> optional = this.userCrudRepository.findById(4);
User u1 = optional.get();
System.out.println(u1);
//查询所有的记录
List<User> list =(List<User>) this.userCrudRepository.findAll();
for (User user2: list) {
System.out.println(user2);
}
//根据主键ID删除记录
this.userCrudRepository.deleteById(7);
}
3.2.3 运行结果
3.3 PagingAndSortingRepository接口
该接口用来进行分页和排序操作,该接口继承了CrudRepository接口。
3.3.1 接口
package com.liulg.repository;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.liulg.pojo.User;
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User, Integer > {
}
3.3.2 测试代码
@Test
public void testPagingAndSortingRepository() {
/***排序查询**/
//Order 定义了排序规则
Order order = new Order(Direction.DESC,"id");
Sort sort = new Sort(order);
//Sort对象封装了排序规则
List <User> list =(List<User>) userPagingAndSortingRepository.findAll(sort);
System.out.println("排序查询:**********************************");
for (User user : list) {
System.out.println(user);
}
/***分页查询**/
//page :第几页,从0开始
//size:每页查询多少条
int page = 1;
int size = 2;
Pageable pageable = new PageRequest (page,size);
Page <User> pageU =userPagingAndSortingRepository.findAll(pageable);
System.out.println("分页查询:**********************************");
System.out.println("总条数:"+pageU.getTotalElements());
System.out.println("总页数:"+pageU.getTotalPages());
List<User> content = pageU.getContent();
for (User user : content) {
System.out.println(user);
}
/***排序+分页**/
Sort sort2 = new Sort(new Order(Direction.DESC,"id"));
int page2 = 1;
int size2 = 2;
Pageable pageable2 = new PageRequest (page2,size2,sort2);
Page <User> pageU2 =userPagingAndSortingRepository.findAll(pageable2);
System.out.println("分页+排序:**********************************");
System.out.println("总条数:"+pageU2.getTotalElements());
System.out.println("总页数:"+pageU2.getTotalPages());
List<User> content2 = pageU2.getContent();
for (User user : content2) {
System.out.println(user);
}
}
3.3.3 运行结果
3.4 JpaRepository接口
该接口继承了PagingAndSortingRepository接口。对继承的父接口中的方法的返回值进行适配,例如在PagingAndSortingRepository接口中的findAll方法,返回值是Iterable,需要进行强制转换,在JpaRepository中就能够直接返回List类型进行使用。
3.4.1 接口
package com.liulg.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.liulg.pojo.User;
/**
* 参数-T:当前需要映射的实体
* 参数-ID:当前映射实体中的ID(主键)的类型
*
*/
public interface JPARepository extends JpaRepository<User, Integer> {
}
3.4.2 测试代码
@Test
public void testJpaRepository() {
Order order = new Order(Direction.DESC,"id");
Sort sort = new Sort(order);
List<User> list = jpaRepository.findAll(sort);
for (User user : list) {
System.out.println(user);
}
}
3.4.3 运行结果
3.5 JPASpecificationExecutor接口
该接口主要用来进行多条件的查询操作,并且能够在查询中添加分页与排序。JPASpecificationExecutor接口是单独存在的,没有继承上述章节的接口。JPASpecificationExecutor接口使用时一般和JpaRepository组合使用,否则Spring在注入该对象时会报错。
3.5.1 接口
package com.liulg.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import com.liulg.pojo.User;
public interface UserJPASpecificationExecutor extends JpaRepository<User, Integer>,JpaSpecificationExecutor<User> {
}
3.5.2 测试代码
@Test
public void testJpaSpecificationExecutor() {
/**
* 单条件查询
* Specification用来封装查询条件
*/
System.out.println("单条件查询:**************************************");
Specification< User> spec = new Specification<User>() {
//Predicate封装了单个的查询条件
/**
* Root:查询对象属性的封装
* CriteriaQuery:封装SQL中的各个部分信息,select order
* CriteriaBuilder:查询条件构造器。定义构造不同的查询条件
*/
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// where name ="老二哥"
/**
* arg0:查询条件的属性
* arg1:查询的值
*/
Predicate predicate = cb.equal(root.get("name"), "老二哥");
return predicate;
}
};
List<User> findAll = userJPASpecificationExecutor.findAll(spec);
for (User user : findAll) {
System.out.println(user);
}
/**
* 多条件查询,第一种方式
*/
System.out.println("多条件查询,第一种方式:**************************************");
Specification< User> spec2 = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// where name ="老二哥" and age=20
List<Predicate> list = new ArrayList<Predicate>();
list.add(cb.equal(root.get("name"), "老二哥"));
list.add(cb.equal(root.get("age"), 20));
Predicate [] array = new Predicate[list.size()];
return cb.and(list.toArray(array));
}
};
List<User> findAll2 = userJPASpecificationExecutor.findAll(spec2);
for (User user : findAll2) {
System.out.println(user);
}
/**
* 多条件查询+排序,第二种方式
*/
System.out.println("多条件查询,第二种方式:**************************************");
Specification< User> spec3 = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// where (name ="老二哥" and age=20) or id=1
Predicate p = cb.or(cb.and(cb.equal(root.get("name"), "老二哥"), cb.equal(root.get("age"), 20)),cb.equal(root.get("id"), 1));
return cb.and(p);
}
};
List<User> findAll3= userJPASpecificationExecutor.findAll(spec3,new Sort(new Order(Direction.DESC,"id")));
for (User user : findAll3) {
System.out.println(user);
}
}
3.5.3 运行结果
4 关联映射操作
4.1 一对多双向关联
场景需求:
角色(一):一个角色可以分配给多个用户
用户(多):一个用户只可以有一个角色
4.1.2 User
package com.liulg.pojo;
import javax.persistence.Column;
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="user")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name="name")
private String name;
@Column(name="age")
private Integer age;
@Column(name="address")
private String address;
@ManyToOne
@JoinColumn(name="role_id")
private Role role;
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public Integer getId() {
return id;
}
public void setId(Integer 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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
4.1.2 Role
package com.liulg.pojo;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
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 = "role")
public class Role {
/*
* TABLE:使用一个特定的数据库表格来保存主键。
* SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
* IDENTITY:主键由数据库自动生成(主要是自动增长型)
* AUTO:主键由程序控制。
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name = "role_name")
private String roleName;
//mappedBy双向关联时用到的属性,表示当前角色关联的用户必须是roleID相等的。
@OneToMany(mappedBy="role")
private Set<User> user = new HashSet<User>() ;
public Set<User> getUser() {
return user;
}
public void setUser(Set<User> user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Override
public String toString() {
return "Role [id=" + id + ", roleName=" + roleName + "]";
}
}
4.1.3 测试代码
执行查询操作时,如果出现no session异常,在application.properties文件中添加spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true配置
package com.liulg.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.liulg.Application;
import com.liulg.dao.JPARepository;
import com.liulg.pojo.Role;
import com.liulg.pojo.User;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { Application.class })
public class OneToManyTest {
@Autowired
private JPARepository jpaRepository;
@Test
public void testSave() {
User u = new User();
u.setName("老五弟");
u.setAge(70);
u.setAddress("广州");
Role role = new Role();
role.setRoleName("管理员");
u.setRole(role);
jpaRepository.save(u);
}
@Test
public void testFindUser() {
User user = jpaRepository.getOne(9);
System.out.println(user);
System.out.println(user.getRole());
}
}
4.1.4 运行结果
4.2 多对多双向关联
场景需求:
角色:多个角色可以拥有多个资源。
资源:多个资源也可以对应多个角色
4.2.1 Role
package com.liulg.pojo;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
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.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "role")
public class Role {
/*
* TABLE:使用一个特定的数据库表格来保存主键。
* SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
* IDENTITY:主键由数据库自动生成(主要是自动增长型)
* AUTO:主键由程序控制。
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name = "role_name")
private String roleName;
//mappedBy双向关联时用到的属性,表示当前角色关联的用户必须是roleID相等的。
@OneToMany(mappedBy="role")
private Set<User> user = new HashSet<User>() ;
//JoinTable:映射中间表信息
//joincolums:当前表中的主键所关联的中间表中的外键字段
@ManyToMany
@JoinTable(name="menu_role",joinColumns=@JoinColumn(name="role_id"),inverseJoinColumns=@JoinColumn(name="menu_id"))
private Set<Menu> menu = new HashSet<Menu>();
public Set<Menu> getMenu() {
return menu;
}
public void setMenu(Set<Menu> menu) {
this.menu = menu;
}
public Set<User> getUser() {
return user;
}
public void setUser(Set<User> user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Override
public String toString() {
return "Role [id=" + id + ", roleName=" + roleName + "]";
}
}
4.2.2 Menu
package com.liulg.pojo;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "menu")
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer menuId;
@Column(name = "menu_name")
private String menuName;
@Column(name = "menu_url")
private String menuUrl;
@Column(name = "parent_id")
private Integer parentId;
@ManyToMany(mappedBy="menu")
private Set<Role> roles = new HashSet<Role>();
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public Integer getMenuId() {
return menuId;
}
public void setMenuId(Integer menuId) {
this.menuId = menuId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuUrl() {
return menuUrl;
}
public void setMenuUrl(String menuUrl) {
this.menuUrl = menuUrl;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
}
4.2.3 测试代码
package com.liulg.test;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.liulg.Application;
import com.liulg.pojo.Menu;
import com.liulg.pojo.Role;
import com.liulg.repository.RoleRepository;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { Application.class })
public class ManyToManyTest {
@Autowired
private RoleRepository roleRepository;
@Test
public void testSave() {
Role role = new Role();
role.setRoleName("项目经理");
Menu menu = new Menu();
menu.setMenuName("项目管理");
menu.setParentId(0);
Menu menu2 = new Menu();
menu2.setMenuName("人员管理");
menu2.setParentId(0);
role.getMenu().add(menu);
role.getMenu().add(menu2);
menu.getRoles().add(role);
menu2.getRoles().add(role);
roleRepository.save(role);
}
@Test
public void testFind() {
Role role = roleRepository.getOne(6);
System.out.println(role);
Set<Menu> menu = role.getMenu();
for (Menu menu2 : menu) {
System.out.println(menu2);
}
}
}