在前面的章节已经讲述了SpringDataJpa的CRUD操做以及其底层代理实现的分析,下面介绍SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)java
文章字数较多,请各位按需阅读。mysql
不清楚JPA的小伙伴能够参考这篇文章:JPA简介;程序员
不清楚SpringDataJPA环境搭建的小伙伴能够参考这篇文章:SpringDataJPA入门案例;spring
想了解SpringDataJPA代理类实现过程能够参考这篇文章:SpringDadaJPA底层实现原理sql
如需转载,请注明出处。数据库
方法名查询:只须要按照SpringDataJpa提供的方法名称规则去定义方法,在dao接口中定义方法便可。后端
其中对于方法的名称有一套约定。bash
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);
}
复制代码
使用 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);
}
复制代码
注意:在执行springDataJpa中使用jpql完成更新,删除操做时,须要手动添加事务的支持 必须的;由于默认会执行结束后,回滚事务。
@Test
@Transactional//添加事务的支持
@Rollback(value = false)
public void updateCustomerName(){
customerDao.updateCustomerName("学生公寓",4L);
}
复制代码
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);
复制代码
springdatajpa的接口规范:
JpaRepository<操做的实体类型,实体类型中的 主键 属性的类型>
封装了基本的CRUD的操做,分页等;
JpaSpecificationExecutor<操做的实体类类型>
封装了复杂查询。
上述查询方法使用到的是接口JpaRepository中的方法,下面分析JpaSpecificationExecutor中的方法。
可能有些许疑惑,为何还须要动态查询呢?有时候咱们在查询某个实体的时候哦,给定的查询条件不是固定的,这个时候就须要动态构建相应的查询语句,能够理解为上述的查询条件是定义在dao接口中的,而动态查询条件定义在实现类中。
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);
}
复制代码
在上述方法中,咱们能够看到接口Specification。能够简单理解为,Specification构造的就是查询条件。咱们看看Specification中定义的方法。
/* * root :T表示查询对象的类型,表明查询的根对象,能够经过root获取实体中的属性 * query :表明一个顶层查询对象,用来自定义查询 * cb :用来构建查询,此对象里有不少条件方法 **/
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
复制代码
与上述查询方法不一样,复杂查询定义在dao接口中,而动态查询定义在实现类中。
@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);
}
}
复制代码
@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);
}
}
复制代码
@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);
}
}
}
复制代码
@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());
}
}
复制代码
@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);
}
}
}
复制代码
上述复杂查询和动态查询都是基于单表查询,只须要指定实体类与数据库表中一对一的映射。而多表查询须要修改实体类之间的映射关系。
在数据库中表与表之间,存在三种关系:多对多、一对多、一对一。
那么与之对应的实体映射也应该有三种关系。那么在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;
复制代码
注意:使用的注解都是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略... */
}
复制代码
注意:在上述实体中,均对外键进行了维护。
做用:创建一对多的关系映射 属性:
做用:创建多对一的关系 属性:
做用:用于定义主键字段和外键字段的对应关系。 属性:
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=?
复制代码
分析:
执行了两条insert语句以及一条update语句,有一条update的语句是多余的。产生这种现象的缘由是:咱们在两个实体类中均对外键进行了维护,至关于维护了两次,解决的办法是放弃一方的维权。
修改:将主表中的关系映射修改成:
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();
复制代码
级联操做:操做一个对象同时操做它的关联对象
使用方法:只须要在操做主体的注解上配置casade
/** * 放弃外键维护权:个人一对多映射参照对方的属性就能够了 * 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 (?, ?, ?, ?, ?, ?, ?, ?) 复制代码
删除公司的同时,删除对应公司的全部员工。
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=?
复制代码
注意:通常使用级联删除是比较危险的,在一对多的状况下。若是没有使用级联操做,应该如何删除数据?
只删除从表数据:能够任意删除。
删除主表数据:
建立方法:根据customer删除员工。(使用复杂查询中的自定义方法)
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=?
复制代码
案例:用户和角色。
用户:指社会上的某我的。
角色:指人们可能有多种身份信息
好比说:小明有多种身份,即便java工程师,仍是后端攻城狮,也是CEO;而Java工程师除了小明,还有张3、李四等等。
因此咱们说,用户和角色之间的关系是多对多。
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;
}
}
复制代码
做用:用于映射多对多关系 属性:
做用:针对中间表的配置 属性:
做用:用于定义主键字段和外键字段的对应关系。 属性:
数据库表:(其实能够直接由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;
复制代码
dao接口:
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
复制代码
缘由:
在多对多(保存)中,若是双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据, 中间表的 2 个字段又做为联合主键,因此报错,主键重复,解决保存失败的问题:只须要在任意一 方放弃对中间表的维护权便可,推荐在被动的一方放弃,配置以下:
//放弃对中间表的维护权,解决保存中主键冲突的问题
@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并添加数据。
保存用户的同时,保存其关联角色。
只须要在操做对象的注解上配置cascade
@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 (?, ?)
复制代码
/** * 级联操做:删除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=?
复制代码
注意:
如下例子采用一对多的案例实现。
对象导航查询的方式就是根据已加载的对象,导航到他的关联对象。利用实体与实体之间的关系来检索对象。例如:经过ID查询出一个Customer,能够调用Customer对象中的getLinkMans()方法来获取该客户的全部联系人。
对象导航查询使用的要求是:两个对象之间必须存在关联联系。
案例:查询公司,获取公司下全部的员工
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);
}
}
}
复制代码
问题:咱们在查询Customer时,必定要把LinkMan查出来吗?
分析:若是咱们不查的话,在须要的时候须要从新写代码,调用方法查询;可是每次都查出来又会浪费服务器的内存。
解决:查询主表对象时,采用延迟加载的思想,经过配置的方式,当咱们须要使用的时候才查询。
因为上述调用的对象为Customer,故而在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时,是否须要把Customer查出来?
分析:因为一个用户只属于一家公司,及每一个LinkMan都有惟一的Customer与之对应。若是咱们不查,在使用的时候须要额外代码查询。且查询出的是单个对象,对内存消耗较小。
解决:在从表中采用当即加载的思想,只要查询从表实体,就把主表对象同时查出来。
@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();
方法,则将不会执行关联对象的查询。
/** * 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);
}
}
复制代码