第1章 Specification动态查询
JpaSpecificationExecutor 方法列表
1. T findOne(Specification<T> spec); //查询单个对象
2. List<T> findAll(Specification<T> spec); //查询列表
//查询全部,分页
//pageable:分页参数
//返回值:分页pageBean(page:是springdatajpa提供的)
3. Page<T> findAll(Specification<T> spec, Pageable pageable);
//查询列表
//Sort:排序参数
4. List<T> findAll(Specification<T> spec, Sort sort);
5. long count(Specification<T> spec);//统计查询
对JpaSpecificationExecutor接口基本上是围绕Specification接口定义的,Specification构造的就是查询条件。
1.1 使用Specification完成条件查询
1.1.1 根据客户名称查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testSpecifications() {
//匿名内部类
/**
* 案例:根据客户名称查询,查询客户名为传智播客的客户
* 查询条件
* 1.查询方式
* cb:构建查询,添加查询方式
* 2.比较的属性名称
* root:从实体Customer对象中按照CustName属性进行查询
*/
//使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1.获取比较的属性
Path<Object> custName = root.get("custName");
//2.构造查询方式
return cb.equal(custName,"传智播客");//精准匹配
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
1.1.2 多条件查询,根据客户名称或(和)所属行业进行查询
/**
* 多条件查询
* 案例:根据客户名称和见客户所属行业查询
*/
@Test
public void testSpec2() {
Specification <Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
//1.构造查询
//构造客户名称的精准匹配查询
Predicate p1 = cb.equal(custName, "传智播客");
//构造所属行业的精准匹配查询
Predicate p2 = cb.equal(custIndustry, "政府");
//2.将多个查询条件组合到一起 //(与 ,或)
Predicate or = cb.or(p1, p2);//以与的形式拼接多个查询条件
//cb.and(p1,p2);//以或的方式拼接多的查询条件
return or;
}
};
List<Customer> customers = customerDao.findAll(spec);
for (Customer customer:customers
) {
System.out.println(customer);
}
}
1.1.3 根据客户名称模糊查询
/**
* 案例:完成根据客户名称的模糊查询,返回客户列表
* equal:直接得到path对象(查询属性)进行比较即可
* gt,lt,ge,le,like:得到path对象(查询属性),根据path对象执行比较参数类型,再进行比较
* path.as(类型字节码)
*/
@Test
public void testSpec3() {
Specification spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
//1.查询属性:客户名称
Path<Object> custName = root.get("custName");
//2.查询方式:模糊匹配
Predicate like = cb.like(custName.as(String.class), "传智播客%");
return like;
}
};
/* List<Customer> customers = customerDao.findAll(spec);
for (Customer customer:customers
) {
System.out.println(customer);
}*/
//添加排序(根据id进行倒序)
//Sort.Direction.DESC(倒序)
//Sort.Direction.ASC(升序)
//创建排序对象
Sort sort = new Sort(Sort.Direction.DESC,"custId");
List<Customer> customers = customerDao.findAll(spec, sort);
for (Customer customer:customers
) {
System.out.println(customer);
}
}
1.1.4 分页查询
/**
* 分页查询
* Specification:查询条件
* Pageable:分页查询
* 分页参数: 查询的页码,每页查询的条数
* findAll(Specification, Pageable): 带有条件的分页
* findAll(Pageable): 没有条件的分页
* 返回:Page(Spring Data JPA为我们封装好的pageBean对象,数据列表,共条数)
*/
@Test
public void testSpec4() {
Specification spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
//1.查询属性:客户名称
Path<Object> custName = root.get("custName");
//2.查询方式:模糊匹配
Predicate like = cb.like(custName.as(String.class), "传智播客%");
return like;
}
};
//PageRequest对象是Pageable接口实现类
/**
* 创建PageRequest的过程中,需要调用它的构造方法传入两个参数
* 第一个参数:当前查询的页数
* 第二个参数:每页查询的数量
*/
Pageable pageable = new PageRequest(0,2);
Page<Customer> page = customerDao.findAll(spec, pageable);//有条件的查询
// Page<Customer> page2 = customerDao.findAll(pageable);//无条件的查询
System.out.println(page.getTotalElements());//得到总条数
System.out.println(page.getContent());//得到数据集合列表
System.out.println(page.getTotalPages());//得到总页数
}
第2章 多表设计
表的关系:
- 一对一关系
- 一对多关系
- 一的一方:主表
- 多的一方:从表
- 外键:需要再从表上新建一列作为外键,取值来源于主表的主键
- 多对多关系
实体类中的关系:
- 包含关系:可以通过实体类中的包含关系描述表关系
- 继承关系
分析步骤:
-
- 明确表关系
-
- 确定表关系
-
- 编写实体类,在实体类中描述表关系。
-
- 配置映射关系
第3章 JPA中的一对多
3.1 明确表关系
客户:指一家公司。
联系人:指公司员工。
关系:一对多
3.2 确定表关系
3.3 编写实体类建立映射关系配置
3.3.1 客户的实体类:
@Entity
@Table(name="cst_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="cust_id")
private Long custId;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_name")
private String custName;
@Column(name="cust_phone")
private String custPhone;
@Column(name="cust_source")
private String custSource;
/**
* 配置客户和联系人之间的关系(一对多的关系)
* 使用注解方式配置多表关系
* 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<LinkMan>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custAddress='" + custAddress + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custLevel='" + custLevel + '\'' +
", custName='" + custName + '\'' +
", custPhone='" + custPhone + '\'' +
", custSource='" + custSource + '\'' +
'}';
}
}
3.3.2 联系人的实体类
@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.配置外键(中间表)
* @JoinColumn:配置外键
* name:外键字段名称
* referencedColumnName:参照的主表的主键字段名称
*配置外键过程,配置到了多的一方就会再多的以法国维护外键
*/
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan{" +
"lkmId=" + lkmId +
", lkmName='" + lkmName + '\'' +
", lkmGender='" + lkmGender + '\'' +
", lkmPhone='" + lkmPhone + '\'' +
", lkmMobile='" + lkmMobile + '\'' +
", lkmEmail='" + lkmEmail + '\'' +
", lkmPosition='" + lkmPosition + '\'' +
", lkmMemo='" + lkmMemo + '\'' +
'}';
}
}
3.4 映射的注解说明
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.5 一对多的操作
3.5.1 添加
@RunWith(SpringJUnit4ClassRunner.class)//指定单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 保存一个客户,保存一个联系人
* 目前的效果:客户和联系人作为独立的数据保存到数据库中
* 联系人的外键为空
* 原因?? 实体类中没有配置关系
*/
@Test
@Transactional//配置事务
@Rollback(false) //不自动回滚
public void testAdd() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("baidu");
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("mi");
//设置了双向的关系(会产生一条多余的uodate语句:解决方法就是在一的一方放弃维护权) customer.getLinkMans().add(linkMan);
LinkMan.setCustomer(customer);
customerDao.save(customer);
linkManDao.save(linkMan);
}
}
3.5.2 如何放弃维护权
在一个一方中修改配置
/**
*放弃外键维护权的配置将如下配置改为
*/
//@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
//设置为(mappedBy:指定从表实体类中引用主表对象的名称。)
@OneToMany(mappedBy="customer")
3.5.3 删除
@Autowired
private CustomerDao customerDao;
@Test
@Transactional
@Rollback(false)//设置为不回滚
public void testDelete() {
customerDao.delete(1l);
}
删除操作的说明如下:
删除从表数据:可以随时任意删除。
删除主表数据:
-
有从表数据
1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
3、如果还想删除,使用级联删除引用 -
没有从表数据引用:随便删
在实际开发中,级联删除请慎用!(在一对多的情况下)
3.5.4 级联操作
使用方法:在操作主体的注解上使用cascade
/**
* cascade:配置级联操作
* CascadeType.MERGE 级联更新
* CascadeType.PERSIST 级联保存:
* CascadeType.REFRESH 级联刷新:
* CascadeType.REMOVE 级联删除:
* CascadeType.ALL 包含所有
*/
@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)
第4章 JPA中的多对多
4.1 明确表关系
用户:包含角色的集合。
角色:包含用户的集合。
关系:多对多
4.2 确定表关系
4.3 编写实体类建立映射关系配置
4.3.1 用户的实体类:
@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 = "user_age")
private Integer age;
/**
* 配置用户到角色的多对多关系
* @return
*/
@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@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<Role>();
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
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;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
4.3.2 角色的实体类
@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(mappedBy = "roles")
/* @ManyToMany(targetEntity = User.class)
@JoinTable(name = "sys_user_role",
joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")})*/
private Set<User> users;
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
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;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
'}';
}
}
4.4 映射的注解说明
@ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
@JoinTable
作用:针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
4.5 多对多的操作
4.5.1 保存
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class testManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
/**
* 需求:
* 保存用户和角色
* 要求:
* 创建2个用户和3个角色
* 让1号用户具有1号和2号角色(双向的)
* 让2号用户具有2号和3号角色(双向的)
* 保存用户和角色
* 问题:
* 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
* 解决办法:
* 让任意一方放弃维护关联关系的权利
*/
@Test
@Transactional
@Rollback(false)
public void test1() {
//创建对象
User u1 = new User();
u1.setUserName("用户1");
Role r1 = new Role();
r1.setRoleName("角色1");
//建立关联关系
u1.getRoles().add(r1);
//保存
roleDao.save(r1);
userDao.save(u1);
}
在多对多保存中,如果双向都设置关,意味着双方都维护中间表,都会往中间表插入数据。中间表的2个字段又作为联合主键,所以报错。
解决:只需要任意一方放弃对中间表的维护权即可,
4.5.2 删除
/**
* 案例:删除id为1的用户,同时删除它的关联对象
* 由于已经配置了级联,所有在删除的时候会把User,Role,和关联表也删除。
*/
@Test
@Transactional
@Rollback(false)
public void testCasCadeDel() {
//查询1号用户
User user = userDao.findOne(1l);
//删除1号用户
userDao.delete(user);
}
第5章 Spring Data JPA中的多表查询
5.1 对象导航查询
对象导航查询利用类与类之间的关系来检索对象,根据已经加载的对象,导航到它的关联对象。
5.1.1 查询一个客户并获取客户下的所有联系人
@Autowired
private CustomerDao customerDao;
@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional
public void testFind() {
Customer customer = customerDao.findOne(5l);
Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
for(LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
}
5.1.2 查询一个联系人,并获取联系人下的所有客户
@Autowired
private LinkManDao linkManDao;
@Test
public void testFind() {
LinkMan linkMan = linkManDao.findOne(4l);
Customer customer = linkMan.getCustomer(); //对象导航查询
System.out.println(customer);
}
5.2 对象导肮查询中的延迟策略
在一对多中,对象导航查询默认为延迟加载。
- 修改为立即加载方式 :
在一的一方实体类中配置:fetch=FetchType.EAGER
在多对一中,对象导航查询默认为立即加载。
- 修改为延迟加载方式:
在多的一方实体类中配置:fetch=FetchType.LAZY
5.3 使用Specification查询
/**
* 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);
}
}