1、JPA介绍
-
JPA(Java Persistence API)中文名 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对 象持久化到数据库中。
-
JPA 包括以下三方面的内容 一套 API 标准,在 javax.persistence 的包下面,用来操作实体对象,执行 CRUD 操作,框架在后台替 代我们完成所有的事情,开发者从繁琐的 JDBC 和 SQL 代码中解脱出来。 面向对象的查询语言:Java Persistence Query Language(JPQL),这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的 SQL 语句紧密耦合。
-
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实 现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使 用 Spring Data JPA 可以极大提高开发效率!
2、Spring Data JPA的主要类和结构图
需要掌握和使用到的类
-
七个大 Repository 接口:
- Repository(org.springframework.data.repository);
- CrudRepository(org.springframework.data.repository);
- PagingAndSortingRepository(org.springframework.data.repository);
- JpaRepository(org.springframework.data.jpa.repository);
- QueryByExampleExecutor(org.springframework.data.repository.query);
- JpaSpecificationExecutor(org.springframework.data.jpa.repository);
- QueryDslPredicateExecutor(org.springframework.data.querydsl)。
-
两大 Repository 实现类:
- SimpleJpaRepository(org.springframework.data.jpa.repository.support);
- QueryDslJpaRepository(org.springframework.data.jpa.repository.support)。
类的结构关系图
3、Spring Data JPA快速入门
环境准备
情景需要:创建spring boot项目,整合spring data jpa项目使用
环境要求:1、JDK8+ 2、Maven3+ 3、IDEA 4、Spring Boot2+ 5、MySQL8+ 6、Spring Data JPA
//pom.xml 注意:spring boot版本为2.1.7
<!--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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version>
<relativePath/>
</parent>
//application.properties
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/db_jpa?useUnicode=true&characterEncoding=UTF- 8&serverTimezone=UTC spring.datasource.username=root
spring.datasource.password=root
#创建数据库的方式类型
spring.jpa.hibernate.ddl-auto=update
#显示sql语句
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto 是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种 配置如下:
update :最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后 加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当 部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
create :每次加载hibernate时都会删除上一次的生成的表,然后根据你的实体类再重新来生成新表,哪怕两次没有任何 改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
none :不会创建表
create-drop :每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
validate :每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入 新值。
实体类
//注意:1. 属性名若是驼峰命名法,数据库表生成的字段会有下划线_,如属性名为userName,列名则是user_name
//2. 实体类所用注解与Hibernate注解一致
package com.kazu.springdatajpa01.entity;
import javax.persistence.*;
@Entity//标识是一个实体类
@Table(name="t_user")//表名
public class User {
@Id//主键
@GeneratedValue(strategy=GenerationType.IDENTITY)//主键自增类型
//当属性名与列名一致时,可以省略@Column
@Column
private Integer id;
private String userName;
private Integer age;
private String address;
......
}
接口
//接口推荐命名方式:实体类名称+Repository
//接口需要继承Spring Data JPA提供的接口
//CrudRepository<T,ID>,其中T为泛型,一般放入持久化类(实体类),ID则指的是主键类型
/*** 用户接口 */
public interface UserCrudRepository extends CrudRepository<User,Integer> { }
测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloWorldTest {
@Resource
private UserCrudRepository userCrudRepository;
@Test
public void test() {
User user = new User("王五",20,"广州市天河区");
userCrudRepository.save(user);
}
}
4、基本注解
@Entity 用于定义对象将会成为被 JPA 管理的实体,将字段映射到指定的数据库表
@Table 用于指定数据库的表名
@Id 定义属性为数据库的主键,一个实体里面必须有一个,并且必须和 @GeneratedValue 配合使用和成对出现
@Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。
@Transient 表示该属性并非一个到数据库表的字段的映射。JPA 映射数据库的时候忽略它,与 @Basic 相反的作用
@Column 定义该属性对应数据库中的列名
@GeneratedValue 主键生成策略
GenerationType 一共有以下四个值:
public enum GenerationType {
//通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
TABLE,
//通过序列产生主键,通过 @SequenceGenerator 注解指定序列名, MySql 不支持这种方式;
SEQUENCE,
//采用数据库ID自增长, 一般用于mysql数据库
IDENTITY,
//JPA 自动选择合适的策略,是默认选项;
UTO }
@Temporal 用来设置 Date 类型的属性映射到对应精度的字段。
@Temporal(TemporalType.DATE)映射为日期 // date (只有日期)
@Temporal(TemporalType.TIME)映射为日期 // time (是有时间)
@Temporal(TemporalType.TIMESTAMP)映射为日期 // date time (日期+时间)
@Lob 将属性映射成数据库支持的大对象类型,支持以下两种数据库类型的字段
Clob(Character Large Ojects)类型是长字符串类型,java.sql.Clob、Character[]、char[] 和 String 将被映射为 Clob 类 型。
Blob(Binary Large Objects)类型是字节类型,java.sql.Blob、Byte[]、byte[]和实现了 Serializable 接口的类型将被映射为 Blob 类型。
由于 Clob,Blob 占用内存空间较大一般配合 @Basic(fetch=FetchType.LAZY) 将其设置为延迟加载。
@IdClass 利用外部类的联合主键,
//假设 UserBlog 的联合主键是 createUserId 和 title,
//新增一个 UserBlogKey 的类。 UserBlogKey.class
import java.io.Serializable;
public class UserBlogKey implements Serializable {
private String title; //主键一
private Integer createUserId;//主键二
.......
}
//UserBlogEntity.java 稍加改动,实体类上需要加 @IdClass 注解和两个主键上都得加 @Id 注解,如下。
@Entity
@Table(name = "user_blog", schema = "test")
@IdClass(value = UserBlogKey.class)
public class UserBlogEntity {
@Column(name = "id", nullable = false)
private Integer id;
@Id
@Column(name = "title", nullable = true, length = 200)
private String title;
@Id
@Column(name = "create_user_id", nullable = true)
private Integer createUserId;
......
}
//UserBlogRepository 我们做的改动:
public interface UserBlogRepository extends JpaRepository<UserBlogEntity,UserBlogKey>{
}
//使用的时候:
@RequestMapping(path = "/blog/{title}/{createUserId}")
@ResponseBody
public Optional<UserBlogEntity> showBlogs(@PathVariable(value = "createUserId") Integer createUserId,@PathVariable("title") String title) {
return userBlogRepository.findById(new UserBlogKey(title,createUserId));
}
- Blog 的案例
@Entity
@Table(name = "user_blog", schema = "test")
public class UserBlogEntity {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "title", nullable = true, length = 200)
private String title;
@Basic
@Column(name = "create_user_id", nullable = true)
private Integer createUserId;
@Basic
@Column(name = "blog_content", nullable = true, length = -1)
@Lob
private String blogContent;
@Basic(fetch = FetchType.LAZY)
@Column(name = "image", nullable = true)
@Lob
private byte[] image;
@Basic
@Column(name = "create_time", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@Basic
@Column(name = "create_date", nullable = true)
@Temporal(TemporalType.DATE) private Date createDate;
@Transient
private String transientSimple;
}
5、JPA基础查询方法
5.1 Repository接口
Repository 位于 Spring Data Common 的 lib 里面,是 Spring Data 里面做数据库操作的最底层的抽象接口,最顶级的父类,源码 里面其实什么方法都没有,仅仅起到一个标识作用。管理域类以及域类的 ID 类型作为类型参数,此接口主要作为标记接口来捕获 要使用的类型,并帮助用户发现扩展此接口的接口。Spring 底层做动态代理的时候发现只要是它的子类或者实现类,都代表储存 库操作。
5.2 CrudRepository接口
CrudRepository接口的方法
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
//我们发现他是先出查一下传进去的实体是不是存在,然后判断是新增还是更新,是不是存在根据两种机制,一种是根据主键来判 断,还有一种是根据 Version 来判断,后面介绍 Version 的时候详解,所以如果去看 JPA 的控制台打印出来的 SQL 最少会有两 条,一条是查询,一条是 Insert 或者 Update。
//保存或修改
<S extends T> S save(S var1);//(1)
//批量保存,原理和上面的那一条相同,我们去看实现的话,就是 for 循环调用上面的 save 方法
//批量保存
<S extends T> Iterable<S> saveAll(Iterable<S> var1);//(2)
//根据主键查询实体,返回 JDK 1.8 的 Optional,这可以避免 null exception。
//根据主键查询实体
Optional<T> findById(ID var1);//(3)
//根据主键判断实体是否存在
boolean existsById(ID var1);//(4)
//查询实体的所有列表
Iterable<T> findAll//(5)
//根据主键列表查询实体列表
Iterable<T> findAllById(Iterable<ID> var1);//(6)
//查询总数
long count();//(7)
//根据主键删除,查看源码会发现,其是先查询出来再进行删除。
//根据主键删除
void deleteById(ID var1);//(8)
//根据 entity 进行删除。
//根据实体对象删除
void delete(T var1);//(9)
//根据实体对象批量删除
void deleteAll(Iterable<? extends T> var1);//(10)
//删除所有,原理:通过刚才的类关系查看其的实现类,SimpleJpaRepository 里面的 delete 实现方法如下,都是调用 delete 进 行删除。
//删除所有数据
void deleteAll();//(11)
}
CrudRepository接口的使用
具体环境与快速入门案例环境一致,包括:数据库、实体类等
- UserCrudRepository接口
/*** 用户接口 */
public interface UserCrudRepository extends CrudRepository<User,Integer> {
}
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserCrudRepositoryTest {
@Resource
private UserCrudRepository userCrudRepository;
/*** 新增 */
@Test
public void testInsert() {
//创建用户
User user = new User("王名",20,"广州市海珠区");
userCrudRepository.save(user);
}
/*** 查询所有数据 */
@Test
public void testFindAll(){
//调用查询所有的方法
Iterable<User> iterable = userCrudRepository.findAll();
//获取迭代器
Iterator<User> it = iterable.iterator();
//循环遍历
while (it.hasNext()){
//获取每一个user对象
User user = it.next();
System.out.println(user);
}
}
.........
}
5.3PagingAndSortingRepository接口
PagingAndSortingRepository接口的方法
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
//查询列表,支持排序
Iterable<T> findAll(Sort var1);
//分页查询
Page<T> findAll(Pageable var1);
}
PagingAndSortingRepository接口的使用
- 定义UserPagingAndSortingRepository接口
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserPagingAndSortingRepositoryTest {
@Resource
private UserPagingAndSortingRepository userPagingAndSortingRepository;
/*** 排序查询 */
@Test
public void testFindAllBySort(){
//创建排序对象(参数1:升序或降序,参数2:排序属性名)
Sort sort = new Sort(Sort.Direction.DESC,"id");
//调用查询方法
Iterable<User> userIterable = userPagingAndSortingRepository.findAll(sort);
//获取查询迭代器
Iterator<User> userIterator = userIterable.iterator();
//循环遍历
while(userIterator.hasNext()){
//获取每一个用户对象
User user = userIterator.next();
System.out.println(user);
}
}
/*** 排序分页查询 */
@Test
public void testFindAllBySortAndPage(){
int pageIndex = 1;//当前页码
int pageSize =2;//每页显示数量
//调用查询方法并指定分页参数
Iterable<User> userIterable = userPagingAndSortingRepository.findAll(PageRequest.of((pageIndex- 1)*pageSize,pageSize,Sort.Direction.ASC,"id"));
//获取查询迭代器
Iterator<User> userIterator = userIterable.iterator();
//循环遍历
while(userIterator.hasNext()){
//获取每一个用户对象
User user = userIterator.next();
System.out.println(user);
}
}
}
5.4JpaRepository接口
JpaRepository接口的方法
//重点关注查询方法,通过源码和CrudRepository接口相比较,JpaRepository接口将默认实现的查询结果换成了List。
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
JpaRepository接口的使用
- 定义UserJpaRepository接口
public interface UserJpaRepository extends JpaRepository<User,Integer> {
}
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserJpaRepositoryTest {
@Resource
private UserJpaRepository userJpaRepository;
@Test
public void testFindAll(){
//调用查询用户的方法
List<User> userList = userJpaRepository.findAll();
for (User user : userList) {
System.out.println(user);
}
}
}
5.4 JpaSpecificationExecutor接口
JpaSpecificationExecutor接口的方法
//重点关注Page findAll(@Nullable Specification var1, Pageable var2);此方法
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
//分页查询(支持动态查询+分页查询+排序)
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
JpaSpecificationExecutor接口的使用
- 定义接口
/*** 分页查询(支持动态查询+分页查询+排序) */
public interface UserJpaSpecificationExecutor extends JpaRepository<User,Integer>,JpaSpecificationExecutor<User> {
}
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserJpaSpecificationExecutorTest {
@Resource
private UserJpaSpecificationExecutor userJpaSpecificationExecutor;
@Test
public void test(){
User user = new User();
user.setUserName("李");
user.setAge(20);
user.setAddress("广州");
//排序等定义
Sort sort = new Sort(Sort.Direction.ASC,"id");
//分页的定义
PageRequest pageable = PageRequest.of(0,2,sort);
Page<User> page = userJpaSpecificationExecutor.findAll(new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//获取条件参数对象
Predicate predicate = cb.conjunction();
//user对象不为空
if(user!=null){
//用户名
if(user.getUserName()!=null && !user.getUserName().equals("")){ predicate.getExpressions().add(cb.like(root.get("userName"),"%"+user.getUserName()+"%")); }
//年龄
if(user.getAge()!=null){ predicate.getExpressions().add(cb.ge(root.get("age"),user.getAge())); }
//地址
if(user.getAddress()!=null && !user.getAddress().equals("")){ predicate.getExpressions().add(cb.like(root.get("address"),"%"+user.getAddress()+"%"));
}
}
return predicate;
}
},pageable);
//获取用户列表
List<User> users = page.getContent();
for (User user1 : users) {
System.out.println(user1);
}
System.out.println("总页数:"+page.getTotalPages());
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("当前页码:"+page.getNumber());
System.out.println("每页显示数量:"+page.getSize());
}
}
6、JPA查询方法
6.1 方法命名查询
概述
-
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的 名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
-
按照Spring Data JPA 定义的规则,查询方法以 findBy 开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
关键字
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname ,findByFirstnameIs ,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended % ) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended % ) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in % ) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
案例:方法命名查询
- 环境准备
需求:以User类为示例,对User类进行查询操作
步骤:
1.定义User实体类
2.定义UserRepository接口
3.测试方法命名查询
- 实体类
@Entity//标识是一个实体类
@Table(name="t_user")//表名
public class User {
@Id//主键
@GeneratedValue(strategy=GenerationType.IDENTITY)//主键自增类型
//当属性名与列名一致时,可以省略
@Column
@Column private Integer id;
private String userName;
private Integer age;
private String address;
.......
}
- repository接口
public interface UserRepository extends JpaRepository<User,Integer> {
//注意:方法的名称必须要遵循驼峰式命名规则。xxxBy(关键字)+属性名称(首字母大写)+查询条件(首字母大写) /*** 根据用户名精确查询 * @param userName * @return */
List<User> findByUserName(String userName);
/*** 模糊查询 * @param userName* @return */
List<User> queryByUserNameLike(String userName);
/*** 根据用户名和地址模糊查询 * @param userName * @param address * @return */
List<User> findByUserNameLikeAndAddressLike(String userName,String address); }
//注意:方法名称除了find的前缀之外,还有以下前缀,具体可查看PartTree类的源码(快速搜索查看类快捷键:ctrl + shift + T)
private static final String QUERY_PATTERN = "find|read|get|query|stream";
private static final String COUNT_PATTERN = "count";
private static final String EXISTS_PATTERN = "exists";
private static final String DELETE_PATTERN = "delete|remove";
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springdatajpa02ApplicationTests {
@Resource
private UserRepository userRepository;
@Test
public void test1() {
List<User> users = userRepository.findByUserName("张三");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void test2() {
List<User> users = userRepository.queryByUserNameLike("李%");
for (User user : users) {
System.out.println(user);
}
}
@Test public void test3() {
List<User> users = userRepository.findByUserNameLikeAndAddressLike("李%","%海珠%");
for (User user : users) {
System.out.println(user);
}
}
}
小结:
- 方法名称必须要遵循驼峰式命名规则。xxxBy(关键字)+属性名称(首字母大写)+查询条件+(首字母大写)+查询条件…
- 方法前缀可以为 find 、 read 、 get 、 query 、 stream 、 count 、 exists 、 remove 、 delete 具体查看 PartTree 类
6.2 注解式查询方法
概述
使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询。
@Query注解的优缺点与实战经验分享
优点
- 可以灵活快速的使用 JPQL 和 SQL
- 对返回的结果和字段可以自定义
- 支持连表查询和对象关联查询,可以组合出来复杂的 SQL 或者 JPQL
- 可以很好的表达你的查询思路
- 灵活性非常强,快捷方便
缺点
-
不支持动态查询条件,参数个数如果是不固定的不支持
-
若将返回结果用 Map 或者 Object[] 数组接收结果,会导致调用此方法的开发人员不知道返回结果里面到底有些什么数据
实战经验
-
当出现很复杂的 SQL 或者 JPQL 的时候建议用视图
-
返回结果一定要用对象接收,最好每个对象里面的字段和你返回的结果一一对应
-
能用 JPQL 的就不要用 SQL
@Query用法
- 使用命名查询为实体声明查询是一种有效的方法,对于少量查询很有效。一般只需要关心 @Query 里面的 value 和 nativeQuery 的值。使用声明式 JPQL 查询有个好处,就是启动的时候就知道你的语法正确不正确。
- 注意:使用原生SQL,需要将 nativeQuery 属性设置为 true
案例1-JPQL查询
使用JPQL语句完成单条件和多条件查询
- 接口
/*** 根据用户名模糊查询 * @param username 用户名 * @return */
@Query("from User where userName like ?1%")
List<User> findUserListByName(String username);
/*** 根据用户名和地址模糊查询 * @param username 用户名 * @param address 地址 * @return */ @Query("from User where userName like ?1% and address like %?2%")
List<User> findUserList(String username,String address);
//注意:参数下标从1开始,占位符要与参数位置正确匹配
- 测试类
@Test
public void testJPQL1(){
List<User> users = userRepository.findUserListByName("李");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testJPQL2(){
List<User> users = userRepository.findUserList("李","海珠");
for (User user : users) {
System.out.println(user);
}
}
案例2-原生SQL语句
- 接口
/*** 原生SQL查询 * @param username 用户名 * @param address 地址 * @return */
@Query(value = "select * from - where user_name like %?1% and address like %?2%",nativeQuery = true)
List<User> findUserListBySQL(String username,String address);
- 测试类
@Test
public void testSQL(){
List<User> users = userRepository.findUserListBySQL("李","海珠");
for (User user : users) {
System.out.println(user);
}
}
@Param用法
概述
默认情况下,参数是通过顺序绑定在查询语句上的,这使得查询方法对参数位置的重构容易出错。为了解决这个问题,可以使用 @Param 注解指定方法参数的具体名称,通过绑定的参数名字做查询条件,这样不需要关心参数的顺序,推荐这种做法,比较利 于代码重构
@Param的使用
- 接口
/*** 命名参数查询 * @param username * @param address * @return */
@Query("from User where userName like :username% and address like %:address%")
List<User> findUserListByParam(@Param("username") String username,@Param("address") String address);
//注意:1. @Param注解必须提供且注解内容与命名参数一致 2. 命名参数定义与Hibernate一致,使用英文冒号定义
- 测试
@Test
public void testParam(){
List<User> users = userRepository.findUserListByParam("李","海珠");
for (User user : users) {
System.out.println(user);
}
}
@Modifying修改查询之更新
- 接口
@Modifying//修改查询,标记为更新
@Query("update User set userName =?1 where id =?2")
int updateUser(String username,int id);
- 测试类
@Test
@Transactional//需要开启事务
public void testUpdate(){
userRepository.updateUser("test",2);
}
//注意:需要加入 @Transactional 注解开启事务
@Modifying修改查询之删除
- 接口
@Modifying//修改查询,标记为删除
@Query("delete from User where id =?1")
int deleteUser(int id);
- 测试类
@Test
@Transactional//需要开启事务
public void testDelete(){
userRepository.deleteUser(10);
}
//注意:需要加入 @Transactional 注解开启事务
7、多表设计
JPA表关系分析步骤
第一步:首先确定两张表之间的关系。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
关联关系注解
@OneToOne、@JoinColumn、@ManyToOne、@ManyToMany、@JoinTable、@OrderBy
注: @JoinColumn 定义外键关联的字段名称
@JoinColumn 主要配合 @OneToOne、@ManyToOne、@OneToMany 一起使用,单独使用没有意义。
@OneToOne 一对一关联关系
用法 @OneToOne 需要配合 @JoinColumn 一起使用。注意:可以双向关联,也可以只配置一方,看实际需求
- Student实体类
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String studentname;
//一对一关联
@OneToOne(cascade = CascadeType.PERSIST)
//关联的外键字段 会有一个Grade实体类
@JoinColumn(name = "grade_id")
private Grade grade;
......
}
- Grade实体类
@Entity
@Table(name = "t_grade")
public class Grade {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String gradename;
//一对一 双向一对一用mappedBy
@OneToOne(mappedBy = "grade")
private Student student;
......
}
- StudentRepository接口
public interface StudentRepository extends JpaRepository<Student,Integer> {
}
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class OneToOneTest {
@Resource
private StudentRepository studentRepository;
/*** 一对一添加:添加学生同时添加年级 */
@Test
public void testAdd(){
//创建年级对象
Grade grade = new Grade();
grade.setGradename("一年级");
//创建学生对象
Student student = new Student();
student.setStudentname("张浩");
student.setSex("男");
//设置关联关系
student.setGrade(grade);
//保存
studentRepository.save(student);
}
/*** 根据学生 ID 查询学生,同时查询年级 */
@Test
public void testSearch(){
Optional<Student> student = studentRepository.findById(1);
System.out.println("学生信息:"+student);
System.out.println("年级信息:"+student.get().getGrade());
}
}
@OneToMany 一对多 & @ManyToOne 多对一
需求:从角色到用户的一对多的关联关系 角色:一方 用户:多方
- Users实体类
// toString()方法不添加Roles属性
@Entity
@Table(name = "t_users")
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
//多个用户拥有同一个角色
@ManyToOne
@JoinColumn(name = "roles_id")
private Roles roles;
......
}
- Roles实体类
//toString()方法不添加users属性
@Entity
@Table(name = "t_roles")
public class Roles {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String rolename;
//一个角色被多个用户拥有
//fetch = FetchType.EAGER:立即加载
@OneToMany(mappedBy = "roles",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@OrderBy("Users_username DESC") //进行排序
private Set<Users> users = new HashSet<Users>();
......
}
- 创建RolesRepository接口
public interface RolesRepository extends JpaRepository<Roles,Integer> { }
- 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class OneToManyTest {
@Resource
private RolesRepository rolesRepository;
/*** 级联添加:添加角色同时添加用户 */
@Test
public void test1(){
//创建角色对象
Roles roles = new Roles("经理");
//创建两个用户
Users users1 = new Users("王五",roles);
Users users2 = new Users("赵六",roles);
//设置关联关系
roles.getUsers().add(users1);
roles.getUsers().add(users2);
//级联保存用户
rolesRepository.save(roles);
}
/*** 级联查询:查询角色同时查询角色下的用户列表(关闭延迟加载) */
@Test
public void test2(){
Optional<Roles> roles = rolesRepository.findById(1);
System.out.println("角色名称:"+roles.get().getRolename());
for (Users users :roles.get().getUsers()){
System.out.println(users);
}
}
}
@ManyToMany 多对多
- @ManyToMany 表示多对多,和 @OneToOne、@ManyToOne 一样也有单向双向之分,单项双向和注解没有关系,只看实体类之 间是否相互引用。 主要注意的是当用到 @ManyToMany 的时候一定是三张表,不要想着偷懒,否则会发现有很多麻烦.
- @JoinTable 是指如果对象与对象之间有个关联关系表的时候,就会用到这个,一般和 @ManyToMany 一起使用。
需求:一个项目由多个员工负责,一个员工参与多个项目(多对多关系)–给项目分配员工
员工:多方
项目:多方
- Project实体类
//toString()方法不添加Employee属性
@Entity
@Table(name = "t_project")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer projectid;
private String projectname;
/**
* 配置项目到员工的多对多关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany(targetEntity = Employee.class) //多对多
* targetEntity:代表对方的实体类字节码
* 2.配置中间表(包含两个外键)
* @JoinTable
* name : 中间表的名称
* joinColumns:配置当前对象在中间表的外键
* @JoinColumn的数组
* name:外键名
* referencedColumnName:参照的主表的主键名
* inverseJoinColumns:配置对方对象在中间表的外键 */
@ManyToMany(targetEntity =Employee.class ,cascade = CascadeType.ALL)
//第三张表(外键关系表、中间表)
//name属性:第三张表的表名称
@JoinTable(name = "t_employee_project",
//joinColumns:当前对象在中间表中的外键
joinColumns = @JoinColumn(name = "project_id",referencedColumnName = "projectid"),
//inverseJoinColumns:对方对象在中间表的外键
inverseJoinColumns = @JoinColumn(name = "employee_id",referencedColumnName = "empid")) private Set<Employee> employees = new HashSet<Employee>();
.......
}
- Employee实体类
//toString()方法不添加Project属性
@Entity
@Table(name = "t_employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer empid;
private String empname;
//多对多放弃维护权:被动的一方放弃
@ManyToMany(mappedBy = "employees")
private Set<Project> projects = new HashSet<Project>();
.......
}
- 创建ProjectRepository接口
public interface ProjectRepository extends JpaRepository<Project,Integer> { }
- 测试多对多
思路:
创建2个项目(超市管理系统、酒店管理系统),3个员工(张三、李四、王五)
张三、李四、王五负责超市管理系统
张三、李四同时负责酒店管理系统
/*** 级联添加:添加角色同时添加用户 */
@Test
//以下两个注解必须提供
@Transactional//开启事务
@Rollback(false)//取消回滚
public void testAdd() {
//创建两个项目对象
Project project1 = new Project("超市管理系统");
Project project2 = new Project("酒店管理系统");
//创建三个员工对象
Employee employee1 = new Employee("张三");
Employee employee2 = new Employee("李四");
Employee employee3 = new Employee("王五");
//设置关联关系 //给超市管理系统分配员工
project1.getEmployees().add(employee1);
project1.getEmployees().add(employee2);
project1.getEmployees().add(employee3);
//给酒店管理系统分配员工
project2.getEmployees().add(employee1);
project2.getEmployees().add(employee2);
//保存
projectRepository.save(project1);
projectRepository.save(project2);
}
//注意:必须在事务环境中运行,否则会出现 detached entity passed to persist 错误