Spring Data JPA 实现多表操作,Java常见排序算法面试题

linkMan.setLkmName("联系人1");

linkMan.setLkmGender("male");

linkMan.setLkmMobile("11111111111");

linkMan.setLkmPhone("111-11111111");

linkMan.setLkmEmail("abc@qq.com");

linkMan.setLkmPosition("IT讲师");

linkMan.setLkmMemo("很厉害老师");



/**

 * 配置了联系人到客户的关系(多对一关系)

 * 只发送了两条保存语句insert

 * 因为我们配置了联系人对客户的映射关系(多对一)

 */

linkMan.setCustomer(customer);



/**

 * 配置了客户到联系人的关系(一对多关系)

 * 从客户角度:发送两条insert语句,发送一条更新语句更新数据库(更新外键)

 * 由于我们配置了客户到联系人的关系,客户可以对外键进行维护

 */

customer.getLinkMans().add(linkMan);



/**

 * 会有一条多余的update语句

 * 由于一的一方可以维护外键,会发送update语句

 * 解决此问题:只需要在一的一方放弃维护权即可

 */

//由于配置了多的一方到一的一方的关联关系(当保存时就已经对外键赋值)

linkMan.setCustomer(customer);

//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

customer.getLinkMans().add(linkMan);



customerDao.save(customer);

linkManDao.save(linkMan);

}




运行结果分析:



1.当我们只配置了多的一方到一的一方的关联关系时 linkMan.setCustomer(customer);执行了两条insert语句



2.当我们只配置了配置了一的一方到多的一方的关联关系时customer.getLinkMans().add(linkMan);执行了两条insert语句之后,还执行了update语句



3.当我们建立了双向的关联关系之后,先保存主表,再保存从表时:执行了两条insert语句之后,还执行了update语句



可以看出,在设置了双向关系之后,update语句语句是多余的,想要解决这个问题,我们可以让主表的一方放弃维护权,即修改Customer中的如下字段:



//@OneToMany(targetEntity=LinkMan.class)

//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")

//设置为

@OneToMany(mappedBy="customer")




**2\. 测试删除数据**



@Test

@Transactional

@Rollback(false)//设置为不回滚

public void testDelete() {

customerDao.delete(1l);

}




在执行删除操作时,对于从表数据可以随时任意删除,当我们要删除主表数据数据时:



*   **1\. 有从表数据**  

    在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。

    

    如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段,这时如果还想删除,可以使用级联删除引用,在实际开发中,级联删除请慎用!(在一对多的情况下)

    

*   **2\. 没有从表数据引用:随便删**

    



**级联操作:** 指操作一个对象同时操作它的关联对象,只需要在操作主体的注解上配置cascade,就可以设置级联操作



/**

  • 放弃了外键的维护权

  • mappedBy:对方配置的属性名称(可以配置到设置多表的映射关系的注解上)

  • cascade :配置级联

  • CascadeType.ALL 所有

  • CascadeType.MERGE 更新

  • CascadeType.PERSIST 保存

  • CascadeType.REMOVE 删除

*/

@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)




[](

)JPA中的多对多

---------------------------------------------------------------------



确定两张表之间的关系,对于用户和角色之间的关系,用户可以表示某一个人,它可以有很多中身份,在学校可以是学生,做兼职时可以被称作是临时工,在家里又有了子女的身份。同时对于某一个角色,也可以被多个用户拥有,每个人都可以是子女,都可以是学生,都可以是临时工。



**表关系建立:**



多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:  

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20201202092752646.png?,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0x6eTQxMDk5Mg==,size_16,color_FFFFFF,t_70#pic_left)



**实体类关系建立以及映射配置:**



在实体类中描述出两个实体的关系,一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:



/**

  • @Author: Ly

  • @Date: 2020-12-01 16:57

*/

@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;



/**

 * 配置用户到角色的多对多关系

 * 配置多对多关系

 * 1.声明表关系的配置

 *     @ManyToMany(targetEntity = Role.class)//多对多

 *     targetEntity:代表对方的实体类字节码

 * 2.配置中间表(包含两个外键)

 * @JoinTable

 * name:中间表的名称

 * joinColumns: 配置当前对象在中间表的外键

 * {@JoinColumn的数组 name:外键名,referencedColumnName 参照主表的主键名}

 * inverseJoinColumns:配置对方对象在中间表的外键

 */

@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>();



/**

 * Get、Set、toString

 */

}




一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息,代码如下:



/**

  • @Author: Ly

  • @Date: 2020-12-01 16:58

*/

@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;



/**

 * 配置角色到用户的多对多关系

 * 配置多对多关系

 * 1.声明表关系的配置

 * 2.配置中间表

 */

// @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<User>();



/**

 * Get、Set、toString

 */

}




**多对多的操作:**



**新建测试类:**



/**

  • @Author: Ly

  • @Date: 2020-12-01 09:06

*/

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = "classpath:applicationContext.xml")

public class ManyToManyTest {

@Autowired

private UserDao userDao;



@Autowired

private RoleDao roleDao;

}




**1\. 测试保存操作**



保存用户和角色,用户与角色之间满足多对多关系



/**

  • 保存一个用户,保存一个角色

*/

@Test

@Transactional

@Rollback(false)

public void testAdd(){

User user=new User();

user.setUsername("张三");

user.setAge(20);



Role role=new Role();

role.setRoleName("程序员");



//配置用户到角色的关系,可以对中间表中的数据进行维护

user.getRoles().add(role);

//配置角色到用户的关系,可以对中间表中的数据进行维护

role.getUsers().add(user);



userDao.save(user);

roleDao.save(role);

}




**出现的问题:** 在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,



**解决保存失败的问题:** 只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:



//放弃对中间表的维护权,解决保存中主键冲突的问题

@ManyToMany(mappedBy="roles")

private Set users = new HashSet();




**级联操作:**



/**

  • 放弃了外键的维护权

  • mappedBy:对方配置的属性名称(可以配置到设置多表的映射关系的注解上)

  • cascade :配置级联

  • CascadeType.ALL 所有

  • CascadeType.MERGE 更新

  • CascadeType.PERSIST 保存

  • CascadeType.REMOVE 删除

*/

@ManyToMany(mappedBy = "customer",cascade = CascadeType.ALL)




**测试级联操作:**



/**

  • 测试级联添加(保存一个用户的同时保存关联的角色)

*/

@Test

@Transactional

@Rollback(false)

public void testCascadeAdd(){

User user=new User();

user.setUsername("张三");

user.setAge(20);



Role role=new Role();

role.setRoleName("程序员");



//配置用户到角色的关系,可以对中间表中的数据进行维护

user.getRoles().add(role);

//配置角色到用户的关系,可以对中间表中的数据进行维护

role.getUsers().add(user);



userDao.save(user);

}

/**

  • 测试级联删除(禁用)

  • 在多对多的删除时,双向级联删除根本不能配置

  • 如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据

*/

@Test

@Transactional

@Rollback(false)

public void testCascadeRemove(){

testCascadeAdd();

//查询1号用户

User user=userDao.findOne(1l);

//删除1号用户

userDao.delete(user);

}




[](

)Spring Data JPA中的多表查询

----------------------------------------------------------------------------------



**1\. 对象导航查询:**



对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。



**新建测试类:**



/**

  • @Author: Ly

  • @Date: 2020-12-01 22:05

*/

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = "classpath:applicationContext.xml")

public class ObjectQueryTest {

@Autowired

private CustomerDao customerDao;



@Autowired

private LinkManDao linkManDao;

}




查询一个客户,获取该客户下的所有联系人



//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中

//测试对象导航查询

@Test

@Transactional //解决在Java代码中的no session问题

public void testQuery1(){

//查询id为1的客户

Customer customer=customerDao.getOne(1l);

//对象导航查询,此客户下的所有联系人

Set<LinkMan> linkMans = customer.getLinkMans();



for (LinkMan linkMan:linkMans){

    System.out.println(linkMan);

}

}

//测试对象导航查询

@Test

@Transactional //解决在Java代码中的no session问题

public void testQuery2(){

//查询id为1的客户

Customer customer=customerDao.findOne(1l);

//对象导航查询,此客户下的所有联系人

Set<LinkMan> linkMans = customer.getLinkMans();



for (LinkMan linkMan:linkMans){

    System.out.println(linkMan);

}

}




**对象导航查询的问题分析:**



问题1:我们查询客户时,要不要把联系人查询出来?



如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。



解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。



/**

  • 在客户对象的@OneToMany注解中添加fetch属性

  •  FetchType.EAGER	:立即加载
    
  •  FetchType.LAZY	:延迟加载
    

*/

@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)

private Set linkMans = new HashSet<>();




问题2:我们查询联系人时,要不要把客户查询出来?



例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。



解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来



/**

  • 在联系人对象的@ManyToOne注解中添加fetch属性

  •  FetchType.EAGER	:立即加载
    
  •  FetchType.LAZY	:延迟加载
    

*/

@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)

@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")

private Customer customer;




**2\. 使用Specification查询**



/**

  • Specification的多表查询

*/

@Test

public void testFind() {

Specification<LinkMan> spec = new Specification<LinkMan>() {

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】

Spring Data JPA 实现多表操作,Java常见排序算法面试题

Spring Data JPA 实现多表操作,Java常见排序算法面试题

上一篇:springboot+jpa+MySQL模拟后端数据接口api


下一篇:spring data jpa 分页查询