1.主键的生成策略
@GeneratedValue:
为一个实体类生成一个唯一标识的主键(JPA要求每一个实体Entity,必须有且只有一个主键)。 主键是关系数据库中的一个基本概念,它用来保证记录的唯一性。简单来说,就是同一张数据库表中,不允许存在多条相同主键的记录。
主键的生成也应该交给jpa来管理
主键分为代理主键和自然主键
1.1 自然主键:
有业务含义的主键
业务含义的字段:身份证,手机号,
此字段必须唯一,因为主键必须唯一。
早期的开发中,可能会存在设计,现在一般都用代理主键
JPA直接不定义@GeneratedValue就可以了,在保存之前先设置主键值
1.2.代理主键:
代理主键:没有实际意义,只是用来区分数据库每条数据的标识
JPA标准策略有4种:auto策略、table策略、sequence策略、identity策略;
在实体类上面打上 @GeneratedValue
1.2.1 GenerationType.AUTO:
把主键生成策略交给持久化引擎(persistence engine)。
可以是Table/Sequence/Identity中的一种。
假如数据库是Oracle,则选择Sequence,
假如数据库是MySQL,则选择Identity。
如果不特别指定,这是默认的主键生成策略:此种主键生成策略比较常用,由于JPA默认的生成策略就是GenerationType.AUTO,所以使用此种策略时.可以显式的指定@GeneratedValue(strategy = GenerationType.AUTO)也可以直接@GeneratedValue。例如:
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//等价于
@GeneratedValue
private Long id;
1.2.2 GenerationType.TABLE
有时候为了不依赖于数据库的具体实现,在不同数据库之间更好的移植,可以在数据库中新建序列表来生成主键,序列表一般包含两个字段:第一个字段引用不同的关系表,第二个字段是该关系表的最大序号。这样,只需要一张序列就可以用于多张表的主键生成。 用法:
@TableGenerator(name = "SEQ", table = "OidDomain_TABLE", pkColumnName = "SEQUENCE_NAME", valueColumnName = "SEQUENCE_COUNT", initialValue = 1, allocationSize = 1)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ")
private Long id;
1.2.3 GenerationType.SEQUENCE
Oracle不支持ID自增长列而是使用序列的机制生成主键ID,
对此,可以选用序列作为主键生成策略:
create table OidDomain (id number(19,0) not null, name varchar2(255 char), primary key (id))
JPA中SEQUENCE类型的主键生成策略用法如下:
@SequenceGenerator:
name:序列生成器的名称,会在@GeneratedValue中进行引用
sequenceName:oracle数据库中的序列生成器名称
支持的数据库: Oracle、PostgreSQL、DB2
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
@SequenceGenerator(name = "SEQ", sequenceName = "OIDDOMAIN_SEQUENCE")
private Long id;
//上述声明等同于在数据库上创建一个名称为OIDDOMAIN_SEQUENCE的序列
//如果不指定序列生成器的名称sequenceName = "OIDDOMAIN_SEQUENCE",
//则使用厂商提供的默认序列生成器,比如Hibernate默认提供的序列名称为HIBERNATE_SEQUENCE。
1.2.4 GenerationType.IDENTITY
数数据库支持IDENTITY列,数据库会在新行插入时自动给ID赋值,这也叫做ID自增长列,
比如MySQL中可以在创建表时声明“AUTO_INCREMENT”, 就是一个ID自增长列:
多数数据库支持IDENTITY策略:MySQL, SQL Server, DB2, Derby, Sybase, PostgreSQL。
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;``
2.JPA持久对象的状态
2.1.学习持久对象的状态
JPA持久对象的状态对我们来说非常非常重要,
主要是我们用的时候,遇到异常、问题的时候,都需要通过状态来分析,才知道这到底是为什么?是什么原因出现这种情况?怎样才可以正确使用JPA
2.2.临时状态(transient):瞬时状态
刚刚用new语句创建,没有和entityManager发生关系
没有被持久化,不处于entityManager中。该对象成为临时对象
2.3.持久化状态(persistent):托管状态
和entityManager发生关系
已经被持久化,加入到entityManager的一级缓存中(填充一级缓存)。
该状态的对象为持久化对象。
@Test
public void save() throws Exception {
StateDomain stateDomain = new StateDomain();// 临时状态
stateDomain.setName("itsource");// 临时状态
EntityManager entityManager = JPAUtils.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(stateDomain);// 持久状态
entityManager.getTransaction().commit();
entityManager.close();// 游离状态
}
2.4.游离状态(detached):脱管状态
已经被持久化,但不处于entityManager中。
该状态的对象为游离对象。
2.5.删除状态(removed):从JPA才开始有的状态
只有调用了entityManager.remove(domain对象)方法
对象有关联的ID,并且在entityManager管理下,
但是已经被计划删除,事务提交就真的被删除了。
2.6脏数据更新
一个持久化对象在事务管理期内,如果改变原来非主键的值,此时出现的数据就叫脏数据,在事务提交的时候回自动发送update sql语句取修改
测试代码
@Test
public void update() throws Exception {
EntityManager entityManager = JPAUtils.getEntityManager();
entityManager.getTransaction().begin();
StateDomain stateDomain = entityManager.find(StateDomain.class, 1L);// 持久状态
stateDomain.setName("stateDomain");
// 写不写效果是一样
entityManager.merge(stateDomain);// 持久状态
entityManager.getTransaction().commit();
entityManager.close();// 游离状态
}
执行流程分析 :
第一步:拿到entityManager,开启事务
第二步:通过entityManager拿到一个对象,那么现在这个对象就是持久化的对象
这个对象会放到一级缓存里面
第三步:提交事务
它会把快照 与 你现在这个对象的数据进行对比
如果相同,就不需要修改,也不会发送SQL(性能就高了)
当不相同的时候,JPA就会认为现在这个数据是脏数据
脏数据它就会在事务提交的时候,把它进行数据库的同步(发送update SQL语句)
重点:持久状态的对象是不能修改OID(不能修改主键)
2.7.持久对象(domain层)定义规则
1.类不能定义为final
2.数据类型必须用包装类型
因为JPA内部代码很多判断都是基于是否等于null
3.必须要有无参构造方法
如果你写了有参构造,那么一定要加上一个无参构造
3.域对象之间的关系
3.1依赖关系
JavaBean之间的依赖关系
分层:表现层,业务层,持久层(依赖关系)
依赖:一般指controll,service,dao层之间的关系
Controller表现层依赖于Service业务层,Service依赖于Dao持久层
3.2关联关系
类之间的引用关系,以属性定义的方式表现。
关联按照多重性可分为一对一、一对多、多对一和多对多。 主要指的数据库(类)的关系
按照导航性可分为单向关联和双向关联。
主要指的java类的导航,导航可以从一个类获取另一个的类的实例
注意:
不管理一对多,还是多对多(不管它们是双向还是单向),数据库的结构不变(多方有外键)
多对多:必有一张中间表
一对一:设计方案(共享主键,唯一外键),主从关系
3.1聚合关系(本质还是双向多对一,一对多)
表示整体与部分的关系,整体和部分可以分开单独存在。
电脑(主板,CPU,IO,内存条)
3.1组合关系(本质还是聚合关系,双向多对一,一对多)
3.4.组合关系(本质还是聚合关系,双向多对一,一对多)
强聚合关系,整体和部分之间不能分开。
人(头,手),订单模型,超市购物购物清单
有的设计,是一定要在强聚合关系下才使用
组合关系在后面进销存系统详细讲,只需要了解。
指的是类与类之间的继承关系设置
Animal --》 Person Pig Cat Bird
4.单向多对一 (重点)
@Entity
public class ProductDir {
@Id
@GeneratedValue
private Long id;
private String name;
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
// 多Product对一ProductDir
// 多个产品属于一个产品类型
// 外键在那个表,这个表就是多
@ManyToOne(fetch = FetchType.LAZY) // 实现延迟加载
// @ManyToOne(fetch = FetchType.EAGER) // 默认值,左外连接查询
// JoinColum设置了外键的名字,不配置默认一方属性名_id
// @JoinColumn(name = "dir_id")
private ProductDir dir;
@Test
public void testName (){
//测试看能否创建成功
EntityManager entityManage = JapUtil.getEntityManage();
}
注意:多方对象里面的关联对象一方ProductDir不要默认实例化
保存数据
两个产品,一个产品类型
@Before
public void persist() throws Exception {
// 一方
ProductDir dir = new ProductDir();
dir.setName("类型1");
// 多方
Product product = new Product();
product.setName("产品1");
// 设置外键
product.setDir(dir);
Product product2 = new Product();
product2.setName("产品2");
product2.setDir(dir);
EntityManager entityManager = JPAUtils.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(dir);
entityManager.persist(product);
entityManager.persist(product2);
entityManager.getTransaction().commit();
entityManager.close();
}
注意:先保存一方(产品类型),再保存多方(多方产品)
5.抓取和延迟加载
抓取策略:
JPA告诉程序使用哪种方式去获取数据
抓取策略配置:
@ManyToOne(fetch = FetchType.LAZY ) 表示懒加载 – 需要使用的时候,才发送sql查询
类不要定义成final
@ManyToOne(fetch = FetchType.EAGER) 表示迫切加载 – 不使用都把数据加载出来 发送左外连接查询数据
6.二级缓存
添加jar包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.8.Final</version>
</dependency>
配置persistence.xml配置信息
<!-- 启用二级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true" />
<!-- 二级缓存的实现类,文档里面的包名有错的 -->
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory" />
<!-- 启用查询缓存 -->
<property name="hibernate.cache.use_query_cache" value="true" />
在上面添加配置二级缓存扫描的策略
<!-- ALL:所有的实体类都被缓存 -->
<!-- NONE:所有的实体类都不被缓存. -->
<!-- ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存 一般使用这个-->
<!-- DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类 -->
<!-- UNSPECIFIED:默认值,JPA 产品默认值将被使用 -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
在实体类上面// @Cacheable(true)
二级缓存类配置:命中条件:同一个SessionFactory,不同的entityManager,OID相同
// 一条sql
// @Entity
// @Cacheable(true)
// public class Department
@Test
public void test2() throws Exception {
EntityManager entityManager = JPAUtils.getEntityManager();
//先从一级缓存,二级缓存取数据?取不到,发出sql去获取,填充一级缓存,二级缓存
Department department1 = entityManager.find(Department.class, 1L);
//先从一级缓存,二级缓存取数据?一级缓存取到了,返回
Department department2 = entityManager.find(Department.class, 1L);// 一级缓存命中
entityManager.close();
EntityManager entityManager2 = JPAUtils.getEntityManager();
//先从一级缓存,二级缓存取数据?一级缓存没有取到,但是二级缓存取到了,返回
Department department3 = entityManager2.find(Department.class, 1L);// 二级缓存命中
//先从一级缓存,二级缓存取数据?一级缓存取到了,返回
Department department4 = entityManager2.find(Department.class, 1L);// 一级缓存命中
entityManager2.close();
}
查询缓存
@Test
public void testQueeryAll (){
/* 测试查询缓存
* 命中条件:同一个entityManagerFactory 不同的entityManager 同一个OID(object ID)
* 注意:1.如果查询有条件 ,就不要使用查询缓存,命中率很低
* 2.find不走查询缓存 ,这个createQuery方法走查询缓存
* 3. 配置domain Cacheable(true)
* mybatis支持缓存
* */
EntityManager entityManage1 = JapUtil.getEntityManage();
String jpql1 = "select o from Product o where o.id=?";
Query query1 = entityManage1.createQuery(jpql1).setParameter(1, 1L);
//把查询的结果放入缓存 不放进去没有效果
query1.setHint(QueryHints.HINT_CACHEABLE,true);
System.out.println(query1.getResultList().size());
EntityManager entityManage2 = JapUtil.getEntityManage();
String jpql2 = "select o from Product o where o.id=?";
Query query2 = entityManage2.createQuery(jpql2).setParameter(1, 1L);
//把查询的结果放入缓存
query2.setHint(QueryHints.HINT_CACHEABLE,true);
System.out.println(query2.getResultList().size());
}
**缓存命中条件**
1.一级缓存
同一个EntityManagerFactory,同一个EntityManager,OID相同
2.Domain类的二级缓存配置
同一个EntityManagerFactory,不同一个EntityManager,OID相同
Domain类上面配置@Cacheable(true)
3.查询缓存
同一个EntityManagerFactory,不同一个EntityManager,发出的sql语句必须相同并且查询的条件值也要相同
如果有查询条件,就不能使用查询缓存,命中率非常低
添加代码query.setHint(QueryHints.HINT_CACHEABLE, true);