JPA的一些简单应用

本文介绍了JPA中的主键生成策略,包括 AUTO、TABLE、SEQUENCE 和 IDENTITY,以及JPA持久对象的四种状态:临时、持久化、游离和删除。详细解释了每种状态的特性和应用场景,强调了持久状态对象的脏数据更新机制。此外,还提及了JPA中单向多对一关系的处理和抓取策略,如懒加载和急加载的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值