SpringDataJPA学习笔记

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值