一.JPA:是一个ORM规范
JPA:JPA是基于JDBC的封装,是Sun公司推出的接口规范,交给其他厂商去实 现,实现得最完整的是Hibernate(创始人:Gavin King)
ORM:对象关系映射
Hibernate是一个ORM框架(实现了ORM规范)
- JPA(Hibernate)和JDBC区别:
- JDBC更偏底层,开发效率低,运行效率高,但是要求你是一个SQL高手
- JPA(Hibernate)开发效率高,性能低(不好控制),兼容所有数据,好的缓存机制
- JPA是完全基于面向对象方式来操作数据库【核心就是domain实体类+注解】
二.2.JPA入门案例
重点
- 1.导入jar包【maven】(hibernate-core,entityManager,mysql,junit)
<!-- 导入Hibernate核心包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.8.Final</version>
</dependency>
<!-- 导入Hibernate对JPA的实现包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.8.Final</version>
</dependency>
<!-- 导入mysql的驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.13</version>
</dependency>
<!-- 导入junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- 2.配置文件
- \META-INF\persistence.xml
配置一个持久化单元(连接数据库的信息,方言,建表策略,显示SQL)
persistence.xml:必须放在src/main/resource/META-INF之下
- \META-INF\persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--
persistence-unit 持久单元
name属性可以随便写,遵守命名规范
【当前xml文件中可以写很多个persistence-unit,但是每个持久单元的name属性值不能重复】
transaction-type 事务类型
RESOURCE_LOCAL 本地数据库默认的事务隔离级别
JTA 分布式事务
-->
<persistence-unit name="cn.itsource.jpa01" transaction-type="RESOURCE_LOCAL">
<properties>
<!-- JDBC访问数据库必须那4个基本配置 -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql:///java0307" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="root" />
<!-- 使用JPA必须配置方言【Hibernate是依靠方言包来实现跨数据库产品】 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<!-- 下面的是可选配置 -->
<!-- 是否自动生成表 -->
<property name="hibernate.hbm2ddl.auto" value="update" />
<!-- 是否显示sql -->
<property name="hibernate.show_sql" value="true" />
<!-- 格式化sql -->
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
- 3.准备Domain
@Entity
@Table(name=“xx”)
//一个类必需要加主键
@Id
@GeneratedValue
private Long id;
domain
package cn.itsource.domain;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
@Entity//表示当前实体类交给JPA来管理,默认是以当前类名称来作为表名称
@Table(name="tb_user")// 表示指定表名称,如果不指定就默认使用类名称作为表名称
public class User {
/**
* @GeneratedValue 表示主键生成策略
* GenerationType.AUTO 表示自动【默认】 Hibernate会自动检测你配置的数据库产品类型,自动选择适合该数据库产品的主键生成策略
* GenerationType.IDENTITY 表示自动递增 mysql sqlServer DB2 SQLITE Sysbase
* GenerationType.SEQUENCE 表示序列 DB2 Oracle
* GenerationType.TABLE 所有数据库支持这种,性能低
*/
@Id//表示主键
@GeneratedValue
private Long id; //要求:主键,使用数据库的自动生成策略
@Column(name="name",length=20,nullable=false,unique=true)
private String name; //要求:varchar(20),数据库名称为username,不能为空,唯一
private String password;
/**
* insertable 插入数据的时候是否允许插入这个列的值,默认是true
* updatable 修改数据的时候是否允许修改这个列的值,默认是true
*/
@Column(name="age",insertable=false)
private Integer age = 25; //要求:默认值是25,在插入数据时不允许覆盖(添加数据时不操作该字段)
private Boolean sex;// 数据库没有布尔类型,bit对象
@Column(name="salary",columnDefinition="decimal(9,2) check(salary>0.0)")
private BigDecimal salary;// 9,2 工资不能为负数 check(salary>0.0)
@Column(name="createTime",updatable=false)
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;//包含年月日时分秒,不允许修改
@Temporal(TemporalType.DATE)
private Date birthday;//包含年月日
@Temporal(TemporalType.TIME)
private Date time;//包含时分秒
@Lob//长字符串
private String text;//这是一个大文本
@Transient//临时状态
private String temp;//这一个字段是临时字段,不需要同步到数据库
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
......
}
4.代码完成功能
创建工厂
EntityManagerFactory f =Persistence.createEntityManagerFactory();
创建entitymanager对象
EntityManager eg = f.createEntityManager();
增删改需要事务
删除的对象是查出来的(最好判断是否为空)
查询所有(jpql) from Employee
提取工具类
package cn.itsource.utils;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaUtils {
private JpaUtils(){}
private static EntityManagerFactory factory;
static{
factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");
}
/**
* 获取EntityManager对象
* @return
*/
public static EntityManager getEntityManager(){
return factory.createEntityManager();
}
public static void close(EntityManager entityManager){
entityManager.close();
}
public static void close(){
factory.close();
}
}
- 增删查改
package cn.itsource.jpa02test;
import cn.itsource.domain.Teacher;
import cn.itsource.utils.JpaUtils;
import org.junit.Test;
import javax.persistence.EntityManager;
import java.math.BigDecimal;
public class Jpa02Test {
@Test
public void testSave() throws Exception {
//得到EntityManager对象
EntityManager entityManager = JpaUtils.getEntityManager();
try {
//开启事务
entityManager.getTransaction().begin();
//准备数据[临时状态]
Teacher teacher = new Teacher();
teacher.setName("小明");
teacher.setSalary(new BigDecimal(5000.89));
//保存数据[持久状态]
entityManager.persist(teacher);
//提交事务
entityManager.getTransaction().commit();
//事务提交后,teacher对象变成游离状态
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭资源
JpaUtils.close(entityManager);
}
}
@Test
public void testUpdate() throws Exception {
//得到EntityManager对象
EntityManager entityManager = JpaUtils.getEntityManager();
try {
//开启事务
entityManager.getTransaction().begin();
// //准备数据[临时状态]
// Teacher teacher = new Teacher();
// teacher.setName("小明");
// teacher.setSalary(new BigDecimal(7000.89));
// teacher.setTid(1L);
//准备数据:查询【持久状态】
Teacher teacher = entityManager.find(Teacher.class, 1L);
teacher.setSalary(new BigDecimal(8000));
System.out.println("=====================================");
//保存数据[持久状态]
//merge 先通过id去查询[持久状态],然后再修改
entityManager.merge(teacher);//merge修改数据【主键必须有值,主键没有值就是新增数据】
//提交事务
entityManager.getTransaction().commit();
//事务提交后,teacher对象变成游离状态
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭资源
JpaUtils.close(entityManager);
}
}
/**
* 持久--》删除--》游离
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
//得到EntityManager对象
EntityManager entityManager = JpaUtils.getEntityManager();
try {
//开启事务
entityManager.getTransaction().begin();
//准备数据:查询【持久状态】
Teacher teacher = entityManager.find(Teacher.class, 1L);
System.out.println("=====================================");
//删除【删除状态】
entityManager.remove(teacher);
//提交事务
entityManager.getTransaction().commit();
//事务提交后,teacher对象变成游离状态
System.out.println(teacher);
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭资源
JpaUtils.close(entityManager);
}
}
}
补充
/**
* 测试单表查询单行数据
* @throws Exception
*/
@Test
public void testFindOne() throws Exception {
/**
* 生成实体管理工厂【可以理解成一个数据库连接池对象】
* 方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
*/
EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");
//获取EntityManager对象[可以吧它理解成一个Connection链接对象]
EntityManager entityManager = factory.createEntityManager();
//通过id去查询一个对象[持久状态的对象]
Employee employee = entityManager.find(Employee.class, 2L);
System.out.println(employee);
//关闭资源
entityManager.close();
factory.close();
}
/**
* 测试单表查询多行数据
* @throws Exception
*/
@Test
public void testFindAll() throws Exception {
/**
* 生成实体管理工厂【可以理解成一个数据库连接池对象】
* 方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
*/
EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");
//获取EntityManager对象[可以吧它理解成一个Connection链接对象]
EntityManager entityManager = factory.createEntityManager();
//创建执行JPQL语句的工具[select e 表示查询所有字段 相当于select * ]
// String jpql = "select e from cn.itsource.domain.Employee e";
// String jpql = "select e.id,e.name from Employee e";
String jpql = "from Employee";
Query query = entityManager.createQuery(jpql);
//查询所有数据
List<Employee> list = query.getResultList();
list.forEach(e -> System.out.println(e));
//关闭资源
entityManager.close();
factory.close();
}
/**
* 测试单表查询单行数据[一级缓存命中]
* @throws Exception
*/
@Test
public void testFindOneFirstLevelCache() throws Exception {
/**
* 生成实体管理工厂【可以理解成一个数据库连接池对象】
* 方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
*/
EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");
//获取EntityManager对象[可以吧它理解成一个Connection链接对象]
EntityManager entityManager = factory.createEntityManager();
//通过id去查询一个对象[持久状态的对象]
/**
* 一级缓存[默认开启的]
* 一级缓存是在entityManager对象身上保存的,entityManager只要未关闭,缓存数据就一直存在,直到程序运行完毕为止
* 当JPA收到一条查询语句,都会先到缓存【内存】中查找是否有目标数据:
* 1)有:直接取出并且返回给用户【不再执行SQL语句】 ------ 一级缓存命中
* 2)没有:发送SQL语句到数据库服务器进行查询,将查询到的数据保存到内存中【键值对】,再将此数据返回用户
* 当用户或者其他再次查询相同sql的时候,直接从内存中取出就返回。
*/
for (int i = 0; i < 5; i++) {
Employee employee1 = entityManager.find(Employee.class, 2L);
System.out.println(employee1);
}
//关闭资源
entityManager.close();
factory.close();
}
- entityManager的方法改变持久对象的状态
三.建表策略
create-drop -> 删 - 建 -删(保存关闭EntityManagerFactory)【一般不用】
create -> 删 -建 【第一次需要建表的时候用】
update -> 修改(如果没有表会创建)【只增不减】update【推荐,有表有数据】
validate -> 验证 【只验证domain中有的数据】
四.四大对象
Persistence:工具类,创建EntityManagerFactory(解析xml)
EntityManagerFactory:【重量级对象】
重(数据库连接池,JPQL,二级缓存,domain关系(对象之间的关系)等)
线程安全 , 1:1:1
创建EntityManager
EntityManager:【轻量级对象】
轻(数据库连接对象,一级缓存)
线程不安全的
一个请求(线程)一个EntityManager
一级缓存:重点
一级缓存默认开启
一级缓存命中:同一个EntityManagerFactory,同一个EntityManager,同一个OID
OID:对象的全限定名#id的值
提高性能、降低数据库服务器的压力
EntityManagerTransaction【事务】:
EntityManager拿到的事务是同一个
分布式:JTA
五、主键生成策略
AUTO:自动检测你的数据库产品类型,自动选择对应的主键生成策略
IDENTITY:主键自动递增【mysql、db2、sqlserver】
SEQUENCES:序列【oracle、db2】
TABLE:额外创建一张hibernate_sequences表来保存其他所有表的主键最大值
六.关系
- 1.domain实体类对象之间的关系
- 泛化【继承】
- 实现
- 组合【整体与部分不可分割】
- 聚合【整体与部分可以分割】
- 关联【A类持有一个B类对象作为属性】
- 依赖【将一个对象作为方法调用时的实参传入】
- 2.JPA的关联关系重点
- 多重性
- 单向多对多、双向多对多
- 单向一对一、双向一对一
- 导航性
- 单向
- 双向
七、单向多对一
- 配置@ManyToOne
- @JoinColumn可以去指定外键列的名称,如果不指定默认使用 字段名称_id
Transient instance:不能删除临时对象 - 测试代码
package cn.itsource.jpa02test;
import cn.itsource.domain.Department;
import cn.itsource.domain.Employee;
import cn.itsource.utils.JpaUtils;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.List;
public class ManyToOneTest {
@Test
public void testSave() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
try {
/**
* 保存数据
* 先保存一方,再保存多方【效率更高】(部门:一方,员工:多方)
* 3条insert语句sql
* 先保存多方,再保存一方
* 5条sql 3条insert 2条update
* 先执行2条 insert into tb_employee(name,deptid) values(?, null)
* 再执行1条 insert into tb_department(name) values(?) [才能得到部门的id]
* 再执行2条 update tb_employee set deptid=? where id=?
*/
Department d = new Department();
d.setName("公关部");
Employee employee01 = new Employee();
employee01.setName("刘伟2");
employee01.setDepartment(d);
Employee employee02 = new Employee();
employee02.setName("佳佳2");
employee02.setDepartment(d);
//开启事务
entityManager.getTransaction().begin();
entityManager.persist(employee01);
entityManager.persist(employee02);
entityManager.persist(d);
//提交事务
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
JpaUtils.close(entityManager);
}
}
/**
* 修改数据[执行的SQL语句有4条] 先查询员工 --》 再查询原部门 --》 再查询新部门 --》 修改
* @throws Exception
*/
@Test
public void testUpdate() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
try {
Department d = new Department();
d.setName("技术部");
d.setId(1L);
Employee employee01 = new Employee();
employee01.setName("刘伟2");
employee01.setId(3L);
employee01.setDepartment(d);
//开启事务
entityManager.getTransaction().begin();
entityManager.merge(employee01);
//提交事务
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
JpaUtils.close(entityManager);
}
}
/**
* 删除一方数据【保证多方没有数据关联一方】
* @throws Exception
*/
@Test
public void testDelete() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
try {
Department d = entityManager.find(Department.class, 1L);
//开启事务
entityManager.getTransaction().begin();
entityManager.remove(d);
//提交事务
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
JpaUtils.close(entityManager);
}
}
/**
* 查询多方数据,会默认将其关联的一方数据一并查询出来
* 使用left outer join 左外连接去查询,相同的部门也会查询很多次,有点浪费资源
* 使用懒加载就可以解决这个问题
* @throws Exception
*/
@Test
public void testFindOne() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
Employee employee = entityManager.find(Employee.class, 1L);
System.out.println(employee);
System.out.println("-----------------------------");
System.out.println(employee.getDepartment().getName());
//关闭资源
JpaUtils.close(entityManager);
}
}
-
异常
- Transient instance:不能删除临时对象
- N to N:不能修改持久对象的主键
-
脏数据更新:持久对象不需要调用merge方法直接提交事务就会修改数据
- 执行流程分析
第一步:拿到entityManager,开启事务
第二步:
通过entityManager拿到一个对象,那么现在这个对象就是持久化的对象
这个对象会放到一级缓存里面
JPA会为当前这个对象准备一个快照(把这个对象进行了备份)
第三步:提交事务
它会把快照 与 你现在这个对象的数据进行对比
如果相同,就不需要修改,也不会发送SQL(性能就高了)
当不相同的时候,JPA就会认为现在这个数据是脏数据
脏数据它就会在事务提交的时候,把它进行数据库的同步(发送update SQL语句)
- 执行流程分析
/**
* 测试脏数据更新 merge调用与否都会执行脏数据更新(前提是当前对象时是持久状态)
* @throws Exception
*/
@Test
public void testUpdateZang() throws Exception {
//得到EntityManager对象
EntityManager entityManager = JpaUtils.getEntityManager();
try {
//开启事务
entityManager.getTransaction().begin();
//准备数据:查询【持久状态】
Teacher teacher = entityManager.find(Teacher.class, 1L);
teacher.setSalary(new BigDecimal(8000));
teacher.setName("张三丰");
//保存数据[持久状态]
//merge 先通过id去查询[持久状态],然后再修改
//entityManager.merge(teacher);
//提交事务
entityManager.getTransaction().commit();
//事务提交后,teacher对象变成游离状态
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭资源
JpaUtils.close(entityManager);
}
}
- 懒加载:配置@ManyToOne(fetch=FetchType.LAZY) 提高性能
实体类字段:
@ManyToOne(fetch = FetchType.LAZY)//懒加载
@JoinColumn(name = "depar_id")
private Department department;
/**
* 查询多方数据,会默认将其关联的一方数据一并查询出来
* 使用left outer join 左外连接去查询,相同的部门也会查询很多次,有点浪费资源
* 使用懒加载就可以解决这个问题
* @throws Exception
*/
@Test
public void testFindOne() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
Employee employee = entityManager.find(Employee.class, 1L);
System.out.println(employee);
System.out.println("-----------------------------");
System.out.println(employee.getDepartment().getName());
//关闭资源
JpaUtils.close(entityManager);
}
-
懒加载原理[方法重写+多态] 重点
- 利用java动态编译技术,编译成class文件
- 利用类加载器将其加载到内存中,得到一个Class对象
- 再利用反射技术创建这个对象
- 而这个子类里面重写父类的所有属性的get方法,内部发送SQL去查询
- 打印输出懒加载对象的完全限定名的时候,打印结果是…Department_$$_jvst_r68_1
- 最终返回给调用者的其实是子类对象
-
异常:no session
八、二级缓存
- 导入jar包
<!-- 导入二级缓存jar包【ehcache】 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.8.Final</version>
</dependency>
- 开启二级缓存
实体类加注解@Cacheable
import javax.persistence.*;
@Cacheable(true)//二级缓存
@Entity
@Table(name="t_employee")
public class Employee {}
- 配置文件persistence.xml
<!-- 启用二级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true" />
<!-- 配置二级缓存的中间件【类】 -->
<!-- 二级缓存的实现类,文档里面的包名有错的 -->
<!-- 错误 org.hibernate.cache.internal.EhCacheRegionFactory -->
<!-- 正确的 org.hibernate.cache.ehcache.EhCacheRegionFactory -->
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory" />
<!-- 启用查询缓存 -->
<property name="hibernate.cache.use_query_cache" value="true" />
- 在 < properties > 上面添加配置二级缓存扫描的策略
<!-- ALL:所有的实体类都被缓存 -->
<!-- NONE:所有的实体类都不被缓存. -->
<!-- ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存 -->
<!-- DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类 -->
<!-- UNSPECIFIED:默认值,JPA 产品默认值将被使用 -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
- 测试二级缓存命中
命中条件:同一个EntityManagerFactory,不同的EntityManager,同一个OID
/**
* 测试使用二级缓存
* @throws Exception
*/
@Test
public void testUseSecondLevelCache() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
/**
* 第一次查询:
* 先到一级缓存在查找有没有目标数据
* 没有
* 再到二级缓存中查找
* 没有 发送sql语句去查询 一级缓存中保存 二级缓存中也保存
*/
Employee employee1 = entityManager.find(Employee.class, 1L);
System.out.println(employee1);
/**
* 第二次查询:
* 先到一级缓存在查找有没有目标数据
* 有【同一个factory 同一个EntityManager,同一个OID】 直接获取出来进行返回 没有sql
* 查找一下二级缓存中有没有,如果没有,那就copy一份过去
*/
Employee employee2 = entityManager.find(Employee.class, 1L);
System.out.println(employee2);
//关闭资源
JpaUtils.close(entityManager);
EntityManager entityManager2 = JpaUtils.getEntityManager();
/**
* 第三次查询:
* 先到一级缓存在查找有没有目标数据
* 没有【已经更换了一个EntityManager对象】
* 再到二级缓存中找
* 有 不发送sql copy一份到一级缓存中
*/
Employee employee3 = entityManager2.find(Employee.class, 1L);
System.out.println(employee3);
/**
* 第4次查询:
* 先到一级缓存在查找有没有目标数据
* 有【同一个factory 同一个EntityManager,同一个OID】 直接获取出来进行返回 没有sql
* 查找一下二级缓存中有没有,如果没有,那就copy一份过去
*/
Employee employee4 = entityManager2.find(Employee.class, 1L);
System.out.println(employee4);
//关闭资源
JpaUtils.close(entityManager2);
}
- 适用场景
- 查询多于增删改
- 财务、金融不适合【二级缓存有可能不准确】
- 数据量太大不适合使用二级缓存
- 数据库不允许被多方系统增删改
- 了解ehcache.xml
- 默认这个缓存配置文件,使用默认配置文件
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
九、单向一对多
- 1.配置@OneToMany【默认就是懒加载,建议所有关系都配置懒加载】
- 2.性能很差
- 3.集合类型的字段必须使用List或者Set接口来声明
- JPA底层会创建一个PersistentBag或者PersistentSet赋值给集合字段
- List和Set集合类型字段最好直接new出来,方便保存数据的时候使用
- 4.级联
- cascade表示级联【级联操作一般不用,特别是级联删除非常危险】
- 级联保存: CascadeType.PERSIST
- 级联更新:CascadeType.MERGE
- 级联删除: CascadeType.REMOVE
- 最强级联: CascadeType.ALL
- 如果两边都配置了级联称为双向级联,双向级联删除更危险【删除其中一行数据,可能导致整个数据库的所有表的数据全部清空】
- 最好不要级联,要用也不要使用级联删除,千万不要使用双向级联删除
- 今后项目的单据这种数据必须使用双向级联,而且使用最强级联
/**
* 测试级联删除
* @throws Exception
*/
@Test
public void testCascadeRemove() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
try {
entityManager.getTransaction().begin();
//准备数据
//Department department = entityManager.find(Department.class, 1L);
Employee employee = entityManager.find(Employee.class, 3L);
//删除
entityManager.remove(employee);
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭
JpaUtils.close(entityManager);
}
}
- 5.孤儿清除orphanRemoval
- orphanRemoval表示是否清除孤儿数据【与一方对象断开联系的多方数据】 默认是false 写一对多的一方实体类中
- 一方对象的集合字段中添加或者删除数据,提交事务的时候自动拿内存中的数据与数据库中的数据进行对比,自动执行insert或者delete语句
/**
* 测试孤儿清除
* @throws Exception
*/
@Test
public void testOrphanRemovel() throws Exception {
EntityManager entityManager = JpaUtils.getEntityManager();
try {
entityManager.getTransaction().begin();
//准备数据【持久状态的】
Department department = entityManager.find(Department.class, 4L);
//将Department对象的employees【集合】中的数据删除一些,被删除的多方数据将会变成孤儿,只是与一方断开了联系。
//这里的删除只是从内存中的集合里面删除
department.getEmployees().remove(0);
Employee employee = new Employee();
employee.setName("阿伟");
employee.setDepartment(department);
department.getEmployees().add(employee);
//内存中的一方对象的集合变量中已经少了一些对象了,但是数据库表中没有少,事务提交的时候为了让二者中的数据保存一致,就自动发送delete语句去删除孤儿
entityManager.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
//事务回滚
entityManager.getTransaction().rollback();
} finally {
//关闭
JpaUtils.close(entityManager);
}
}
- 6.组合关系的数据【单据】
- 最强级联
- 孤儿清除
package cn.itsource.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 部门【一个部门有多个员工】
*/
@Entity
@Table(name="tb_department")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
/**
* 单向一对多 基本上不用 性能都差
* @OneToMany 表示一对多 默认就是懒加载
* 记不住:
* 今后只要你配置字段关系,你可以全部给他写成懒加载
* 单向一对多的关系字段,目前只能使用List和Set接口来定义,而且只能使用接口,不能用实现类:
* 为什么?
* 因为懒加载导致集合类型的变量对象的类型是 org.hibernate.collection.internal.PersistentBag
* 底层其实是创建了一个PersistentBag对象赋值给当前字段,如果你使用实现类的话,就无法赋值【语法错误】
* List org.hibernate.collection.internal.PersistentBag
* Set org.hibernate.collection.internal.PersistentSet
*
* 集合类型字段建议直接new出来
* 方便保存数据的时候使用 department.getEmployees().add(employee01);
* 而非集合的字段类型千万不能new出来
* 如果new出来就会报错:对象关联了一个未保存的临时对象 transient instance
* 不管是单向一对多还是单向多对一,数据库中的结构都一样!!!
*
* mappedBy表示放弃维护关系,填关联的多方实体类中关联一方的字段名称
*
* cascade表示级联【级联操作一般不用,特别是级联删除非常危险】
* CascadeType.PERSIST 表示级联保存
* CascadeType.MERGE 表示级联修改
* CascadeType.REMOVE 级联删除
* CascadeType.ALL 最强级联
* 如果两边都配置了级联称为双向级联,双向级联删除更危险【删除其中一行数据,可能导致整个数据库的所有表的数据全部清空】
* 最好不要级联,要用也不要使用级联删除,千万不要使用双向级联删除
* 今后项目的单据这种数据必须使用双向级联,而且使用最强级联
* orphanRemoval表示是否清除孤儿数据【与一方对象断开联系的多方数据】 默认是false 写一对多的一方实体类中
*/
@OneToMany(fetch = FetchType.LAZY,mappedBy="department",cascade=CascadeType.ALL,orphanRemoval=true)
//@JoinColumn(name = "dept_id") //单向一对多 如果不写@JoinColumn就会额外创建一张中间表
private List<Employee> employees = new ArrayList<>();
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
// return "Department{" +
// "id=" + id +
// ", name='" + name + '\'' +
// '}';
return super.toString();
}
}
十、双向一对多/多对一
- 1.双向都可以提取数据
- 2.为了提高性能,让一方放弃维护关系,保存数据的时候就相当于单向多对一了
- mappedBy:填多方关联字段的字段名称
@ManyToMany(fetch = FetchType.LAZY,mappedBy = "products")
- 3.搞不清楚的时候优先配置单向多对一
- 4.两边指定的外键列名称必须一致,否则会创建两个外键,多此一举
配置关系的时候,要根据实际的业务功能来分析到底使用单向还是双向,如果搞不清楚业务功能的情况下,先配置一个单向多对一就可以了。
十一、多对多
1.@ManyToMany
- 也可以其中一方放弃维护关系
- name:中间表名称
2.@JoinTable - joinColumns:指定当前实体类与中间表的关系外键列名称
- inverseJoinColumns:指定另一个实体类与中间表的关系外键列名称
package cn.itsource.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "tb_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
/**
* @ManyToMany 表示多对多
* 默认会创建一张中间表 叫tb_user_tb_product 将两边的表名称之间用下划线连在一起 作为中间表名称
* 中间表里面有两个外键,默认名称为当前实体类对应的表名称_id[tb_user_id],另一个是集合类型字段名称_id[products_id]
* 可以指定中间表名称和外键列名称
* @JoinTable 指定中间表名称
* joinColumns 指定当前实体类与中间表产生关联关系的外键名称
* inverseJoinColumns 指定另一个实体与中间表产生关联关系的外键名称
* 双向多对多:
* 在另一个实体类中仍然配置一个集合字段
* 同样加@ManyToMany注解
* @JoinTable 里面的中间表名称要一致,joinColumns和inverseJoinColumns刚好位置交换
*
* mappedBy表示放弃维护关系
*/
@ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
@JoinTable(name="tb_user_product",
joinColumns={@JoinColumn(name="userId")},
inverseJoinColumns={@JoinColumn(name="productId")})
private List<Product> products = new ArrayList<>();
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", products=" + products +
'}';
}
}
十二、一对一
- 1.唯一外键【推荐】
- 与单向多对一没有太大区别:@OneToOne
- @JoinColumn里面加上unique=true
- optional=true表示关联字段【外键列】不允许为null
@Entity
@Table(name="tb_qqzone")
public class QQZone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//QQ空间名称
private String zoneName;
/**
* @JoinColumn 指定一个外键列名称,同时添加外键列的唯一约束
*/
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="qqid",unique=true)
private QQ qq;
- 2.共享主键
- 需要自定义主键生成策略、
- 使用自定义的主键生成策略
- 关系字段上面@PrimaryKeyJoinColumn:让它既是主键又是外键
@Entity
public class QQZone {
@Id
@GeneratedValue(generator = "pkGenerator")
@GenericGenerator(name = "pkGenerator", strategy = "foreign", parameters = @Parameter(name = "property", value = "qq"))
private Long id;
private String name;
// 一对一,一个qq空间输入一个qq号码
@OneToOne(optional = false)
// 如果不加这个注解,添加QQZone信息时,就会自动在QQZone表中增加了一个外键qq_id
@PrimaryKeyJoinColumn
private QQ qq;
}
关系小结
- 单向多对一是用得最多的关系
- 根据实际业务功能需求而定,到底要不要配置成双向
- 分析:一个一个的去分析关系
如何优化JPA
-
1.使用双向一对多多对一关联,不使用单向一对多
-
2.灵活使用单向多对一关联
-
3.不用一对一,用多对一取代(不要使用共享主键一对一,使用唯一外键一对一)
-
4.配置对象二级缓存,不使用集合二级缓存,如果使用了集合二级缓存,集合里面的对象也必须二级缓存;查询缓存(jpql查询),没有查询条件才使用查询缓存
-
5.组合关系集合使用List(顺序,重复),多对多集合使用Set
-
6.表字段要少,表关联不要怕多,有二级缓存撑腰,设计表尽量达到第三范式
-
JPA难在什么地方 -> 细节(练,思考)太多了,性能太难控制了(经验)