什么是SpringDataJPA?
① SpringData是spring提供的操作数据的框架,jpa是其中的一个模块。
②SpringDataJPA用于简化操作持久层(操作数据库)的代码,只需要编写接口即可。JPA会根据你提供的实体类和持久层接口,自动创建数据库表并插入数据保存。意味着你不需要自己去创建数据库表!!!
SpringBoot整合SpringDataJPA步骤
- 在pom文件中添加坐标
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<!-- spring-boot启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 添加thymeleaf启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- devtools坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- jpa启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- junit启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
- 编写全局配置文件
application.properties
# mysql-message
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=leidada
# datasource-pool
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# spring-data-jpa
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
- 添加实体类 (重点)
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="t_users") //表示和数据库中的哪张表整合
public class Users {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) //主键
@Column(name="id") //数据库表中对应id属性的列名
private Integer id;
@Column(name="username")
private String username;
@Column(name="password")
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Users [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
- 编写Dao接口 (重点)
JpaRepository<T, ID>,参数含义:
①参数一(T):当前需要映射的实体;
②参数二(ID):当前映射的实体中的主键(OID)的类型;
public interface UsersRepository extends JpaRepository<Users, Integer>{
}
- 创建启动类
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
- 编写测试代码
期望的测试结果:
①如果实体类中@Table(name="t_users")
的t_users
这张表不存在,创建t_users
这张表。
②将数据插入这张表中。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private UsersRepository usersRepository;
@Test
public void saveUser() {
Users user = new Users();
user.setUsername("张三");
user.setPassword("zs");
usersRepository.save(user);
}
}
测试结果:
如果程序执行正确,则控制台会输出一下两句sql语句。
Hibernate: create table t_users (id integer not null auto_increment, password
Hibernate: insert into t_users (password, username) values (?, ?)
SpringDataJPA提供的核心接口
- Repository接口
- CrudRepository接口
- PagingAndSortingRepository接口
- JpaReposiroty接口
- JPASpecificationExecutor接口
SpringDataJPA-Repository接口使用
①提供了方法名称命名查询方式;注意下面表格的命名规则
关键字 | 属性名 | 查询条件 |
---|---|---|
findBy | 实体类中的属性名,首字母大写 | Is、Equals、默认(相等方式); |
findBy | 实体类中的属性名,首字母大写 | 并列:And、或者:Or; |
findBy | 实体类中的属性名,首字母大写 | 模糊查询:Like; |
- 编写接口
/**
* Repository接口的方法名称命名查询
*/
public interface UsersRepositoryByName extends Repository<Users, Integer> {
List<Users> findByUsername(String username);
List<Users> findByUsernameAndPassword(String username, String password);
List<Users> findByUsernameLike(String username);
}
- 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private UsersRepositoryByName usersRepositoryByName;
@Test
public void testFindByName() {
// List<Users> list = this.usersRepositoryByName.findByUsername("张三");
// List<Users> list = this.usersRepositoryByName.findByUsernameAndPassword("张三", "zs");
List<Users> list = this.usersRepositoryByName.findByUsernameLike("张%");
for(Users s : list) {
System.out.println(s);
}
}
}
②提供了基于@Query
注解查询与更新;
- 编写接口
注意 |
---|
@Query 注解默认支持HQL,其属性nativeQuery=false (默认);使用标准SQL,要设置nativeQuery=true |
@Query 对方法名称没有要求,当方法参数数量多时,推荐使用该方法,否则会造成代码可读性降低。 |
@Query 用于更新操作时,需要使用@Modifying 搭配使用。 |
/**
* Repository接口的@Query查询操作
*/
public interface UsersRepositoryQueryAnnotation extends Repository<Users, Integer> {
//使用HQL
@Query("from Users where username=?1")
List<Users> queryByUsernameUseHQL(String username);
//使用标准SQL
@Query(value="select * from t_users where username=?1", nativeQuery=true)
List<Users> queryByUsernameUseSQL(String username);
//执行更新操作
@Query("update Users set password = ?1 where id = ?2")
@Modifying //需要执行一个更新操作
void updateUsersByIdHQL(String password, Integer id);
}
- 编写测试代码
注意 |
---|
@Transactional 注解与@Test 注解一起使用时,事务是自动回滚的。需要使用@Rollback(false) ,取消自动回滚 |
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private UsersRepositoryQueryAnnotation usersRepositoryQueryAnnotation;
//查询操作
@Test
public void testQueryAnnotation() {
// List<Users> list = this.usersRepositoryQueryAnnotation.queryByUsernameUseHQL("张三");
List<Users> list = this.usersRepositoryQueryAnnotation.queryByUsernameUseSQL("张三");
for(Users s : list) {
System.out.println(s);
}
}
//更新操作
@Test
@Transactional
@Rollback(false)
public void testUpdateUsersById() {
this.usersRepositoryQueryAnnotation.updateUsersByIdHQL("zss", 1);
}
}
- 错误:
JDBC style parameters (?) are not supported for JPA queries.
解决:@Query("from Users where username=?1")
,在?
后面加上参数的index
,低版本的jpa不加也可以运行。
SpringDataJPA-CrudRepository接口使用
CrudRepository接口主要是完成增删改查的作用,继承了
Repository
接口。
- 创建接口
public interface UsersCrudRepository extends CrudRepository<Users, Integer> {
}
- 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private UsersCrudRepository usersCrudRepository;
@Test
public void testCrudRepository() {
//添加
Users user = new Users();
user.setUsername("张三");
user.setPassword("zs");
this.usersCrudRepository.save(user);
//更新
Users user1 = new Users();
user1.setId(2);
user1.setUsername("张三");
user1.setPassword("zss");
this.usersCrudRepository.save(user1);
//查询单个
Optional<Users> user2 = this.usersCrudRepository.findById(2);
System.out.println(user2);
//删除
this.usersCrudRepository.deleteById(2);
}
}
SpringDataJPA-PagingAndSortingRepository接口使用
该接口提供了分页和排序功能,但是只能对查询全部进行排序和分页,继承了
CrudRepository
。下面的JPASpecificationExecutor
接口会对该功能进行优化。
排序对象 | 作用 |
---|---|
Order | 定义排序规则 |
Sort | 封装排序规则 |
分页对象 | 作用 |
---|---|
Pageable | 封装了分页的参数,包括当前页、每页显示的条数。注意:当前页从0开始。 Pageable pageable = new PageRequest(page, size); |
- 创建接口
public interface PagingAndSortingRepository
extends org.springframework.data.repository.PagingAndSortingRepository<Users, Integer> {
}
- 编写测试代码
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private PagingAndSortingRepository pagingAndSortingRepository;
@Test
public void testPagingAndSortRepository() {
//排序
//order定义排序规则,根据id降序
Order order = new Order(Direction.DESC, "id");
Sort sort = new Sort(order);
List<Users> users = (List<Users>) this.pagingAndSortingRepository.findAll(sort);
for(Users u : users) {
System.out.println(u);
}
//分页
Pageable pageable = new PageRequest(0, 1);
Page<Users> page = this.pagingAndSortingRepository.findAll(pageable);
System.out.println("总条数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
List<Users> list = page.getContent();
for(Users u : list) {
System.out.println(u);
}
//排序+分页
Order order1 = new Order(Direction.DESC, "id");
Sort sort1 = new Sort(order1);
Pageable pageable1 = new PageRequest(0, 2, sort1);
Page<Users> page1 = this.pagingAndSortingRepository.findAll(pageable1);
System.out.println("总条数:"+page1.getTotalElements());
System.out.println("总页数:"+page1.getTotalPages());
List<Users> list1 = page1.getContent();
for(Users u : list1) {
System.out.println(u);
}
}
}
SpringDataJPA-JpaRepository接口使用
该接口继承了
PagingAndSortingRepository
接口。对继承的父接口的方法的返回值进行适配。主要的作用就是对其所继承的所有父接口的返回值做了修改,不用再进行强制类型转换,开发中常用。
- 编写接口
public interface UsersRepository extends JpaRepository<Users, Integer>{
}
- 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
@Autowired
private UsersRepository usersRepository;
@Test
public void testJpaRepositorySort() {
//order定义排序规则,根据id降序
Order order = new Order(Direction.DESC, "id");
//sort对象封装了排序规则
Sort sort = new Sort(order);
List<Users> users = this.usersRepository.findAll(sort);
for(Users u : users) {
System.out.println(u);
}
}
}
SpringDataJPA-JPASpecificationExecutor接口使用
该接口提供了多条件查询或者复杂查询的方法,并且可在查询中添加分页和排序功能。可以根据自己的需求进行查询。
注意 |
---|
该接口是单独存在的!!!没有继承以上任何一种接口。但是会同JpaRepository 等接口一起使用!!! |
- 编写接口
public interface UsersJPASpecificationExecutor extends JpaRepository<Users, Integer>,JpaSpecificationExecutor<Users> {
}
- 编写测试方法
参数类型 | 作用 |
---|---|
Specification | 用于封装查询条件,使用匿名内部类的方式使用,可以少维护一个class。Specification<Users> spec = new Specification<Users>() {} |
Predicate | 封装了单个查询条件。 |
Root<T> | 查询对象的属性封装,如:Root<Users> 即Users的属性都在root中。 |
CriteriaQuery<?> | 封装了我们要执行的查询中的各个部分的信息。如:select、from、order、by等。 |
CriteriaBuilder | 查询条件构造器,定义不同的查询条件。 |
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class JpaSpecificationExecutorTest {
@Autowired
private UsersJPASpecificationExecutor usersJPASpecificationExecutor;
/**
* 单条件测试
*/
@Test
public void testJPASpecificationExecutor1() {
/**
* Specification用于封装查询条件
*/
Specification<Users> spec = new Specification<Users>() {
//Predicate封装了单个查询条件
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
/**
* sql条件:where username= '张三'
* Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
*/
Predicate pre = cb.equal(root.get("username"), "张三");
return pre;
}
};
List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
for(Users u : users) {
System.out.println(u);
}
}
/**
* 多条件测试,方法一
*/
@Test
public void testJPASpecificationExecutor2() {
/**
* Specification用于封装查询条件
*/
Specification<Users> spec = new Specification<Users>() {
//Predicate封装了单个查询条件
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
/**
* sql条件:where username= '张三' and password='zs'
* Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
*/
List<Predicate> list = new ArrayList<>();
list.add(cb.equal(root.get("username"), "张三"));
list.add(cb.equal(root.get("password"), "zs"));
Predicate[] arr = new Predicate[list.size()];
return cb.and(list.toArray(arr));
}
};
List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
for(Users u : users) {
System.out.println(u);
}
}
}
/**
* 多条件测试,方法二
*/
@Test
public void testJPASpecificationExecutor3() {
/**
* Specification用于封装查询条件
*/
Specification<Users> spec = new Specification<Users>() {
//Predicate封装了单个查询条件
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
/**
* sql条件:where username= '张三' and password='zs'
* Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
*/
//where (username='张三' and password='zs') or id='3'
return cb.or(cb.and(cb.equal(root.get("username"), "张三"),cb.equal(root.get("password"), "zs")),cb.equal(root.get("id"), "3"));
}
};
List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
for(Users u : users) {
System.out.println(u);
}
}
/**
* 多条件测试、排序
*/
@Test
public void testJPASpecificationExecutor3() {
/**
* Specification用于封装查询条件
*/
Specification<Users> spec = new Specification<Users>() {
//Predicate封装了单个查询条件
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
/**
* sql条件:where username= '张三' and password='zs'
* Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
*/
//where (username='张三' and password='zs') or id='3'
return cb.or(cb.and(cb.equal(root.get("username"), "张三"),cb.equal(root.get("password"), "zs")),cb.equal(root.get("id"), "3"));
}
};
//排序
Sort sort = new Sort(new Order(Direction.DESC,"id"));
List<Users> users = this.usersJPASpecificationExecutor.findAll(spec, sort);
for(Users u : users) {
System.out.println(u);
}
}
关联映射操作
一对多的关联关系
- 需求:角色与用户的一对多的关联关系,一个角色对应多个用户;
角色:一方;
用户:多方;
注解 | 作用 |
---|---|
@ManyToOne | 多对一,写在多方实体类中。 |
@JoinColumn(name=“外键”) | 维护外键,写在多方实体类中。 |
@OneToMany | 一对多,写在一方实体类中。 |
- 实体类代码:
注意 |
---|
因为下面实体类存在关联属性,所以在写toString 方法时,不要把关联对象也写入,否则会出现java.lang.StackOverflowError 错误,因为对象之间相互打印,导致了栈溢出错误!!! |
@Entity //声明实体类
@Table(name="t_users") //表示和数据库中的哪张表整合
public class Users {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) //主键
@Column(name="id") //数据库表中对应id属性的列名
private Integer id;
@Column(name="username")
private String username;
@Column(name="password")
private String password;
//建立用户和角色的关联关系,一个用户对应一个角色。
@ManyToOne(cascade=CascadeType.PERSIST) //多对一关系,添加用户的同时添加角色
@JoinColumn(name="role_id") //维护外键
private Roles role;
//get \ set \ toString不包含关联对象
}
@Entity
@Table(name="t_roles")
public class Roles {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="role_id")
private Integer role_id;
@Column(name="role_name")
private String role_name;
//建立用户和角色的关联关系,一个角色对应多个用户。
@OneToMany(mappedBy="role",fetch=FetchType.EAGER) //一对多关系
private Set<Users> users = new HashSet<>();
//get \ set \ toString不包含关联对象
}
- 测试一对多的关联关系
public interface UsersRepository extends JpaRepository<Users, Integer>{
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class OneToManyTest {
@Autowired
private UsersRepository usersRepository;
/**
* 一对多,添加
*/
@Test
public void testSave() {
//第一步:创建一个用户
Users user = new Users();
user.setUsername("李四");
user.setPassword("ls");
//第二步:创建一个角色
Roles role = new Roles();
role.setRole_name("管理员");
//第三步:关联
role.getUsers().add(user);
user.setRole(role);
//第四步:保存
this.usersRepository.save(user);
}
/**
* 一对多,查询
*/
@Test
public void testSelect() {
Optional<Users> user = this.usersRepository.findById(4);
System.out.println(user);
Users users = user.get();
System.out.println(users.getRole().getRole_name());
}
}
- 结果
Hibernate: insert into t_roles (role_name) values (?)
Hibernate: insert into t_users (password, role_id, username) values (?, ?, ?)
Optional[Users [id=4, username=李四, password=ls]]
管理员
多对多的关联关系
- 需求:角色与菜单的多对多关系;一个菜单有多个角色,一个角色对应多个菜单;
- 角色:多方;
- 菜单:多方;
- 编写实体类
注解 | 作用 |
---|---|
@ManyToMany | 多对多关系。在多对多的哪一侧添加都可以。(cascade=CascadeType.PERSIST) 在关联后一起保存到数据库。fetch=FetchType.EAGER 将延迟加载修改为立即加载。 |
@JoinTable | 映射中间表。name 表名、joinColumns 当前表中的主键所关联的中间表中的外键字段、inverseJoinColumns 另一张表中的主键所关联的中间表中的外键字段。 |
@Entity
@Table(name="t_roles")
public class Roles {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="role_id")
private Integer role_id;
@Column(name="role_name")
private String role_name;
//建立角色和菜单的关联关系
@ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.EAGER)
@JoinTable(name="t_roles_menus",joinColumns=@JoinColumn(name="role_id"),inverseJoinColumns=@JoinColumn(name="menus_id")) //@JoinTable:映射中间表
private Set<Menus> menus = new HashSet<>();
}
@Entity
@Table(name="t_menus")
public class Menus {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="menus_id")
private Integer menus_id;
@Column(name="menus_name")
private String menus_name;
@Column(name="menus_url")
private String menus_url;
@Column(name="father_id")
private String father_id;
//建立角色和菜单的关联关系,多个Menus对应多个角色
@ManyToMany(mappedBy="menus") //对应role中的menus对象名
private Set<Roles> roles = new HashSet<>();
}
- 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class ManyToManyTest {
@Autowired
private RolesRepository rolesRepository;
@Test
public void testSave() {
//第一步:创建角色对象
Roles role = new Roles();
role.setRole_name("项目经理");
//第二步:创建菜单对象
Menus menus = new Menus();
menus.setMenus_name("xxxx管理系统");
menus.setFather_id("0");
Menus menus2 = new Menus();
menus.setFather_id("1");
menus.setMenus_name("项目管理");
//第三步:关联--@ManyToMany(cascade=CascadeType.PERSIST)
role.getMenus().add(menus);
role.getMenus().add(menus2);
menus.getRoles().add(role);
menus2.getRoles().add(role);
//保存
this.rolesRepository.save(role);
}
/**
* 测试查询
*/
@Test
public void testSelect() {
Optional<Roles> roles = this.rolesRepository.findById(2);
System.out.println(roles);
Roles role = roles.get();
System.out.println(role);
System.out.println(role.getMenus());
}
}
- 结果
Hibernate: insert into t_roles (role_name) values (?)
Hibernate: insert into t_menus (father_id, menus_name, menus_url) values (?, ?, ?)
Hibernate: insert into t_menus (father_id, menus_name, menus_url) values (?, ?, ?)
Hibernate: insert into t_roles_menus (role_id, menus_id) values (?, ?)
Hibernate: insert into t_roles_menus (role_id, menus_id) values (?, ?)
来源:CSDN
作者:磊大大的编程之路
链接:https://blog.csdn.net/qq_42197800/article/details/104480475