JPA简介:
JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中
JPA入门案例:
导包
- mysql驱动包
- hibernate核心包
- hibernate对JPA的实现包
配置文件:
<persistence-unit name="jpa_day01" transaction-type="RESOURCE_LOCAL">
<properties>
<!-- JDBC四大金刚 -->
<property name="hibernate.connection.driver_class"
value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url"
value="jdbc:mysql:///test" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="123456" />
<!--
方言:有一个父类Dialect,每一款数据库对应都有一个子类去继承这个父类,然后重
写了父类的方法,利用父类类型的变量去指向子类的对象,利用java的多态效果来实现跨数据库产品兼容。
所以要切换数据库,只需要将这个方言类改成你要用的数据库产品对应的那个方言类即
可,当然上面的四大金刚也要改
-->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<!-- 可选配置 -->
<!-- 建表策略 -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- 是否显示sql -->
<property name="hibernate.show_sql" value="true" />
<!-- 格式化sql -->
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
domain实体类:
@Entity
@Table(name = "tb_employee")
public class Employee {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer gender;
private String email;
private String phone;
private BigDecimal salary;
private Long department_id;
// 省略getset
}
单元测试
@Test
public void test() throws Exception{
/**
* 读取xml文件配置信息,创建一个EntityManagerFactory工厂对象【工厂模式】 参数填
persistence-unit标签的name属性
* 当你调用Persistence工具类的静态方法的时候:
* * ① 首先解析xml文件,读取xml文件的配置信息字符串
* ② 获取出来JDBC的四大金刚,创建数据库连接对象Connection
* ③ 调用方言对象中重写父类的方法,生成SQL语句
* ④ 检测建表策略,如果是create的话,就会自动执行建表语句
* 默认使用实体类名称作为表名称,使用成员变量名称作为列名称
*/
Persistence.createEntityManagerFactory("jpa_day01");
运行结果如下:
可以看到JPA会自动帮助我们创建表结构,我们只需要配置对应的domain实体类就好了;
建表策略
JPA一共有四种建表策略:
- create-drop:先删除再建表,完了以后再删除表
- create:先删除表,再创建
- update:当数据库中不存在实体类对应的表,那么就创建表;当数据库中已经存在表,则不建表;此时若在实体类中添加字段,则自动执行 Alter Table 表名称 add column 。。。。来添加列;若在实体类中删除字段,则不做删除列的操作。
- validate : 验证你的domain实体类与表的映射关系是否正确(主要检查实体类名称与表名称是否对应,字段名称与列名称是否对应,实体类之间的关系是否与表之间关系对应) 用得最多的
JPA核心API:
Persistence:
是一个工具类,类中全是静态方法,主要用来调用createEntityManagerFactory 来读取xml配置 创建一个EntityManagerFactory工厂对象EntityManagerFactory:
是一个重量级对象,创建和销毁都比较占用时间和内存空间,在项目中一个项目一般就一个EntityManagerFactory对象,而且随着服务器启动而创建,随着服务器关闭而销毁。
内部有很多东西:
xml配置信息
数据库连接池(重量级对象)
JPQL语句
domain实体类的映射关系
二级缓存数据EntityManager:
是一个轻量级对象,创建和销毁都不会占用太多时间和内存空间,所以每次访问的请求的时候都会
创建一个这样的对象;随着请求来而创建,随着响应结束而销毁
内部有:
一级缓存数据EntityTransaction:
JPA的事务管理工具对象,主要负责开启事务、提交事务、回滚事务Query:
是JPA的查询工具对象
一级缓存:
缓存是为了提高性能的,一级缓存数据是保存在EntityManager对象身上,是使用键值对方式保存的,
也就是说EntityManager对象身上有一个Map集合类型的属性,以OID为键,以对象为值
OID:domain实体类的完全限定名#对象的id
一级缓存是默认开启的,不需要任何设置即可使用一级缓存
/**
* 测试一级缓存
* 一级缓存命中的条件:
* 同一个EntityManagerFactory,同一个EntityManager,同一个OID
* JPA查询数据的原理:
* JPA会自动先到一级缓存(Map集合)中查找你需要的对象,有两种结果:
* 找不到:发出SQL语句去查询数据,然后将查询到的数据保存到一级缓存中,以备下一次查询
使用
* 找到了:不发送SQL语句去查询了,而是直接从内存中的EntityManager对象身上的Map集
合中直接获取,效率极高
* @throws Exception
*/
@Test
public void testFistLevelCache() throws Exception{
//传入一个persistence-unit标签的name属性,创建一个工厂对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa_day01");
//通过工厂对象创建一个EntityManager对象,这是JPA的核心对象,JPA的所有操作都要依赖
于EntityManager对象
EntityManager entityManager = factory.createEntityManager();
//第一次查询:一级缓存未命中,所以发出SQL语句去查询
Employee employee01 = entityManager.find(Employee.class, 1L);
System.out.println(employee01);
//第二次查询:一级缓存命中,所以直接从内存中获取出来,没有去查询了
Employee employee02 = entityManager.find(Employee.class, 1L);
System.out.println(employee02);
//关闭资源
entityManager.close();
factory.close();
}
单表映射:
- @id: 此注解加在主键上,表明是主键
- @GeneratedValue:
主键采用的生成策略策略,可选值有:
GenerationType.TABLE:
GenerationType.SEQUENCE
GenerationType.IDENTITY:
GenerationType.AUTO:
@Column:
是写在字段上面,表示指定当前字段与表中的列进行映射的时候的一些细节;
name 表示指定列名称,若不写name属性,则默认使用当前字段名称来作为列名称的 > 一般不建议使用
length 表示指定字符串类型的长度 默认是255
nullable 表示当前字段对应的列是否允许为null,true表示允许为null,false表示非空
unique 表示当前字段对应的列上面是否添加唯一约束
insertable 表示当前字段的值是否允许插入数据库表中 一般不用
updatable 表示当前字段的值是否允许修改 一般不用
columnDefinition 表示由我们自己来指定当前列的语句
@Temporal:
表示映射日期时间类型的字段
TemporalType.TIMESTAMP 年月日时分秒
TemporalType.DATE 只包含年月日
TemporalType.TIME 只包含时分秒
@Lob:
表示长文本字符串,不限制长度,对应数据类型是 longtext
@Transient
表示临时字段,也就是建表的时候请JPA忽略这个字段
常用方法:
- persist
- remove
- merge
- find
- createQuery
- getResultList
- getSingleResult
- setFirstResult
- setMaxResults
主键生成策略:
主键生成策略是JPA中提供多种适合各种数据库产品的主键生成方案,比如:
mysql中使用 auto_increment 实现主键自动递增
SQL Server中使用 identity 实现主键自动递增
Oracle 使用序列 + 触发器实现主键自动递增
SQLite 中使用 autoincrement 实现主键自动递增
GenerationType.AUTO 表示自动检测数据库产品类型,选择适当的主键生成策略* 默认值
GenerationType.IDENTITY 才表示主键自动递增* 适用于mysql、sql server、db2
GenerationType.SEQUENCE 表示使用序列来实现主键自动递增,适用于Oracle、db2数据库*
GenerationType.TABLE 使用单独的一张表来保存当前数据库中其他所有表的当前主键最大值
对象的状态
顺时状态Transient:
又叫瞬时状态,使用new、或者反射创建出来的对象就是临时状态,此时对象还没有跟JPA产生关系
持久状态Persistent
又叫托管状态,当一个临时状态对象,调用了entityManager的persist或者merge方法
后,变成持久状态,此时对象已经与JPA产生了关系
游离状态Detached
Detached 又叫脱管状态,当你事务提交后,对象状态变成游离状态,这个时候内存中的对象数据与数据库表中的一行数据完全匹配
删除状态Removed
预删除,当调用了remove方法后变成删除状态,此时数据还没有被删除,而是要等到事务提交的时候才会执行delete语句去删除数据
对象状态的切换:
脏数据更新:
使用entityManager查询一个对象出来,然后设置对象的属性值,然后提交事务,我们发现即使吧merge方法这一行代码去掉,JPA也能去修改数据,太神奇了!!!这个现象就称为脏数据更新。
当你执行查询代码的时候,JPA会自动在内存中创建一个对象的快照,事务提交时,JPA会自动去比对那个持久状态对象和那个快照对象,如果发现数据不一致了,就称那个持久状态对象为脏数据;由于这个对象是持久状态的,而JPA规定持久状态数据必须与数据库表中的数据完全保持一致,为了达到这个目的,所以在事务提交时会自动发update语句去修改数据,这就是脏数据更新,所以即使不要merge方法也能够修改数据
孤儿清除(orphanRemoval):
从一方关联多方的持久状态的集合中移除一个数据,这个被移除的这个对象就变成了孤儿,当事务提交时,JPA会自动去删除数据库里面的这个对象对应的数据。
这也属于脏数据更新的一种情况。
JPA实体类的定义规则:
- 不能为final修饰的类
因为实体类需要被继承,如果是final修饰的类,那么延迟加载会失败。- 需要提供默认无参构造方法
JPA实例化实体类对象是通过默认的无参构造实例化的。- 数据类型不能为8个基本数据类型,只能是包装类型
JPA内部很多代码判断都是基于是否为null
对象之间的关系:
UML统一建模语言规定,对象之间的关系有:
泛化:继承
实现:接口实现
组合、聚合、关联:
这三种关系,如果光看代码,看不出什么区别,只能从关系紧密程度上来划分,
- 组合:人和人脑袋的关系,不可分割
- 聚合: 计算机中各个零件的关系,虽然需要彼此协同,但是还是可以更换某个零件。
- 关联:你和你手机的关系,属于以上三种关系中,紧密程度最低的。
依赖:一般把对象当做方法参数传递进来,比如Setter,Getter,Spring的依赖注入就是将原本需要手动传入的对象,通过getset方法传递进去。
JPA中的关系:
JPA中只关注以上六种关系中的三种,组合、聚合、关联,并把这三种关系拆分为:
- 单向多对一,单向一对多,双向多对一,双向一对多
- 单向多对多,双向多对多
- 单向一对一,双向一对一
关系配置在实体类中,到底是哪种关系要根据两个实体类之间的关系来定
单向:通过一方能找到另一方,
双向:两方都能找到对方
双向与单向的区别:
两个有关系的实体类,只配置了一方对另一方的关系,为单向,双方都配置了关系,则为双向。
如:
多个员工属于一个部门, 只在员工方配置了关系,此时属于单向
多个员工属于一个部门,在员工方和部门方都配置了关系,此时就属于双向。
一对多,多对多的数据类型只能是List或者Set因为JPA会自动创建一个PersistentBag或PersistentSet对象来赋值给这个成员变量,如果你没写成接口类型的话,则属于Java语法错误!
单向一对多
例:一个部门有多个员工:
这时候就可以在部门里面放一个List集合,上面标注@OneToMany表示一对多。
这个注解加在List或者Set集合类型变量上面,要求集合的泛型必须是另一个domain实体类;
建议List集合类型的变量直接new一个对象出来,上一次我们讲了单向多对一的地方千万不要new出来
如果不写@JoinColumn注解,则JPA会自动创建一张中间表,中间表内有两个外键,所以单向一对多配置中必须写@JoinColumn注解,
@ManyToOne可以不写这个注解,但是为了方便记忆,就统一都写吧
单向一对多保存数据性能差!!!
测试一共使用三条数据,一条部门数据,两条员工数据
先保存一方,再保存多方:3条insert,2条update
先保存一方,一方要设置关系,但是外键不在一方表中,所以它设置不了 1条insert插入一个部门,再保存多方,又执行2条insert 插入两个员工,但是由于员工方设置不了关系,事务提交时,发现还有关系没有设置好,所以只能再发出update语句去修改外键列的值
先保存多方,再保存一方:3条insert,2条update
先保存多方,虽然外键在多方,但是多方设置不了关系,所以它不管;
在保存一方,1条insert插入一个部门,虽然他能够设置关系,但是外键列又不在部门
方,所以也不管;
事务提交时,发现还有关系没有设置好,所以只能再发出update语句去修改外键列的值
结论:
单向一对多关系性能差,建议不要使用单向一对多,即使要用也是使用双向一对多/多对一
双向一对多/多对一
双向一对多/多对一关系的配置要点就是:在两个类中都去配置关系,员工类中配置单向多对一,部门类中配置单向一对多,然后注意外键列名称要一致,否则会创建出两个外键在一对多关系的一方配置单向一对多,在多方配置单向多对一,两边连接起来就形成了双向一对多/多对一了
测试保存数据:
保存数据
先保存一方,再保存多方:3条insert 2条update 性能差
先保存一方,由于外键不在一方,所以设置不了关系,暂时不管
再保存多方,由于一方也需要设置关系,所以也暂时不管
事务提交时,发出update语句去修改外键列的值
先保存多方,再保存一方:3条insert 4条update 性能更差
先保存多方,由于一方也需要设置关系,所以暂时不管
再保存一方,外键不在一方,所以设置不了关系,暂时也不管
事务提交时,发出update语句去修改外键列的值,由于两边都需要设置关系,所以有4条update语句
结论:
先保存一方,再保存多方,性能较差;如果反过来性能更差!
单向多对多
@ManyToMany 表示多对多,为了方便记忆,我们都统一写成懒加载(fetch = FetchType.LAZY)
JPA会自动创建一张中间表,名称为: 当前实体类对应表名称_对方实体类对应表名称
中间表内有两个外键
其中一个外键列名称为:当前实体类对应表名称_id
另一个外键列名称为:List集合类型的变量名称_id
我们可以通过@JoinTable注解来指定中间表的名称和两个外键列的名称
@JoinTable 的name表示指定中间表的名称
joinColumns 表示指定当前实体类对应表与中间表关联的外键列名称
inverseJoinColumns 表示指定对方实体类对应表与中间表关联的外键列名称
双向多对多
双向多对多指定的中间表名称必须一致,否则会出现两张中间表
JoinColumns和inverseJoinColumns的外键列名称刚好交换一下位置,否则会出现三个甚至四个列。
@ManyToMany 表示多对多,为了方便记忆,我们都统一写成懒加载
JPA会自动创建一张中间表,名称为: 当前实体类对应表名称_对方实体类对应表名称,中间表内有两个外键:
其中一个外键列名称为:当前实体类对应表名称_id
另一个外键列名称为:List集合类型的变量名称_id
我们可以通过@JoinTable注解来指定中间表的名称和两个外键列的名称
@JoinTable 的name表示指定中间表的名称
joinColumns 表示指定当前实体类对应表与中间表关联的外键列名称
inverseJoinColumns 表示指定对方实体类对应表与中间表关联的外键列名称
一对一
唯一外键(推荐):一张主表,一张从表(添加一个外键约束,并且将外键列设置唯一>约束)
@OneToOne可以换成@ManyToOne
@Entity
public class QQ {
@Id
@GeneratedValue
private Long id;
private String qqNum;
}
--------------------------------------------------------------------------
@Entity
public class QQZone {
@Id
@GeneratedValue
private Long id;
private String name;
/**
* optional 表示这个关联字段是否允许为null
* unique 表示在外键列上面添加唯一约束
*/
@OneToOne(fetch = FetchType.LAZY,optional = false)
@JoinColumn(name = "qq_id",unique = true)
private QQ qq;
}
共享主键(不推荐):两张主表(共享主键)
第二张表的主键,既是主键又是外键
@Entity
public class QQ {
@Id
@GeneratedValue
private Long id;
private String qqNum;
}
------------------------------------------------------------------------------
@Entity
public class QQZone {
/**
* @GenericGenerator 注解的作用是自定义了一个主键生成策略
* @GeneratedValue(generator = "pkGenerator") 表示使用自定义的主键生成策略
*/
@Id
@GenericGenerator(name = "pkGenerator", strategy = "foreign", parameters =
@org.hibernate.annotations.Parameter(name = "property", value = "qq"))
@GeneratedValue(generator = "pkGenerator")
private Long id;
private String name;
/**
* optional 表示这个关联字段是否允许为null
* unique 表示在外键列上面添加唯一约束
* @PrimaryKeyJoinColumn 注解的作用是将当前表的主键来作为外键
*/
@OneToOne(fetch = FetchType.LAZY,optional = false)
@PrimaryKeyJoinColumn
private QQ qq;
}
解决双向关系的性能问题:
让一方(Department)放弃维护关系,
因为外键在多方,多方来设置关系更方便
@OneToMany 的mappedBy属性表示:本方放弃维护关系,mappedBy的值填的是对方关联本方的那个字段名称;
写了mappedBy属性之后,@JoinColumn(name = “department_id”)就可以去掉了
配置关系常用注解:
@ManyToOne:多对一
@OneToMany:一对多
@ManyToMany:多对多
@OneToOne:一对一
- optional:表示这个关联字段是否允许为null
- mappedBy:用于双向关系中,表明本方放弃维护关系,mappedBy的值填的是对方关联本方的那个字段名称;
- cascade:级联操作,可选值为:
- CascadeType.PERSIST 级联保存
- CascadeType.PERSIST 级联保存
- CascadeType.MERGE 级联修改
- CascadeType.REMOVE 级联删除
- CascadeType.ALL
只写一方为单向,双方都写为双向。
@JoinColumn:
- 写在关系对象上,
- name:指定此列在数据库中的名称
- unique 表示在外键列上面添加唯一约束
@JoinTable
- name: 指定中间表的名称
- joinColumns : 表示指定当前实体类对应表与中间表关联的外键列名称
- inverseJoinColumns : 表示指定对方实体类对应表与中间表关联的外键列名称
级联操作
假设我们要保存一方数据,多方数据不想调用persist方法,我们想要即使只保存一方数据,它的List集合中包含的多方也一起保存,这就是级联
上一次我们做过测试,删除一方数据的时候,如果多方还有数据关联这个一方数据,是删不掉的,我们想要这个情况也能删除,这也叫级联
级联就是@OneToMany注解的cascade属性,可选值有:
CascadeType.PERSIST 级联保存
CascadeType.MERGE 级联修改
CascadeType.REMOVE 级联删除
CascadeType.ALL 最强级联(包含级联保存、级联修改、级联删除)
配置
@OneToMany(cascade=CascadeType.PERSIST)此处的OneToMany可以替换为其他。
懒加载:
@OneToMany(fetch = FetchType.LAZY)
在JPA中,查询某个实体类对象时,会默认将此类关联的其他类的信息也查出来,这样会比较影响性能,如果我们不想让它自动去查询员工所属的部门,那么可以配置成懒加载,什么时候使用,什么时候再去查询。
问题:懒加载中什么时候JPA会去查询关联数据呢?
答:在调用关联的属性的时候,比如get方法,或者是重写的toString调用了关联的属性,这时候就会去查询关联属性的信息了。
原理:
JPA会给配置了懒加载的关联对象,通过javassist生成一个子类对象,这个子类对象重写了get方法,当一个持久状态的对象调用已配置懒加载的关联对象(或属性)的时候,JPA就会去查询数据。
二级缓存:
一级缓存和二级缓存的区别:
一级缓存是存储在EntityManager上面的,不同的EntityManager不能互相读取数据,命中条件需要是:同一个EntityManagerFactory,同一个EntityManager,同一个OID;
二级缓存是存储在EntityManagerFactory上的,只要是同一个EntityManagerFactory对象所制造的所有EntityManager都可以共享。
开启二级缓存:
由于二级缓存功能不是由Hibernate或者JPA实现的,是第三方厂商实现的,所以要添加jar包
hibernate-ehcache.jar
配置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" />
使用注解标志,哪些类需要启用二级缓存。
<!-- NONE:所有的实体类都不被二级缓存. -->
<!-- ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被二级缓存 -->
<!-- DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类 -->
<!-- UNSPECIFIED:默认值,JPA 产品默认值将被使用 -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
二级缓存使用场景:
读取大于修改;— 查询次数远远高于增删改
对数据要有独享控制,数据不会被第三方修改;如果有多个项目同时连接同一个数据库,不适合使用二级缓存
可以容忍出现无效数据,非关键数据(不是财务数据等)
数据量不能超过内存容量,数据量特别巨大,此时不适合于二级缓存(钝化)
钝化:将对象数据从内存中写入到硬盘文件中,采用了Java的 ObjectOutputStream 来传输,要求实体类必须实现Serializable接口
JPA经典错误:
N to N
测试修改数据
修改非主键属性值,没有问题,可以直接脏数据更新
修改主键值,报错:org.hibernate.HibernateException: identifier of an instance of cn.itsource.domain.Employee was altered from 1 to 2
原因:JPA规定持久状态对象的主键值不允许修改
JPQL
JPQL和SQL很像,查询关键字都是一样的
规则
用类名,属性名替换表名、列名
类名、属性名区分大小写
不能出现*
事务并发:
事务的特性:
- 原子性:Atomic,是指事务中的逻辑工作单元已经是最小单位,不能再分割
- 一致性:Consistent,是指事务中的各个逻辑工作单元保持一致的,要么全部成功,要么全部失败
- 隔离性:Insulation,多个事务操作的时候,每个事务的操作与其他事务无关
- 持久性:Duration,通过事务对数据库的操作是永久有效的
事务并发带来的问题
脏读
- 它所指的就是未提交的数据。也就是说,一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条数据是处于待定状态的(,这时,第二个事务来读取这条没有提交的数据,并据此做进一步的处理,就会产生对未提交的数据的依赖关系。这种现象被称为脏读
不可重复读
- 一个事务先后读取同一条记录,事务在两次读取之间该数据被其它事务所修改,则两次读取的数据不同,称之为不可重复读
幻读
- 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。
事务隔离机制:(从上到下性能递减,安全性递增)
mysql默认的事务隔离级别为可重复读
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 Read Uncommitted | 是 | 是 | 是 |
读已提交 Read Commited | 否 | 是 | 是 |
可重复读 Read Repeatable | 否 | 否 | 是 |
串行化 Read Serializable | 否 | 否 | 否 |
通过数据库自带的事务隔离级别可以解决5中问题中的三种,如上图。
剩下的需要通过锁来解决
悲观锁:修改一行数据时,锁定整张表
乐观锁:修改一行数据时,时锁定单行数据
JPA之乐观锁:
####实现方式有两种
实现原理都一样:在一张表中添加一个列,数据类型采用datetime或者是integer,区别> 在于integer比datetime所占用的数据库空间更小;
这个列的默认值是0,你每一次查询数据,进行修改的时候要执行:
update 表名称 set num=num-1,version=version+1 where id=1 and version=0
乐观锁实现代码:
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
//商品名称
private String name;
//商品剩余数量
private Integer number;
//@Version 注解就表示JPA要使用乐观锁
@Version
private Integer version;
//省略getset和构造方法;
}
------------------------------------------------------------------------------
/**
* 在一个单元测试方法中测试,模拟两个事务并发,今后我们实际开发中就不需要写两个事务了
* 不同的用户在浏览器上访问,JavaWeb会自动开启多线程
* @throws Exception
*/
@Test
public void test() throws Exception{
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("jpa_day04");
EntityManager entityManager01 = factory.createEntityManager();
EntityManager entityManager02 = factory.createEntityManager();
entityManager01.getTransaction().begin();
entityManager02.getTransaction().begin();
Product product01 = entityManager01.find(Product.class, 1L);
Product product02 = entityManager02.find(Product.class, 1L);
//第一个事务开始秒杀
product01.setNumber(product01.getNumber() - 1);
entityManager01.getTransaction().commit();
/**
* 第二个事务开始秒杀:org.hibernate.StaleObjectStateException:
* Row was updated or deleted by another transaction (or unsaved-value
mapping was incorrect)
* 这一行数据已经被另一个事务修改了或者删除了
* 所以,使用JPA的乐观锁,秒杀失败就一定会报出StaleObjectStateException异常
*/
product02.setNumber(product02.getNumber() - 1);
entityManager02.getTransaction().commit();
entityManager01.close();
entityManager02.close();
factory.close();
}
重点:
JPA重点为对象状态,对象之间的关系。