В предыдущих главах были описаны операции CRUD Spring Data Jpa и анализ лежащей в его основе реализации прокси-сервера, а далее представлены сложные запросы, динамические запросы и многотабличные запросы в Spring Data Jpa. (Обучение уровня няни)
Количество слов в статье большое, просьба читать по мере необходимости.
Неясные мелкие партнеры JPA могут обратиться к этой статье:Введение в JPA;
Небольшие партнеры, которые не уверены в среде SpringDataJPA, могут обратиться к этой статье:Случай входа SpringDataJPA;
Чтобы понять процесс реализации прокси-класса SpringDataJPA, вы можете обратиться к этой статье:Основной принцип реализации SpringDadaJPA
Для перепечатки просьба указывать источник.
1. Сложный запрос
I. Запрос правила имени метода
Запрос имени метода: вам нужно только определить метод в соответствии с правилами имени метода, предоставленными SpringDataJpa, и определить метод в интерфейсе dao.
Существует набор соглашений для имен методов.
KeyWord | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | where x.lastname = ?1 or x.firstname = ?2 |
Between | findByAgeBetween | where x.Age between ?1 and ?2 |
LessThan | findByAgeLessThan | where x.age < ?1 |
GreaterThan | findByAgeGreaterThan | where x.age > ?1 |
Like | findByFirstnameLike | where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | where x.firstname not like ?1 |
TRUE | findByActiveTrue() | where x.active = true |
FALSE | findByActiveFalse() | where x.active = false |
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 方法名的约定:
* findBy:查询
* 对象中的属性名(首字母大写):查询条件
* *默认情况:使用 =的方式查询
* 特殊的查询方式,比如模糊查询
* findByCustName-----根据客户名称查询 findBy表示要查询 CustName属性名
* springDataJpa在运行阶段
* 会根据方法名称进行解析 findBy from XXX(实体类)
* 属性名称 where custName
* 1. findBy+属性名称(根据属性名称进行完成匹配任务)
* 2. findBy+属性名称+查询方式(Like|isnull)
* 3. 多条件查询
* findBy+属性名称+查询方式+多条件连接符(and|or)+属性名+查询方式
*/
public List<Customer> findByCustName(String name);
//查询id为3且name中含有大学的用户
public Customer findByCustId(Long id);
public Customer findByCustIdAndCustNameLike(Long id,String name);
}
ii. JPQL-запрос
Использование методов запросов, предоставляемых Spring Data JPA, может решить большинство сценариев приложений, но для некоторых бизнес-процессов Сказал, что нам также нужно гибко построить условия запроса, тогда мы можем использовать аннотацию @Query в сочетании с оператором JPQL для завершения Спросите .
Использовать аннотацию @Query очень просто, просто отметьте аннотацию на методе и предоставьте оператор запроса JPQL.
Уведомление:
Используя @Query для выполнения операции обновления, нам нужно использовать @Query одновременно с @Modifying, чтобы пометить операцию как изменяющий запрос, чтобы фреймворк в конечном итоге сгенерировал операцию обновления вместо запроса.
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 1.根据客户名称查询客户
* jpql:from Customer where custName=?
*/
@Query(value="from Customer where custName =?")
public List<Customer> findCustomerJpql(String name);
/**
* 2.根据客户名称和客户id查询
* 对多个占位符参数
* 默认情况下,占位符的位置需要和方法参数中的位置保持一致
* 也可以指定占位符参数的位置(注意:中间不要有空格)
* ? 索引的方式,指定此占位符的取值来源 eg ?2表示此占位符对应第二个参数
*/
@Query(value="from Customer where custName=?2 and custId=?1")
public Customer findByNameAndId(Long id,String name);
/**
* 3.根据id更新客户的name
* sql:update cst_customer set cust_name=? where cust_id=?
* jpql:update Customer set custName=? where custId=?
*
* @query:代表的是进行查询
* 需要声明此方法是执行更新操作
* 使用 @Modifying
*/
@Query(value = "update Customer set custName=? where custId=?")
@Modifying
public void updateCustomerName(String name,Long id);
}
Примечание: при использовании jpql для завершения операции обновления и удаления при выполнении springDataJpa необходимо вручную добавить поддержку транзакций, потому что по умолчанию транзакция будет откатываться после завершения выполнения.
@Test
@Transactional//添加事务的支持
@Rollback(value = false)
public void updateCustomerName(){
customerDao.updateCustomerName("学生公寓",4L);
}
3. SQL-запрос
Spring Data JPA также поддерживает запрос инструкции sql следующим образом:
/**
* 查询所有用户:使用sql查询
* Sql:select * from cst_customer
* nativeQuery = true配置查询方式,true表示Sql查询,false表示Jpql查询
* 注意:返回值是一个Object[]类型的list
*/
// @Query(value = "select * from cst_customer",nativeQuery = true)
// public List<Object []>findSql();
@Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
public List<Object []>findSql(String name);
2. Динамический запрос
Спецификация интерфейса springdatajpa:
-
JpaRepository
Он инкапсулирует основные операции CRUD, пейджинг и т. д.;
-
JpaSpecificationExecutor
Инкапсулирует сложные запросы.
В приведенном выше методе запроса используются методы интерфейса JpaRepository, далее анализируются методы JpaSpecificationExecutor.
I. Зачем нужен динамический запрос
Могут возникнуть сомнения, зачем вам динамический запрос? Иногда, когда мы запрашиваем сущность, данные условия запроса не фиксируются. В это время нам нужно динамически построить соответствующий оператор запроса. Можно понять, что вышеуказанные условия запроса определены в интерфейсе dao, а условия динамического запроса определено в классе реализации.
ii. Методы, определенные в JpaSpecificationExecutor
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);
List<T> findAll(Specification<T> var1);
Page<T> findAll(Specification<T> var1, Pageable var2);
List<T> findAll(Specification<T> var1, Sort var2);
long count(Specification<T> var1);
}
В приведенном выше методе мы видим спецификацию интерфейса. Можно просто понять, что Спецификация создает условия запроса. Давайте посмотрим на методы, определенные в Спецификации.
/*
* root :T表示查询对象的类型,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
В отличие от приведенного выше метода запроса,Сложные запросы определяются в интерфейсе dao, а динамические запросы определяются в классе реализации.
1) запрос с одним условием
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
@Test
public void conditionTest(){
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询对象类型,需要那个对象就写哪个泛型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借书方法参数中的两个形参
* root:用于获取查询的对象属性
* CriteriaBuilder:构造查询条件,内部封装了很多的查询条件(例如:模糊匹配,精准匹配)
* 需求:根据客户名称查询,查询客户名称为大学
* 查询条件
* 1.查询方法 (精准匹配,是否为空...)
* CriteriaBuilder对象
* 2.比较的属性名称(与哪个字段去以什么方式去比较)
* root对象
*/
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取比较的属性(不是字段名)
Path<Object> custName = root.get("custName");
//2.构造查询条件
/**
* 第一个参数:需要比较的属性(Path)
* 第二个参数:当前比较的取值
*/
Predicate predicate = cb.equal(custName, "三峡大学");//进行精准匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
//根据返回的对象个数选择findOne或者findAll
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}
2) Запрос с несколькими условиями
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 多条件查询:根据用户名和所属行业进行查询
* root:获取属性
* 用户名
* 所属行业
* cb:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3,将以上两个查询联系起来
*/
@Test
public void findByNmaeAndIndustray(){
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取属性
Path<Object> custName = root.get("custName");
Path<Object> industry = root.get("custIndustry");
//2.构造查询
Predicate p1 = cb.equal(custName, "6测试数据-coderxz");
Predicate p2 = cb.equal(industry, "6测试数据-java工程师");
//3。将多个查询条件组合到一起(and/or)
Predicate predicate = cb.and(p1, p2);
return predicate;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}
3) Нечеткий запрос
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 案例:根据客户名称进行模糊配置,返回客户列表
*
* equal:直接的path对象(属性),然后直接进行比较即可
*
* 对于gt,lt,le,like:得到path对象,根据path对象指定比较参数的类型(字符串or数字...),再进行比较
* 指定参数类型 path.as(类型的字节码对象)
*/
@Test
public void findVagueCustomer(){
Specification<Customer>spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
return predicate;
}
};
List<Customer> customers = customerDao.findAll(spec);
for(Customer c:customers){
System.out.println(c);
}
}
}
4) Пейджинговый запрос
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 分页查询
* findAll(Pageable) 没有条件的分页查询
* findAll(Specification,Pageable)
* Specification查询条件
* Pageable分页参数 查询的页码,每页查询的条件
* 返回:Pahe(StringDataJpa)为我们封装好的pageBean对象,数据列表,
*/
@Test
public void pageCustomer(){
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return null;
}
};
/**
* Pageable 接口
* PageRequest是其实现类
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
* 注意:在新版本的jpa中,此方法已过时,新方法是PageRequest.of(page,size)
*/
Pageable pageable = new PageRequest(0,1);
//分页查询 page是SpringDataJpa为我们封装的一个JavaBean
Page<Customer> page = customerDao.findAll(spec, pageable);
//获得总页数(这些数据需要分几页)
System.out.println("查询总页数:"+page.getTotalPages());
//获得总记录数(数据库的总记录数)
System.out.println("查询总记录数:"+page.getTotalElements());
//得到数据集合列表
System.out.println("数据集合列表:"+page.getContent());
}
}
5) Сортировка результатов запроса
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 对查询结果进行排序
*/
@Test
public void findSortCustomer(){
Specification<Customer>spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
return predicate;
}
};
/**
*创建排序对象,需要调用构造方法实例化对象
* 第一个参数:排序的顺序(正序,倒序)
* sort.Direction.DESC:倒序
* sort.Direction.ASC:升序
* 第二个参数:排序的属性名称
*/
Sort sort = new Sort(Sort.Direction.DESC, "custId");
List<Customer> customers = customerDao.findAll(spec,sort);
for(Customer c:customers){
System.out.println(c);
}
}
}
3. Многотабличный запрос
Вышеупомянутые сложные запросы и динамические запросы основаны на запросах к одной таблице, и необходимо указать только однозначное сопоставление между классами сущностей и таблицами базы данных. В многотабличном запросе необходимо изменить отношение сопоставления между классами сущностей.
Между таблицами в базе данных существует три отношения: «многие ко многим», «один ко многим» и «один к одному».
Тогда соответствующее сопоставление сущностей также должно иметь три отношения. Итак, как проанализировать взаимосвязь между таблицами в JPA?
1. Установите связь между таблицей и таблицей
- Шаг 1: Сначала определите взаимосвязь между двумя таблицами. Если отношение определено неправильно, все последующие операции не могут быть правильными.
- Шаг 2: Реализуйте связь между двумя таблицами в базе данных.
- Шаг 3. Опишите взаимосвязь между двумя сущностями в классе сущностей.
- Шаг 4. Настройте сопоставление отношений между классами сущностей и таблицами базы данных (ключевые точки).
4. Один ко многим в JPA
анализ случая:
Принимает два объекта сущности: компанию и сотрудника.
Без учета работы на условиях неполного рабочего дня каждый сотрудник соответствует компании, а в каждой компании работает несколько сотрудников.
В отношении «один ко многим» мы привыкли называть одну сторону главной таблицей, а другую сторону — подчиненной таблицей. Создать пару в базе Многие отношения требуют использования ограничений внешнего ключа базы данных.
**Что такое внешний ключ? ** означает, что в подчиненной таблице есть столбец, и значение относится к первичному ключу в основной таблице, и этот столбец является внешним ключом.
Таблица базы данных:
CREATE TABLE `cst_customer` (
`cust_id` bigint(20) NOT NULL AUTO_INCREMENT,
`cust_address` varchar(255) DEFAULT NULL,
`cust_industry` varchar(255) DEFAULT NULL,
`cust_level` varchar(255) DEFAULT NULL,
`cust_name` varchar(255) DEFAULT NULL,
`cust_phone` varchar(255) DEFAULT NULL,
`cust_source` varchar(255) DEFAULT NULL,
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(20) NOT NULL AUTO_INCREMENT,
`lkm_email` varchar(255) DEFAULT NULL,
`lkm_gender` varchar(255) DEFAULT NULL,
`lkm_memo` varchar(255) DEFAULT NULL,
`lkm_mobile` varchar(255) DEFAULT NULL,
`lkm_name` varchar(255) DEFAULT NULL,
`lkm_phone` varchar(255) DEFAULT NULL,
`lkm_position` varchar(255) DEFAULT NULL,
`lkm_cust_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`lkm_id`),
KEY `FKh9yp1nql5227xxcopuxqx2e7q` (`lkm_cust_id`),
CONSTRAINT `FKh9yp1nql5227xxcopuxqx2e7q` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
1. Установите отношение сопоставления между сущностями и таблицами
Примечание. Все используемые аннотации являются спецификациями JPA, и пакет импорта должен импортировать пакет в javac.persistence.
package ctgu.pojo;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
*我们需要配置:
* 1.实体类与表的映射关系(此pojo与数据库中的那一张表关系映射)
* @ Entity
* @ Table(name="cst_customer")name表示数据库中表的名称
* 2.实体类中属性与表中字段的映射关系
* @ Id声明主键的设置
* @ GeneratedValue配置主键是生成策略(自动增长)
* strategy=
* GenerationType.IDENTITY:自增 Mysql(底层数据库支持的自增长方式对id自增)
* GenerationType.SEQUENCE:序列 Oracle(底层数据库必须支持序列)
* GenerationType.TABLE:jpa提供的一种机制,通过一张数据库表的形式帮助我们完成自增
* GenerationType.AUTO:有程序自动的帮助我们选择主键生成策略
* @ Column(name = "cust_id")数据库中表中字段的名字
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
/**
* @ Id声明主键的设置
* @ GeneratedValue配置主键是生成策略(自动增长)
* GenerationType.IDENTITY
* @ Column(name = "cust_id")数据库中表中字段的名字
*/
@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_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
/**
* 配置客户与联系人之间的关系(一个客户对应多个联系人)
* 使用注解的形式配置多表关系
* 1 声明关系
* @ OnetoMany:配置一对多关系
* targetEntity:对方对象的字节码对象
* 2.配置外键(中间表)
* @ JoinColumn
* name:外键的在从表的字段名称(不是属性,是数据库的字段名称)
* referencedColumnName:参照的主表的字段名称
*/
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Set<LinkMan> linkMans=new HashSet<>();
/*
get/set/toString()方法略......
*/
}
package ctgu.pojo;
import javax.persistence.*;
@Entity
@Table(name="cst_linkman")
public class LinkMan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="lkm_id")
private Long lkmId;
@Column(name="lkm_name")
private String lkmName;
@Column(name="lkm_gender")
private String lkmGender;
@Column(name="lkm_phone")
private String lkmPhone;
@Column(name="lkm_mobile")
private String lkmMobile;
@Column(name="lkm_email")
private String lkmEmail;
@Column(name="lkm_position")
private String lkmPosition;
@Column(name="lkm_memo")
private String lkmMemo;
/**
* 配置联系人到客户的多对一关系
* 外键字段是设置在从表中的,且该字段并未作为对象的属性去配置,而实作为外键去配置
*
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
*
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
/*
get/set/toString略...
*/
}
Примечание. В указанных выше объектах сохраняются внешние ключи.
2. Описание аннотации сопоставления
i.@OneToMany
Роль: установить сопоставление отношений «один ко многим». Атрибуты:
- targetEntityClass: указывает байт-код многостороннего класса (обычно используется)
- mappedBy: указывает имя, которое ссылается на основной объект таблицы из класса сущностей таблицы. (обычно используется)
- каскад: указывает каскадную операцию для использования
- fetch: указывает, использовать ли отложенную загрузку
- orphanRemoval: использовать ли удаление сирот
ii.@ManyToOne
Роль: установить отношение «многие к одному». Атрибуты:
- targetEntityClass: указывает байт-код класса сущности одной стороны (обычно используется)
- каскад: указывает каскадную операцию для использования
- fetch: указывает, использовать ли отложенную загрузку
- необязательно: является ли ассоциация необязательной. Если установлено значение false, всегда должна существовать ненулевая связь.
iii.@JoinColumn
Роль: используется для определения соответствия между полями первичного ключа и полями внешнего ключа. Атрибуты:
- имя: укажите имя поля внешнего ключа (обычно используется)
- referencedColumnName: указывает имя поля первичного ключа основной таблицы, на которую делается ссылка (обычно используется)
- уникальный: он уникален. Значение по умолчанию не уникально
- nullable: разрешать ли null. Значение по умолчанию позволяет.
- insertable: разрешить ли вставку. Значение по умолчанию позволяет.
- обновляемый: разрешать ли обновления. Значение по умолчанию позволяет.
- columnDefinition: Информация об определении столбца.
3. Тест «один ко многим»
I. Сохранить компанию и контакты
package ctgu.OntoMany;
import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 保存一个客户,保存一个联系人
* 现象:从表(联系人)的外键为空
* 原因:
* 主表中没有配置关系
*/
@Test
@Transactional
@Rollback(value = false)
public void addTest(){
Customer customer = new Customer();
LinkMan linkMan = new LinkMan();
customer.setCustName("TBD云集中心");
customer.setCustLevel("VIP客户");
customer.setCustSource("网络");
customer.setCustIndustry("商业办公");
customer.setCustAddress("昌平区北七家镇");
customer.setCustPhone("010-84389340");
linkMan.setLkmName("小明");
linkMan.setLkmGender("male");
linkMan.setLkmMobile("13811111111");
linkMan.setLkmPhone("010-34785348");
linkMan.setLkmEmail("123456@qq.com");
linkMan.setLkmPosition("老师");
linkMan.setLkmMemo("还行吧");
/**
* 配置了客户到联系人的关系
* 从客户的角度上,发送了两条insert语句,发送一条更新语句更新数据库(更新从表中的外键值)
* 由于我们配置了客户到联系人的关系,客户可以对外键进行维护
*/
linkMan.setCustomer(customer);
//此添加可以不写会
customer.getLinkMans().add(linkMan);
customerDao.save(customer);
linkManDao.save(linkMan);
}
}
результат операции:
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?
анализировать:
Выполняются два оператора вставки и один оператор обновления, а один оператор обновления является избыточным. Причина этого явления в том, что мы поддерживаем внешний ключ в обоих классах сущностей, что равносильно его сохранению дважды Решение состоит в том, чтобы отказаться от защиты прав одной стороны.
Изменить: изменить реляционное сопоставление в основной таблице, чтобы:
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();
ii. Каскадные добавления
Каскадные операции: манипулирование объектом при манипулировании связанными с ним объектами.
Как использовать: Просто настройте каскад на аннотацию тела операции.
/**
* 放弃外键维护权:我的一对多映射参照对方的属性就可以了
* mappedBy:对方维护关系的属性名称
* cascade = CascadeType.ALL 进行级联操作,all表示级联所有(insert,delete,update)
* .merge 更新
* .persist保存
* .remove 删除
* fetch 配置延迟加载
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>()
Как правило, конфигурация находится в основной таблице, но: Примечание. Используйте CascadeType.ALL с осторожностью.
package ctgu.OntoMany;
import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 级联添加:
* 保存一个客户的同时,保存客户的所有联系人
* 需要在操作主题的实体类上,配置casache属性
*/
@Test
@Transactional
@Rollback(value = false)
public void cascadeAdd(){
Customer customer = new Customer();
LinkMan linkMan = new LinkMan();
customer.setCustName("测试公司1");
linkMan.setLkmName("测试员工张三1");
//注意此处添加
linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);
customerDao.save(customer);
}
}
Результаты теста:
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
iii. Каскадное удаление
При удалении компании удалите всех сотрудников соответствующей компании.
Удаление в JPA заключается в том, чтобы сначала выполнить запрос, а затем выполнить удаление.
/**
* 级联删除:删除1号客户的同时,删除1号客户的所有联系人
* 1.需要区分操作主体(你对那个对象进行操作)
* 2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
* 3.cascade(配置级联)
*/
@Test
@Transactional
@Rollback(value = false)
public void cascadeDelete(){
// Customer customer = customerDao.findOne(1L);
customerDao.delete(40L);
}
Результаты теста:
Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?
Примечание. Опасно использовать каскадное удаление вообще, в случае один-ко-многим. Как следует удалять данные, если каскадные операции не используются?
Удалить только данные из таблицы: можно удалить произвольно.
Удалить данные основной таблицы:
- есть данные из таблицы
- По умолчанию для полей внешнего ключа устанавливается значение null перед выполнением удаления. В это время, если в поле внешнего ключа из структуры таблицы есть ненулевое ограничение, будет сообщено об ошибке.
- Используйте каскадное удаление.
- Сначала следует удалить данные в подчиненной таблице в соответствии со значением внешнего ключа, а затем удалить данные в основной таблице.
- Нет данных из таблицы: просто удалить
iv. Удаление «один ко многим» (некаскадное удаление)
Способ создания: удалить сотрудников по желанию заказчика. (использование пользовательских методов в сложных запросах)
package ctgu.dao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
//根据外键值进行删除
public void deleteByCustomer(Customer customer);
}
Ключевое сопоставление основной таблицы в настоящее время заключается в установке каскадной операции:
@OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();
контрольная работа:
package ctgu.OntoMany;
import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
@Test
@Transactional
@Rollback(value = false)
public void cascadeDelete(){
Customer customer = customerDao.findOne(47L);
linkManDao.deleteByCustomer(customer);
customerDao.delete(47L);
}
}
Результаты теста:
Hibernate: select linkman0_.lkm_id as lkm_id1_1_, linkman0_.lkm_cust_id as lkm_cust9_1_, linkman0_.lkm_email as lkm_emai2_1_, linkman0_.lkm_gender as lkm_gend3_1_, linkman0_.lkm_memo as lkm_memo4_1_, linkman0_.lkm_mobile as lkm_mobi5_1_, linkman0_.lkm_name as lkm_name6_1_, linkman0_.lkm_phone as lkm_phon7_1_, linkman0_.lkm_position as lkm_posi8_1_ from cst_linkman linkman0_ left outer join cst_customer customer1_ on linkman0_.lkm_cust_id=customer1_.cust_id where customer1_.cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?
5. Многие ко многим в JPA
Кейс: пользователи и роли.
Пользователь: Относится к кому-то в обществе.
Персона: относится к людям, которые могут иметь несколько личностей.
Например: у Сяо Мина несколько личностей. Даже если он инженер Java, он является осадным львом и генеральным директором. Помимо Сяо Мина, у инженеров Java также есть Чжан Сан, Ли Си и так далее.
Таким образом, мы говорим, что отношения между пользователями и ролями являются многими ко многим.
1. Установите прямое сопоставление отношений между классами сущностей и таблицами.
package ctgu.pojo;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;
/**
* 配置用户到角色的 多对多 关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany()
* targetEntity = Role.class声明对方的实体类字节码
* 2.配置中间表(两个外键)
* @JoinTable
* name :中间表的名称
* joinColumns,当前对象在中间表的位置
* @JoinColumn
* name:外键在中间表的字段名称
* referencedColumnName:参照的主表的主键名称
* inverseJoinColumns,对方对象在中间表的位置
*/
// @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表的位置
joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns,对方对象在中间表的位置
inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
package ctgu.pojo;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
@ManyToMany(targetEntity = User.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表的位置
joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
//inverseJoinColumns,对方对象在中间表的位置
inverseJoinColumns ={@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
)
//@ManyToMany(mappedBy="roles")应该有一方放弃维护
private Set<User> users = new HashSet<>();
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
2, Описание аннотации сопоставления
i.@ManyToMany
Роль: используется для сопоставления отношений «многие ко многим».Атрибуты:
- каскад: настройка каскадных операций.
- fetch: настроить, использовать ли отложенную загрузку.
- targetEntity: класс сущности цели конфигурации. Не нужно писать при отображении многие ко многим.
- mappedBy: указывает имя, которое ссылается на основной объект таблицы из класса сущностей таблицы. (обычно используется)
ii.@JoinTable
Роль: конфигурация для промежуточной таблицыАтрибуты:
- nam: имя промежуточной таблицы конфигурации
- joinColumns: поле внешнего ключа промежуточной таблицы связано с полем первичного ключа таблицы, соответствующей текущему классу сущностей.
- inverseJoinColumn: поле внешнего ключа промежуточной таблицы связано с полем первичного ключа противоположной таблицы.
iii.@JoinColumn
Роль: используется для определения соответствия между полями первичного ключа и полями внешнего ключа.Атрибуты:
- name: указывает имя поля внешнего ключа
- referencedColumnName: указывает имя поля первичного ключа основной таблицы, на которую делается ссылка.
- уникальный: он уникален. Значение по умолчанию не уникально
- nullable: разрешать ли null. Значение по умолчанию позволяет.
- insertable: разрешить ли вставку. Значение по умолчанию позволяет.
- обновляемый: разрешать ли обновления. Значение по умолчанию позволяет.
- columnDefinition: Информация об определении столбца.
3. Тестирование «многие ко многим»
I. Сохраните пользователей и роли
Таблица базы данных: (на самом деле она может быть автоматически сгенерирована непосредственно SpringdataJPA)
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
дао интерфейс:
package ctgu.dao;
import ctgu.pojo.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface RoleDao extends JpaRepository<Role,Long>, JpaSpecificationExecutor<Role> {
}
package ctgu.dao;
import ctgu.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}
Прецедент:
package ctgu;
import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
/**
* 保存一个用户,保存一个角色
* 多对多放弃维护权:
* 被动的一方放弃,谁被选择谁放弃
*/
@Test
@Transactional
@Rollback(false)
public void addUserAndRole(){
User user = new User();
Role role1 = new Role();
Role role2 = new Role();
Role role3 = new Role();
user.setUserName("李大明");
role1.setRoleName("后端攻城狮");
role2.setRoleName("java程序员");
role3.setRoleName("CEO");
//用户和角色都可以对中间表进行维护,添加两次就重复了
//配置角色到用户的关系,可以对中间表中的数据进行维护
role1.getUsers().add(user);
role2.getUsers().add(user);
role3.getUsers().add(user);
//配置用户到角色的关系,
user.getRoles().add(role1);
user.getRoles().add(role2);
user.getRoles().add(role3);
userDao.save(user);
roleDao.save(role1);
roleDao.save(role2);
roleDao.save(role3);
}
}
Результаты теста:
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
причина:
В режиме «многие ко многим» (сохранение), если отношение установлено в обоих направлениях, это означает, что обе стороны поддерживают промежуточную таблицу и будут вставлять данные в промежуточную таблицу. Два поля промежуточной таблицы также используются как совместный первичный ключ, поэтому сообщается об ошибке, первичный ключ дублируется, и решается проблема невозможности сохранения: нужно только быть в каком-то одном Нужно только отказаться от права обслуживания промежуточной таблицы.Рекомендуется отказаться от пассивной стороны.Конфигурация следующая:
//放弃对中间表的维护权,解决保存中主键冲突的问题
@ManyToMany(mappedBy="roles")
private Set<SysUser> users = new HashSet<SysUser>(0);
Правильный результат:
Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_role_id, sys_user_id) values (?, ?)
Система автоматически создаст таблицу sys_user_role и добавит данные.
ii. Каскадное сохранение
При сохранении пользователя сохраните связанные с ним роли.
Просто настройте каскад на аннотацию объекта операции
@ManyToMany(mappedBy = "roles",cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
package ctgu;
import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
/**
* 级联操作:保存一个用户的同时,保存用户的关联角色
* 只需要在操作对象的注解上配置cascade
*/
@Test
@Transactional
@Rollback(false)
public void addCasecade() {
User user = new User();
Role role = new Role();
user.setUserName("张三");
role.setRoleName("java程序员");
//用户和角色都可以对中间表进行维护,添加两次就重复了
//配置角色到用户的关系,可以对中间表中的数据进行维护
role.getUsers().add(user);
//配置用户到角色的关系,
user.getRoles().add(role);
roleDao.save(role);
}
}
Результаты теста:
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
iii. Каскадное удаление
/**
* 级联操作:删除id为1的用户,同时删除他的关联对象
*/
@Test
@Transactional
@Rollback(false)
public void deleteCasecade() {
roleDao.delete(23L);
}
Результаты теста:
Hibernate: select role0_.role_id as role_id1_0_0_, role0_.role_name as role_nam2_0_0_ from sys_role role0_ where role0_.role_id=?
Hibernate: select users0_.sys_role_id as sys_role2_2_0_, users0_.sys_user_id as sys_user1_2_0_, user1_.user_id as user_id1_1_1_, user1_.age as age2_1_1_, user1_.user_name as user_nam3_1_1_ from sys_user_role users0_ inner join sys_user user1_ on users0_.sys_user_id=user1_.user_id where users0_.sys_role_id=?
Hibernate: delete from sys_user_role where sys_user_id=?
Hibernate: delete from sys_user where user_id=?
Hibernate: delete from sys_role where role_id=?
Уведомление:
- Вызываемый объект является ролью, все нужно настроить cascade cascade = CascadeType.ALL в объекте роли;
- Используйте с осторожностью! Связанные данные могут быть очищены;
6. Многотабличный запрос в Spring Data JPA
В следующем примере используется реализация case «один ко многим».
I. Запрос навигации по объекту
Способ, которым объект перемещается по запросу, заключается в переходе к связанным с ним объектам на основе загруженного объекта. Извлекайте объекты, используя отношения между объектами. Например, чтобы запросить клиента по идентификатору, вы можете вызвать метод getLinkMans() в объекте Customer, чтобы получить все контакты клиента.
Требование объектной навигации состоит в том, что между двумя объектами должна быть связь ассоциации.
Кейс: Запросить компанию и получить всех сотрудников компании
package ctgu.QueryTest;
import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.Set;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ObjectQuery {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 测试导航查询(查询一个对象的时候,通过此查询他的关联对象)
* 对于对象导航查询,默认使用的是延迟加载的形式来查询的,(需要才去查询)
* 调用get方法并不会立即发送查询,而实在关联对象使用的时候才会查询
* 修改配置,将延迟加载改为立即加载
* fetch 需要配置多表映射关系发注解上
*
*/
@Test
@Transactional//解决在java代码中的no Session问题
public void QueryTest01(){
Customer customer = customerDao.findOne(26L);
Set<LinkMan> linkMans = customer.getLinkMans();
for(LinkMan man:linkMans){
System.out.println(man);
}
}
}
Вопрос: Должны ли мы проверять LinkMan при запросе клиента?
Анализ: если мы не проверяем это, нам нужно переписать код и вызывать метод query, когда это необходимо; но проверка его каждый раз приведет к пустой трате памяти сервера.
Решение: при запросе объекта основной таблицы принимается идея ленивой загрузки, и запрос выполняется только тогда, когда нам нужно использовать его через конфигурацию.
ленивая загрузка
Поскольку объектом вышеприведенного вызова является Customer, необходимо настроить ленивую загрузку в объекте Customer.Объект клиента
@OneToMany(mappedBy = "customer",fetch = FetchType.LAZY)
private Set<LinkMan> linkMans=new HashSet<>();
Результаты теста:
Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?
Hibernate: select linkmans0_.lkm_cust_id as lkm_cust9_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_cust_id as lkm_cust9_1_1_, linkmans0_.lkm_email as lkm_emai2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_memo as lkm_memo4_1_1_, linkmans0_.lkm_mobile as lkm_mobi5_1_1_, linkmans0_.lkm_name as lkm_name6_1_1_, linkmans0_.lkm_phone as lkm_phon7_1_1_, linkmans0_.lkm_position as lkm_posi8_1_1_ from cst_linkman linkmans0_ where linkmans0_.lkm_cust_id=?
LinkMan{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
Анализ: мы обнаружили, что он выполнил два оператора select.
Вопрос: Нужно ли нам проверять клиента, когда мы проверяем LinkMan?
Анализ: Поскольку пользователь принадлежит только одной компании, и каждому LinkMan соответствует уникальный Клиент. Если мы не проверяем, нам нужен дополнительный код для проверки при его использовании. И запрос представляет собой один объект, который потребляет меньше памяти.
Решение: в подчиненной таблице принята идея немедленной загрузки.Пока запрашивается сущность подчиненной таблицы, объект главной таблицы будет извлечен одновременно.
загрузить сейчас
@OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();
Результаты теста:
Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
Результаты анализа: мы обнаружили, что был выполнен только один оператор select.
Для сравнения можно обнаружить, что немедленная загрузка заключается в одновременном извлечении объекта запроса и связанных объектов, в то время как отложенная загрузка заключается в том, чтобы сначала запросить целевой объект, если он не вызывается.
Set<LinkMan> linkMans = customer.getLinkMans();
метод, запрос для связанного объекта не будет выполнен.
ii. Используйте запрос спецификации
/**
* Specification的多表查询
*/
@Test
public void testFind() {
Specification<LinkMan> spec = new Specification<LinkMan>() {
public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//Join代表链接查询,通过root对象获取
//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
return cb.like(join.get("custName").as(String.class),"传智播客1");
}
};
List<LinkMan> list = linkManDao.findAll(spec);
for (LinkMan linkMan : list) {
System.out.println(linkMan);
}
}