导包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1、使用
先配置yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/jpa2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
jpa:
hibernate:
ddl-auto: update #无表就创建,有表就不创建
show-sql: true
使用很简单写一个实体类,就能创建出一个数据库
@Entity//声明实体类
@Table(name = "cst_customer")//配置数据库表的名字
@Data
public class Customer {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "cust_id")
private Long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_phone")
private String custPhone;
@Column(name = "cust_address")
private String custAddress;
}
@Entity
:声明实体类@Table(name = "cst_customer")
:配置数据库表的名字@Id
:主键@GeneratedValue(strategy = GenerationType.IDENTITY)
:自增@Column(name = "cust_id")
:对应的列名
2、dao、service、controller
dao:2行就行
//JpaRepository中第一个泛型写实体类,第二个写实体类主键,用于简单的CRUD代码
//JpaSpecificationExecutor泛型写实体类对象,封装了复杂查询
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
}
JpaRepository
中第一个泛型写实体类,第二个写实体类主键,用于简单的CRUD代码JpaSpecificationExecutor
泛型写实体类对象,封装了复杂查询
service:
@Service
public class CustomerService {
@Autowired
CustomerDao customerDao;
public List<Customer> findAll(){
return customerDao.findAll();
}
public Customer findByI(Long id){
return customerDao.findById(id).get();
}
}
controller
@RestController
public class CustomerController {
@Autowired
CustomerService customerService;
@GetMapping("/")
public List<Customer> findAll(){
return customerService.findAll();
}
@GetMapping("/{id}")
public Customer findById(@PathVariable("id") Long id){
return customerService.findByI(id);
}
}
现在只实现了查全部和查单个
3、CRUD
简单的有,dao层写好的,
xxxDao.findAll();
:查询所有数据,返回List<实体类>
xxxDao.findById(id).get();
:查询ID的数据,返回单个数据
xxxDao.save(实体类对象)
:①当没ID时,保存数据,返回对象;②当有ID,更新数据,返回对象
xxxDao.deleteById(id);
:根据ID删除数据
xxxDao.count();
:查询总数是多少
xxxDao.exists(Id)
:查询Id数据是否存在,返回boolean
4、JQPL
这是一个半自动化
的sql语句功能,就是在DAO层写底层代码,但是也不用你写完整的语句,只需要写一半的sql语句就可以
一、比如我们要根据name查找对象
1)dao:
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
//自定义dao层代码
@Query(value = "from Customer where custName = ?1")
public Customer findJpql(String custName);
}
@Query
里面写sql语句,但也不用写完整,里面的值默认是select *
的,我们只要补全后面就行。- 但是这里补全的很神奇,sql表中数据库名是
cst_customer
,列是cust_name
,但是这个不看数据库的,只看你的实体类名字,所以就是from 实体类 where 实体类属性 = ?1
,后面为什么加个1下面再讲
2)service:
public Customer findJpql(String cust_name){
return customerDao.findJpql(cust_name);
}
3)controller
@GetMapping("/test")
public Customer findJpql(){
String custName = "php";
return customerService.findJpql(custName);
}
查看执行的语句就是:
select customer0_.cust_id as cust_id1_0_,
customer0_.cust_address as cust_add2_0_,
customer0_.cust_industry as cust_ind3_0_,
customer0_.cust_level as cust_lev4_0_,
customer0_.cust_name as cust_nam5_0_,
customer0_.cust_phone as cust_pho6_0_,
customer0_.cust_source as cust_sou7_0_
from cst_customer customer0_ where customer0_.cust_name=?
二、更深入地了解JPQL
dao:
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
@Query(value = "from Customer where custName = ?2 and custId = ?1")
public Customer findCustNameAndCustId(Long id,String name);
}
- 首先,得先知道,@Query里面的值与参数名字可以不一样,是根据占位符后面的数字相关的。比如
custName = ?2
,在运行的时候,就是把该方法的第二个参数载入custName
,custId = ?1
就是把第一个参数载入,所以理解就是from Customer where custName = 方法中的name and custId = 方法中的id
service
public Customer findCustNameAndCustId(String name,Long id){
return customerDao.findCustNameAndCustId(id,name);
}
controller
@GetMapping("/test2")
public Customer findCustNameAndCustId(){
String name = "java";
Long id = 1l;
return customerService.findCustNameAndCustId(name,id);
}
三、Jpql的更新操作
dao:
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
@Query(value = "update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateJpql(String name,Long id);
}
- 更新操作呢就要写完整的sql代码了,其中,还要添加
@Modifying
注解,因为@Query
默认是查询的,加了@Modifying
才识别出来是要更新的操作
service
public void updateJpql(String name,Long id){
customerDao.updateJpql(name,id);
}
controller
@GetMapping("/test3")
@Transactional//添加事务性,我也不懂,反正Jpql的添加就是要加这个注解
public Customer updateJpql(){
String name = "Java真好玩";
Long id = 3l;
customerService.updateJpql(name,id);
return customerService.findByI(id);
}
@Transactional
:添加事务性,我也不懂,反正Jpql的添加就是要加这个注解
更新的操作i:①写完整sql语句②dao层添加@Modifying
注解③controller层添加@Transactional
注解
5、sql语句的查询
sql大家懂的都懂,但是怎么在jpa中使用纯净的sql语句查询?
就是@Query中有个参数nativeQuery,nativeQuery: false(使用jpa查询) | true(使用本地查询:sql查询)
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
@Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery=true)
public List<Object[]> findSql(String name);
}
public List<Object[]> findSql(String name){
return customerDao.findSql(name);
}
@GetMapping("/test4")
public List<Object[]> findSql(){
String name = "%av%";
return customerService.findSql(name);
}
6、方法命名规则查询
这个东西啊,就更厉害了,都不用写代码了,什么基本查询,条件查询,模糊查询,你按规则写个方法名,也不用加什么注解,他自动就会补全了,真实非常神奇呢
1)基本查询
为了简便,以下我就不写service层了,controller直接调用dao层
dao层直接创建方法:findBy + 属性名字
:根据属性名字查询对象
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
public Customer findByCustName(String name);
}
@GetMapping("/test5")
public Customer findByCustName(){
return customerDao.findByCustName("java");
}
我的上帝啊,就写个方法名就好了,啥都不用做直接调用就可以用,被震惊到了
2)模糊查询
findBy + 属性名 +查询方式(Like | Isnull )
,如果不写查询方式,默认是=
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
public List<Customer> findByCustNameLike(String name);
}
@GetMapping("/test6")
public List<Customer> findByCustNameLike(){
return customerDao.findByCustNameLike("%av%");
}
3)多条件查询
findBy + 属性名 + 查询方式 + 多条件连接符(And | Or) + 属性名 + 查询方式
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
//这里的参数顺序一定要写好
public Customer findByCustNameLikeAndCustAddress(String custName,String custAddress);
}
@GetMapping("/test7")
public Customer findByCustNameLikeAndCustAddress(){
return customerDao.findByCustNameLikeAndCustAddress("%av%","123");
}
4)前几个
findTopNxxx
:N为查找的前几个的数量
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
//这里的参数顺序一定要写好
public Customer findTop6ByCustName(String custName);
}
5)不等于
findByxxxNot(ccc)
相当于where xx != ccc
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
//这里的参数顺序一定要写好
public Customer findTop6ByCustNameNot(String custName);
}
6)order by 排序
findByAOrderByB(Asc||Desc)
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
//这里的参数顺序一定要写好
public Customer findByCustNameOrderByIdAsc(String custName);
}
7、JpaSpecificationExecutor
底层的dao我们继承了JpaSpecificationExecutor,这个东西只有几个方法
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);
很明显,这些都是查找的东西,只是要我们往里面添加东西。
- Pageable :分页的类
- Sort :排序
- Specification:我们自定义条件的类,一般重点都是这个里面的
1)Specification 查询条件
service调用dao层嘛,JpaSpecificationExecutor都是dao层的代码,就有
public Customer testSpec(){
Customer customer = customerDao.findOne(new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return null;
}
}).get();
return customer;
}
用dao.findOne(Specification<T>)
方法,我们就要创建一个Specification
类进行条件查询,其主要的一个方法toPredicate
,在这里构造条件,其有三个参数:
-
Root<Customer> root
:查询的根对象,也就是Customer
,查询的任何属性都可以从根对象获得 -
CriteriaQuery<?> criteriaQuery
:顶层查询对象,自定义查询方式,但是这个一般都不用!!,了解就行 -
CriteriaBuilder criteriaBuilder
:查询构造器,封装查询条件,一般都是用该参数进行条件构造的,这个是重点!!展示一些查询条件:
-
所以,自定义查询条件步骤是
- 实现Specification 接口
- 实现toPredicate方法
- 借助参数构造条件,
root
获取对象属性,criteriaBuilder
构造查询条件,内部封装了很多查询条件(模糊查询,精准查询等等)
①用Spce达到findBycustName的效果
从简单入手吧,根据名字查找,虽然有了但是先学嘛
dao层就不用写了,因为dao层的就那几个方法,需要写的代码应该是在service层的
public Customer specFindByCustName(String name){
Customer customer = customerDao.findOne(new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//获取属性,返回一个我不知道的对象。。
Path<Object> custName = root.get("custName");
/*构造语句: select * from cst_customer where cust_name = ?
第一个参数:要被比较的参数
第二个参数:想要拿去比较的值
*/
Predicate predicate = criteriaBuilder.equal(custName, name);
return predicate;
}
}).get();
return customer;
}
@GetMapping("/test8")
public Customer specFindByCustName(){
return customerService.specFindByCustName("java");
}
达到了根据名字查询的功能,下面来点复杂的
②模糊查找与多条件查询
service
/*
custName模糊查找与(and)custAddress精准查找,两个条件是与的关系
*/
public Customer specFindByCustNameLikeAndCustAddress(String name,String address){
Customer customer = customerDao.findOne(new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//两个属性
Path<Object> custName = root.get("custName");
Path<Object> custAddress = root.get("custAddress");
//where custName like '%av%' and custAddress = '123'
//其中,这个like的第一个参数,不像精准查询的那样简单,要添加个.as(类型 .class)
Predicate p1 = criteriaBuilder.like(custName.as(String.class), name);
Predicate p2 = criteriaBuilder.equal(custAddress, address);
//and连接起来,返回来也是一个对象
return criteriaBuilder.and(p1, p2);//如果是 .or() 就是或
}
}).get();
return customer;
}
@GetMapping("/test9")
public Customer specFindByCustNameLikeAndCustAddress(){
return customerService.specFindByCustNameLikeAndCustAddress("%av%","123");
}
③排序
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
,第一个就是Specification,第二个就是一个Sort类,其创建不是new而是对象.by(排序类型,排序属性)
降序类型:Sort.Direction.DESC
降序,Sort.Direction.ASC
升序
//查询名字不为空的数据,并且降序排序
public List<Customer> specSort(){
//查询用户名字不为空的
Specification specification = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
//查询用户名字不为空的
Path custName = root.get("custName");
Predicate notNull = criteriaBuilder.isNotNull(custName);
return notNull;
}
};
//desc降序排序
Sort sort = Sort.by(Sort.Direction.DESC, "custId");
//List<T> findAll(@Nullable Specification<T> var1, Sort var2);
List<Customer> all = customerDao.findAll(specification, sort);
return all;
}
④排序+分页
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
返回Page对象,其有主要3个方法
所以,返回来的东西是个对象还挺好的
下面来说Pageable
,在findAll(spec,page)
中,第一个参数是spec查询,第二个是分页类,分页类创建用PageRequest
而不是Pageable
,通过of创建而不是new
Pageable pageable = PageRequest.of(参数);
又要分页又要排序就用第二个
//index,当前查询页数
//num,每页查询的数量
public Page findAllPage(int index,int num){
Specification specification = null;//暂时不写查的
Sort sort = Sort.by(Sort.Direction.DESC,"custId");//ID降序
//当前查询页数,每页查询数量,排序方式
Pageable pageable = PageRequest.of(index, num,sort);
// Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
Page page = customerDao.findAll(specification, pageable);
//返回Page类,里面可以获得页数,数据集合,总条数等东西
return page;
}
@GetMapping("/test11/{index}/{num}")
public List<Customer> findAllPage(@PathVariable("index")int index,@PathVariable("num")int num){
Page allPage = customerService.findAllPage(index, num);
System.out.println("总页数:"+allPage.getTotalPages());
System.out.println("总条数:"+allPage.getTotalElements());
return allPage.getContent();
}
控制台:
Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ order by customer0_.cust_id desc limit ?, ?
Hibernate: select count(customer0_.cust_id) as col_0_0_ from cst_customer customer0_
总页数:3
总条数:5
8、一对多
1)准备工作
众所周知,表和表之间有1对1,1对多,多对多的关系,先来讲经典的1对多,公司对员工
这里的东西也是不用写sql就可以完成的了
公司Company:
@Entity
@Table(name = "company")
@Data
public class Company {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "company_id")
private Long companyId;
@Column(name = "company_name")
private String companyName;
@Column(name = "company_num")
private Long companyNum;
/*
虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
@OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
targetEntity:对方对象的字节码对象
@JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
name:从表外键(即使类中没有这个属性)
referencedColumnName:主表主键
*/
@OneToMany(targetEntity = Employee.class)
@JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
private Set<Employee> employees = new HashSet<>();
}
@Entity
@Table(name = "employee")
@Data
public class Employee {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "employee_id")
private Long employeeId;
@Column(name = "employee_name")
private String employeeName;
@Column(name = "employee_job")
private String employeeJob;
/*
@ManyToOne:配置多对1,用于从表类中外键属性前
targetEntity:对方对象的字节码对象
@JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
name:从表外键(即使类中没有这个属性)
referencedColumnName:主表主键
*/
@ManyToOne(targetEntity = Company.class)
@JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
private Company company;
}
- 一个@OneToMany一个@ManyToOne,两者的属性选项targetEntity 都是写
本类.class
,一个用于主表一个用于从表 - @JoinColumn的name都是从表外键(即使数据库表中没有这个列),referencedColumnName 是主表主键
- @JoinColumn的参数属性都是数据库列中的名字而不是类中的属性
public interface CompanyDao extends JpaRepository<Company,Long>, JpaSpecificationExecutor<Company> {
}
public interface EmployeeDao extends JpaRepository<Employee,Long>, JpaSpecificationExecutor<Employee> {
}
运行的时候控制台创建表:
Hibernate: create table company (company_id bigint not null auto_increment, company_name varchar(255), company_num bigint, primary key (company_id)) engine=InnoDB
Hibernate: create table employee (employee_id bigint not null auto_increment, employee_job varchar(255), employee_name varchar(255), employee_company_id bigint, primary key (employee_id)) engine=InnoDB
Hibernate: alter table employee add constraint FKhl48rixcnan3anhl6itgwheuc foreign key (employee_company_id) references company (company_id)
2)测试
@SpringBootTest
class JpaApplicationTests {
@Autowired
CompanyDao companyDao;
@Autowired
EmployeeDao employeeDao;
@Test
void contextLoads() {
Company company = new Company();
company.setCompanyName("小强公司");
company.setCompanyNum(444l);
Employee employee = new Employee();
employee.setEmployeeJob("程序猿");
employee.setCompany(company);//添加外键的值
companyDao.save(company);
employeeDao.save(employee);
}
}
控制台:
Hibernate: insert into company (company_name, company_num) values (?, ?)
Hibernate: insert into employee (employee_company_id, employee_job, employee_name) values (?, ?, ?)
3)更加优化的方法
那个教程是真的坑,那个主表的类,直接改成这样更好,这种叫做主表放弃维护权
@Entity
@Table(name = "company")
@Data
public class Company {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "company_id")
private Long companyId;
@Column(name = "company_name")
private String companyName;
@Column(name = "company_num")
private Long companyNum;
/*
虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
@OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
targetEntity:对方对象的字节码对象
@JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
name:从表外键(即使类中没有这个属性)
referencedColumnName:主表主键
*/
// @OneToMany(targetEntity = Employee.class)
// @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
//或者写以下的方式
@OneToMany(mappedBy = "company")//mappedBy参数写从表中外键的属性名
private Set<Employee> employees = new HashSet<>();
}
- @OneToMany改
@OneToMany(mappedBy =从表中外键的属性名)
,不要@JoinColumn,就可以了,这样更简洁,前面的都是弟弟。。。。
4)级联查询
人话讲就是你这个表更新,相对应的表也更新,有点类似触发器
注意!!级联查询不能用@Data,否则hashCode会无限栈溢出!!
@Data包括@Getter、 @Setter、 @RequiredArgsConstructor 、@ToString 、@EqualsAndHashCode
,因为我们不吭重写hashCode,所以只能拆开一个个写了
用法:在@OneToMany(一般用在此处)或者@ManyToOne中添加参数cascade
,其有几个值是值得讲的
- cascade = CascadeType.ALL :所有
- xx.MERGE:更新
- xx.REMOVE:删除
- xx.PERSIST:保存
@Entity
@Table(name = "company")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class Company {
//xxx
@OneToMany(mappedBy = "company",cascade = CascadeType.PERSIST)//mappedBy参数写从表中外键的属性名
private Set<Employee> employees = new HashSet<>();
}
@Test
void test(){
//删除3号公司,因为使用了级连查询,所以3号公司的员工都会被删除
Company company = companyDao.findById(3l).get();
companyDao.delete(company);
}
9、多对多
1)准备
众所周知多对多是要建立中间表的。比如角色和用户之间的关系就是多对多的关系。
User:
@Entity
@Table(name = "user")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class User {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "user_id")
private Long userId;
@Column(name = "user_name")
private String userName;
@Column(name = "user_age")
private int userAge;
/*
@ManyToMany
targetEntity = 对方表的类
@JoinTable
name = 中间表的名字(自己起)
joinColumns 配置当前用户在中间表的外键
@JoinColumn 数组
name 外键名(自己起)
referencedColumnName 参照主表中的主键名
inverseJoinColumns 配置对方表在中间表的外键,
@JoinColumn 数组
name 外键名(自己起)
referencedColumnName 参照主表中的主键名
*/
// @ManyToMany(targetEntity = Role.class)
// @JoinTable(name = "sys_user_role",
// joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
// inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
// )
@ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinTable(name = "sys_user_role",joinColumns = @JoinColumn(name = "sys_user_id"),inverseJoinColumns = @JoinColumn(name = "sys_role_id"))
private Set<Role> roles = new HashSet<>();
}
- 要说的很多点都在注释里面说了。
- 注意,多对多一定要用到
cascade = CascadeType.xxx
,没有就报错。至于后面的fetch = FetchType.LAZY
,我也不知道是什么。。
@Entity
@Table(name = "role")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class Role {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
@ManyToMany(mappedBy = "roles",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private Set<User> users = new HashSet<>();
}
@ManyToMany(mappedBy = "roles")
,mappedBy 参照另一个表的类属性名
继承JPA的DAO层就不写了
2)测试
@SpringBootTest
class JpaApplicationTests2 {
@Autowired
UserDao userDao;
@Autowired
RoleDao roleDao;
@Test
void test1(){
User user = new User();
user.setUserName("小明");
user.setUserAge(23);
Role role = new Role();
role.setRoleName("程序猿");
user.getRoles().add(role);
userDao.save(user);
roleDao.save(role);
}
}
- 这里有一个很重要的点,就是添加中间表数据一定要在两表保存数据之前,代码就是
user.getRoles().add(role);
一定要在xx.save()
之前
10、对象导航查询
看下之前的一对多:公司对员工,这里给出简洁属性
public class Company {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "company_id")
private Long companyId;
@Column(name = "company_name")
private String companyName;
@Column(name = "company_num")
private Long companyNum;
private Set<Employee> employees = new HashSet<>();
}
public class Employee {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "employee_id")
private Long employeeId;
@Column(name = "employee_name")
private String employeeName;
@Column(name = "employee_job")
private String employeeJob;
private Company company;
}
可以看到公司类中Set就是为了保存员工,才有这个属性的。然后员工类中有Company就是为了显示出其是哪个公司的
对象导航查询人话模式:根据ID找到该公司,然后公司.get员工
,就可以得到所有在这个公司工作的员工数据
重大坑
这个B有个重大的坑,就是一切都很正常地理所应当嘛
service
@Service
public class CompanyAndEmployeeService {
@Autowired
CompanyDao companyDao;
@Autowired
EmployeeDao employeeDao;
//输入公司ID,显示该公司所有员工
public Set<Employee> findEmployeeByCompany(Long id){
Company company = companyDao.findById(id).get();
Set<Employee> employees = company.getEmployees();
return employees;
}
}
controller
@RestController
public class CompanyAndEmployeeController {
@Autowired
CompanyAndEmployeeService companyAndEmployeeService;
@GetMapping("test12/{id}")
public Set<Employee> findEmployeeByCompany(@PathVariable("id") Long id){
Set<Employee> employeeByCompany = companyAndEmployeeService.findEmployeeByCompany(id);
return employeeByCompany;
}
}
在service层根据ID找到公司,并且返回这个公司的员工Set集合。然后controller直接打印输出。
但是!!!就会发生这样的错误
可以看到,重复的数据在不停地查询
原因
当使用JPA查询Company,会自动查询Employee,同理当查询Employee时,会自动查询Company。当出现上述问题中的两种情况时,会导致toString()或序列化的实体对象一直相互调用,进入死循环。所以,不能重写toString()。不能序列化实体类。
解决办法
对实体类中的级联对象加入@JsonIgnoreProperties注解,序列化时就会忽略对级联对象的序列化,从而中断死循环。
重写实体类
@Entity
@Table(name = "employee")
@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "employee_id")
private Long employeeId;
@Column(name = "employee_name")
private String employeeName;
@Column(name = "employee_job")
private String employeeJob;
/*
@ManyToOne:配置多对1,用于从表类中外键属性前
targetEntity:对方对象的字节码对象
@JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
name:从表外键(即使类中没有这个属性)
referencedColumnName:主表主键
*/
@ManyToOne(targetEntity = Company.class,fetch = FetchType.LAZY)
@JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
@JsonIgnoreProperties(value = {"employees"})
private Company company;
}
@Entity
@Table(name = "company")
@Getter
@Setter
@RequiredArgsConstructor
public class Company {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "company_id")
private Long companyId;
@Column(name = "company_name")
private String companyName;
@Column(name = "company_num")
private Long companyNum;
/*
虽然说主表不用写外键这个东西,但是说写了这个可以双向选择什么的,我也不懂
@OneToMany:配置1对多,用于主表类中,但是这个数据库不会显示这个列
targetEntity:对方对象的字节码对象
@JoinColumn:配置外键(主表也可以配置这个,这样双方都能获取对方的值)
name:从表外键(即使类中没有这个属性)
referencedColumnName:主表主键
*/
// @OneToMany(targetEntity = Employee.class)
// @JoinColumn(name = "employee_company_id",referencedColumnName = "company_id")
//或者写以下的方式
@OneToMany(mappedBy = "company",cascade = CascadeType.ALL,fetch = FetchType.LAZY)//mappedBy参数写从表中外键的属性名
@JsonIgnoreProperties(value = {"company"})
private Set<Employee> employees = new HashSet<>();
}
这里@JsonIgnoreProperties的值结合 Spring 补充知识 的13点一起看
这样就变正常了
2、延迟加载
一般都是延迟加载,要调成立马加载也可以
用在@ManyToOne、@OneToMany、@ManyToMany之中
@ManyToMany(fetch = FetchType.xx)
xx有LAZY延迟加载和EAGER立即加载
代码:
https://github.com/E-10000/spring-data-jpa-demo
11、总结
来总结一下吧,用了jpa就最好不要用lombok,如果要用,就尽量只用
@Getter
@Setter
@RequiredArgsConstructor
不要重写tostring和hashcode。
使用步骤就参照1.2那里,建立实体类和继承jpa,然后查询就继续往下看,标语表之间的联系先做好