2.在JavaSE环境下使用EntityManager
JPA核心就是EntityManager,EntityManager负责管理JPA持久化上下文中的所有实体,它负责跟踪所有实体的保存,
更新和修改的情况,并根据指定的flush模式将这些修改保存到数据库中。
从功能上看,EntityManager类似于Hibernate框架中的Session,他负责完成实体操作和数据库操作之间的转换:
当EntityManager保存一个实体时,底层会产生一条insert语句:当修改一个实体时,底层会产生一条Update语句:
当EntityManager删除一个实体时,底层会生成一条delete语句。
为了在应用程序中使用EntityManager,大致分为如下3种情况
1.在EJB(主要是作为EAO组件的EJB)中使用EntityManager:直接使用依赖注入来管理EntityManager即可
2.在Servlet,JSF的托管Bean中使用EntityManager:不能直接使用依赖注入来管理EntityManager。
因为多个请求线程可能共享一个Servlet或JSF的托管Bean,而EntityManager并没有被设计成线程安全的,
因此可能导致线程安全问题。在这种情况下有两种解决方法:
1.使用JNDI来查找获得EntityManager对象
2.使用依赖注入管理EntityManagerFactory(它是线程安全的)对象,再通过EntityManagerFactory来获取EntityManager对象
3.在JavaSE应用中使用EntityManager:需要通过应用程序显示地创建EntityManager。
3.在JavaSE应用程序中创建EntityManager对象通过如下步骤进行即可
1.通过javax.persistence.Persistence工厂提供的createEntityManagerFactory()静态方法创建EntityManagerFactory对象。
调用该方法时需要传入persistence.xml文件中持久化单元的名称
2.调用EntityManagerFactory的createEntityManagerFactory()或createEntityManagerFactory(Map map)方法来创建EntityManager对象。
第二个createEntityManager方法可以传入一个Map参数,这个Map参数传入的属性将会补充或覆盖persistence.xml文件中配置的属性
实体操作和数据库的转换
3.理解实体
JPA的主要思想就是让实体来映射底层数据表,而JPA通过EntityManager保存,删除,检索实体,JPA实例则将这种面向对象的操作转换为SQL语句
1.持久化上下文和持久化单元
在JPA规范中涉及两个常用的概念:持久化上下文(persistence context)和持久化单元(persistence unit)
持久化上下文是一组处于托管状态下实体所组成的集合(是一种范围 定义了JPA管理的边界),
当应用程序改变了持久化上下文中的实体状态后,EntityManager与持久化上下文进行交互
EntityManager负责跟踪持久化上下文中所有实体的状态,当应用程序改变了持久化上下文中的实体状态后,
EntityManager将会根据指定的flush模式将实体的状态写入底层数据库。
如果持久化上下文被关闭,该上下文中的所有实体都将脱离EntityManager的管理,进入脱管状态,
此时对实体所做的修改将不会被自动同步到底层数据库
持久化单元则由persistence.xml文件定义,正如前面配置文件中看到,该文件中包含了一个<persistence-unit.../>元素,
该元素就用于定义持久化单元,还元素的name属性的值就是该持久化单元的名称,应用程序可通过该名称来访问持久化单元
持久化·单元由persistence.xml文件定义(该文件要放在META-INF路径下(JavaSE,web应用 war下,EJB jar包))
4.实体类的要求
JPA摒弃了EJB2中实体Bean的侵入式设计,吸收了Hibernate框架低侵入式设计的特点,
这种设计导致JPA对实体类几乎不做任何要求。也就是说,JPA操作的实体类基本上都是普通,传统的Java对象(POJO)。
对于这种Java类,程序开发可以采用更灵活的领域建模方式
基本准则:
1.提供一个无参构造器:
所有的实体类都应该提供一个无参构造器,这个构造器不一定要采用Public访问控制符。
但由于JPA(实例化对象时必须采用一个无参构造器) 实现通常会采用Constructor.newInstance()方式来创建实体的实例,
通常,为了方便JPA在运行时生成代理,构造器的访问控制修饰符至少是包可见的,即大于或等于默认的访问控制符。
创建对象1.new方法 2.类.newInstance()(调用类的无参构造方法)
2.提供一个标识属性(唯一标识):
标识属性通常映射数据库表的主键字段。这个属性可以叫任何名字,其类型可以是任何原始类型,原始类型的包装类型,
java.lang.String或者java.util.Date。如果使用了数据库表的联合主键,甚至可以用一个用户自定义的类,
该类拥有这些类型的属性。当然,也可以不指定任何标识属性,
而是在映射文件中直接将多个普通属性映射成一个联合主键,但通常不推荐这么做
JPA可以允许实体类没有标识属性 但会导致JPA许多功能无法使用 JPA建议使用允许接收null值的类型(是一个引用类型)
来作为标识符类型,int接收的是整形数字,接收不了null值,改成Integer可以设置为null(包装类:可以生成为对象)
3.为每个实体类提供getter setter方法:
JPA默认采用属性方式来访问实体类属性。JPA持久化JavaBean风格的属性,
认可如下形式的方法名getXxx,isXxx和setXxx。如果需要,也可以切换属性的访问策略
4.使用非final的类:(不能派生子类 变量final 数据不能修改)
许多JPA实现都需要在运行时生成动态。如果实体类没有实现任何接口的话,那么JPA就需要为他动态的生成CGLOB代理类。
该代理对象是实体类的子类的实例。如果使用final类,则无法生成CGLIB代理,将无法进行性能优化
5.重写equals()和hashCode()方法:
如果需要把实体类的实例放入Set中(当需要进行关联映射时,推荐这么做),则应该为实体类重写equals()和hashCode()方法。
实现equals()/hashCode()最显而易见的方法是比较两个对象的标识符的值,如果值相同,则两个对象对应于数据库的同一行,
因此它们是相等的(如果都被添加到Set中,则在Set中只有一个元素)。
遗憾的是,对采用自动生成标识值的对象不能使用这种方法。JPA仅为那些持久化后的实体指定标识值,
一个新创建的实体将不会有任何标识值。因此,如果一个实体没有被保存过,但它却是在一个Set中,
保存他将会给这个对象赋一个标识值。如果equals()和hashCode()是基于标识值实现的,
则其hashCode值码会发生改变,这将违反Set的规则
比较两个对象标识符的值 相同 则两个对象对应于数据库的同一行
对于自增不能使用
5.实体的三种状态
1.新建new:(瞬时对象)内存中
实体由new创建,且尚未与EntityManager关联过的实体被认为处于新建状态。新建状态的实体不会被持久化数据库中,
如果程序失去了新建实体的引用,垃圾回收机制将会回收新建实体。通过EntityManager可以将其变为持久化状态。
除此之外,如果应用程序调用EntityManager的delete()方法删除某个实体,那么这个实体也会转入瞬态
2.托管managed:(新建状态通过EntityManager变为持久化状态)
托管状态下的实体在数据库中有对应的记录,并拥有一个持久化标识(identity)。托管下的实体也可以是刚刚保存的,
也可以是刚刚被加载的。无论是哪一种,托管的实体都是处于EntityManager管理之下。
JPA会自动检测到处于托管状态下实体的改动,当程序修改托管实体的属性时,
JPA负责将这种修改转换成对数据记录的修改,开发者不需要手动执行UPDATE语句
eg:
@Test//托管状态
public void test2() {
EntityManager em = emf.createEntityManager();
try {
//该对象处于瞬时(新建状态)
Emp emp = new Emp();
emp.setEmpno(6);
emp.setEname("l1");
emp.setJob("l1");
em.getTransaction().begin();
//持久化对象但没有进行保存
em.persist(emp);
//持久化之后马上进行修改设置
//相当于先执行insert后执行了Update
emp.setEname("l2");
em.getTransaction().commit();
//并没有保存 没有提交事务 提交事务才会形成对应的SQL语句
emp.setEname("l3");
System.out.println("SUCCESS");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
em.getTransaction().rollback();
}
}
3.脱管detached:
如果某个实体曾处于托管状态,那么他将有一个持久化标识,但随着与之关联的EntityManager被关闭,
该实体就变成了脱管状态。托管状态下的实体的引用依然有效,实体可继续被修改。之所以把实体的这种状态称为托管,
指的就是该实体脱离了EntityManager的管理
该状态实体引用依然有效 可继续被修改
4.被删除removed:
当EntityManager调用remove()方法指定删除后,该实体进入被删除状态。被删除的实体还关联着某个持久化上下文,
而且它对应的记录即将从数据库中被删除
但在程序中还存在
eg:
@Test//被删除状态
public void test3() {
EntityManager em = emf.createEntityManager();
try {
Emp emp = new Emp();
emp.setEmpno(4);
emp.setEname("zz");
emp.setJob("sm");
em.getTransaction().begin();
em.persist(emp);
em.remove(emp);
em.getTransaction().commit();
//持久化状态中删除是删除数据表中记录删除,但对象里面的内容并没有删除,持久化状态对象又变为瞬时状态对象
System.out.println(emp.getEmpno());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
em.getTransaction().rollback();
}finally {
em.close();
}
}