写在前面
本文接SpringDataJpa学习(2)——SpringDataJpa的单表使用,上次我们学习了单表的使用,这次我们来学习下多表的配置和使用
一对多的配置
这里我们先定义一个新的实体类:
@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.声明关系 @OneToMany 配置一对多关系
* 2.配置外键 @JoinColumn 配置外键 name:外键字段名称 referencedColumnName:参照的主表的主键字段名称
* 在客户实体类上(一的那方)添加了外键配置,所以对于客户而言,也具备了维护外键的作用
*
* mappedBy:对方配置关系的属性名称,放弃外键维护权
* cascade:配置级联(可以配置到设置多表的映射关系的注解上)
* CascadeType.ALL:所有 CascadeType.MERGE:更新 CascadeType.PERSIST 保存,CascadeType.REMOVE 删除
*/
// @OneToMany(targetEntity = LinkMan.class)
// @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
/**
* fetch:配置关联对象的加载方式
* EAGER: 立即加载
* LAZY: 延迟加载
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set linkMen = new HashSet<>();
在多的那方配置一下:
/**
* 配置联系人到客户的多对一关系
* 使用注解的形式配置多对一关系
* 1.配置表关系 @ManyToOne 配置多对一关系
* 2.配置外键(中间表)
* @JoinColumn(外键名称)
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
到此配置即告一段落。
下面写一个测试类来测试一下:
@Test
@Transactional
@Rollback(false)
public void testAdd1() {
// 创建一个客户
Customer customer = new Customer();
customer.setCustName("淫荡");
// 创建一个联系人
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小白");
/**
* 配置联系人到客户的关系(多对一)
* 只发送了两条insert语句
* 由于配置了联系人到客户的映射关系(多对一)
*/
linkMan.setCustomer(customer);
customerDao.save(customer);
linkManDao.save(linkMan);
}
可以看到数据库保存成功了。这样就说明我们的配置没有问题了。
Specifications动态查询
有的时候我们的条件是不固定的,这时候我们就需要动态的构建对应的sql语句。SpringDataJpa是提供了该功能的,我们来试一试:
/**
* 根据条件查询单个对象
*/
@Test
public void testSpec() {
// 匿名内部类
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询的对象类型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借助方法参数中的两个参数 (root:获取需要查询的对象属性,CriteriaBuilder:构造查询条件)
* 根据客户名称查询
* 查询条件:1.查询方式 :cb对象
* 2.比较的属性名称:root对象
*/
Specification spec = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder cb) {
// 1.获取比较的属性
Path
可以看到使用的方法也是比较简单的,直接定义一个内部类实现里面的方法即可。
关于两个条件的And或者or,如下:
/**
* 多条件查询
* 案例:根据客户名和客户所属行业查询
*/
@Test
public void testSpec1(){
/**
* root:获取属性 :客户名称/所属行业
* cb:构造查询
* 1. 构造客户名的精准匹配查询
* 2. 构造所属行业的精准匹配查询
* 3. 将以上两个查询联系起来
*/
Specification specification = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder cb) {
// 客户名称
Path
而模糊查询的like略有不同:
/**
* 根据客户名称的模糊匹配
*
* equal:直接使用path对象即可
* gt,lt,ge,le,like 根据path对象,指定比较的参数类型,再去进行比较
* 指定参数类型,path.as(类型的字节码对象)
*/
@Test
public void testSpec2(){
// 构造查询条件
Specification spec = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder cb) {
// 查询的属性
Path custName = root.get("custName");
// 查询方式:模糊匹配
Predicate like = cb.like(custName.as(String.class), "我%");
return like;
}
};
List all = customerDao.findAll(spec);
for (Customer customer : all) {
System.out.println(customer);
}
}
需要提前指定类型。
同样的,我们也可以实现分页查询:
/**
* 分页查询
* findAll() (Specification,Pageable)
* 分页参数:查询的页码,每页查询的条数
* 返回:Page(springDataJpa为我们封装好的PageBean对象)
*/
@Test
public void testSpec4(){
Specification specification = null;
// 第一个参数:当前查询的页数
// 第二个参数:每页查询的数量
Pageable pageable = PageRequest.of(0,2);
// 分页查询
Page page = customerDao.findAll(specification, pageable);
// 得到数据列表
List content = page.getContent();
for (Customer o : content) {
System.out.println(o);
}
// 得到总条数
System.out.println(page.getTotalElements());
// 得到总页数
System.out.println(page.getTotalPages());
}
多对多的配置
与一对多的配置类似,我们先新建两个实体类:
@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")
private Set users = new HashSet<>();
}
@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.声明表关系的配置
* 2.配置中间表(包含两个外键)
*
* @ManyToMany 多对多
* JoinTable name:中间表的名称
*/
@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 roles = new HashSet<>();
}
并且生成对应的get和set方法
之后编写一个测试类测试一下:
/**
* 保存一个用户,保存一个角色
*/
@Test
@Transactional
@Rollback(false)
public void testAdd(){
User user = new User();
user.setUsername("小丽");
Role role = new Role();
role.setRoleName("爱吃饭");
// 配置用户到角色的关系
user.getRoles().add(role);
role.getUsers().add(user);
userDao.save(user);
}
对象导航功能
在多对多种,SpringDataJpa本身还具有对象导航的特性,即可以直接查询到对应的对象,如我们使用之前使用的一对多里的实体类编写测试类:
/**
* 测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象)
*/
@Test
@Transactional
public void testQuery1() {
// 查询id为1的客户
Customer customer = customerDao.getOne(1L);
// 对象导航查询此客户下的所有联系人
Set linkMen = customer.getLinkMen();
for (LinkMan linkMAN : linkMen) {
System.out.println(linkMAN);
}
}
这里也有延迟加载和立即加载的区别:
/**
* 对象导航查询;默认使用的是延迟加载的形式查询的
* 调用get方法并不会立即发送查询,而是在使用关联对象时才会查询
* 不想用:修改配置,将延迟加载改为立即加载 fetch:配置到多表映射关系的注解上
*/
@Test
@Transactional
public void testQuery2() {
// 查询id为1的客户
Optional customer = customerDao.findById(1L);
// 对象导航查询此客户下的所有联系人
Set linkMen = customer.get().getLinkMen();
}
/**
* 从联系人查询他的所属客户
* * 默认使用立即加载
*
*/
@Test
@Transactional
public void testQuery3() {
Optional linkManOptional = linkManDao.findById(2L);
LinkMan linkMan = linkManOptional.get();
// 对象导航查询
Customer customer = linkMan.getCustomer();
System.out.println(customer);
}
这里我们可以看到,由于linkMan是多的那一方,查询到的一般就一个,速度不会有太大影响,所以默认是立即加载。而customer是一的那一方,查询的结果有可能会很多,这时默认使用延迟加载就会更加利于性能。
总结
到这里SpringDataJpa的学习就差不多结束了。体会到了一种不写sql的持久层框架,用起来还是蛮舒服的。日后的工程估计也会尝试使用吧。