SpringDataJPA学习笔记
0. 第零部分 (前言)
全文基于哔站图灵教育的SpringDataJpa课程的学习笔记,同时文章末尾会附有github上传的基于SpringBoot+SpringDataJPA的小Demo。欢迎大家批评指正!
1.第一部分(简介)
(简单)介绍SpringDataJPA
1.1概念
-
JPA
JPA全称Java Persistence API(2019年重新命名为 Jakarta Persistence API ),是Sun官方提出的一种ORM规范,属于接口,需要具体的ORM框架来实现。
1)ORM映射元数据:JPA支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对 象持久化到数据库表中; 如:@Entity 、 @Table 、@Id 与 @Column等注解。
2)JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和 SQL代码中解脱出来。 如:entityManager.merge(T t);
3)JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。 如:from Student s where s.name = ?
-
ORM框架
主要解决实体对象与数据库中的表进行字段和属性的映射,通俗讲就是将实体对象抽象成数据库的一张张表,从而我们可以不用sql直接编码,能够操作对象一样从数据库获取数据。常见的ORM框架就有Mybatis,Hibernate。
-
SpringDataJPA
SpirngDataJPA是Spring提供的一套简化JPA开发的框架,它集成了实现JPA接口规范的Hibernate框架,同时提出按照约定好的规则进行【方法命名】去写Dao层接口,从而达到简化开发的效果(省去了简单的CRUD),另外还提供了分页、查询、排序等方法。
Hibernate官方文档
1.2 作用
- 简化Dao层接口的实现
- 降低程序与SQL语句的耦合度
1.3 组成框架图
2. 第二部分(前期准备)
说明:因为之前写了简单的Book的Controller层,因此就在Demo项目中添加了Swagger配置,有使用过的大伙可以直接略过~
2.1 依赖和Properties设置
-
Swagger依赖配置
- 引入依赖
<!-- 图像化依赖UI --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!--引入swagger2依赖 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.20</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>1.5.21</version> </dependency> <!-- swagger2 增强UI ,拥有好看的界面, 和接口分组,排序等功能,如不引用可自行删除--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency
-
properties文件设置
tips:之前我就是遇到过这个版本问题,很坑!
#springboot与swagger2版本冲突问题(需要配置) spring.mvc.pathmatch.matching-strategy = ant_path_matcher
- 编写SwaggerConfig文件
package com.example.jpa_demo.Config; import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration//该注解标记为spring管理的配置类文件 @EnableSwagger2//注解对应上的pom.xml配置的依赖swagger2 @EnableSwaggerBootstrapUI public class SwaggerConfig { //模板swaggerConfig(C/V就完事了以后就可以安装这个模板就行了~) @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("图书管理接口") .apiInfo(apiInfo()) .host("localhost:8080") .select() //要扫描标记过注解的api接口(Controller) .apis(RequestHandlerSelectors.basePackage("com.example.jpa_demo.Controller")) .paths(PathSelectors.any()) .build(); } //可自定义说明文档信息 private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("JpaTestDemo") .description("Test") .contact(new Contact("louis", "", ""))//作者 .termsOfServiceUrl("") .version("1.0") .build(); } }
-
效果图
是不是感觉很简洁好看呢(嘻嘻~)
-
安装lombok和JPABuddy
tips:简化实体类开发和dao层接口的规定方法提示
- 引入依赖
<!--安装lombok依赖 配置相关依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
- 安装插件
-
数据库连接驱动(Mysql)
- 引入依赖
<!-- 数据库连接驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
- properties设置数据库信息
#指定库名testbook spring.datasource.url = jdbc:mysql://localhost:3306/testbook?serverTimezone=Asia/Shanghai #mysql数据库的用户名和密码 spring.datasource.username = root spring.datasource.password = xxx #数据库连接驱动 spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
-
springdataJpa连接配置
- 引入依赖
<!--jpa依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
- properties文件设置
spring.jpa.hibernate.ddl-auto= update #控制台显示sql语句 spring.jpa.show-sql = true #其他常见配置信息 #jpa 配置 #spring.jpa.database-platform=mysql # 是否开启生成表结构 #spring.jpa.generate-ddl=false # 通过实体类生成表的策略 #spring.jpa.hibernate.ddl-auto=none # 在日志中打印SQL语句 #spring.jpa.show-sql=true # 格式化日志中的SQL语句 #spring.jpa.properties.hibernate.format_sql=true # 日志级别 #spring.jpa.properties.hibernate.type=trace # 是否在SQL语句中输出便于调试的注释信息,show_sql=true时有效 #spring.jpa.properties.hibernate.use_sql_comments=true # 在一个session中累计多少条SQL之后批量提交,提交后session会清除缓存并关闭 #spring.jpa.properties.hibernate.jdbc.batch_size=50
3.第三部分(测试实例)
3.0 pojo实体类对象
package com.example.jpa_demo.Pojo.Entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* @author LouisBrilliant
* @version 1.0
*/
@Entity//spring_data_jpa注解
@Data //lombok的注解
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookID;
private String bookName;
}
3.1 SpringDataJpa的自定义操作
-
原生Sql(Jpql)
-
注解@Query
1)索引 =?索引值
2)具名 =:参数名 (方法中需结合@Param注解指定参数名)
-
注解@Transactional @Modifying
用于增删改的方法
package com.example.jpa_demo.Repository; import com.example.jpa_demo.Pojo.Entity.Book; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import javax.transaction.Transactional; import java.util.List; /** * @author LouisBrilliant * @version 1.0 */ //Repository数据访问层 public interface TestBookRepository0 extends JpaRepository<Book,Integer> {//俩参数 实体类 和 ID类型 //如若不写 直接调用jpa框架实现好的crud的API //自定义原生SQL方法即JPQL 无需理会JPA自定义命名规则 注解@Query //索引 格式 =?数字 //具名 格式 =:参数名 结合@Param注解指定参数名 @Query("FROM Book where bookName=?1")//将对象Book转化成数据表 即表名等于类名 Book findBookByBookName(String name); @Query("update Book b set b.bookName=:bookName where b.bookID=:bookId") //增删改 需要下面两个注解 !!! @Transactional @Modifying //类型是int或void 修改指定书籍id更改对应书籍的书名 int updateBookByBookNameAndId(@Param("bookName")String bookName,@Param("bookId")Integer bookId); //纯sql 将nativeQuery设置成true value后跟的是sql语句 // @Query(value = "select * from book where bookName=:bookName",nativeQuery = true) // Book queryBookByName(@Param("bookName") String bookName); }
-
-
规定方法名
ps:一定要按照规定写方法名,要不然就会报错,之前我就遇到过这样的问题(捂脸)
springboot与jpa集成后Bean始终无法注入的问题_jpa无法注入bean_Brilliant.Louis的博客-CSDN博客
-
命名规则
1)主题关键词
决定对数据库的操作(只有查询和删除)
-
2)谓语关键词和修饰符
决定查询条件
-
Query by Example(接口库QueryByExampleExecutor)
-
优缺点
1)实现动态查询代码过于冗长
2)只能匹配字符串类型,无法比较大小
3)不支持嵌套使用 如 firstname = ?0 or (firstname = ?1 and lastname = ?2)
4)可以精确匹配和模糊匹配
-
使用步骤
- 实现Dao接口
package com.example.jpa_demo.Repository; import com.example.jpa_demo.Pojo.Entity.Book; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.QueryByExampleExecutor; import java.util.List; public interface TestBookRepository2 extends PagingAndSortingRepository<Book,Integer> , QueryByExampleExecutor<Book> { }
- 设置匹配器 自定义查询条件ExampleMatcher
- 整合条件和实例对象
基于Test进行测试,并没有在Service进行演示
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Book; import com.example.jpa_demo.Repository.TestBookRepository2; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import java.util.List; @SpringBootTest public class RepositoryTest2 { @Autowired TestBookRepository2 testBookRepository2;//继承了QueryByExampleExecutor(查询对象加匹配器(内嵌筛选条件)) @Test void findUDomesticBook(){//静态查询 Book book = new Book(); book.setBookName("屠龙记");//1.这都可以是前端传过来的数据 这里直接创一个 //2.通过匹配器 对条件进行自定义设置(可以不写) ExampleMatcher matching = ExampleMatcher.matching() .withIgnorePaths("bookName")//设置忽略属性 .withIgnoreCase("bookName")//设置忽略大小写 要对应该类的属性名(要保持一致) .withStringMatcher(ExampleMatcher.StringMatcher.ENDING)//对所有条件字符串进行结尾匹配(模糊查询) .withMatcher("bookName",m-> m.endsWith());//针对单个条件进行锁定查询(模糊查询) //3.通过Example整合查询条件和持久化对象 Example<Book> example=Example.of(book,matching);//俩参数 类对象Object和匹配器ExampleMatcher //4.强制类型转换 List<Book> list=(List<Book>)testBookRepository2.findAll(example); System.out.println(list); } }
-
-
Specifiaction(多条件查询)
-
优缺点
- 代码冗长
- Specification可以任意类型都可以进行查询
-
使用说明
- 实现Dao接口
public interface TestBookRepository4 extends PagingAndSortingRepository<Book,Integer>, JpaSpecificationExecutor<Book> { //Specifications 代码冗长 有点烦 //对于query for example(QueryByExampleExecutor接口) 只能针对字符串模糊查询起作用 而Specification可以任意类型都可以进行查询 }
-
参数
-
root
通过root动态获取持久化对象表中的列 (获取字段)
-
criteriaBuilder
通过CriteriaBuilder设置各种条件 (大于(gt) 小于(lessthan) 范围(in) ) 多种类型进行大小比较
返回类型为Predicate
通过CriteriaBuilder设置各种条件 (降升序(desc/asc))
返回类型为Order
-
query
通过query 组合多条件(order by,where)
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Book; import com.example.jpa_demo.Pojo.Entity.QBook; import com.example.jpa_demo.Repository.TestBookRepository4; import com.querydsl.core.Tuple; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import io.swagger.models.auth.In; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.jpa.domain.Specification; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Table; import javax.persistence.criteria.*; import java.util.ArrayList; import java.util.List; @SpringBootTest public class RepositoryTest4 { @Autowired TestBookRepository4 testBookRepository4; @Test void test04(){ Book book = new Book(); book.setBookName("降龙十八掌"); System.out.println(testBookRepository4.findAll(new Specification<Book>() {// 匿名内部类 函数接口!(就涉及到了类Predicate) @Override public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //root from Book(实体类对象表) 1.通过root动态获取持久化对象表中的列 (获取字段) Path<Integer> bookID = root.get("bookID");//泛型类型改成对应对象的属性类型 Path<String> bookName = root.get("bookName"); // CriteriaBuilder where 2.通过CriteriaBuilder设置各种条件 (大于小于 in) 多种类型进行大小比较 //参数一 为哪个字段设置条件 参数二 value值类型是Object //动态查询 逻辑判断查询 原生sql语句 //使用list集合存储Predicate(条件)类型 List<Predicate> list = new ArrayList<>();//Predicate数组 存储多个过滤条件 if (book.getBookName() != null) { //添加一个equal的lambda表达式 list.add(cb.equal(bookName, "倚天屠龙记"));//符合不为空的条件就把equal的条件添加到泛型为Predicate(函数式编程接口) 的集合list中 } Predicate and = cb.and(list.toArray(new Predicate[list.size()]));//合并(&&)多条件 需要传进去的参数是Predicate数组 通过toarray将集合list转换为数组 同时list赋值给数组Predicate Predicate数组 因为需要的参数是一个可变的数组Predicate...arr 低层实现就是一个数组Predicate ....predicate //非动态查询(无判断进行筛选) //Predicate bookNameP = cb.equal(bookName, "倚天屠龙记");简化lambda表达式 用Predicate类型接收lambda表达式 //Predicate bookIdP = cb.greaterThan(bookID,0); //Predicate and = cb.and(bookIdP, bookNameP);多条件(n个)合并&& //query 组合多条件(order by,where) Order desc = cb.desc(bookName); return query. where(and). orderBy(desc). getRestriction(); } })); } }
-
-
Predicate类
为函数式编程而生,简化代码 (介绍Predicate)
-
-
Qdsl(通用的查询框架 生成Q类对象)
-
配置Qdsl框架依赖配置和插件Plugin
然后用maven去compile!!!要不然就会狠狠地报错!(doge)
<!-- querydsl依赖 生成Q类对象--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> </dependency>
<!--加入插件,让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)--> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <!-- 生成文件存放的目标位置 (记得标记为Sources资源)--> <outputDirectory>target/generated-sources/queries</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin>
- 生成Q类型确定对象资源
-
优缺点
- 需要配置依赖和Plugin(有点麻烦)
- 代码简洁
-
使用说明
- Dao层接口继承
package com.example.jpa_demo.Repository; import com.example.jpa_demo.Pojo.Entity.Book; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.PagingAndSortingRepository; public interface TestBookRepository3 extends PagingAndSortingRepository<Book,Integer>, QuerydslPredicateExecutor<Book>{ }
-
动态查询
-
静态查询
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Book; import com.example.jpa_demo.Pojo.Entity.QBook; import com.example.jpa_demo.Repository.TestBookRepository3; import com.querydsl.core.Tuple; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.List; @SpringBootTest public class RepositoryTest3 { @Autowired TestBookRepository3 testBookRepository3;//继承QuerydslPredicateExecutor接口 (该接口为Qdsl通用查询框架) @PersistenceContext EntityManager em; @Test public void test01(){ QBook book = QBook.book;//以静态方式引用形式进行实例化Q类 //通过id查找 静态查询 //eq 相等 in 加多个参数(String类型)and连接多个条件 //查询单个进行多条件查询 id和bookName BooleanExpression eq = book.bookID.eq(1).and(book.bookName.in("天龙八部","仙剑奇侠传","天外飞仙")); System.out.println(testBookRepository3.findOne(eq));//参数类型是Predicate 而BooleanExpression是Predicate的子类 } @Test public void test02(){ //动态查询querydsl //同样需要对传过来的对象数据进行逻辑判断 然后进行查询 Book book1 = new Book();//相当于前端传来的数据 这里做测试直接实例一个对象 book1.setBookName("平凡的世界"); QBook book = QBook.book;//从数据库获取持久化数据对象(Q类) //初始化Qdsl的查询条件 BooleanExpression exp = book.isNotNull().or(book.isNull()); exp=book1.getBookID()>-1?exp.and(book.bookID.gt(book1.getBookID())):exp;//前端传来的book1与后端获取的持久化数据进行比较 } @Test void test03(){ JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em);//基于接口Querydsl动态查询(推荐) //面向querydsl的api编程 QBook book = QBook.book;//持久化对象表 //类似原生的sql语句查询 简洁易读易上手 动态改变Repository中的表 原生语句是不基于Repository JPAQuery<Tuple> limit = jpaQueryFactory .select(book.bookID, book.bookName)//自定义查询Q类中某一属性 如id和name 如果可以通过select判断返回的类型泛型类型就可以确定 如若不能则Tuple或者? .from(book) .where() .orderBy(book.bookID.desc())//降序 .limit(4);//故返回一个tuple类型的数组 List<Tuple> fetch = limit.fetch();//转换成一个list数组 for (Tuple tuple : fetch) { System.out.println(tuple.get(book.bookID)); System.out.println(tuple.get(book.bookName)); } } }
-
-
Q类对象常用方法
-
等于 EQ : equal .eq
-
不等于 NE : not equal .ne
-
小于 LT : less than .lt
-
大于 GT : greater than .gt
-
小于等于 LE : less than or equal .loe
-
大于等于 GE : greater than or equal .goe
-
3.2 多表查询
补充额外的pojo实体类,为后面多表查询做实例用
package com.example.jpa_demo.Pojo.Entity;
import lombok.Data;
import javax.persistence.*;
import java.util.List;
@Entity
@Data
@Table(name="tb_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//具体岗位
private String position;
//可双向可单向连接 这里我就单向了 不想多一张表出来
// @ManyToMany(cascade = CascadeType.ALL)
// @JoinColumn(name = "customer_id")
// private List<Customer> customerList;
}
package com.example.jpa_demo.Pojo.Entity;
import jdk.nashorn.internal.runtime.logging.Logger;
import lombok.Data;
import lombok.Value;
import javax.persistence.*;
@Data
@Entity
@Table(name = "tb_account")//对象映射表名
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
@OneToOne
//双向关联 设置外键
@JoinColumn(name = "customer_id")
private Customer customer;
}
-
一对一(@OneToOne)
采用的是Qdsl通用框架实现多表查询
-
注解
-
@OneToOne(标记多表的关联关系是一对一)
-
@JoinColumn (设置外键增加字段 将两张表连接起来)
-
@Entity(标记这是Entity实体)
-
@Table(可以自定义设置数据库中表名)
-
@Id、@GeneratedValue(strategy = GenerationType.IDENTITY)
主键标记 其生成策略为自增(相当于sql语句中的auto increment)
-
-
Dao接口和Test测试代码和Pojo类
package com.example.jpa_demo.Pojo.Entity; import lombok.Data; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Data @Entity @Table(name = "tb_customer")//对象映射表名 //@RequiredArgsConstructor//有final的属性有加上构造器 没有则加上无参构造器 @Data含有这个注解 public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY)//相当于mysql中id自增(auto increment) private Integer id; private String name; private Integer age; //单向关联一对一 @OneToOne(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.LAZY)//需要在在配置文件设置开启懒加载 //设置外键id字段名 @JoinColumn(name = "account_id") private Account account; //用户可以有多条信息List集合 需要容器! //一对多默认懒加载 且customer表是被维护端 @OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,fetch = FetchType.LAZY/*,mappedBy = "customer"*/) //添加外键字段名 因为一个用户就有n条信息 如果外键字段名是message_id 那每找一条信息就要查询一次customer就很没有必要啊 //如果外键字段名是customer_id,则查一次customer_id就可以找到多条信息 这不很好? //此外建是建立在tb_message表中的 //一对多,两张表,多的表加主表外键 @JoinColumn(name = "customer_id") private List<Message> message; @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY) //需要容器 可以List或者是Set集合 //下面代码可写可不写(要想自定义那就没关系 可以写) 因为JPA会自动生成一张中间表 /*@JoinTable( name = "tb_customer_role",//设置中间表名 joinColumns = {@JoinColumn(name = "c_id")},//设置本表的外键名称 inverseJoinColumns = {@JoinColumn(name = "r_id")}//设置关联表的名称 )*///多行注释 Ctrl+shift+/ private List<Role> roleList=new ArrayList<>(); private @Version Long version;//原理乐观锁 防止对数据库进行并发操作 通过改变版本号 使得紧接着的修改无法进行对数据进行修改 }
package com.example.jpa_demo.Repository; import com.example.jpa_demo.Pojo.Entity.Customer; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.PagingAndSortingRepository; public interface CustomerRepository extends JpaRepository<Customer,Integer>,QuerydslPredicateExecutor<Customer{ }
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Account; import com.example.jpa_demo.Pojo.Entity.Customer; import com.example.jpa_demo.Pojo.Entity.QCustomer; import com.example.jpa_demo.Repository.CustomerRepository; import com.querydsl.core.types.dsl.BooleanExpression; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class OneToOneTest { @Autowired CustomerRepository customerRepository; @Test public void test06(){ Account account = new Account(); account.setPassword("123456"); account.setUsername("hjy"); // account.setId(1); Customer customer = new Customer(); // customer.setId(1); customer.setName("hjy"); customer.setAge(88); customer.setAccount(account); customerRepository.save(customer); QCustomer customer1 = QCustomer.customer;//qdsl是通用的查询框架 拿来添加插入肯定是行不通的! BooleanExpression id = customer1.id.eq(1); System.out.println(customerRepository.findOne(id)); } @Test public void test07(){ QCustomer customer = QCustomer.customer; System.out.println(customerRepository.findOne(customer.id.eq(3))); } }
-
@OneToOne注解参数
- 配置关联操作(一定要配置!)
1. cascade
所有关联操作(入门无脑选这个)
PERSIST 插入
MERGE 修改
REMOVE 删除
2. fetch 设置是否懒加载
EARGE 默认立即加载
LAZY 只有用到对象才开始加载(挺好的这个 提高多表查询的效率)
3. mappedBy 当一对一双向关联,需要有一方放弃关联 要不然会造成闭环 使得出现一些多余的多表关联操作(update更新) 从而影响效率
- 配置关联操作(一定要配置!)
-
单双向连接一对一
说白了就是连接的两张表都有@OneToOne 则就表示双向连接 否则就是单向连接
-
-
一对多
- Dao接口和测试代码和Pojo类
package com.example.jpa_demo.Repository; import com.example.jpa_demo.Pojo.Entity.Message; import org.springframework.data.jpa.repository.JpaRepository; public interface MessageRepository extends JpaRepository<Message,Integer> { }
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Customer; import com.example.jpa_demo.Pojo.Entity.Message; import com.example.jpa_demo.Repository.CustomerRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.rmi.MarshalException; import java.util.ArrayList; @SpringBootTest public class OneToManyTest { @Autowired CustomerRepository customerRepository; @Test public void test08(){ //插入 Customer customer = new Customer(); customer.setAge(18); ArrayList<Message> msgList = new ArrayList<>(); msgList.add(new Message("八嘎呀路")); customer.setMessage(msgList); customerRepository.save(customer); System.out.println(customer); } }
-
@OneToMany(一个用户可以有多个Message)
- Many指的对象类型是通常是Set或者是List(需要容器,不是容器怎么可以装多个(Many)呢?)
//用户可以有多条信息List集合 需要容器! //一对多默认懒加载 且customer表是被维护端 @OneToMany(orphanRemoval = true,cascade = CascadeType.ALL,fetch = FetchType.LAZY/*,mappedBy = "customer"*/) //添加外键字段名 因为一个用户就有n条信息 如果外键字段名是message_id 那每找一条信息就要查询一次customer就很没有必要啊 //如果外键字段名是customer_id,则查一次customer_id就可以找到多条信息 这不很好? //此外建是建立在tb_message表中的 //一对多,两张表,多的表加主表外键 @JoinColumn(name = "customer_id") private List<Message> message;
-
多对一(结合一对多一起讲)
- Pojo类和测试
package com.example.jpa_demo.Pojo.Entity; import lombok.Data; import javax.persistence.*; @Data @Entity @Table(name = "tb_message") public class Message { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer messageId; private String content; @ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.REMOVE},fetch = FetchType.LAZY)//REMOVE删除子表信息同时也会将关联的父表的数据删除 //外键为主表(customer)的id @JoinColumn(name = "customer_id") private Customer customer; //要添加无参构造器! public Message() { } public Message(String content) { this.content = content; } public Message(String content, Customer customer) { this.content = content; this.customer = customer; } @Override public String toString() { return "Message{" + "messageId=" + messageId + ", content='" + content + '\'' + ", customerId=" + customer.getId() + ", customerName=" + customer.getName() + '}'; } }
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Customer; import com.example.jpa_demo.Pojo.Entity.Message; import com.example.jpa_demo.Repository.CustomerRepository; import com.example.jpa_demo.Repository.MessageRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Commit; import javax.transaction.Transactional; import java.util.ArrayList; import java.util.Optional; @SpringBootTest public class ManyToOneTest { @Autowired MessageRepository messageRepository; @Autowired CustomerRepository customerRepository; @Test @Transactional public void test09(){ ArrayList<Message> msgList = new ArrayList<>(); Customer customer = new Customer(); //一个用户 customer.setName("黄景瑜"); //多条信息 msgList.add(new Message("加油",customer)); msgList.add(new Message("你可以的",customer)); //多个持久化操作saveAll和findAll 需要加 @Transactional!!! messageRepository.saveAll(msgList); System.out.println(messageRepository.findAll()); } @Test @Transactional @Commit//(单元测试会认为你的事务方法@Transactional,只是测试而已,它不会为你提交事务,需要单独加上@Commit) public void test10(){ Optional<Customer> byId = customerRepository.findById(9); customerRepository.delete(byId.get()); } }
-
注解
-
@ManyToOne(双向连接)
-
@Transactional
在进行多个持久化操作 是需要@Transactional 目的就是让方法中的Session囊括一整个方法在内
-
@Commit
使用了@Transactional注解 使得单元测试会认为你的事务方法是测试,因此它不会为你提交事务,需要单独加上@Commit
-
-
多对多
-
Pojo类和测试
package com.example.jpa_demo.Pojo.Entity; import lombok.Data; import javax.persistence.*; import java.util.List; @Entity @Data @Table(name="tb_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; //具体岗位 private String position; //可双向可单向连接 这里我就单向了 不想多一张表出来 // @ManyToMany(cascade = CascadeType.ALL) // @JoinColumn(name = "customer_id") // private List<Customer> customerList; }
package com.example.jpa_demo.RepositoryTest; import com.example.jpa_demo.Pojo.Entity.Customer; import com.example.jpa_demo.Pojo.Entity.Role; import com.example.jpa_demo.Repository.CustomerRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.List; import java.util.Optional; @SpringBootTest public class ManyToManyTest { @Autowired private CustomerRepository customerRepository; @Test public void test13(){ //在多对多的表关系中 不适合删除(REMOVE) Role role = new Role(); Role role1 = new Role(); role.setPosition("总监"); role.setPosition("CEO"); //当前单个用户 Customer customer = new Customer(); //对应多个身份 customer.getRoleList().add(role); customer.getRoleList().add(role1); //保存当前用户的多个身份信息(持久化操作) customerRepository.save(customer); } }
-
-
注解
@JoinTable//可以不写 因为会自动生成表 要是自定义就是可以使用@JoinTable
@JoinTable(
name = "tb_customer_role",//设置中间表名
joinColumns = {@JoinColumn(name = "c_id")},//设置本表的外键名称
inverseJoinColumns = {@JoinColumn(name = "r_id")}//设置关联表的名称
)
3.3 乐观锁
为了处理对数据库数据的并发操作的问题 引出乐观锁 (通过对数据库的操作进行版本改变 使得同一时间同一动作的操作不能生效 因为对象的版本号改变了)
private @Version Long version;//原理乐观锁 防止对数据库进行并发操作 通过改变版本号 使得紧接着的修改无法进行对数据进行修改