SpringDataJPA
(一)简介
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可使开发者用极简的代码就可以实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用Spring Data JPA 可以极大提高开发效率!
它是把JPA又封装了一遍,我们的 dao层中只需要写接口,就自动具有了增删改查、分页查询等方法,持久层不用写了.
(二)基本使用
1.搭建开发环境
依赖配置文件
编写配置文件
简单单表增删改查操作
编写dao层
/*
- JpaRepository:此接口提供了CRUD基本的操作
- 接口只需要extends JpaRepository 即可, 参数1是你要操作当前实体类, 参数2是当前要操作实体类主键的类型
*/
public interface CustomerDao extends JpaRepository<Customer, Long>{
编写service层代码
直接操作service层就行,dao不用管理
/**
-
基本增删改查
*/
@Service
public class CustomerServiceImpl implements CustomerService {@Autowired
private CustomerDao customerDao;/**
- 保存
- @param customer
*/
public void save(Customer customer) {
//保存和修改都是save方法,如果对象里面有id能跟表里面的id对上就是保存
//如果没有id就是保存
customerDao.save(customer);
}
/**
- 根据id查询
- @param l
- @return
*/
public Customer findById(long id) {
//返回值是Optional 是包装类,你直接用.get方法就可以取出来了,是通过dao层你 extends的JpaRepository里面的泛型决定的返回值类型
Optional opt = customerDao.findById(id);
//直接GET出来可以,没有参数
return opt.get();
}
/**
- 删除
- @param c
*/
public void delete(Customer c) {
customerDao.delete©;
}
/**
- 根据id删除
- @param l
*/
public void delete(long id) {
customerDao.deleteById(id);
}
编写controller层代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class Test_SpringDataJPA {
@Autowired
private CustomerService cs;
@Test
public void testSave(){
Customer c = new Customer();
c.setCustName("佳佳洗脚城");
cs.save(c);
}
@Test
public void testFindById(){
Customer c = cs.findById(1L);
System.out.println(c.getCustName());
}
@Test
public void testUpdate(){
Customer c = cs.findById(1L);
c.setCustName("佳佳洗浴中心");
cs.save(c);
}
/**
* 删除,通过传入对象删除.
* Test_SpringDataJPA.class
*/
@Test
public void testDelete() {
/*
* 源码里面会执行一次查询,如果你再查询的话,就会出现控制台打印两次查询语句
* 原因是为了保证要删除的数据库里面的数据一致,它会自己再查询一遍
为什么先查一遍,因为有可能别人先给你数据删除了,没了你再删除就重复了
这样就会报错了,它为了保证不报错,就先查询一下,再对比一级缓存里面有没有数据
这样保证数据库里面有,一级缓存也有,这样就不会报错了.
Customer c = cs.findById(1L);
*/
Customer c = new Customer();
/*
* 即使你中途设置了值,只要是主键对上,
* 也能删除.
*/
c.setCustId(1L);
c.setCustName("yyyy");
cs.delete(c);
// cs.delete(1L);
}
}
JPQL复杂的查询
可以写其它条件,需要程序员自己编写条件查询
dao层
public interface CustomerDao extends JpaRepository<Customer, Long> {
/*
* 使用JPQL查询
* 问号后面的值代表形参的参数的位置
* 就是一个形参也得把 后面的1写上
*/
@Query(“from Customer where custName like ?1 and custId=?2”)
public List findAllCustomer(String name, Long id);
/*
* /SQL 语句查询(基本不用,因为如果你要是使用SQL还不如用Mybatis,性能更高)
*
* nativeQuery = true (默认是false)
*必须要加这个东西,否则框架无法识别,认为它是jpql语句
*/
@Query(value = "select * from cst_customer where cust_name like ?1 and cust_id = ?2", nativeQuery = true)
public List<Customer> findAllCustomerBySQL(String name, Long id);
/*
* 方法命名规则查询,
* 按照 Spring Data JPA 定义的规则,查询方法以 findBy 开头,涉及条件查询时,条件的属性用条件关键
字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后
对剩下部分进行解析。
*/
public List<Customer> findByCustNameLikeAndCustId(String name, Long id);
service层
/**
* JPQL查询
* @param name
* @param id
* @return
*/
public List findAllCustomer(String name, Long id) {
return customerDao.findAllCustomer(name, id);
}
/**
* sql查询
* @param name
* @param id
* @return
*/
public List<Customer> findAllCustomerBySQL(String name, Long id) {
return customerDao.findAllCustomerBySQL(name, id);
}
/**
* 方法命名规则查询
* @param name
* @param id
* @return
*/
public List<Customer> findByCustNameLikeAndCustId(String name, Long id) {
return customerDao.findByCustNameLikeAndCustId(name, id);
}
controller层
@Test
//JPQL查询
public void test1() {
List<Customer> list = cs.findAllCustomer("%集团%", 1L);
for (Customer c : list) {
System.out.println(c.getCustName());
}
}
@Test
//sql查询
public void test2() {
List<Customer> list = cs.findAllCustomerBySQL("%集团%", 1L);
for (Customer c : list) {
System.out.println(c.getCustName());
}
}
@Test
//方法命名查询
public void test3() {
List<Customer> list = cs.findByCustNameLikeAndCustId("%集团%", 1L);
for (Customer c : list) {
System.out.println(c.getCustName());
}
}
Specifications 动态查询(底层是JPA的QBC查询)
QBC查询就是动态加条件查询,通过使用Hibernate提供的Query By Criteria API来查询对象,这种API封装了SQL语句的动态拼装,动态就是条件查询时候不确定有哪些条件查询,dao层不需要编写代码.
controller层
@Test
// Specifications 动态查询(JPA的QBC查询)
public void test4() {
/*
* 查询什么泛型就写什么,然后用匿名内部实现
*
*
/
Specification spec = new Specification() {
/
* cb:该对象中定义着查询条件的方法 (封装查询条件的)
* cq:用于生成sql语句()
* root:获取实体类对象的封装对象,有此对象之后,所有实体类都可以看成此类型(你Root 的话root就是customer对象)
*/
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
/*
* root获取对象里面的值
* as后面跟类型
*/
Predicate p1 = cb.like(root.get("custName").as(String.class), "%集团%"); // custName like ?
Predicate p2 = cb.equal(root.get("custId"), 1L); // custId = ?
//组合上面的两个条件对象
Predicate p3 = cb.and(p1, p2);
//返回组合好的条件对象
return p3;
}
};
List<Customer> list = cs.findByQBC(spec);
for (Customer c : list) {
System.out.println(c.getCustName());
}
}
service层
/**
* QBC查询
* @param spec
* @return
*/
public List findByQBC(Specification spec) {
return customerDao.findAll(spec);
}
dao层
以后dao建议extends两个都写上,
- JpaSpecificationExecutor:提供了复杂查询和分页查询,你需要extends这个才能使用qbc查询等等
*/
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor {
带条件的分页查询
@Test
/*分页查询带条件的Specifications 动态查询(JPA的QBC查询)
*
*/
public void test5() {
Specification spec = new Specification() {
//cb:该对象中定义着查询条件的方法
//cq:用于生成sql语句
//root:获取实体类对象的封装对象,有此对象之后,所有实体类都可以看成此类型
public Predicate toPredicate(Root root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Predicate p1 = cb.like(root.get("custName").as(String.class), "%集团%"); // custName like ?
Predicate p2 = cb.equal(root.get("custId"), 1L); // custId = ?
Predicate p3 = cb.and(p1, p2);
return p3;
}
};
/*分页查询操作
* 导包用import org.springframework.data.domain.Pageable的
* Pageable pageable = PageRequest.of(1, 2);
* 参数1:页码 从零开始
* 参数2:每页显示条数
参数3是 升序降序的
/
Pageable pageable = PageRequest.of(0, 2, Sort.by(Order.desc(“custId”))); //参数1:页码 参数2:每页显示条数
/
* 返回值是一个page对象 .泛型是customer了
* 参数1 是条件对象,如果没有分页条件对象就写null
* 参数2 是 每页显示的条数
*/
Page page = cs.findAllPage(null, pageable);
//取值问题
List list = page.getContent();
for (Customer c : list) {
System.out.println©;
}
}
多表查询
controller层
@Test
/*多表查询
*Specifications 动态查询(多表查询)(JPA的QBC查询)
/
public void test6() {
Specification spec = new Specification() {
/
* cb:该对象中定义着查询条件的方法 (封装查询条件的)
* cq:用于生成sql语句()
* root:获取实体类对象的封装对象,有此对象之后,所有实体类都可以看成此类型(你Root 的话root就是customer对象)
/
public Predicate toPredicate(Root root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
/
* 关联查询 root.join
* 参数一:主表 里面引用从表的实体类的属性名
* 主表里面的: private Set linkMans = new HashSet(0)
* 参数二:JoinType.INNER 是连接状态,这个是内连接(如果不写就是默认内连接) 如果是left 就是左连接 如果是right就是右连接
*
*返回值类型写两个关联的实体类
*/
Join<Customer, LinkMan> join = root.join(“linkMans”, JoinType.INNER);
//条件 通过主键 1 的查询
return cb.equal(root.get(“custId”), 1L);
}
};
List<Customer> list = cs.findByManyCondition(spec);
Customer c = list.get(0);
System.out.println(c);
Set<LinkMan> linkMans = c.getLinkMans();
for (LinkMan lkm : linkMans) {
System.out.println(lkm);
}
}
service层
/**
* 多表查询
* @param spec
* @return
*/
public List findByManyCondition(Specification spec) {
return customerDao.findAll(spec);
}
HibernateJPA
(一)简介
1.ORM的简介
ORM(Object-Relational Mapping) 表示对象关系映射。
Object Relational Mapping
对象 关系 映射
为什么使用 ORM?
通过建立实体类和数据库表的对应关系,从而让开发者不再关心SQL语句怎么写。
直接实现最终效果。减化开发代码。
简单的说:ORM 就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。
常见的 orm 框架: Mybatis(ibatis)、Hibernate、Jpa
2.jpa概述
JPA 的全称是Java Persistence API, 即 Java 持久化 API,是SUN 公司推出的一套基于 ORM 的规范,内部是由一系列的接口和抽象类构成。
JPA 通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
3.JPA 与 hibernate 的关系
JPA规范本质上就是一种ORM规范,提供了一些编程的 API 接口,但具体实现则由服务厂商来提供实现。
JPA 和 Hibernate 的关系就是规范与实现的关系。
(二)入门配置使用
1.基本入门
坐标依赖
org.hibernate
hibernate-c3p0
5.0.7.Final
mysql
mysql-connector-java
5.1.6
org.hibernate
hibernate-entitymanager
5.0.7.Final
编写实体类并配置和表的映射关系
package com.itheima.pojo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
//CREATE TABLE cst_customer
(
// cust_id
BIGINT(32) NOT NULL AUTO_INCREMENT COMMENT ‘客户编号(主键)’,
// cust_name
VARCHAR(32) NOT NULL COMMENT ‘客户名称(公司名称)’,
// cust_source
VARCHAR(32) DEFAULT NULL COMMENT ‘客户信息来源’,
// cust_industry
VARCHAR(32) DEFAULT NULL COMMENT ‘客户所属行业’,
// cust_level
VARCHAR(32) DEFAULT NULL COMMENT ‘客户级别’,
// cust_address
VARCHAR(128) DEFAULT NULL COMMENT ‘客户联系地址’,
// cust_phone
VARCHAR(64) DEFAULT NULL COMMENT ‘客户联系电话’,
// PRIMARY KEY (cust_id
)
//) ENGINE=INNODB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;
@Entity //告知框架这是持久化类,如果不写这个注解,框架不会扫描。
@Table(name=“cst_customer”) //此注解表示映射类与表的关系
public class Customer {
@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;
getset方法…
编写配置文件
在 maven 工程的 resources 路径下创建一个名为 META‐INF 的文件夹,在此文件夹下创建一个名为persistence.xml的配置文件。注意:META‐INF文件夹名称不能修改。persistence.xml文件名称不能改
直接打开看看
2.工具类抽取
代码:
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
-
抽取工具类,
-
重复代码抽取出来
*/
public class JpaUtils {
//声明工厂
private static EntityManagerFactory factory;static {
// 注意:该方法参数必须和persistence.xml中persistence‐unit标签name属性取值一致
//只找META-INF目录下的persistence.xml配置完美
factory = Persistence.createEntityManagerFactory(“jpaUnit”);
}/**
- 获取连接池对象
- JpaUtils.class
*@return
*/
public static EntityManager getEntityManager() {
return factory.createEntityManager();
}
3.基本增删改查排序分页使用
基本的增删改查操作
条件查询分页查询排序查询操作,基本都是固定的操作,拿过来粘贴上就行了.
(三)主键使用策略
1.基本介绍
需要思考哪个列是否适合主键:
自然主键:把具有业务含义的字段作为主键,称之为自然主键。
也可以理解把一个事物具备的属性作为主键
比如人的身份证号(是有含义)
代理主键:把不具备业务含义的字段作为主键,称之为代理主键。 尽量多使用代理主键.
身份证号码作为主键,以前身份证是15位,后来升级18位的,但是主键需要修改,主键一修 改一系列问题来了,引用主键的其他表也需要修改,最后大量修改很麻烦,甚至项目废了
适合主键的列应该永远都不要改才好
代理主键:比如,id自增,有具体的业务含义,只是序号,只是作为每一列的唯一标识而已.
2.JPA的主键生成策略
整数型策略
TABLE,SEQUENCE,IDENTITY,AUTO 四个类型都适用于整数数据类型,想要字符串作为主键都不好使,如果是随机字符串就不行.,下面就得使用hibernate自己的策略
对于这四个数据库只需要记住两个就可以了.java程序开发企业基本只用 MySQL,要不就是oracle数据库,其它很少用
IDENTITY:它适用于数据库中表中的列支持自增长的情况。例如:mysql,db2(ibm公司的),sqlserver 所以这三种数据库直接用这个策略就可以了,底层采用各自数据库的自增
SEQUENCE:它适用于数据库支持序列的情况,通常情况下支持序列的数据不支持表中列的自增长,但是也有例外。例如: oracle支持序列,但是不支持字段自增长。db2也支持序列,也支持字段自增长。 使用oracle数据库请用这个
TABLE:它用了一张表来维护,维护主键的值,值是下一个ID的值。
这个策略是给框架用的,不是给数据库用的
AUTO:就是TABLE
意思就是你是什么数据库,我就产生什么策略,封装了上面三个策略,如果你是MySQL就自动用IDENTITY策略,如果是oracle就自动用SEQUENCE策略.如果是其他数据库的话就是TABLE
但是现在不管用,它只支持table,所以AUTO就是TABLE.
JPA使用hibernate的主键生成策略
总结:
如果是数值类型的就用native策略,如果是字符串就用uuid策略
其实不仅仅只有下面的六个
-
increment :增长,适用类型:short,int,long类型主键.在多进程和集群下不要使用.
-
用的不是数据库的自动增长,hibernate底层的增长策略,select max(custId) from customer; 然后+1作为下一条记录的ID.(不用,在多进程和集群下面不行,因为多线程访问必然会出现问题,假如写两个测试方法,一个新增,但是没有提交这时候上一个主键是2,另一个也新增,上一个主键也是2最后两个都提交主键会重复,都是3)
-
identity:自动增长,用的是数据库的自动增长。适用类型:short,int,long类型主键.支持自动增长数据库如Mysql sqlserver(不用说,跟整数型策略,上面介绍的是一样的,采用数据口自己的自增策略)
-
sequence :序列,适用类型:short,int,long类型主键.支持序列的数据库如:Oracle.(不用解释, 和上面的一样,是oracle的策略)
-
native :本地策略,根据数据库的底层采用使用identity还是sequence.(用数值的都用这个多,是根 据数据库底层而自动采用identity还是sequence,把上面的两个封装了,问题来了,框架怎么 知道你用的是什么数据库,因为你配置文件里面配置方言了,框架会自动根据方言来判断你 用的是什么数据库)
下面两个是整数型的主键使用的
-
uuid :随机的字符串,适用于字符串类型的主键.(是hibernate的策略,底层就是uuid策略)
-
assigned :需要用户手动输入OID的.(需要程序员自己手动输入,一般不用,因为你让用户输入 主键他知道是什么东西)
主键策略的使用(在持久化类)
/* 步骤,先让hibernate的注解指定策略并声明一个值,再用jpa注解,把hibernate的生成好的值的赋值给主键
*- @GenericGenerator注解是 hibernate 提供的。导包是hibernate的包,不是jpa的
- 属性介绍:
- name属性:
-
用于给使用的生成规则起个名称,名称随便写以供 JPA引用 ,
- strategy属性:
-
用于指定 hibernate 中提供的主键生成策略
*@GenericGenerator(name = “hibernate_uuid”, strategy = “uuid”)的含义是
- 代表我声明了一个变量为hibernate_uuid,变量的值是由strategy = "uuid"生成的然后赋给hibernate_uuid
上面的有值的实体,还需要用JPA注解赋值给主键,用下面的注解实现
generator 属性: 用于引用@GenericGenerator 注解name 属性的值
@GeneratedValue(generator = “hibernate_uuid”)
**/
@Id
@GeneratedValue(generator = “hibernate_uuid”)
private String sid;
(四)缓存机制和快照机制
1.一级缓存
缓存就是有一个集合,或者另一个对象持有了这个对象的引用,才叫缓存
什么是一级缓存:
被另一个对象所引用了,那么这个对象就保存到一级缓存了.一级缓存是EntityManager级别的缓存,可以提高查询效率。
/**
* 演示JPA的一级缓存(一级缓存即是EntityManager对象)
* TestHIbernateJpa.class
* 怎么区分三种状态,看临界点, 看stu对象与em之间的关系
* 主要是打断点看到:
* 持久态和托管态区别: 有没有id
*
* 我们关注的就是持久态,至少持久态才能涉及到一级缓存
*
一级缓存什么时候结束?close时候就关闭了一级缓存
* 一级缓存作用:
* 提高查询效率
* 自动更新修改数据能力(快照机制)
*/
@Test
public void test2() {
//得到EntityManager
EntityManager em = JpaUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
tx.begin();
//瞬时态(刚new出来,和em没有发生关系,就叫瞬时态 ,还没有分配id)
Student stu = new Student();
stu.setUserName("jack");
/*持久态(被em管理了,或者和em发生关系了,就叫持久态)
* 不管你调用em的什么方法,只要你操作了stu那么stu就在一级缓存了
* 持久化特点 有id 并且通常和表里面的主键对应的
*/
em.persist(stu); //一级缓存中
//还没提交,只是在内存里面有数据,一旦提交数据库里面就有数据了,
tx.commit();
//em还没有关闭 那么 stu都是持久态
em.close();
//一旦 em close了, 不管stu了,那么stu就是托管态了
//托管态(游离态,就是不要了)
System.out.println(stu);
}
2.快照机制
自动更新数据能力
/**
* 演示JPA的一级缓存的快照机制(可以自动更新数据)
* 一级缓存里面有一系列集合,一级缓存集合里面有 数据区域和快照区域 两个区域
* 提交数据,检查一级缓存中的(数据区和快照区)的数据是否一致,如果不一致,则自动执行update语句。
*
* 所以即使不需要em.merge(stu); 也可以保存数据
* 面试:一级缓存怎么就能自动更新数据? 答 : 因为有快照机制
* TestHIbernateJpa.class
*/
@Test
public void test4() {
EntityManager em = JpaUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Student stu = em.find(Student.class, "8a7e874e65c79d9d0165c79d9f3a0000");
stu.setUserName("tom");
//下面这行代码注释了照样可以修改(原因就是快照机制,可以自动更新数据)
//在一级缓存集合中,数据区域值已经发生改变了
//em.merge(stu);
//!!!提交数据,检查一级缓存中的(数据区和快照区)的数据是否一致,如果不一致,则自动执行update语句。
tx.commit();
em.close();
//这时候查出来 UserName 是 tom
System.out.println(stu);
}
(五)多表关系操作
1.基本概念和步骤分析
系统设计的三种实体关系分别为:多对多、一对多和一对一关系。
注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
明确:我们今天只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。
在JPA框架中表关系的分析步骤
第一步:首先确定两张表之间的关系。(主从关系,如果关系确定错了,后面做的所有操作就都不可能正确。)
第二步:在数据库中实现两张表的关系 (在数据库里面把两个表创建出来)
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(描述完了框架不知道,需要配置注解才行)
2.JPA的一对多多对一配置
我们采用的示例为客户和联系人。
客户:指的是一家公司。
联系人:指的是公司中的员工。
在不考虑兼职的情况下,公司和员工的关系即为一对多。
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多多对一实体类的配置
/*
*配置一对多
* 用list集合和set集合,通常习惯用set集合,因为不重复,泛型里面是从表的实体类
* 到底要不要new? 如果不new的话,你getlinkMans.add 的时候会报出空指针异常
* 因为没有被实例化(没有new出来)
* 但是new出来会了浪费内存,所以就new出来是时候指定一下长度,把长度指定为0
* 下面写法,既避免了空指针,又节省了内存
*
* private Set linkMans = new HashSet(0);
*
* 需要用注解告诉框架实体类之间的关系
* !!!在主表里面配置类与类的关系,在从表里面配置表与表的关系
* 配置一对多@OneToMany
* 属性:targetEntity 是目标实体, 里面写多的一方的实体类的字节码文件
* mappedBy:指定子表实体类中引用主表对象的名称.
*/
@OneToMany(targetEntity = LinkMan.class, mappedBy = “customer”)
private Set linkMans = new HashSet(0);
/*多对一的配置
* 不能写外键字段,需要写一的一方的实体类 private Customer customer;
* 但是又有问题了,是对象名字和数据库的字段对不上,就得需要配置来搞定
*
* !!!在主表里面配置类与类的关系,在从表里面配置表与表的关系
* 1.配置多对一注解:@ManyToOne
* 2.@JoinColumn 连接列
* 属性
* name:指定外键字段的名称(表里面的字段,不是实体类的属性)
* 这个name值的含义等价于@Column(name = “xxxxx”)
referencedColumnName:指定引用主表的主键字段名称
* 写主表里面的主键(不是实体类里面的属性,是表字段)
* */
@ManyToOne
@JoinColumn(name = “lkm_cust_id”, referencedColumnName = “cust_id”)
private Customer customer;
一对多多对一保存
/**
* 保存操作
* 需求:
* 保存一个客户和一个联系人
* 要求:
* 创建一个客户对象和一个联系人对象
* 建立客户和联系人之间关联关系(双向一对多的关联关系)
* 先保存客户,再保存联系人
*
*/
@Test
public void test1() {
EntityManager em = JpaUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//创建一个客户和两个联系人
Customer c = new Customer();
c.setCustName("冠希洗脚城");
LinkMan lkm1 = new LinkMan();
lkm1.setLkmName("菜10");
LinkMan lkm2 = new LinkMan();
lkm2.setLkmName("张宪");
//告知客户有哪些联系人!!!如果不写外键没有值
c.getLinkMans().add(lkm1);
c.getLinkMans().add(lkm2);
//告知联系人属于哪个客户!!!!!!如果不写外键没有值
lkm1.setCustomer(c);
lkm2.setCustomer(c);
//保存客户和联系人
em.persist(c);
em.persist(lkm1);
em.persist(lkm2);
tx.commit();
em.close();
}
一对多多对一的删除(慎用)
/**
* 删除操作
* 删除从表数据:可以随时任意删除。
* 删除主表数据:
* 有从表数据引用
* 1、不能删除 (会报错)
* 2、如果还想删除,使用级联删除(操作一个对象,同时操作其关联的对象,慎用,尤其是一对多情况下,避免从删库跑路)
*
* 没有从表数据引用:随便删
*
* 在实际开发中,级联删除请慎用!(在一对多的情况下)
* 在企业里面 逻辑删除用的多, 就是status 逻辑删除状态字段
*/
@Test
public void test2() {
EntityManager em = JpaUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//先查询再删除
Customer c = em.find(Customer.class, 1L);
//如果客户有联系人引用的情况下,删除必然会报错
//非得删除就是先把从表数据里面删除,再删除主表数据(级联删除)
em.remove©;
tx.commit();
em.close();
}
需要在一的一方@OneToMany注解加个配置cascade
级联操作:
指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
/**
- cascade:配置级联操作
- CascadeType.MERGE 级联更新
- CascadeType.PERSIST 级联保存:
- CascadeType.REFRESH 级联刷新:
- CascadeType.REMOVE 级联删除:
- CascadeType.ALL 包含所有
*/
@OneToMany(mappedBy=“customer”,cascade=CascadeType.ALL,targetEntity=LinkMan.class)
@JoinColumn(name=“lkm_cust_id”,referencedColumnName=“cust_id”)
private Set linkmans = new HashSet(0)
3.多对多的配置使用
需要使用到中间表
多对多的实体类配置问题
/*
* @JoinTable:针对中间表的配置
* 属性:
* name:配置中间表的名称
*
* joinColumns:当前这个类所对应的表的外键
* 中间表的外键字段对应多个表的外键字段
* name:当前的文件列名
* referencedColumnName:主键列名
*
* inverseJoinColumn:对方表在中间表对应的关系
* 中间表的外键字段关联对方表的主键字段
*/
@ManyToMany(cascade = CascadeType.ALL)
@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<SysUser> users = new HashSet<SysUser>(0);
/*
* 配置多对多
* @ManyToMany注解
* 属性: targetEntity 对应多对多对方的实体类.class
* mappedBy 对应对方实体类引用这个实体类的属性的名字
*
*/
@ManyToMany(targetEntity = SysRole.class, mappedBy = "users", cascade = CascadeType.ALL)
private Set<SysRole> roles = new HashSet<SysRole>(0);
多对多保存用户和角色操作
/**
* 需求:
* 保存用户和角色
* 要求:
* 创建 2个用户和 3个角色
* 让 1号用户具有 1号和 2号角色(双向的)
* 让 2号用户具有 2号和 3号角色(双向的)
* 保存用户和角色
* 问题:
* 在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
* 解决办法:
* 让任意一方放弃维护关联关系的权利
*/
@Test
public void test1() {
EntityManager em = JpaUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// 创建 2个用户
SysUser u1 = new SysUser();
u1.setUserName("用户1");
SysUser u2 = new SysUser();
u2.setUserName("用户2");
//和 3个角色
SysRole r1 = new SysRole();
r1.setRoleName("角色1");
SysRole r2 = new SysRole();
r2.setRoleName("角色2");
SysRole r3 = new SysRole();
r3.setRoleName("角色3");
//*******这步操作是让中间表产生关系
// * 让 1号用户具有 1号和 2号角色(双向的)
// * 让 2号用户具有 2号和 3号角色(双向的)
//给用户分配角色
u1.getRoles().add(r1);
u1.getRoles().add(r2);
u2.getRoles().add(r2);
u2.getRoles().add(r3);
//把角色授予用户
r1.getUsers().add(u1);
r2.getUsers().add(u1);
r2.getUsers().add(u2);
r3.getUsers().add(u2);
//********************
em.persist(u1);
em.persist(u2);
em.persist(r1);
em.persist(r2);
em.persist(r3);
tx.commit();
em.close();
}
多对多的删除用户和角色操作
不要使用!!!很多不相关的数据也会被删除掉.
/**
* 删除操作
需要配置级联操作
* 删除从表数据:可以随时任意删除。
* 删除主表数据:
* 有从表数据引用
* 1、不能删除
* 2、如果还想删除,使用级联删除
* 没有从表数据引用:随便删
*
* 在实际开发中,级联删除请慎用!(在多对多的情况下) ,多对多的关联数据都会删除,
*/
@Test
public void test2() {
EntityManager em = JpaUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
SysUser u1 = em.find(SysUser.class, 1L);
em.remove(u1);
tx.commit();
em.close();
}
在用户实体类我里面配置级联 也可以双向级联,就是两个实体类都配置级联,这样三张表的数据都会删除, 不要使用
@ManyToMany(targetEntity = SysRole.class, mappedBy = “users”, cascade = CascadeType.ALL)
private Set roles = new HashSet(0);