SPRING-DATA-JPA 为什么出这个课程:中小企业当中,老代码,jpa
若依、eladmin二次开发的框架,jpa
jpa场景:国外软件,jpa使用量,大于国内mybatis。
第一章 ORM概述-了解 ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射
简单的说:ORM就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。
一、为什么使用ORM 当实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数据、修改数据、删除数据,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。
二、常见ORM框架 常见的orm框架:Mybatis(ibatis)、Hibernate、Jpa
第二章 hibernate与JPA的概述-了解 一、hibernate概述 Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
二、 JPA概述 JAVA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。
JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
三、JPA的优势 1. 标准化 JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
2. 容器级特性的支持 JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
3. 简单方便 JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
4. 查询能力 JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
5. 高级特性 JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象 的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
四、 JPA与hibernate的关系 JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服务厂商来提供实现。
image-20220807233843298 JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。
第三章 JPA的入门案例 新建maven boot项目
image-20220808000147721 image-20220808000234306 一、配置数据源 这里用的yml方式,里面的jpa就是数据库的名称,大家也可以写其他的,前提是这个数据库存在,用户名和密码写自己的就好。
From: 元动力 1 2 3 4 5 6 7 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/jpa?serverTimezone=UTC username: root password: ydlclass666
二、导入依赖
From: 元动力 1 2 3 4 5 6 7 8 9 < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-data-jpa< /artifactId> < /dependency> < dependency> < groupId> mysql< /groupId> < artifactId> mysql-connector-java< /artifactId> < version> 8.0.28< /version> < /dependency>
三、创建实体类 通过注解方式让数据库知道我们的表长什么样
这里就可以知道表的名称是users(当然你可以任意取名),表的创建一般都有主键和自增操作,这里全部通过注解来完成。这里第一次创建表的时候表名会爆红,我们需要给他手动指定数据库,也就是数据源配置时的数据库
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.ydlclass.jpa.ydljpa.domain;import lombok.Data;import javax.persistence.*;@Data @Entity @Table(name = "user") public class User { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) int id; @Column(name = "username") String username; @Column(name = "password") String password; }
这里我们还需要设置自动表定义ddl-auto
From: 元动力 1 2 3 4 5 spring: jpa: show-sql: true hibernate: ddl-auto: create
ddl-auto属性用于设置自动表定义,可以实现自动在数据库中为我们创建一个表,表的结构会根据我们定义的实体类决定,它有4种
create 启动时删数据库中的表,然后创建,退出时不删除数据表 create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错 update 如果启动时表格式不一致则更新表,原有数据保留 validate 项目启动表结构进行校验 如果不一致则报错 四、启动测试类完成表创建 这时候可以看到控制台打印这两句话,就会发现表已经创建成功了(和我们自己去敲命令行十分类似,hibernate帮我们完成这些操作,是不是很方便) 删表是因为选择了create策略创建表,后面还会讲其他的策略。
我们可以添加一条记录测试一下表 发现没有问题
image-20220808002952122 五、如何访问我们的表 这里我们需要自定义借口继承接口JpaRepository
From: 元动力 1 2 3 @Repository public interface UserRepository extends JpaRepository <User,Integer> { }
注意JpaRepository有两个泛型,前者是具体操作的对象实体,也就是对应的表,后者是ID的类型,接口中已经定义了比较常用的数据库操作。编写接口继承即可,我们可以直接注入此接口获得实现类: 这是查操作
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.ydlclass.jpa.ydljpa;import com.ydlclass.jpa.ydljpa.dao.UserRepository;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest class YdljpaApplicationTests { @Autowired UserRepository userRepository; @Test public void testQuery () { userRepository.findById(1 ).ifPresent(System.out::println); } }
这里需要注意把create策略改成update,因为create策略会删表中数据
image-20220808004442911 这是增操作
From: 元动力 1 2 3 4 5 6 7 8 9 10 @Test public void testAdd () { User user=new User (); user.setUsername("itnanls" ); user.setPassword("ydl666" ); User saveUser = userRepository.save(user); System.out.println(saveUser); }
image-20220808003840259 这是删操作
这是删操作
From: 元动力 1 2 3 4 @Test public void testDel () { userRepository.deleteById(3 ); }
image-20220808003949975 这是分页操作 每页只显示一个数据 ,大家可以先多添加几条记录后再来测试
From: 元动力 1 2 3 4 @Test public void testPageable () { userRepository.findAll(PageRequest.of(0 ,2 )).forEach(System.out::println); }
image-20220808004236628 自带的方法肯定不止这几个,这里就是一一举例。
自带的方法肯定不止这几个,这里就是一一举例。
image-20220808004306106 第四章 方法命名规则查询 顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询.
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
From: 元动力 1 2 List<User> findAllByUsername(String str);
image-20220808013823671
From: 元动力 1 List<User> findByUsernameAndPassword(String username,String password);
image-20220808014059171
From: 元动力 1 List<User> findByUsernameLike(String username);
image-20220808014320185 具体的关键字,使用方法和生产成SQL如下表所示
Keyword Sample JPQL And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2 Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2 Is,Equals 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)
第五章 使用JPQL的方式查询 使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询。
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可,注意:
大多数情况下将*替换为别名 表名改为类名 字段名改为属性名 搭配注解@Query进行使用
From: 元动力 1 2 @Query("select 表别名 from 表名(实际为类名) 别名 where 别名.属性='itlils'") public List<User> findUsers();
一、入门
From: 元动力 1 2 @Query("select u from User u") List<User> findAllUser();
image-20220808021357039 二、筛选条件
From: 元动力 1 2 @Query("select u from User u where u.username='itlils'") List<User> findAllUserByUsername();
image-20220808021559832 三、投影结果 真实的业务当中,我们只想要表里某一些列。
From: 元动力 1 2 @Query("select u.id from User u") List<Integer> findAllUser2();
image-20220808021946929 四、聚合查询
From: 元动力 1 2 @Query("select count(u) from User u") List<Integer> findAllUser4();
image-20220808022257969 五、传参
From: 元动力 1 2 3 4 5 @Transactional @Modifying @Query("update User set username=?1 where id=?2") int updateUsernameById2 (String username,Integer id) ;
image-20220808022944792 第六章 使用原生sql nativeQuery = true
From: 元动力 1 2 3 4 5 @Transactional @Modifying @Query(value = "update user u set u.username=?1 where u.id=?2",nativeQuery = true) int updateUsernameById3 (String username,Integer id) ;
image-20220808023202904 其他就和mybatis一样使用了
第七章 一对一关系 账户和账户明细
image-20220808030240590
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Data @Entity @Table(name = "account") public class Account { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @Id int id; @Column(name = "username") String username; @Column(name = "password") String password; @JoinColumn(name = "detail_id") @OneToOne AccountDetail detail; }
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Data @Entity @Table(name = "account_details") public class AccountDetail { @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) @Id int id; @Column(name = "address") String address; @Column(name = "email") String email; @Column(name = "phone") String phone; @Column(name = "real_name") String realName; }
这里从日志中可以看出hibernate帮我们完成外键的创建
但这还不能完成同时对两张表进行操作 设置懒加载完成想查什么就查什么功能,设置关联级别完成同时操作两张表
From: 元动力 1 2 3 @JoinColumn(name = "detail_id") @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) AccountDetail detail;
这里的关联级别也是有多个,一般设置为all就行
ALL:所有操作都进行关联操作 PERSIST:插入操作时才进行关联操作 REMOVE:删除操作时才进行关联操作 MERGE:修改操作时才进行关联操作 测试:运行测试类任意方法,边创建完成
image-20220808031126088 现在我们就可以同时添加数据和删除了
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Autowired AccountRepository accountRepository;@Test public void testFind11 () { Account account=new Account (); account.setUsername("itlils" ); account.setPassword("ydl666" ); AccountDetail accountDetail=new AccountDetail (); accountDetail.setPhone("13000000000" ); accountDetail.setRealName("itlils" ); accountDetail.setAddress("cn" ); accountDetail.setEmail("123@qq.com" ); account.setDetail(accountDetail); Account save = accountRepository.save(account); System.out.println("插入之后account id为:" +save.getId()+"|||accountDetail id" +save.getDetail().getId()); }
image-20220808031623110
From: 元动力 1 2 3 4 @Test public void testFind12 () { accountRepository.deleteById(1 ); }
image-20220808031753444 两表都没了数据
image-20220808031813618 第八章 对多关系
From: 元动力 1 2 3 4 5 6 7 8 @OneToMany: 作用:建立一对多的关系映射 属性: targetEntityClass:指定多的多方的类的字节码 mappedBy:指定从表实体类中引用主表对象的名称。 cascade:指定要使用的级联操作 fetch:指定是否采用延迟加载 orphanRemoval:是否使用孤儿删除
image-20220808033918256 实体 Author:作者。
实体 Article:文章。
Author 和 Article 是一对多关系(双向)。那么在JPAopen in new window 中,如何表示一对多的双向关联呢?
JPAopen in new window 使用@OneToMany和@ManyToOne来标识一对多的双向关联。一端(Author)使用@OneToMany,多端(Article)使用@ManyToOne。
在JPAopen in new window 规范中,一对多的双向关系由多端(Article)来维护。就是说多端(Article)为关系维护端,负责关系的增删改查。一端(Author)则为关系被维护端,不能维护关系。
一端(Author)使用@OneToMany注释的mappedBy="author"属性表明Author是关系被维护端。
多端(Article)使用@ManyToOne和@JoinColumn来注释属性 author,@ManyToOne表明Article是多端,@JoinColumn设置在article表中的关联字段(外键)。
代码:
Author
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.ydlclass.jpa.ydljpa.domain;import lombok.Data;import javax.persistence.*;import java.util.List;@Data @Entity @Table(name = "author") public class Author { @Column(name = "id") @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 20,name = "name") private String name; @OneToMany(mappedBy = "author",cascade=CascadeType.ALL,fetch=FetchType.LAZY) private List<Article> articleList; }
Article
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.ydlclass.jpa.ydljpa.domain;import lombok.Data;import javax.persistence.*;@Data @Entity @Table(name = "article") public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(nullable = false, length = 50) private String title; @Lob @Basic(fetch = FetchType.LAZY) @Column(nullable = false) private String content; @ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false) @JoinColumn(name="author_id") private Author author; }
测试: 运行,建表
image-20220808034143395 新增:
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Autowired AuthorRepository authorRepository;@Autowired ArticleRepository articleRepository;@Test public void testFind13 () { Author author=new Author (); author.setName("itlils" ); Author author1 = authorRepository.save(author); Article article1=new Article (); article1.setTitle("1" ); article1.setContent("123" ); article1.setAuthor(author1); articleRepository.save(article1); Article article2=new Article (); article2.setTitle("2" ); article2.setContent("22222" ); article2.setAuthor(author1); articleRepository.save(article2); }
image-20220808034855141 image-20220808034909693 image-20220808034902467 删除:
From: 元动力 1 2 3 4 @Test public void testFind14 () { articleRepository.deleteById(2L ); }
image-20220808035211758 查询:
From: 元动力 1 2 3 4 5 6 7 8 9 10 @Transactional @Test public void testFind15() { Optional<Author> byId = authorRepository.findById(1L); if (byId.isPresent()){ Author author = byId.get(); List<Article> articleList = author.getArticleList(); System.out.println(articleList); } }
1
解决方案:
作者 fetch=FetchType.EAGER
2、
image-20220808041032681 解决方案:循环tostring 破坏一方的tostring即可。文章@ToString(exclude = {"author"})
解决后:
image-20220808041147266 第九章 多对多 关系 实体 User:用户。
实体 Authority:权限。
用户和权限是多对多的关系。一个用户可以有多个权限,一个权限也可以被很多用户拥有。
JPAopen in new window 中使用@ManyToMany来注解多对多的关系,由一个关联表来维护。这个关联表的表名默认是:主表名+下划线+从表名。(主表是指关系维护端对应的表,从表指关系被维护端对应的表)。这个关联表只有两个外键字段,分别指向主表ID和从表ID。字段的名称默认为:主表名+下划线+主表中的主键列名,从表名+下划线+从表中的主键列名。
image-20220808041745146 需要注意的:
1、多对多关系中一般不设置级联保存、级联删除、级联更新等操作。
2、可以随意指定一方为关系维护端,在这个例子中,我指定 User 为关系维护端,所以生成的关联表名称为: user_authority,关联表的字段为:user_id 和 authority_id。
3、多对多关系的绑定由关系维护端来完成,即由 User.setAuthorities(authorities) 来绑定多对多的关系。关系被维护端不能绑定关系,即User不能绑定关系。
4、多对多关系的解除由关系维护端来完成,即由Authority.getUser().remove(user)来解除多对多的关系。关系被维护端不能解除关系,即Authority不能解除关系。
5、如果 User 和 Authority 已经绑定了多对多的关系,那么不能直接删除 Authority,需要由 User 解除关系后,才能删除 Authority。但是可以直接删除 User,因为 User 是关系维护端,删除 User 时,会先解除 User 和 Authority 的关系,再删除 Authority。
Users
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.ydlclass.jpa.ydljpa.domain;import lombok.Data;import javax.persistence.*;import java.util.List;@Data @Entity @Table(name = "users") public class Users { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 20, unique = true) private String username; @Column(length = 100) private String password; @ManyToMany @JoinTable(name = "user_authority",joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "authority_id")) private List<Authority> authorityList; }
Authority
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.ydlclass.jpa.ydljpa.domain;import lombok.Data;import javax.persistence.*;import java.util.List;@Data @Entity @Table(name = "authority") public class Authority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(nullable = false) private String name; @ManyToMany(mappedBy = "authorityList") private List<Users> userList; }
测试:添加
From: 元动力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Autowired private UsersRepository usersRepository;@Autowired private AuthorityRepository authorityRepository;@Test public void testFind16 () { Authority authority = new Authority (); authority.setId(1 ); authority.setName("ROLE_ADMIN" ); authorityRepository.save(authority); }@Test public void testFind17 () { Users users = new Users (); users.setUsername("itlils" ); users.setPassword("123456" ); Authority authority = authorityRepository.findById(1 ).get(); List<Authority> authorityList = new ArrayList <>(); authorityList.add(authority); users.setAuthorityList(authorityList); usersRepository.save(users); }
先运行 saveAuthority 添加一条权限记录,
然后运行 saveUser 添加一条用户记录,与此同时,user_authority 表中也自动插入了一条记录
image-20220808042812330 image-20220808042832949 测试 删除
删除用户
From: 元动力 1 2 3 4 @Test public void testFind18 () { usersRepository.deleteById(1L ); }
user 表中删除一条记录,同时 user_authority 能够级联删除一条记录
image-20220808042939402 中间表和users表数据都删除了