Skip to content

Latest commit

 

History

History
599 lines (441 loc) · 15.7 KB

java_persistence_api.md

File metadata and controls

599 lines (441 loc) · 15.7 KB

Entities

标准的实体类的要求

  • 必须被 javax.persistence.Entity注解修饰
  • 实体类必须是 public或者protect 的, 必须得有一个无参的构造器,当然,它可以有其他的构造器。
  • 必须不能为final修饰,没有方法或实体常量必须被声明为final.
  • 如果实体需要被远程调用或传输,必须实现Serializable接口。
  • 实体类可以扩展非实体类和实体类。非实体类可以扩展实体类。
  • 实体的属性必须被声明为privateprotectpackage-privare,并且只能通过实体类的方法访问。客户端必须通过访问者或业务方法访问实体的状态。

实体持久化字段和属性的类型

  • Java 基本数据类型
  • java.lang.String
  • 其他可被序列化的类:
    • Java 基本数据类型的包装类
    • java.math.BigInteger
    • java.math.BigDecimal
    • java.util.Date
    • java.util.Calendar
    • java.sql.Date
    • java.sql.Time
    • java.sql.TimeStamp
    • 用户定义的可序列化的类型
    • byte[]
    • Byte[]
    • char[]
    • Character[]
  • 枚举类型
  • 其他实体和/或实体的集合
  • 内部类

持久化字段

如果一个字段没有注解javax.persitence.Transient或者没有被标注为transient,那么这个字段将会被持久化到数据库。

实体中的主键

每个实体都有一个唯一的对象标识。

简单的主键可以使用javax.persitence.Id注解来表明这个字段是一个主键。

复合主键必须对应于单个持久属性或字段,或者对应于一组单个持久性属性或字段。必须在主键类中定义组合主键 。复合主键可以使用javax.persistence.EmbeddedIdjavax.persistence.IdClass注解来表明。

复合主键的类型必须是下面的几种类型

  • java 基本数据类型
  • java 基本数据类型的包装类
  • java.lang.String
  • java.util.Date
  • java.sql.Date

主键必须是一个整形,而不能是浮点型。

主键类的要求

  • 访问修饰符必须是public
  • 如果使用基于属性的访问,则主键类的属性必须是publicprotect的。
  • 必须可以被序列化(实现Serializable接口)
  • 必须表示复合主键并将其映射到实体类的多个字段或属性,或者必须将其表示并映射为可嵌入类。
  • 如果类映射到实体类的多个字段或属性,则主键类中主键字段或属性的名称和类型必须与实体类的名称和类型匹配。
public final class LineItemKey implements Serializable {
    public Integer orderId;
    public int itemId;

    public LineItemKey() {}

    public LineItemKey(Integer orderId, int itemId) {
        this.orderId = orderId;
        this.itemId = itemId;
    }

    public boolean equals(Object otherOb) {
        if (this == otherOb) {
            return true;
        }
        if (!(otherOb instanceof LineItemKey)) {
            return false;
        }
        LineItemKey other = (LineItemKey) otherOb;
        return (
                    (orderId==null?other.orderId==null:orderId.equals
                    (other.orderId)
                    )
                    &&
                    (itemId == other.itemId)
                );
    }

    public int hashCode() {
        return (
                    (orderId==null?0:orderId.hashCode())
                    ^
                    ((int) itemId)
                );
    }

    public String toString() {
        return "" + orderId + "-" + itemId;
    }
}

实体之间的关系

  • One-to-one:一对一的关系使用javax.persistence.OneToOne注解来声明持久化字段或属性
  • One-to-many:一对多的关系使用javax.persistence.OneToMany来声明持久化字段或属性
  • Many-to-one:多对一的关系使用javax.persistence.ManyToOne来声明持久化字段或属性
  • Many-to-many:多对多的关系使用javax.persistence.ManyToMany来声明持久化字段或属性

实体关系之间的方向

实体之间的关系可以是双向的,也可以是单向的。

双向关系

在一个双向关系中,每一个实体都有一个字段来映射其他的实体。

双向关联的关系必须有如下规则:

  • 双向关系的反面必须通过使用@OneToOne@ OneToMany@ManyToMany注释的mappedBy元素来引用其拥有方。 mappedBy元素指定作为关系所有者的实体中的属性或字段。
  • 多对一的关系中,多的一次不能使用mappedBy。因为多的一方始终是关系的拥有方。
  • 对于一对一的双向关系,拥有方对应于包含相应外键的一侧。
  • 对于多对多双向关系,任何一方都可能是拥有方。

级联删除关系

使用关系的实体通常依赖于关系中另一个实体的存在, 如果一方删除,也另一方也应该删除,这种方式叫做"级联删除"。

使用@OneToOne@OneToMany关系的cascade = REMOVE元素规范指定级联删除关系

@OneToMany(cascade=REMOVE, mappedBy="customer")
public Set<Order> getOrders() { return orders; }

抽象实体

@Entity
public abstract class Employee {
    @Id
    protected Integer employeeId;
    ...
}
@Entity
public class FullTimeEmployee extends Employee {
    protected Integer salary;
    ...
}
@Entity
public class PartTimeEmployee extends Employee {
    protected Float hourlyWage;
}

映射超类

通过使用javax.persistence.MappedSuperclass注解对类进行装饰来指定映射的超类。

@MappedSuperclass
public class Employee {
    @Id
    protected Integer employeeId;
    ...
}
@Entity
public class FullTimeEmployee extends Employee {
    protected Integer salary;
    ...
}
@Entity
public class PartTimeEmployee extends Employee {
    protected Float hourlyWage;
    ...
}

EntityManger

获取EntityManger

@PersistenceContext
EntityManager em;

获取EntityMangerFactory

@PersistenceUnit
EntityManagerFactory emf;

我们可以从EntityMangerFactory中通过调用createEntityManger方法获取EntityManger

EntityManager em = emf.createEntityManager();

**使用EntityManger从data store中通过主键查询实体

@PersistenceContext
EntityManager em;
public void enterOrder(int custID, Order newOrder) {
    Customer cust = em.find(Customer.class, custID);
    cust.getOrders().add(newOrder);
    newOrder.setCustomer(cust);
}

Entity的声明周期

Entity 的声明周期有 new(新实体), managed(被管理), detached(分离), removed(被移除)四种状态

  • new没有持久化标识(主键),和持久化上下文(persistence context)没有关联
  • managed有持久化标识,且和持久化上下文有关联。
  • detached 有持久化标识,并且当前不与持久性上下文相关联。
  • removed有持久化标识,与持久性上下文关联,并计划从数据存储中删除。

持久化实体(保存)

通过调用persist方法或通过在关系注释中设置了cascade = PERSISTcascade = ALL元素的相关实体调用的级联持久操作,新实体实例变为托管和持久化 。如果实体已经是被管理的实例,则会忽略persist操作。如果persist的方法被 removed状态的实体调用,则该实体将会从removed状态转变为managed状态。如果实体为detached状态,则persist将会抛出IllegalArgumentException , 并且事务的提交会失败。

@PersistenceContext
EntityManager em;
...
public LineItem createLineItem(Order order, Product product,
        int quantity) {
    LineItem li = new LineItem(order, product, quantity);
    order.getLineItems().add(li);
    em.persist(li);
    return li;
}

persist(持久化)操作将传播到实体中所有的关系注解(OneToMany等),如果关系注解中的cascade  属性被设置为ALL或者PERSIST。则其注解的对象也会被持久化到数据库。

@OneToMany(cascade=ALL, mappedBy="order")
public Collection<LineItem> getLineItems() {
    return lineItems;
}

删除 Entity 实例(删除)

managed(被管理得)实例通过调用remove方法删除,并且如果被删除的实例中的关系注解的cascade属性被设置为ALLREMOVE,则对应的对象也会被删除。如果删除的对象是一个新的实体,则删除操作将会被忽略。如果删除的是一个detached对象,则会抛出IllegalArgumentException ,且事务提交也会失败。如果删除的是一个已被删除的实体,则操作将被忽略。当事务提交或者执行flush操作时,数据将会从数据库中删除。

public void removeOrder(Integer orderId) {
    try {
        Order order = em.find(Order.class, orderId);
        em.remove(order);
    }...

创建查询语句

EntityManager.createQuery EntityManager.createNamedQuery 被用来使用JPA查询语言从数据库查询数据。

  • createQuery 被用来创建动态查询
public List findWithName(String name) {
return em.createQuery(
    "SELECT c FROM Customer c WHERE c.name LIKE :custName")
    .setParameter("custName", name)
    .setMaxResults(10)
    .getResultList();
}
  • createNamedQuery 被用来创建静态查询,通过使用javax.persistence.NamedQuery 注解在实体类上声明,该注解中的name元素指定了该静态查询语句的名称。query 元素指定了查询的语句。
@NamedQuery(
    name="findAllCustomersWithName",
    query="SELECT c FROM Customer c WHERE c.name LIKE :custName"
)
@PersistenceContext
public EntityManager em;
...
customers = em.createNamedQuery("findAllCustomersWithName")
    .setParameter("custName", "Smith")
    .getResultList();

查询语句中的参数

命名参数在查询中使用(:)作为前缀,它会被javax.persistence.Query.setParameter(String name, Object value) 方法绑定

public List findWithName(String name) {
    Query query = em.createQuery("SELECT c FROM Customer c WHERE c.name LIKE :custName");
    query.setParameter("custName", name);
    return query.getResultList();
}

也可以使用参数下标的形式对参数进行赋值

public List findWithName(String name) {
    return em.createQuery(
        “SELECT c FROM Customer c WHERE c.name LIKE ?1”)
        .setParameter(1, name)
        .getResultList();
}

持久化单元(配置文件)

持久性单元定义由应用程序中的EntityManager实例管理的所有实体类的集合。这组实体类表示单个数据存储中包含的数据。

持久性单元由persistence.xml配置文件定义,META-INF目录包含persistence.xml的JAR文件或目录称为持久性单元的根,持久性单元的范围由持久性单元的根确定。

<persistence>
    <persistence-unit name="OrderManagement">
        <description>
            This unit manages orders and customers.
            It does not rely on any vendor-specific features and can
            therefore be deployed to any persistence provider.
        </description>
        <jta-data-source>jdbc/MyOrderDB</jta-data-source>
        <jar-file>MyOrderApp.jar</jar-file>
        <class>com.widgets.Order</class>
        <class>com.widgets.Customer</class>
    </persistence-unit>
</persistence>

JPA 在WEB 中的应用

定义持久化单元

持久化单元被定义在persistence.xml文件中,其中包含了以下内容:

  • persistence 元素,用于标识描述符验证的模式,并包含persistence-unit元素。
  • persistence-unit 元素定义了持久化单元的名称和事务类型
  • description 可选
  • jta-data-source 它指定JTA数据源的全局JNDI名称。

jta-data-source 元素指示实体管理器参与的事务是JTA事务,这意味着事务由容器管理。 也可以使用resource-local来管理事务,这是由应用程序本身提供的事务。

资源本地实体管理器不能参与全局事务。此外,Web容器不会回滚由编写不良的应用程序留下的待处理事务。

创建持久化实体

@Entity
@Table(name="WEB_BOOKSTORE_BOOKS")
public class Book implements Serializable {
	
    @Id
    private String bookId;
    private String title;
	... //getter and setter
}

通过EntityManager访问数据

public final class ContextListener implements SerlvetContextListener {
...
@PersistenceUnit
private EntityManagerFactory emf;

public void contextInitialized(ServletContexEvent event) {
    context = event.getServletContext();
    ...
    try {
        BookDBAO bookDB = new BookDBAO(emf);
        context.setAttribute("bookDB", bookDB);
    } catch (Exception ex) {
        System.out.println(
            "Couldn’t create bookstore database bean: "
                 + ex.getMessage());
    }
}
}

BookDBAO源码如下

private EntityManager em;

public BookDBAO (EntityManagerFactory emf) throws Exception {
    em = emf.getEntityManager();
    ...
}

也可以使用下面的方式在 DAO 中直接获取 EntityManager

public class BookDBAO {

    @PersistenceContext
    private EntityManager em;
...

我们可以通过EntityManager内提供的方法执行CRUD操作

事务

@Resource
UserTransaction utx;
...
try {
    utx.begin();
    bookDBAO.buyBooks(cart);
    utx.commit();
} catch (Exception ex) {
    try {
        utx.rollback();
    } catch (Exception exe) {
        System.out.println("Rollback failed: "+exe.getMessage());
}
...

JAVA 持久化查询语言

基本查询语句

SELECT p FROM Player p

FROM 元素后面跟的不再试 SQL 语言中的表名,而是JAVA 中的实体名称。

去重

SELECT DISTINCT
 p
FROM Player p
WHERE p.position = ?1

命名参数

SELECT DISTINCT p
FROM Player p
WHERE p.position = :position AND p.name = :name

关联查询

一对多 或 多对多

SELECT DISTINCT p
FROM Player p, IN(p.teams) t

也可以使用 JOIN 操作符

SELECT DISTINCT p
FROM Player p JOIN p.teams t

-- 也可以重写为:
SELECT DISTINCT p
FROM Player p
WHERE p.team IS NOT EMPTY

一对一

SELECT t
 FROM Team t JOIN t.league l
 WHERE l.sport = ’soccer’ OR l.sport =’football’

整合使用

SELECT DISTINCT p
FROM Player p, IN (p.teams) AS t
WHERE t.city = :city

遍历多条关系

SELECT DISTINCT p
FROM Player p, IN (p.teams) t
WHERE t.league = :league

通过关联对象的属性查询

SELECT DISTINCT p
FROM Player p, IN (p.teams) t
WHERE t.league.sport = :sport

LIKE 查询

SELECT p
 FROM Player p
 WHERE p.name LIKE ’Mich%’

IS NULL

SELECT t
 FROM Team t
 WHERE t.league IS NULL

IS EMPTY

SELECT p
FROM Player p
WHERE p.teams IS EMPTY

BETWEEN

SELECT DISTINCT p
FROM Player p
WHERE p.salary BETWEEN :lowerSalary AND :higherSalary

比较

SELECT DISTINCT p1
FROM Player p1, Player p2
WHERE p1.salary > p2.salary AND p2.name = :name

更新

UPDATE Player p
SET p.status = ’inactive’
WHERE p.lastPlayed < :inactiveThresholdDate

删除

DELETE
FROM Player p
WHERE p.status = ’inactive’
AND p.teams IS EMPTY