本章主要讲了域模型和元数据,以一个CaveatEmptor在线拍卖系统为例讲解了从项目架构分层,分析业务域,域模型,域模型的持久化类和持久化层之间的关注点完全分离,持久化类不清楚-且不依赖-持久化机制,然后讲解了域模型域元数据的关系,以及在运行时的方法方式。
域模型:被创建的实体类与属性被称为域模型.
元数据:即告知Hibernate持久化类及其属性与数据库表和列关联起来的方式。
JPA提供了两个元数据选项:
1、注解
2、XML描述符
JPA提供的访问持久化元模型API:
1、JAVA持久化中的动态元模型API
2、使用一个静态元模型
CaveatEmptor示例
CaveatEmptor是一个揭示ORM技术和Hibernate功能的在线拍卖应用,其中域模型为:
所对应的实体类为:
/model/src/main/java/org/jpwh/model/simple/Item.java
package org.jpwh.model.simple;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Item {
@Id
@GeneratedValue(generator = "ID_GENERATOR")
protected Long id;
public Long getId() { // 可选但有用
return id;
}
@Version
protected long version;
@NotNull
@Size(
min = 2,
max = 255,
message = "Name is required, maximum 255 characters."
)
protected String name;
@Future
protected Date auctionEnd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getAuctionEnd() {
return auctionEnd;
}
public void setAuctionEnd(Date auctionEnd) {
this.auctionEnd = auctionEnd;
}
protected BigDecimal buyNowPrice;
public BigDecimal getBuyNowPrice() {
return buyNowPrice;
}
public void setBuyNowPrice(BigDecimal buyNowPrice) {
this.buyNowPrice = buyNowPrice;
}
@Transient
protected Set<Bid> bids = new HashSet<Bid>();
public Set<Bid> getBids() {
return bids;
}
public void setBids(Set<Bid> bids) {
this.bids = bids;
}
@ManyToOne(fetch = FetchType.LAZY)
protected Category category;
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public void addBid(Bid bid) {
// Be defensive
if (bid == null)
throw new NullPointerException("Can't add null Bid");
if (bid.getItem() != null)
throw new IllegalStateException("Bid is already assigned to an Item");
getBids().add(bid);
bid.setItem(this);
}
public Bid placeBid(Bid currentHighestBid, BigDecimal bidAmount) {
if (currentHighestBid == null ||
bidAmount.compareTo(currentHighestBid.getAmount()) > 0) {
return new Bid(bidAmount, this);
}
return null;
}
}
/model/src/main/java/org/jpwh/model/simple/Item_.java
package org.jpwh.model.simple;
import java.util.Date;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel(Item.class)
public abstract class Item_ {
public static volatile SingularAttribute<Item,Long> id;
public static volatile SingularAttribute<Item,String> name;
public static volatile SingularAttribute<Item,Date> auctionEnd;
}
/model/src/main/java/org/jpwh/model/simple/Bid.java
package org.jpwh.model.simple;
import org.jpwh.model.Constants;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Entity
public class Bid {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
public Long getId() { // 可选的但是有用的
return id;
}
public Bid() {
}
@NotNull
protected BigDecimal amount;
public Bid(BigDecimal amount, Item item) {
this.amount = amount;
this.item = item;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
@ManyToOne(optional = false, fetch = FetchType.LAZY) // NOT NULL
@JoinColumn(name = "ITEM_ID") // 实际上默认名称
protected Item item;
public Bid(Item item) {
this.item = item;
item.getBids().add(this); // 双向
}
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
}
/model/src/main/java/org/jpwh/model/simple/Address.java
package org.jpwh.model.simple;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.NotNull;
/**
*
*而不是<code> @Entity </code>,该组件POJO标有<code> @Embeddable </code>。 它
*没有标识符属性。
*/
@Embeddable
public class Address {
@NotNull // 忽略了DDL生成!
@Column(nullable = false) // 用于DDL生成!
protected String street;
@NotNull
@Column(nullable = false, length = 5) // Override VARCHAR(255)
protected String zipcode;
@NotNull
@Column(nullable = false)
protected String city;
/**
*Hibernate会调用这个无参数的构造函数来创建一个实例,然后
*直接填充字段。
*/
protected Address() {
}
/**
*为方便起见,您可以拥有其他(public)构造函数。
**/
public Address(String street, String zipcode, String city) {
this.street = street;
this.zipcode = zipcode;
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
/model/src/main/java/org/jpwh/model/simple/Category.java
package org.jpwh.model.simple;
import org.jpwh.model.Constants;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Category {
@Id
@GeneratedValue(generator = Constants.ID_GENERATOR)
protected Long id;
public Long getId() {
return id;
}
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
提供的一些测试类:
/examples/src/test/java/org/jpwh/test/simple/AccessJPAMetamodel.java
package org.jpwh.test.simple;
import org.jpwh.env.JPATest;
import org.jpwh.model.simple.Item;
import org.jpwh.model.simple.Item_;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import javax.transaction.UserTransaction;
import java.util.Date;
import java.util.List;
import java.util.Set;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
//JPA访问元模型
public class AccessJPAMetamodel extends JPATest {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimpleXMLCompletePU");
}
@Test
public void accessDynamicMetamodel() throws Exception {
EntityManagerFactory entityManagerFactory = JPA.getEntityManagerFactory();
Metamodel mm = entityManagerFactory.getMetamodel();
Set<ManagedType<?>> managedTypes = mm.getManagedTypes();
assertEquals(managedTypes.size(), 1);
ManagedType itemType = managedTypes.iterator().next();
assertEquals(
itemType.getPersistenceType(),
Type.PersistenceType.ENTITY
);
SingularAttribute nameAttribute =
itemType.getSingularAttribute("name");
assertEquals(
nameAttribute.getJavaType(),
String.class
);
assertEquals(
nameAttribute.getPersistentAttributeType(),
Attribute.PersistentAttributeType.BASIC
);
assertFalse(
nameAttribute.isOptional() // NOT NULL
);
SingularAttribute auctionEndAttribute =
itemType.getSingularAttribute("auctionEnd");
assertEquals(
auctionEndAttribute.getJavaType(),
Date.class
);
assertFalse(
auctionEndAttribute.isCollection()
);
assertFalse(
auctionEndAttribute.isAssociation()
);
}
@Test
public void accessStaticMetamodel() throws Exception {
SingularAttribute nameAttribute = Item_.name;
assertEquals(
nameAttribute.getJavaType(),
String.class
);
}
@Test
public void queryStaticMetamodel() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager entityManager = JPA.createEntityManager();
Item itemOne = new Item();
itemOne.setName("This is some item");
itemOne.setAuctionEnd(new Date(System.currentTimeMillis() + 100000));
entityManager.persist(itemOne);
Item itemTwo = new Item();
itemTwo.setName("Another item");
itemTwo.setAuctionEnd(new Date(System.currentTimeMillis() + 100000));
entityManager.persist(itemTwo);
tx.commit();
entityManager.close();
entityManager = JPA.createEntityManager();
tx.begin();
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// This query is the equivalent of "select i from Item i"
CriteriaQuery<Item> query = cb.createQuery(Item.class);
Root<Item> fromItem = query.from(Item.class);
query.select(fromItem);
List<Item> items =
entityManager.createQuery(query)
.getResultList();
assertEquals(items.size(), 2);
// "where i.name like :pattern"
Path<String> namePath = fromItem.get("name");
query.where(
cb.like(
namePath, // Has to be a Path<String> for like() operator!
cb.parameter(String.class, "pattern")
)
);
items =
entityManager.createQuery(query)
.setParameter("pattern", "%some item%") // Wildcards!
.getResultList();
assertEquals(items.size(), 1);
assertEquals(items.iterator().next().getName(), "This is some item");
query.where(
cb.like(
fromItem.get(Item_.name), // Static Item_ metamodel!
cb.parameter(String.class, "pattern")
)
);
items =
entityManager.createQuery(query)
.setParameter("pattern", "%some item%") // Wildcard!
.getResultList();
assertEquals(items.size(), 1);
assertEquals(items.iterator().next().getName(), "This is some item");
tx.commit();
entityManager.close();
} finally {
TM.rollback();
}
}
}
/examples/src/test/java/org/jpwh/test/simple/CRUD.java
package org.jpwh.test.simple;
import org.jpwh.env.JPATest;
import org.jpwh.model.simple.Item;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.UserTransaction;
import java.util.Date;
import java.util.List;
import static org.testng.Assert.assertEquals;
public class CRUD extends JPATest {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimplePU");
}
@Test
public void storeAndQueryItems() throws Exception {
storeAndQueryItems("findItems");
}
public void storeAndQueryItems(String queryName) throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item itemOne = new Item();
itemOne.setName("Item One");
itemOne.setAuctionEnd(new Date(System.currentTimeMillis() + 100000));
em.persist(itemOne);
Item itemTwo = new Item();
itemTwo.setName("Item Two");
itemTwo.setAuctionEnd(new Date(System.currentTimeMillis() + 100000));
em.persist(itemTwo);
tx.commit();
em.close();
tx.begin();
em = JPA.createEntityManager();
Query q = em.createNamedQuery(queryName);
List<Item> items = q.getResultList();
assertEquals(items.size(), 2);
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
}
/examples/src/test/java/org/jpwh/test/simple/CRUDMetadataHBMXML.java
package org.jpwh.test.simple;
import org.testng.annotations.Test;
//CRUD HBM XML元数据
public class CRUDMetadataHBMXML extends CRUD {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimpleXMLHibernatePU", "simple/Native.hbm.xml");
}
@Test
@Override
public void storeAndQueryItems() throws Exception {
super.storeAndQueryItems("findItemsHibernate");
}
}
/examples/src/test/java/org/jpwh/test/simple/CRUDMetadataOverrideXML.java
package org.jpwh.test.simple;
import org.hibernate.SessionFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
//CRUD XML元数据覆盖
public class CRUDMetadataOverrideXML extends CRUD {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimpleXMLOverridePU");
}
@Test
@Override
public void storeAndQueryItems() throws Exception {
super.storeAndQueryItems();
}
@Test
public void checkMetadataOverride() throws Exception {
//使用Hibernate元数据API来查找覆盖SQL列的名称,JPA不
//支持访问SQL详细信息
// TODO:不知道如何使用Hibernate 5 API访问SQL映射详细信息...
/*
Property nameProperty = itemClass.getProperty("name");
Column nameColumn = (Column) nameProperty.getColumnIterator().next();
// 列的名称来自XML描述符
assertEquals(nameColumn.getName(), "ITEM_NAME");
// 但是Bean验证注释仍然被识别!
assertEquals(nameColumn.isNullable(), false);
assertEquals(nameColumn.getLength(), 255);
*/
}
}
/examples/src/test/java/org/jpwh/test/simple/CRUDMetadataXML.java
package org.jpwh.test.simple;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
//CRUD XML元数据
public class CRUDMetadataXML extends CRUD {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimpleXMLCompletePU");
}
@Test
@Override
public void storeAndQueryItems() throws Exception {
super.storeAndQueryItems();
}
}
/examples/src/test/java/org/jpwh/test/simple/MappingEmbeddables.java
package org.jpwh.test.simple;
import org.jpwh.env.JPATest;
import org.jpwh.model.simple.Address;
import org.jpwh.model.simple.User;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.transaction.UserTransaction;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.testng.Assert.assertEquals;
//映射可嵌入
public class MappingEmbeddables extends JPATest {
private static final Logger LOG = Logger.getLogger(MappingEmbeddables.class.getName());
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimplePU");
}
@Test
public void storeAndLoadUsers() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
User user = new User();
user.setUsername("johndoe");
Address homeAddress = new Address("Some Street 123", "12345", "Some City");
user.setHomeAddress(homeAddress);
em.persist(user);
tx.commit();
em.close();
tx.begin();
em = JPA.createEntityManager();
User u = em.find(User.class, user.getId());
assertEquals(u.getUsername(), "johndoe");
assertEquals(u.getHomeAddress().getStreet(), "Some Street 123");
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
@Test(expectedExceptions = org.hibernate.exception.ConstraintViolationException.class)
public void storeAndLoadInvalidUsers() throws Throwable {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
User user = new User();
user.setUsername("johndoe");
Address homeAddress = new Address("Some Street 123", "12345", null); // NULL city!
user.setHomeAddress(homeAddress);
em.persist(user);
try {
// Hibernate尝试插入但失败
em.flush();
//注意:如果你尝试用tx.commit()和一个冲洗的副作用,你就不会
//得到ConstraintViolationException。Hibernate会在内部捕获它
//简单地将事务标记为回滚。
} catch (Exception ex) {
throw unwrapCauseOfType(ex, org.hibernate.exception.ConstraintViolationException.class);
}
} finally {
TM.rollback();
}
}
}
/examples/src/test/java/org/jpwh/test/simple/MappingManyToOne.java
package org.jpwh.test.simple;
import org.jpwh.env.JPATest;
import org.jpwh.model.simple.Bid;
import org.jpwh.model.simple.Item;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import javax.transaction.UserTransaction;
import java.math.BigDecimal;
import static org.testng.Assert.assertEquals;
//映射多对一
public class MappingManyToOne extends JPATest {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimplePU");
}
@Test
public void storeAndLoadBids() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
//存储在一个持久性上下文(事务)
Item anItem = new Item();
anItem.setName("Example Item");
Bid firstBid = new Bid(new BigDecimal("123.00"), anItem);
Bid secondBid = new Bid(new BigDecimal("456.00"), anItem);
// 订单在这里很重要,Hibernate不够聪明了!
em.persist(anItem);
em.persist(firstBid);
em.persist(secondBid);
tx.commit();
em.close();
tx.begin();
em = JPA.createEntityManager();
Long BID_ID = firstBid.getId();
// 加载另一个持久性上下文
Bid someBid = em.find(Bid.class, BID_ID); // SQL SELECT
//初始化Item代理,因为我们调用getId(),它是
//未映射为标识符属性(the field is!)
assertEquals(someBid.getItem().getId(), anItem.getId()); // SQL SELECT
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
}
/examples/src/test/java/org/jpwh/test/simple/ModelOperations.java
package org.jpwh.test.simple;
import org.jpwh.model.simple.Bid;
import org.jpwh.model.simple.Item;
import org.testng.annotations.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import static org.testng.Assert.*;
//模式操作
public class ModelOperations {
@Test
public void linkBidAndItem() {
Item anItem = new Item();
Bid aBid = new Bid();
anItem.getBids().add(aBid);
aBid.setItem(anItem);
assertEquals(anItem.getBids().size(), 1);
assertTrue(anItem.getBids().contains(aBid));
assertEquals(aBid.getItem(), anItem);
// 再次用方便的方法
Bid secondBid = new Bid();
anItem.addBid(secondBid);
assertEquals(2, anItem.getBids().size());
assertTrue(anItem.getBids().contains(secondBid));
assertEquals(anItem, secondBid.getItem());
}
@Test
public void validateItem() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Item item = new Item();
item.setName("Some Item");
item.setAuctionEnd(new Date());
Set<ConstraintViolation<Item>> violations = validator.validate(item);
// 我们有一个验证错误,拍卖结束日期不在将来!
assertEquals(1, violations.size());
ConstraintViolation<Item> violation = violations.iterator().next();
String failedPropertyName =
violation.getPropertyPath().iterator().next().getName();
assertEquals(failedPropertyName, "auctionEnd");
if (Locale.getDefault().getLanguage().equals("en"))
assertEquals(violation.getMessage(), "must be in the future");
}
}
/examples/src/test/java/org/jpwh/test/simple/SimpleTransitions.java
package org.jpwh.test.simple;
import org.hibernate.Session;
import org.hibernate.jdbc.Work;
import org.jpwh.env.JPATest;
import org.jpwh.model.simple.Address;
import org.jpwh.model.simple.Item;
import org.jpwh.model.simple.User;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FlushModeType;
import javax.persistence.PersistenceUnitUtil;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static org.testng.Assert.*;
import static org.testng.Assert.assertEquals;
//简单的过渡
public class SimpleTransitions extends JPATest {
@Override
public void configurePersistenceUnit() throws Exception {
configurePersistenceUnit("SimplePU");
}
@Test
public void basicUOW() {
EntityManager em = null;
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
em = JPA.createEntityManager(); // 应用程序管理
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit(); // 同步/刷新持久性上下文
} catch (Exception ex) {
// 事务回滚,异常处理
try {
if (tx.getStatus() == Status.STATUS_ACTIVE
|| tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
tx.rollback();
} catch (Exception rbEx) {
System.err.println("Rollback of transaction failed, trace follows!");
rbEx.printStackTrace(System.err);
}
throw new RuntimeException(ex);
} finally {
if (em != null && em.isOpen())
em.close(); // 你创建它,你关闭它!
}
}
@Test
public void makePersistent() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
EntityManager em;
tx.begin();
em = JPA.createEntityManager();
Item item = new Item();
item.setName("Some Item"); // Item#name is NOT NULL!
em.persist(item);
Long ITEM_ID = item.getId(); // Has been assigned
tx.commit();
em.close();
tx.begin();
em = JPA.createEntityManager();
assertEquals(em.find(Item.class, ITEM_ID).getName(), "Some Item");
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
@Test
public void retrievePersistent() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
long ITEM_ID = someItem.getId();
{
tx.begin();
em = JPA.createEntityManager();
// Hit the database if not already in persistence context
Item item = em.find(Item.class, ITEM_ID);
if (item != null)
item.setName("New Name"); // Modify
tx.commit(); // Flush: Dirty check and SQL UPDATE
em.close();
}
{
tx.begin();
em = JPA.createEntityManager();
Item itemA = em.find(Item.class, ITEM_ID);
Item itemB = em.find(Item.class, ITEM_ID); // Repeatable read
assertTrue(itemA == itemB);
assertTrue(itemA.equals(itemB));
assertTrue(itemA.getId().equals(itemB.getId()));
tx.commit(); // Flush: Dirty check and SQL UPDATE
em.close();
}
tx.begin();
em = JPA.createEntityManager();
assertEquals(em.find(Item.class, ITEM_ID).getName(), "New Name");
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
@Test(expectedExceptions = org.hibernate.LazyInitializationException.class)
public void retrievePersistentReference() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
long ITEM_ID = someItem.getId();
tx.begin();
em = JPA.createEntityManager();
/*
如果持久性上下文已经包含具有给定标识符的<code> Item </code>
<code> Item </code>实例由<code> getReference()</code>返回,而不会触发数据库。
此外,如果目前正在管理具有该标识符的持久化实例<em>no</em>,那么一个空
占位符将由Hibernate代理生成。 这意味着<code> getReference()</code>不会
访问数据库,它不返回<code> null </code>,与<code> find()</code>不同。
*/
Item item = em.getReference(Item.class, ITEM_ID);
/*
JPA提供<code> PersistenceUnitUtil </code>辅助方法,例如<code> isLoaded()</code>
检测您是否使用未初始化的代理。
*/
PersistenceUnitUtil persistenceUtil =
JPA.getEntityManagerFactory().getPersistenceUnitUtil();
assertFalse(persistenceUtil.isLoaded(item));
/*
一旦您在代理上调用任何方法,例如<code> Item#getName()</code>,
执行<code> SELECT </code>以完全初始化占位符。 这个规则的例外是
一种映射的数据库标识符getter方法,如<code> getId()</code>。 代理人
可能看起来像真实的东西,但它只是一个携带标识符值的占位符
它代表的实体实例。 如果代理服务器上不存在数据库记录
被初始化,将抛出一个<code> EntityNotFoundException </code>。
*/
// assertEquals(item.getName(), "Some Item");
/*
Hibernate有一个方便的静态<code> initialize()</code>方法,加载代理的数据。
*/
// Hibernate.initialize(item);
tx.commit();
em.close();
/*
在持久化上下文关闭之后,<code>item</code>处于分离状态。
如果你这样做在持久化上下文仍然打开时,不初始化代理,如果你访问代理,您将得到一个
<code> LazyInitializationException </code>。
你不能加载,一旦持久化上下文关闭,就按需数据。
解决方案很简单:加载
关闭持久化上下文之前的数据。
*/
assertEquals(item.getName(), "Some Item");
} finally {
TM.rollback();
}
}
@Test
public void makeTransient() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
long ITEM_ID = someItem.getId();
tx.begin();
em = JPA.createEntityManager();
/*
如果调用<code>find()</code>,Hibernate将执行<code>SELECT</code>
加载<code>Item</code>。 如果你调用<code> getReference()</code>,Hibernate
将尝试避免<code>SELECT</code>并返回代理。
*/
Item item = em.find(Item.class, ITEM_ID);
//Item item = em.getReference(Item.class, ITEM_ID);
/*
调用<code>remove()</code>将队列化实体删除
工作单位完成,现在已被删除<em>removed</em>。如果<code>remove()</code>
在代理上调用,Hibernate将执行<code>SELECT</code>来加载数据。
实体实例必须在生命周期过渡期间完全初始化。 你可能
是否启用了生命周期回调方法或实体侦听器
(请参阅<a href="#EventListenersInterceptors"/>),实例必须通过这些
拦截器完成其整个生命周期。
*/
em.remove(item);
/*
被删除状态的实体不再处于持久状态,这可以是
使用<code> contains()</code>操作进行检查。
*/
assertFalse(em.contains(item));
/*
您可以使删除的实例再次持久化,取消删除。
*/
// em.persist(item);
// hibernate.use_identifier_rollback已启用,它现在看起来像一个暂时的实例
assertNull(item.getId());
/*
当事务提交时,Hibernate会同步状态转换
数据库并执行SQL <code> DELETE </code>。 JVM垃圾收集器检测到
<code> item </code>不再被任何人引用,最后删除最后一个跟踪
数据。
*/
tx.commit();
em.close();
tx.begin();
em = JPA.createEntityManager();
item = em.find(Item.class, ITEM_ID);
assertNull(item);
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
@Test
public void refresh() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
final long ITEM_ID = someItem.getId();
tx.begin();
em = JPA.createEntityManager();
Item item = em.find(Item.class, ITEM_ID);
item.setName("Some Name");
// 有人更新数据库中的这一行!
Executors.newSingleThreadExecutor().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Session session = em.unwrap(Session.class);
session.doWork(new Work() {
@Override
public void execute(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement("update ITEM set name = ? where ID = ?");
ps.setString(1, "Concurrent Update Name");
ps.setLong(2, ITEM_ID);
/* 替代方法:在刷新时收到EntityNotFoundException
PreparedStatement ps = con.prepareStatement("delete from ITEM where ID = ?");
ps.setLong(1, ITEM_ID);
*/
if (ps.executeUpdate() != 1)
throw new SQLException("ITEM row was not updated");
}
});
tx.commit();
em.close();
} catch (Exception ex) {
TM.rollback();
throw new RuntimeException("Concurrent operation failure: " + ex, ex);
}
return null;
}
}).get();
String oldName = item.getName();
em.refresh(item);
assertNotEquals(item.getName(), oldName);
assertEquals(item.getName(), "Concurrent Update Name");
tx.commit(); // 刷新:脏检查和SQL UPDATE
em.close();
} finally {
TM.rollback();
}
}
@Test(groups = {"H2", "POSTGRESQL", "ORACLE"})
public void replicate() throws Exception {
Long ITEM_ID;
try {
UserTransaction tx = TM.getUserTransaction();
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
ITEM_ID = someItem.getId();
} finally {
TM.rollback();
}
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager emA = getDatabaseA().createEntityManager();
Item item = emA.find(Item.class, ITEM_ID);
EntityManager emB = getDatabaseB().createEntityManager();
emB.unwrap(Session.class)
.replicate(item, org.hibernate.ReplicationMode.LATEST_VERSION);
tx.commit();
emA.close();
emB.close();
} finally {
TM.rollback();
}
}
protected EntityManagerFactory getDatabaseA() {
return JPA.getEntityManagerFactory();
}
protected EntityManagerFactory getDatabaseB() {
// TODO:失败,因为我们不能在同一事务中招募两个非XA连接
//在MySQL上 XA在MySQL中破坏,所以我们必须使用Bitronix XA包装器,它可以
//每个事务只处理一个非XA资源。 请参阅DatabaseProduct.java
return JPA.getEntityManagerFactory();
}
@Test
public void flushModeType() throws Exception {
UserTransaction tx = TM.getUserTransaction();
Long ITEM_ID;
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Original Name");
em.persist(someItem);
tx.commit();
em.close();
ITEM_ID = someItem.getId();
} finally {
TM.rollback();
}
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item item = em.find(Item.class, ITEM_ID);
item.setName("New Name");
// 禁用刷新之前查询:
em.setFlushMode(FlushModeType.COMMIT);
assertEquals(
em.createQuery("select i.name from Item i where i.id = :id")
.setParameter("id", ITEM_ID).getSingleResult(),
"Original Name"
);
tx.commit(); // Flush!
em.close();
} finally {
TM.rollback();
}
}
@Test
public void scopeOfIdentity() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
Item someItem = new Item();
someItem.setName("Some Item");
em.persist(someItem);
tx.commit();
em.close();
long ITEM_ID = someItem.getId();
tx.begin();
em = JPA.createEntityManager();
Item a = em.find(Item.class, ITEM_ID);
Item b = em.find(Item.class, ITEM_ID);
assertTrue(a == b);
assertTrue(a.equals(b));
assertEquals(a.getId(), b.getId());
tx.commit();
em.close();
// PC is gone, 'a' and 'b' are now references to instances in detached state!
tx.begin();
em = JPA.createEntityManager();
Item c = em.find(Item.class, ITEM_ID);
assertTrue(a != c); // The 'a' reference is still detached!
assertFalse(a.equals(c));
assertEquals(a.getId(), c.getId());
tx.commit();
em.close();
Set<Item> allItems = new HashSet<>();
allItems.add(a);
allItems.add(b);
allItems.add(c);
assertEquals(allItems.size(), 2); // 这似乎是错的和任意的!
} finally {
TM.rollback();
}
}
@Test
public void detach() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
User someUser = new User();
someUser.setUsername("johndoe");
someUser.setHomeAddress(new Address("Some Street", "1234", "Some City"));
em.persist(someUser);
tx.commit();
em.close();
long USER_ID = someUser.getId();
tx.begin();
em = JPA.createEntityManager();
User user = em.find(User.class, USER_ID);
em.detach(user);
assertFalse(em.contains(user));
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
@Test
public void mergeDetached() throws Exception {
UserTransaction tx = TM.getUserTransaction();
try {
tx.begin();
EntityManager em = JPA.createEntityManager();
User detachedUser = new User();
detachedUser.setUsername("foo");
detachedUser.setHomeAddress(new Address("Some Street", "1234", "Some City"));
em.persist(detachedUser);
tx.commit();
em.close();
long USER_ID = detachedUser.getId();
detachedUser.setUsername("johndoe");
tx.begin();
em = JPA.createEntityManager();
User mergedUser = em.merge(detachedUser);
// 合并后丢弃'detachUser'引用!
// 'mergedUser'处于持久状态
mergedUser.setUsername("doejohn");
tx.commit(); // UPDATE在数据库中
em.close();
tx.begin();
em = JPA.createEntityManager();
User user = em.find(User.class, USER_ID);
assertEquals(user.getUsername(), "doejohn");
tx.commit();
em.close();
} finally {
TM.rollback();
}
}
}