科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道将遗留 Hibernate 应用程序迁移到 OpenJPA 和 EJB 3.0

将遗留 Hibernate 应用程序迁移到 OpenJPA 和 EJB 3.0

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

通过使用 EJB 2.1 以及 OpenJPA 和 EJB 3.0 中的等效功能比较 Hibernate 应用程序中的特性和功能,学习如何将 Hibernate 应用程序源代码、对象关系映射和配置参数迁移到 OpenJPA。

作者:ibm 来源:ibm 2007年10月6日

关键字: 技术 程序 Hibernate 中间件

  • 评论
  • 分享微博
  • 分享邮件

引言

Hibernate 是开放源代码持久性和查询框架,提供传统 Java™ 对象 (POJO) 到关系数据库表的与对象相关的映射,以及数据查询和检索功能。Apache OpenJPA 项目将按照 EJB 3.0 Java Persistence API 规范的定义为 POJO 实体提供类似的开放源代码持久性和查询框架。本文介绍 Enterprise JavaBeans™ (EJB) 2.1 中的通用 Hibernate 场景,并将它们与 OpenJPA 和 EJB 3.0 中实现的等效场景进行比较。具体来说,您可以并排查看 Hibernate 应用程序源代码、对象关系映射和配置参数,并将它们与等效的 OpenJPA 源代码、映射和配置进行比较。这里显示的比较不仅使您能够了解如何进行这些更改,而且说明了将使用这些通用场景的遗留 Hibernate 应用程序迁移到 OpenJPA 相当简单。

尽管本文重点介绍将遗留 Hibernate 应用程序迁移到 OpenJPA,但是如果您熟悉 Hibernate,还会发现其中的价值,并希望尽快使用新的 JPA 规范以及使用 OpenJPA 持久性提供程序进行新的应用程序开发。

本文假设您熟悉 Hibernate 的基本概念,并将专门介绍 Hibernate 3.0 实现。本文中的所有示例均在 EJB 2.1 中的 Hibernate 3 中运行过,并在使用 IBM® WebSphere® Application Server V6.1 Feature Pack for EJB 3.0 的 OpenJPA 0.9.7 中运行过。

将遗留 Hibernate 应用程序迁移到 OpenJPA 的原因是多方面的。例如,Hibernate 是一个非标准的、对象关系映射和持久性管理解决方案。Hibernate 3 需要 JDK 1.3.1 或更高版本。通过对比,OpenJPA 可实现 JPA 规范,该规范是 Java 5 规范的核心部分,并且 WebSphere Application Server V6.1 Feature Pack for EJB 3.0 的实现基于该规范。有关这些产品的详细信息,请参见参考资料

考虑到本文的目的,JPA 表示该规范,并且 OpenJPA 表示 JPA 规范的实现。

本文没有介绍 Hibernate 的所有特性和功能,但介绍了该领域中经常使用的最佳实践。





回页首


迁移 Hibernate 应用程序源代码

Java Persistence API (JPA) 是作为 EJB 3.0 规范 (JSR220) 的一部分引入的,目的是让整个 Java 社区支持标准、单一的持久 API。JPA 将采用 Hibernate、TopLink、Java Data Objects 和 Container Managed Persistence (EJB-CMP) 2.1 规范的最佳理念。

JPA 适用于 Java Platform Standard Edition (Java SE) 和 Enterprise Edition (Java EE) 环境,因为它将实体表示为 JPA 持久性提供程序(如 OpenJPA)可以管理的 POJO。关于实体的对象关系映射的元数据是使用 Java 5 注释或在 XML 描述符中指定的。实体用于将 Java 对象持久保存到数据库。

有许多 JPA 持久性提供程序。IBM 的 JPA 规范实现基于 Apache OpenJPA 项目。随着这些 JPA 持久性提供程序的发布,客户现在可以对标准 API 进行编码,不必在不兼容的非标准持久性提供程序之间进行决策。

为帮助您将遗留 Hibernate 应用程序迁移到 OpenJPA,本部分将通常使用的 Hibernate 非标准 API 与等效的 OpenJPA 标准 API 进行了比较。本部分先比较所使用的类和接口,然后通过常规用法场景比较 API。

以下各部分提供了详细信息:

  1. 类和接口
  2. 运行时配置
  3. 会话管理
  4. 事务管理
  5. 实体管理
  6. 分离的实体

1. 类和接口

本文重点介绍 JPA javax.persistence 包,而不是 OpenJPA 实现包,因为您通常会对 JPA 标准 API 进行编码。

下表将通常使用的 Hibernate 类和 OpenJPA 中的等效类进行了比较。所有的 Hibernate 类都位于 org.hibernate 包中。所有的 JPA 接口(和 Persistence 类)都位于 javax.persistence 包中。JPA 接口的 OpenJPA 实现位于 org.apache.openjpa.* 包中。

org.hibernate javax.persistence 说明
cfg.Configuration Persistence 配置会话工厂(在 Hibernate 中)或实体管理器工厂(在 OpenJPA 中)的引导类。通常用于为 JVM 创建单一会话(或实体管理器)工厂。
SessionFactory EntityManagerFactory 提供 API 以打开 Hibernate 会话(或 OpenJPA 实体管理器),并处理用户请求。通常,每个处理客户机请求的线程都打开一个会话(或实体管理器)。
Session EntityManager 提供 API 以便在数据库之间存储和加载实体。它还提供 API 以获取事务和创建查询。
Transaction EntityTransaction 提供 API 以管理事务。
Query Query 提供 API 以执行查询。

2. 运行时配置

  • Hibernate 约定

    在 Hibernate 中,运行时配置按照以下方式进行映射:

    • 使用静态 SessionFactory 变量。
    • 使用 Configuration#configure() 方法。
    • 使用 Configuration#buildSessionFactory() 方法。


    清单 1. Hibernate 运行时配置
                            
    public class ORMHelper {
    
      private static SessionFactory sf;
    
      protected static synchronized 
      SessionFactory getSessionFactory(String name) {
        if (sf == null) {
          sf = new Configuration().configure(name).buildSessionFactory();
        }
        return sf;
      }
      ...
    }

    使用遗留 Hibernate 应用程序时,您通常会发现一个单一的静态 SessionFactory 实例,该实例由 JVM 中处理客户机请求的所有线程共享。Hibernate 还可以创建多个 SessionFactory 实例,但是实际很少这样做。

    可以通过多种方法在 Hibernate 中配置 SessionFactory。最常见的场景是调用 configure() 方法。如果没有向 configure() 传入名称,它将在类路径的根目录中查找 hibernate.cfg.xml。如果传入 XML 配置文件的名称,它将在类路径上查找该名称。

    找到 XML 配置文件后,buildSessionFactory() 方法将使用该配置文件中的元数据创建和初始化 SessionFactory。

    牢记以下事项:

    • 有些应用程序从 JNDI 注册表查找 SessionFactory,而不使用静态变量,但是在第一次查找时,您仍需要调用配置和 buildSessionFactory,因此几乎没有什么效果,而且静态变量是较常用的方法。
    • 您还可以使用 Configuration#setProperties() 方法以编程方式配置 Hibernate 配置参数,而不使用 configure() 方法从文件读取这些参数,但是,较好并且频繁使用的方法是外部化 Hibernate 属性。

与 Hibernate 一样,在创建实体管理器工厂时,OpenJPA 也可以传入额外属性,但是在 XML 文件中将属性外部化到命名的持久单元是较常用的方法。

  • OpenJPA 约定

    在 OpenJPA 中,等效运行时配置按照以下方式进行映射:

    • 使用静态 EntityManagerFactory 变量。
    • 使用 Persistence#createEntityManagerFactory()


    清单 2. OpenJPA 运行时配置
                            
    
    public class ORMHelper {
    
       private static EntityManagerFactory sf;
    
       protected static synchronized 
       EntityManagerFactory getSessionFactory(String name) { 
          if (sf == null) {
             sf = Persistence.createEntityManagerFactory(name);
          }
          return sf;
       }
       ...
    }

    与 Hibernate 一样,处理 JVM 中客户机请求的所有线程都可以使用静态 EntityManagerFactory 实例。如果需要多个实例,还可以定义静态映射。

    createEntityManagerFactory() 在类路径(该类路径包含的持久单元名称与方法调用中指定的名称相同)的 META-INF 文件夹中查找 persistence.xml。如果找到的 persistence.xml 使用的持久单元与给定名称匹配,则 createEntityManagerFactory() 使用该文件中的元数据配置 EntityManagerFactory 实例。如果没有找到具有匹配名称的 persistence.xml,则引发 javax.persistence.PersistenceException。

3. 会话管理

通常,应用程序收到客户机请求时,将从 SessionFactory 获取会话,并在请求结束时关闭会话,其中请求可以是 HttpRequest 或对无状态会话 Bean 的调用等。会话提供处理事务和从数据库加载实体(以及将实体存储到数据库)的方法。

Hibernate 应用程序通常管理该会话。为了达到目标,它们通常将会话与线程本地存储关联,这样无需将会话作为参数传递到需要访问它的所有方法;相反,它们可以从线程本地存储中检索它。Hibernate 3.0.1 还提供了 getCurrentSession(),但是您通常会找到显式会话管理。

就异常而言,Hibernate 3.0 会引发未经检查的异常或运行时异常(对于 OpenJPA 也一样),这意味着在方法签名中,大多数应用程序不会引发 Hibernate 异常;它们也不在自己的方法中捕获和处理 Hibernate 异常。当然,如果需要,仍可以捕获和处理它们。

您通常还会发现,在使用 Java SE 5 实现 OpenJPA 应用程序时,已使用 Java SE 1.4 实现了大多数现有遗留 Hibernate 应用程序。

下面的示例使用 getSessionFactory() helper 方法获取创建/打开会话(或实体管理器)所需的会话工厂(或实体管理器工厂)。(有关 getSessionFactory() 方法的详细信息,请参见运行时配置。)

  • Hibernate 约定

    在 Hibernate 中,会话管理按照以下方式进行映射:

    • 使用 ThreadLocal 获取当前会话。
    • 使用 SessionFactory#openSession() 打开会话。
    • 使用 Session#isOpen() and Session#close() 关闭会话。


    清单 3. Hibernate 会话管理
                            
    public class ORMHelper {
    
       private static final ThreadLocal tls = new ThreadLocal();
    
       public static void openSession() {
          Session s = (Session) tls.get();
          if (s == null) {
             s = getSessionFactory("test.cfg.xml").openSession();
             tls.set(s);
          }
       }
    
       public static Session getCurrentSession() {
          return (Session) tls.get();
       }
    
       public static void closeSession() {
          Session s = (Session)tls.get();
          tls.set(null);
          if (s != null && s.isOpen()) s.close();
       }
       ...
    }

  • OpenJPA 约定

    在 OpenJPA 中,等效的 EntityManager 管理按照以下方式进行映射:

    • 使用 ThreadLocal 获取当前实体管理器。
    • 使用 EntityManagerFactory#createEntityManager() 打开会话。
    • 使用 EntityManager#isOpen() 和 EntityManager#close() 关闭会话。


    清单 4. OpenJPA 会话管理
                            
    public class ORMHelper{
    
       private static final ThreadLocal tls = new ThreadLocal();
    
       public static void openSession() {
          EntityManager s = (EntityManager) tls.get();
          if (s == null) {
             s = getSessionFactory("test").createEntityManager();
             tls.set(s);
          }
       }  
    
       public static EntityManager getCurrentSession() {
          return (EntityManager) tls.get();
       }
    
       public static void closeSession() {
          EntityManager s = (EntityManager) tls.get();
          tls.set(null);
          if (s != null && s.isOpen()) s.close();
       }
       ...
    }

4. 事务管理

Hibernate 应用程序可以运行于使用不同事务策略的环境中。应用程序可以运行于使用本地 JDBC 或全局 Java Transaction API (JTA) 事务的环境中。

使用本地 JDBC 事务是最常见的场景。如果具有异类数据存储(如数据库和消息队列),则 JTA 事务非常有用;JTA 允许您将其视为单个事务。

Hibernate 应用程序通过调用事务 API 来管理自己的事务。您使用的事务策略(JDBC 或 JTA)是在 Hibernate 配置文件中设置的,所以它与应用程序无关。

  • Hibernate 约定

    在 Hibernate 中,事务管理按照以下方式进行映射:

    • 使用 Session#beginTransaction() 开始事务。
    • 使用 Transaction#commit() 提交事务。
    • 使用 Transaction#isActive 和 Transaction#rollback() 回滚事务。


    清单 5. Hibernate 事务管理
                            
    public class ORMHelper {
    
       private static final ThreadLocal tltx = new ThreadLocal();
    
       public static void beginTransaction() {
          Transaction tx = (Transaction) tltx.get();
          if (tx == null) {
             tx = getCurrentSession().beginTransaction();
             tltx.set(tx);
          }
       }
    
       public static void commitTransaction() {
          Transaction tx = (Transaction)tltx.get();
          if (tx != null && tx.isActive()) tx.commit();
          tltx.set(null);
       }
    
       public static void rollbackTransaction() {
          Transaction tx = (Transaction)tltx.get();
          tltx.set(null);
          if (tx != null && tx.isActive()) tx.rollback(); 
       }
       ...
    }

  • OpenJPA 约定

    在 OpenJPA 中,等效的事务管理按照以下方式进行映射:

    • 使用 EntityManager#getTransaction() 和 EntityTransaction#begin()。
    • 使用 EntityTransaction#commit()。
    • 使用 EntityTransaction#isActive() 和 EntityTransaction#rollback()。


    清单 6. OpenJPA 事务管理
                            
    public class ORMHelper {
    
       private static final ThreadLocal tltx = new ThreadLocal();
    
       public static void beginTransaction() {
          EntityTransaction tx = (EntityTransaction) tltx.get();
          if (tx == null) {
             tx = getCurrentSession().getTransaction();
             tx.begin();
             tltx.set(tx);
          }
       }
    
       public static void commitTransaction() {
          EntityTransaction tx = (EntityTransaction)tltx.get();
          if (tx != null && tx.isActive()) tx.commit();
          tltx.set(null);
       }
    
       public static void rollbackTransaction() {
          EntityTransaction tx = (EntityTransaction)tltx.get();
          tltx.set(null);
          if (tx != null && tx.isActive()) tx.rollback(); 
       }
       ...
    }

    尽管 OpenJPA 示例将事务存储在 ThreadLocal 中,但通常仅调用 getCurrentSession().getTransaction().begin(),并在以后调用 getCurrentSession().getTransaction().commit()。因此,使用 OpenJPA,实际上不需要将事务存储在 ThreadLocal 中。

5. 实体管理

实体管理的常见场景包括:

  • 创建持久对象。
  • 使用主键检索持久对象。
  • 更新持久对象。
  • 删除持久对象。

通常,这些场景会映射到单个用例操作,并使用分离的实体在独立的事务中执行,但是它们还可以与连接的实体一起使用。

  • Hibernate 约定

    在 Hibernate 中,实体管理按照以下方式进行映射:

    • 使用 Session#save 使临时对象持久化。
    • 使用 Session#load 检索持久对象。
    • 使用 Session#update 更新分离对象的持久状态。(如果修改持久(连接)对象的状态,则不需要调用更新;在调用 commit() 时,Hibernate 自动将更新传播到数据库。)
    • 使用 Session#delete 使分离或持久对象成为临时对象。


    清单 7. Hibernate 实体管理
                            
    public class ORMHelper {
       ...
       public static void create(Serializable obj) {
          getCurrentSession().save(obj);		    
       }
    
       public static Object retrieve(Class clz, Serializable key) {
          return getCurrentSession().load(clz, key);
       }
    	
       public static void update(Serializable obj ) {
          getCurrentSession().saveOrUpdate(obj);
       } 
    
       public static void delete(Serializable obj ) {
          getCurrentSession().delete(obj);
       }
    }

  • OpenJPA 约定

    在 OpenJPA 中,等效的实体管理按照以下方式进行映射:

    • 使用 EntityManager#persist 创建持久实体。
    • 使用 EntityManager#find 检索持久实体。
    • 使用 EntityManager#merge 更新分离实体。(如果使用持久(连接)实体,则无需调用 merge;OpenJPA 将更新传播到事务末尾的数据库。)
    • 使用 EntityManager#remove 删除分离的或持久实体。


    清单 8. OpenJPA 实体管理
                            
    public class ORMHelper {
       ...
       public static void create( Serializable obj ) {
          getCurrentSession().persist((Object) obj);		    
       }
    
       public static Object retrieve(Class clz, Serializable key) {
          return getCurrentSession().find( clz, (Object) key );
       }
    	
       public static Object update( Serializable obj ) {
          return getCurrentSession().merge((Object) obj);
       } 
    
       public static void delete( Serializable obj ) {
          getCurrentSession().remove( (Object) obj);
       } 
    }

    在本示例中,ORMHelper#update() 方法的签名已更改,因为 Hibernate update() 方法将新托管的持久状态复制到传递给该方法的分离或(临时)对象,所以它没有返回值。相反,在 OpenJPA merge() 方法中,原始的分离(临时)对象未更改,返回值包含新托管的持久状态。

    除更改 ORMHelper#update 签名外,还必须更改调用该签名的遗留应用程序,以便将返回值显式分配给原始的临时对象。

6. 分离的实体

基于分层体系结构的 Hibernate 应用程序中另一个常见场景是,将无状态会话 EJB 用作会话 facade 来将“分离”实体返回给 Web 层。在此场景中,您将发现会话 EJB 启动和停止事务以响应来自 Web 层的调用。

用于分层体系结构的这一模式的好处是,由于 EJB 层根据用户交互来启动和停止事务,因此在用户执行某项工作时,事务从不保持打开状态。因此,所有事务的生存时间很短,应在数秒中完成。

大多数现有 Hibernate 应用程序使用 EJB 2.1 实现会话 EJB,而大多数 OpenJPA 应用程序则使用 EJB 3.0。您最初应使用资源本地实体管理器迁移到 EJB 3.0 会话 Bean,这样不需要对事务逻辑进行更改,但您还可以从 EJB 2.1 会话 Bean 使用 OpenJPA(请参见 通过 WebSphere Application Server V6.1 利用 OpenJPA)。迁移完成后,应考虑使用 JTA 实体管理器将应用程序重构到 EJB 3.0。

对于分离实体,还应注意:如果在一个事务中检索对象,然后在事务外部修改该分离对象,则必须调用该对象的更新才能将其再保存到数据库。这是 Hibernate 和 OpenJPA 中常见的编程方法。类似地,如果您检索对象,并在同一事务中修改该对象,则无需调用该对象的更新,就可以将其保存到数据库;提交事务后,该对象会自动写入数据库。此方法也是 Hibernate 和 OpenJPA 的常见编程方法。

  • Hibernate 约定

    在 Hibernate 中,EJB 2.1 的分离实体按照以下方式进行映射:

    • 使用会话 facade 模式包装实体。
    • 将分离实体 (POJO) 返回到 Web 层。


    清单 9. EJB2.1 中的 Hibernate 分离实体
                            
    public class CustomerFacadeBean implements SessionBean, CustomerFacade{
    	
      public Customer createCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          ORMHelper.create(customerEntity);
          ORMHelper.commitTransaction();
          return customerEntity;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public Customer updateCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          ORMHelper.update(customerEntity);
          ORMHelper.commitTransaction();
          return customerEntity;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public Customer getCustomer( Long customerId ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          Customer customerEntity;
          customerEntity = ORMHelper.retrieve(Customer.class,customerId);
          ORMHelper.commitTransaction();
          return customerEntity;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public void deleteCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          ORMHelper.delete(customerEntity);
          ORMHelper.commitTransaction();
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          Throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
      ...   
    }

  • OpenJPA 约定

    在 OpenJPA 中,EJB 3.0 的分离实体按照以下方式进行映射:

    • 使用会话 facade 模式包装实体。
    • 将分离实体 (POJO) 返回到 Web 层。


    清单 10. EJB 3.0 中的 OpenJPA 分离实体
                            
    @Stateless 
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public class CustomerFacadeBean implements CustomerFacade {
    
      public Customer createCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          ORMHelper.create(customerEntity);
          ORMHelper.commitTransaction();
          return customerEntity;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public Customer updateCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          Customer returnValue;
          returnValue = ORMHelper.update(customerEntity);
          ORMHelper.commitTransaction();
          return returnValue;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public Customer getCustomer( Long customerId ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          Customer customerEntity;
          customerEntity = ORMHelper.retrieve(Customer.class,customerId);
          ORMHelper.commitTransaction();
          return customerEntity;
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
      public void deleteCustomer( Customer customerEntity ) {
        ORMHelper.openSession();
        try {
          ORMHelper.beginTransaction();
          ORMHelper.delete(customerEntity);
          ORMHelper.commitTransaction();
        } catch (RuntimeException ex) {
          ORMHelper.rollbackTransaction();
          throw ex;
        } finally {
          ORMHelper.closeSession();
        }
      }
    
    }

    清单 10 中演示了必须为迁移进行的更改。

    通过比较,EJB 3.0 SessionFacade 类不能实现 SessionBean 类,也不能实现 SessionBean 的回调方法(setSessionContext、ejbCreate、ejbRemove、ejbActivate 和 ejbPassivate)。此外,EJB 3.0 中也不需要组件接口、home 接口和部署描述符。在 EJB 2.1 部署描述符中指定的值包括在具有 Java 5 注释的 EJB 3.0 SessionFacade 类中。

    也就是说,对 SessionFacade 的业务方法没有任何更改,预期会在这里看到对 Hibernate 或 OpenJPA API 的大量调用。这是因为我们在 helper 类中封装了大多数 Hibernate/OpenJPA API,并且会话 bean 使用了 helper 类。当然,您的应用程序可能没有构造为在 helper 类中封装 Hibernate/OpenJPA API,但是,如果并行比较对前些部分中 helper 类进行的更改,应该能够确定对用于会话、事务和实体管理的 EJB 会话 bean 的更改是必需的。





回页首


迁移 Hibernate 对象关系映射

Hibernate 对象关系映射可以在启动时加载的 XML 映射文件集中定义。可以直接使用这些映射文件或从嵌入源代码的 javadoc 样式注释中生成。在 Hibernate 的较新版本中,还可以通过 Java 5 注释定义对象关系映射。

可以在 XML 映射文件集中定义 OpenJPA 对象关系映射,或者通过直接嵌入代码的 Java 5 注释定义它们,该对象关系映射完全不需要映射文件。

在开发中,大多数遗留 Hibernate 应用程序使用 XML 映射文件,而大多数 OpenJPA 应用程序使用 Java 5 注释,但在生产中则将它们移动到 XML,这样对映射的简单更改不需要您修改源代码和重新构建。

由于 XML 对 Hibernate 很常见,并且在 OpenJPA 中通常用于生产,所以在本部分中对映射使用 XML。为帮助了解对象模型 (POJO) 中哪些内容是必需的,哪些不是必需的,还包括了相应的基础代码(不包括注释)。

如果遗留 Hibernate 应用程序不使用映射文件(例如,使用 javadoc 样式的注释或 Java 5 注释),则您仍应能够基于本部分中的信息得出更改,并将应用程序迁移到 OpenJPA。另一方面,如果希望将 Java 5 注释与 OpenJPA 一起使用,附录提供了这样的示例。

还可以使用其他方法在 Java 对象和关系表之间进行映射。本部分将介绍在企业应用程序中出现的通用场景,其中包括:

  1. 继承
  2. 关系
  3. 延迟初始化
  4. 对象标识
  5. 乐观锁定

1. 继承

企业应用程序的数据模型通常有多个位置,类之间的一般化/专业化在这里提供重要的重用机会。Hibernate 和 OpenJPA 都支持可以在关系表中建模继承的三种不同方法。我们将讨论其中的两项,我们认为这两项是最常见的场景:

  1. 单个表继承
  2. 连接继承

第三项(每个具体的类一个表)由 Hibernate 提供,但通常很少用,它是 JPA 持久性提供程序(如 OpenJPA)的可选实现。

a. 单个表继承

对于 Java 基础类包含其所有子类的大多数属性的情况,可以使用单个表映射继承,该表中的一列值标识特定的子类,行所表示的实例属于此类。如果没有任何列映射到特定的子类,则这些列必须声明为可以为空,因为它们在该子类的数据库行中将为空。

此继承策略的缺点是如果子类为该实例定义多个非空属性,则非空约束的丢失会带来数据完整性问题。此方法的主要优点是,它为类层次范围的实体和查询之间的多态关系提供最佳支持,因为不存在复杂的连接。

  • 对象模型

    映射 1. 单个表继承 (POJO)
                            
    // Participant (base) class
    public class Participant implements Serializable{ 
        private Long participantId;
        ... 
    }
    
    // SalesRep (sub) class
    public class SalesRep extends Participant { ... }
    
    // Administrator (sub) class
    public class Administrator extends Participant { ... }

  • Hibernate 约定

    在 Hibernate 中,单个表继承按照以下方式进行映射:

    • 在基础类中,将类与映射到表的辨别器列一起使用;还要为主键和其他属性定义映射(稍后进行介绍,不在示例中显示)。
    • 将子类与子类中独特的辨别器值一起使用;还要为子类独有的属性定义映射。您不能在子类中定义 ID 元素;它们没有自已的表,因此使用(基础)类的 ID。


    映射 2. 单个表继承(Hibernate XML 映射)
                            
    <!-- Participant (base) class -->
    <class name="Participant" table="T_PRTCPNT" >
       <id name="participantId" column="PRTCPNT_TID"/>
       <discriminator column="PRTCPNT_TYPE" type="string"/>
       ...
    </class>
    
    <!-- SalesRep subclass -->
    <subclass 
      name="SalesRep"
      extends="Participant" 
      discriminator-value="SALES_REP">
      ...
    </subclass>
    
    <!-- Administrator subclass -->
    <subclass name="Administrator"
       extends="Participant" 
       discriminator-value="ADMIN">
       ...
    </subclass>

  • OpenJPA 约定

    在 OpenJPA 中,单个表继承按照以下方式进行映射:

    • 在基础类中使用 SINGLE_TABLE 继承策略和辨别器列;还要定义基础类的持久属性及其唯一的 ID。
    • 使用子类中的辨别器值表示其实例;还要定义子类的持久属性,而不是 ID。子类不会有任何表;它们的属性将提升到表示基础类的表。


    映射 3. 单个表继承(OpenJPA XML 映射)
                            
    <entity class="Participant">
       <table name="T_PRTCPNT"/>
       <inheritance strategy="SINGLE_TABLE"/>
       <discriminator-column name="PRTCPNT_CLASS"/>
    
       <attributes>
          <id name="participantId">
             <column name="PRTCPNT_TID"/>
          </id>
          ...
       </attributes>
    </entity>
    
    // SalesRep subclass
    <entity class="SalesRep">
       <discriminator-value>SALES_REP</discriminator-value>
       ...
    </entity>
    
    
    // Administrator subclass
    <entity class="Administrator">
       <discriminator-value>ADMIN</discriminator-value>
       ...
    </entity>

b. 连接继承

对于基础类不包含所有子类的大多数属性的情况,对于每个子类,将一个包含基础类属性的表与一个单独的连接表一起使用。对于非继承属性,该表仅包含列。因此,阅读子类实例需要跨基础类表和子类表进行连接。

连接继承策略的优点是您可以在子类中定义非空属性,而对应的缺点是需要多个连接才能构造实例。此方法也是最灵活的的方法,即您可以定义新的子类,并将属性添加到现有子类,而无需修改基础类表。

  • 对象模型

    映射 4. 连接继承 (POJO)
                            
    // Participant (base) class
    public class Participant implements Serializable { 
        private Long participantId;
        ... 
    }
    
    // SalesRep (sub) class
    public class SalesRep extends Participant { 
        ... 
    }
    
    // Administrator (sub) class
    public class Administrator extends Participant { 
        ... 
    }

  • Hibernate 约定

    在 Hibernate 中,连接继承按照以下方式进行映射:

    • 在基础类中,将类元素与主键 (id) 一起使用;还要为这些属性定义映射,以建立基础类。
    • 在子类中,将连接子类与包含基础类主键的外键列一起使用;还要在连接子类中定义其他属性的映射(本例中不显示)。


    映射 5. 连接继承(Hibernate XML 映射)
                            
    <!-- Participant (base) class -->
    <class name="Participant" table="T_PRTCPNT" >
       <id name="participantId" column="PRTCPNT_TID"/>
       ...
    </class>
    
    <!-- SalesRep joined-subclass -->
    <joined-subclass 
       name="SalesRep" 
       extends="Participant" 
       table="T_SALESREP">
       <key column="PRTCPNT_TID"/>
       ...
    </joined-subclass>
    
    <!-- Administrator joined-subclass -->
    <joined-subclass 
       name="Administrator" 
       extends="Participant" 
       table="T_ADMIN">
       <key column="PRTCPNT_TID"/>
       ...
    </joined-subclass>

  • OpenJPA 约定

    在 OpenJPA 中,连接继承按照以下方式进行映射:

    • 在基础类中,使用 JOINED 继承策略。基础类还定义所有连接子类使用的主键,并可以选择定义版本列。还要定义基础类属性的映射。
    • 在子类中,定义子类的持久属性;其表将包含这些属性,并包含用作外键的主键列,以连接基础类的主键。子类不定义版本列。


    映射 6. 连接继承(OpenJPA XML 映射)
                            
    <!-- Participant (base) class -->
    <entity class="Participant">
       <table name="T_PRTCPNT"/>
       <inheritance strategy="JOINED"/>
       <attributes>
          <id name="participantId">
             <column name="PRTCPNT_TID"/>
          </id>
          ...
       </attributes
    </entity>
    
    
    <!-- SalesRep subclass -->
    <entity class="SalesRep">
       <table name="T_SALESREP"/>
       <primary-key-join-column name="PRTCPNT_TID"/>
       ...
    </entity>
    
    <!—Administrator subclass -->
    <entity class="Administrator">
       <table name="T_ADMIN"/>
       <primary-key-join-column name="PRTCPNT_TID"/>
       ...
    </entity>

2. 关系

对象模型中的对象之间(和数据模型中的表之间)需要多种关系。当数据模型在类似数据类的任何列之间未指定关系时,对象模型必须明确对象之间的关系以支持遍历。此外,数据模型中的关系没有任何固有方向(尽管按一个方向搜索可以比另一个方向更有效)。而对象模型关系固有包含从一个对象到另一个对象的方向。

对象模型关系是在数据模型中实现的,方法是通过一个表中的外键引用另一个表中的主键。具有外键的表称为子对象。其行表示对象,该对象的生命周期依赖于他们引用的对象。因此,其表将包含父对象的外键。由于子对象具有外键,所以它必须始终指向有效的父对象;它不能是孤立的,这就意味着要删除父对象,必须首先删除其子对象或从父对象到子对象执行级联删除。

为了维护关系,子对象也称为关系的所有者,而父对象称为非所有者。“所有者”这一概念非常重要,因为尽管 Java 程序员必须在两边设置双向关系,但是数据库只需更新一个值来反映这些更改;在子对象(或所有者)中该更新针对外键。因此,在子对象中,对表示外键的属性的更改会传播到数据库,而对父对象中反向属性的更改不会传播到数据库。

关系映射分为四个类别:

  1. 一对一
  2. 多对一
  3. 一对多
  4. 多对多

a. 一对一关系

一对一关系定义到另一个持久对象的引用,其中子对象的生命周期完全依赖于父对象的生命周期。

在对象模型中使用组件对象建模一对一关系仍将导致两个独立的类,但在数据模型中不会有两个独立的表。

一对一关系有异常情况。如果发生异常,Hibernate 中的常见做法是将其建模为组件对象,这样子表中的所有属性都将展开到父表中,因此在访问子表的属性时,无需连接父表和子表。

还可以使用两个其他方法在 Hibernate 中建模一对一关系:

  • 将多对一元素与表之间的外键关联一起使用;请按照多对一关系指导原则进行操作。
  • 将一对一元素与表之间的主键关联一起使用;使用 OpenJPA 中的一对一元素进行映射。

本部分的其余内容将介绍在 Hibernate 中使用组件元素迁移一对一关系,并介绍如何将其迁移到 OpenJPA 中的嵌入对象。

  • 对象模型


    映射 7. 一对一关系 (POJO)
                            
    // Employee (parent) class
    public class Employee implements Serializable {
        private Long employeeId;
        private EmployeeRecord employeeRec;
        ...
    }
    
    // EmployeeRecord (child) class
    public class EmployeeRecord implements Serializable { ... }

  • Hibernate 约定

    在 Hibernate 中,使用组件对象的一对一关系按照以下方式进行映射:

    • 使用类建模父类;在父类中,还要定义主键 (id) 和父类的其他属性。
    • 使用嵌套组件元素建模子类;还可以在嵌套组件中定义其他属性(如果需要)。


    映射 8. 一对一关系和组件对象(Hibernate XML 映射)
                            
    <!—Employee (parent) class
    <class name="Employee" table="T_EMPLOYEE">
        ...
        <id name="employeeId" column="EMP_TID"/>
    
        <!-- EmployeeRecord (child) class
        <component name="employeeRec" class="EmployeeRecord">
            ...
        </component>
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,使用可嵌入对象的一对一关系按照以下方式进行映射:

    • 在父实体中声明嵌入字段(例如 Employee)。将嵌入字段映射为父实体的数据库记录的一部分,并嵌入父实体,而不是形成与子实体的关系。
    • 将子实体(例如 EmployeeRecord)定义为可嵌入实体。


    映射 9. 一对一关系和可嵌入对象(OpenJPA XML 映射)
                            
    <!-- Employee (parent) class -->
    <entity class="Employee">
        <table name="T_EMPLOYEE"/>
       <attributes>
          <id name="employeeId">
             <column name="EMP_TID"/>
          </id>
          <embedded name="employeeRec"/>
        ...
        </attributes>
    </entity>
    
    <!-- EmployeeRecord (child) class -->
    <embeddable class="EmployeeRecord">
       ...
    </embeddable>

b. 多对一关系

多对一关系定义到单个持久对象的引用。尽管多对一关系可以是单向的,但是经常将其定义为一对多双向关系的反向过程。

声明多对一关系的实体是子对象(或关系的所有者),因为其表包含外键,而声明多对一关系的实体引用的对象是父对象。由于其表不包含外键,所以它是非所有者或关系的反向。

  • 对象模型


    映射 10. 多对一关系 (POJO)
                            
    // Address (parent) class
    
    public class Address implements Serializable {
        private Long addressId;
      ...
    }
    
    // Phone (child) class
    public class Phone implements Serializable {
        private Address address;
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,多对一关系按照以下方式进行映射:

    • 在子类中使用多对一元素。
    • 在父类中定义主键。


    映射 11. 多对一关系(Hibernate XML 映射)
                            
    <!-- Phone (child) class -->
    <class name="Phone" table="T_PHONE">
        <many-to-one 
            name="address" 
            class="Address" 
            column="ADDR_TID">
        </many-to-one>
        ...
    </class>
    
    <!-- Address (parent) class -->
    <class name="Address" table="T_ADDRESS">
        <id name="addressId" column="ADDR_TID"/>
        ...
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,多对一关系按照以下方式进行映射:

    • 在父实体中定义主键 (id)。
    • 在子实体中使用多对一元素定义关系,并使用嵌套连接列元素定义外键。连接列指定如何按照连接的方法找到此子实体的父实体。


    映射 12. 多对一关系(OpenJPA XML 映射)
                            
    <!-- Address (parent) class -->
    <entity class="Address">
       <table name="T_ADDRESS"/>
       <attributes>
          <id name="addressId">
             <column name="ADDR_TID"/>
          </id>
          ...
       </attributes>
    </entity>
    
    <!-- Phone (child) class -->
    <entity class="Child">
       <table name="T_PHONE"/>
       <attributes>
          <many-to-one name="address">
             <join-column name="ADDR_TID"/>
          </many-to-one>
          ...
       </attributes>
    </entity>

c. 一对多关系

一对多关系定义到对象集合的引用。由于用例通常需要从父对象到子对象的遍历,而可能需要(也可能不需要)从子对象到父对象的遍历,所以一对多关系是对象模型中最常见的关系类型;这意味着单向一对多关系可以满足大多数情况。

也就是说,如果用例需要从子对象到父对象的遍历,则可以在子类中方便地添加多对一关系,使之成为双向关系。

声明一对多关系的实体是父对象(并且是非所有者)。此实体的表定义主键,但是它没有外键(外键在子对象中)。

此一对多关系引用的对象是子对象和关系的所有者。子对象具有外键,并引用父对象的主键。

在 Hibernate 中,一对多关系的映射通常是通过将列添加到外键的子表完成的,但映射的详细内容是不同的,具体取决于是单向一对多关系,还是双向一对多关系。

在单向关系中,子表中的外键列不会映射到子对象中的属性,它在数据模型中,而不是在对象模型中。由于是单向的,所以仅在父对象中有属性,而子对象中没有。此外,必须将外键列定义为可以为空,因为 Hibernate 将首先插入子行(使用 NULL 外键),并在以后更新它。

在双向关系中,对象关系映射比较好,因为子对象中有一个用于外键列的属性,在数据库中该列不必为空。但是,结果对象模型在对象之间有循环依赖关系和更紧密的耦合关系,并需要其他编程来设置关系的两端。

可以看出,关于一对多关系的定义,有多个要考虑的权衡因素,但是通常建议使用单向关系,除非用例指示需要用两个方向导航。

  • 对象模型


    映射 13. 一对多关系 (POJO)
                            
    // Address (parent) entity
    public class Address implements Serializable {
        private Long addressId;
        private Set phones = new HashSet();
        ... 
    }        
    
    // Phone (child) entity
    public class Phone implements Serializable {
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,一对多(单向)关系按照以下方式进行映射:

    • 在父类中,将设置、包或列表与一对多子元素一起使用。
    • 如果关系是单向的,则在表示子类的表中创建外键;否则,使用双向关系的多对一关系


    映射 14. 一对多关系(Hibernate XML 映射)
                            
    <!-- Address (parent) class -->
    <class name="Address" table="T_ADDRESS">
       <id name="addressId" column="ADDR_TID"/>
    
       <set
          name="phones"                 
          table="T_PHONE"
          cascade="all-delete-orphan">
          <key column="ADDR_TID"/>
          <one-to-many class="Phone">
       </set>
    
    </class>
    
    <!-- Phone (child) class -->
    <class name="Phone" table="T_PHONE">
       ...
    </class>

    务必注意 Hibernate 中的专有 cascade="all-delete-orphan" 功能(请参见映射 14)。使用此属性使您能够从数据库删除子对象,方法是直接从父集合中移除它,然后将父对象保存到数据库。使用此专有功能,不在代码中显式删除子对象。尽管在标准 JPA 中没有任何等效功能,但 OpenJPA 中的专有 @ElementDependent 注释提供了自动孤立项清除功能,但是此功能不可移植到其他持久性提供程序,并且在取读代码时可导致混淆。接下来将详细讨论 all-delete-orphan 功能。

  • OpenJPA 约定

    在 OpenJPA 中,您无法使用外键映射一对多单向关系,您必须使用连接表映射它。也就是说,如果关系是双向的,则可以使用外键映射。这样,有两个选项可以从 Hibernate 映射一对多单向关系:

    1. 使用连接表映射并将连接表添加到数据库,或
    2. 使用外键映射并将关系从单向转换为双向,将反向属性添加到对象模型,并更改代码,以便将关系设置为两个方向。

    建议将单向关系转换为双向关系,因为与改变现有数据库模式和关联的行相比,此方法通常更容易修改遗留代码。

    在 OpenJPA 中,一对多(双向)关系按照以下方式进行映射:

    • 在父对象中,使用一对多元素定义子对象的集合,并使用 id 元素为父对象定义主键。
    • 在子类中,使用多对一元素定义父对象,并使用嵌套连接列元素定义子对象中的外键,并指定如何连接父对象和子对象。


    映射 15 一对多关系(OpenJPA XML 映射)
                            
    <!-- Address (parent) class -->
    <entity class="Address">
       <table name="T_ADDRESS"/>
       <attributes>
          <id name="addressId">
             <column name="ADDR_TID"/>
          </id>
          <one-to-many name="phones" mapped-by="address">
             <cascade>
                <cascade-all/>
             </cascade>
          </one-to-many>
          ...
       </attributes>
    </entity>
    
    <!-- Phone (child) class -->
    <entity class="Phone">
       <table name="T_PHONE"/>
       <attributes>
          <many-to-one name="address">
             <cascade>
                <cascade-all/>
             </cascade>
             <join-column name="ADDR_TID"/>
          </many-to-one>
          ...
       </attributes>
    </entity>

在定义一对多关系时,通常使用一些其他功能,您需要知道如何迁移这些功能:

  • Hibernate

    • inverse="true"
      在 Hibernate 中,您可能会遇到在定义双向关系时使用的 inverse="true" 属性。如果是这样,请不要担心,因为此功能等效于 OpenJPA 指定的关系的非所有者(它的表没有外键)。类似地,Hibernate inverse="false" 属性等效于 OpenJPA 的关系的所有者(它的表有外键)。

      通常,这些概念是一致的,但使用 inverse="true" 设置在双向关系的多对一端定义 Hibernate 映射这一情况除外。如果发现此类映射,则应在执行迁移之前更改它,因为它在 Hibernate 中不是最佳实践,并且不能生成最佳的 SQL。例如,如果将多对一端设置为 inverse="true",则每次创建子对象时,Hibernate 将执行两个 SQL 语句,一个创建子对象,一个使用父对象的外键更新子对象。在多对一端将其更改为 inverse="false" 并在一对多端设置 inverse="true" 可以解决这一问题,并使它与 OpenJPA 保持一致。当然,进行该更改后,您应重新测试应用程序。

    • cascade="all-delete-orphan"
      JPA 规范中没有与此 Hibernate 功能等效的功能,但是 OpenJPA 持久性提供程序中的专有 @ElementDependent 注释完全可以执行 Hibernate 的 all-delete-orphan 功能执行的任务。如果需要使用该功能,请参阅 OpenJPA 用户指南。尽管它是专有功能,但是,如果使用它迁移 all-delete-orphan 特性,则不需要对应用程序源代码进行任何更改。

      若要以符合标准的方式迁移 all-delete-orphan 功能,您可以使用 OneToMany 注释中的 cascade=CascadeType.ALL 属性,并更改源代码,不仅从父集合中移除子对象,而且从数据库中显式删除子对象;例如,不需要类似于以下的代码:

      public class Address implements Serializable {
         ...
         public void removePhone(Phone p) {
            this.phones.remove(p); // Explicitly remove p from the set; which
                                   // Implicitly deletes p from the database
         }	
      }

      您应使用类似于以下的代码替换它:

      public class Address implements Serializable {
         ...
         public void removePhone(Phone p) {
            // Explicitly remove p from the set 
            this.phones.remove(p); 
            
            // Explicitly delete p from the database
            ORMHelper.getCurrentSession().delete(p); 
         } 
      }

  • OpenJPA

    • cascade=CascadeType.ALL
      尽管上面的 Hibernate 示例仅指定了从父对象到子对象的级联操作,但是在使用 OpenJPA 持久性提供程序测试此映射时,我们必须将 cascade=CascadeType.ALL 定义为两个方向,并在 OneToMany 注释和 ManyToOne 注释中指明。通常,您不需要从子对象到其父对象的级联操作(在 ManyToOne 注释中),但是这对于使从父对象到子对象的 merge() 级联操作能够工作是必需的。

      如果不按两个方向定义级联操作,则 remove() 和 persist() 的级联操作将从父 Address 对象到其依赖的 Phone 对象进行,但是 merge() 的级联操作会引发异常,指示依赖对象中的 Phone.address 字段不允许级联。

      (这是当前正在使用的 OpenJPA v0.9.7 中的已知问题,在以后的版本中会修复该问题。与此同时,解决办法是按两个方向启用级联。)

d. 多对多关系

多对多关系通过映射表定义到对象集合的引用。在对象模型中,多对多关系并不是全部通用的,但是它们通常是双向的。

与其他双向关系一样,存在所属端和非所属端。在这种情况下,所属端拥有映射表,而不是外键。可以将任意一端指定为所属端;选取哪一端并不重要。

  • 对象模型


    映射 16. 多对多关系 (POJO)
                            
    // Group (non-owner/parent) entity
    public class Group implements Serializable {
        private Long groupId;
        private Set users = new HashSet();
        ...
    }
    
    // User (owner/child) entity
    public class User implements Serializable {
        private Long userId;
        private Set groups = new HashSet();
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,多对多关系按照以下方式进行映射:

    • 非所有者将集合(设置、包或列表)元素与 inverse="true" 属性、表属性、键子元素和多对多子元素一起使用。
    • 关系所有者将集合(设置、包或列表)元素与表属性、键子元素和多对多子元素一起使用。


    映射 17. 多对多关系(Hibernate XML 映射)
                            
    <!—Group (non-owner/parent) class -->
    <class name="Group" table="T_GROUP">
        <id name="groupId" column="GROUP_TID"/>
        <set name="users" table="T_USER_GROUP" inverse="true">
            <key column="GROUP_TID"/>
            <many-to-many column="USER_TID" class="User"/>
        </set>
    </class>
    
    <!—User (owner/child) class -->
    <class name="User" table="T_USER">
        <id name="userId" column="USER_TID"/>
        <set name="groups" table="T_USER_GROUP">
            <key column="USER_TID"/>
            <many-to-many column="GROUP_TID" class="Group"/>
        </set>
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,多对多关系按照以下方式进行映射:

    • 在所有者/子对象中,使用多对多元素和嵌套连接表元素指定如何创建关系。您还将 id 与嵌套列元素一起使用,以指定连接表引用的主键名称。
    • 在非所有者/父对象中,将多对多元素与 mapped-by 属性一起使用,以引用声明连接表的类中的属性。(连接表包含创建关系的所有信息。您还需要通过 id 元素创建主键。)


    映射 18. 多对多关系(OpenJPA XML 映射)
                            
    <!-- User (owner/child) class -->
    <entity class="User">
       <table name="T_USER"/>
       <attributes>
          <id name="userId">
             <column name="USER_TID"/>
          </id>
          <many-to-many name="groups">
             <join-table name="T_USER_GROUP">
                <join-column name="USER_TID"/>
                <inverse-join-column name="GROUP_TID"/>
             </join-table>
          </many-to-many>
          ...
       </attributes>
    </entity>
    
    <!-- Group (non-owner/parent) class -->
    <entity class="Group">
       <table name="T_GROUP"/>
       <attributes>
          <id name="groupId">
             <column name="GROUP_TID"/>
          </id>
          <many-to-many name="users" mapped-by="groups"/>
          ...
       </attributes>
    </entity>

3. 延迟初始化

可以将延迟初始化应用到任何字段,但是它经常与一对多或多对多关系一起使用,在读取父对象时,您还可以控制是否需要数据库返回所有子对象。

为进一步阐述此概念,请考虑使用从 A 到 B 的一对多或多对多关系。如果在该关系中将 fetch 设置为 LAZY,则从数据库读取 A 将不能从该数据库读取 B,直到代码尝试遍历从 A 到 B 的关系。另一方面,如果将 fetch 设置为 EAGER,则从数据库读取 A 也可以从该数据库读取依赖的 B 对象。

在使用 LAZY 或 EAGER 的 fetch 定义关系时,请务必小心,尤其是在按照一般模式,EJB 层将分离实体返回到 Web 层并且 Web 层访问该数据以呈现视图时更要小心。使用 LAZY 加载,Web 层可以没有呈现视图必需的依赖对象。另一方面,您不能使用 EAGER 直接加载所有关系,因为每次返回的信息比必要信息多。

原因在于您必须使用满足业务需求的适当获取策略定制域模型。如果用例需要父对象和子对象,则使用 EAGER 加载。如果用例不需要子对象,则使用 LAZY 加载。

  • 对象模型


    映射 19. 延迟初始化 (POJO)
                            
    // Address (parent) entity
    
    public class Address implements Serializable {
        private Long addressId;
        private Set phones = new HashSet();
        ... 
    }        
    
    // Phone (child) entity
    
    public class Phone implements Serializable {
        private Address address;
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,缺省值是用于集合的延迟初始化,它实现一对多或多对多关系。要禁用延迟(或启用 eager)初始化,请按照以下方式进行映射:

    • 在父对象中,将集合(设置、包或列表)与 lazy=false 属性一起使用。
    • 在子对象中,将类与 lazy=false 属性一起使用以启用获取。


    映射 20. 延迟初始化(Hibernate XML 映射)
                            
    <!-- Address (parent) class -->
    <class name="Address" table="T_ADDRESS">
       ...
       <id name="addressId" column="ADDR_TID"/>
    
       <set
            name="phones"                 
            table="T_PHONE"
            cascade="all-delete-orphan"
            inverse="true"
            lazy="false">
            <key column="ADDR_TID"/>
            <one-to-many class="Phone"/>
       </set>
    
    </class>
    
    <!-- Phone (child) class -->
    <class name="Phone" table="T_PHONE" lazy="false">
       ...
       <many-to-one
          name="address"
          class="Address"
          column="ADDR_TID">
       </many-to-one>
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,一对多和多对一关系的延迟初始化也是缺省值。要禁用延迟初始化(和启用 eager 初始化),请按照以下方式进行映射:

    • 在父实体的集合中使用 fetch=FetchType.EAGER 属性。


    映射 21. 延迟初始化(OpenJPA XML 映射)
                            
    <!-- Address (parent) class -->
    <entity class="Address">
       <table name="T_ADDRESS"/>
       <attributes>
          <id name="addressId">
             <column name="ADDR_TID"/>
          </id>
          <one-to-many name="phones" mapped-by="address" fetch="EAGER">
             <cascade>
                <cascade-all/>
             </cascade>
          </one-to-many>
          ...
       </attributes>
    </entity>
    
    <!-- Phone (child) class -->
    <entity class="Phone">
       <table name="T_PHONE"/>
       <attributes>
          <many-to-one name="address">
             <cascade>
                <cascade-all/>
             </cascade>
             <join-column name="ADDR_TID"/>
          </many-to-one>
          ...
       </attributes>
    </entity>

还值得一提的是,Hibernate 和 OpenJPA 在从分离对象访问延迟加载的集合上是不同的。在 Hibernate 中,如果程序员尝试访问分离对象上延迟加载的集合,则会引发异常;而 OpenJPA 将返回空值,而不是异常。

此差异的原因是 JPA 规范没有指定如何处理在分离对象上访问延迟加载的集合。每个 JPA 供应商可以决定如何处理此条件。它们会引发异常,或者将其保留为未初始化状态,甚至返回具有零个元素的集合。

因此,如果遗留 Hibernate 应用程序正在使用异常检测对分离对象的延迟加载集合的访问,您可以使用 OpenJPA 通过测试空集合执行相同的操作。不过,需要记住的是:JPA 规范没有说明是引发异常还是返回空值,因此依赖于此行为不可移植,并且随时会更改,甚至可能在以后版本中中断您的应用程序。

此外,还要务必注意您是获取异常(在 Hibernate 中获取),还是不获取异常(在 OpenJPA 中不获取),异常可以指示在测试过程中需要检测和修复的应用程序错误。也就是说,问题在于您是否获取异常,所以在 JPA 规范中强制引发异常与否不能解决问题:为了使用分离对象,应用程序需要某些体系结构指导原则。

有以下三个解决方案使用分离对象:

  1. 在返回视图所需的所有集合之前,请显式初始化它们;即您应确定延迟加载是否适用于这些特定的关系。
  2. 保持实体管理器处于打开状态,直到完成呈现视图(即在视图中打开和关闭实体管理器),这可以全部避免使用分离实体。
  3. 与第二个解决方案类似,EJB 3.0 的第三个可能的解决方案使用扩展的持久上下文,以保持实体管理器在事务之间处于活动状态。

如果将实体从 EJB 组件传递到 Web 层,则应遵循第一个解决方案,因为它使您能够保持 EJB(会话 facade)中的事务逻辑,而不是需要 Web 层管理该事务。此外,Web 层难于管理事务,因为它需要提交事务,并在呈现视图后关闭实体管理器(可能在 ServletFilter 中)。

关于分离对象的最后一点是您应该让对象模型符合业务用例。如果某些用例需要集合的延迟加载,而其他用例需要该集合的 eager 加载,则在对象模型中使用集合的延迟加载,并根据需要在会话 facade 中强制执行 eager 加载。即,在会话 facade 中为不同的用例定义不同的方法:一种方法仅返回分离对象,而另一种方法加载子对象,然后返回分离的对象。共有三种方法可加载 OpenJPA 中的子对象;前两种方法为 JPA 标准,最后一种方法为 OpenJPA 扩展:

  1. 通过调用集合上的 size(),进行集合的 Trigger 加载。
  2. 将 JP-QL 与 Fetch Join 功能一起使用,临时覆盖 Lazy fetch 类型。
  3. 使用 OpenJPA FetchGroups 注释加载子对象。

4. 对象标识

数据模型中的所有表与表的主键一样包括称为 OID 的对象标识列。OID 的最佳实践是使用其值由系统分配的整数列。在这种情况下,OID 使用 java.lang.Long 映射到对象模型的主键。同时,表(外键)之间的所有关系也都基于关系表的 OID 列。

在使用通常的 Hibernate Sequence 生成器类插入任何新行时,Hibernate 会分配 ID,它使用基础数据库支持序列,其中每个表都有一行包含表名称和当前 OID 号。在此方法中,OID 对每个表都是唯一的(OID 在表之间可能不是唯一的)。

使用 OID 为没有业务含义的所有数据提供键。这些系统生成的键没有任何业务含义,被称为人工键,而不是具有业务含义的自然键。使用人工键可以更改数据库中任何具有业务含义的数据,而不用担心违反约束。

在现有 Hibernate 模型中,您通常还会发现大多数表具有可以包括许多列的具有业务含义的标识。您可能还会发现在数据模型中的任何此类候选主键上定义了唯一约束。此数据库约束可确保唯一性,并且还基于业务含义为查找此类实体提供索引。

您可能会遇到某些特殊情况,其中数据必须预填充到某些表,以表示没有任何业务含义的数据行(对于代码表和类似内容)。为此,在启动应用程序之前,必须将这些行插入到数据库,这些行的 OID 必须小于用于该表的 SEQUENCE 表的起始值。在这种情况下,您可能会发现初始值大于 1 的序列表。

  • 对象模型


    映射 22. 对象标识 (POJO)
                            
    / Address entity
    
    public class Address implements Serializable {
        private Long addressId;
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,对象标识按照以下方式进行映射:

    • 将 id 与嵌套生成器子元素一起使用。


    映射 23. 对象标识(Hibernate XML 映射)
                            
    <class name="Address">
        ...
        <id name="addressId" column="ADDR_TID">
            <generator class="sequence">
                <param name="sequence">T_ADDRESS_SEQ</param>
            </generator>   
        </id>
       ...
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,同一对象标识按照以下方式进行映射:

    • 使用 Id、SequenceGenerator 和 GeneratedValue 注释。


    映射 24. 对象标识(OpenJPA XML 映射)
                            
    /<!-- Address entity -->
    <entity class="Address">
      <sequence-generator name="AddressSeq" sequence-name="T_ADDRESS_SEQ"/>
      <attributes>
        <id name="addressId">
          <column name="ADDR_TID"/>
          <generated-value strategy="SEQUENCE" generator="AddressSeq"/>
        </id>
        ...
      </attributes>

对于 Hibernate 和 OpenJPA,OID 的现有序列表的 DDL 为:

create sequence T_ADDRESS_SEQ;

关于对象标识符的最后一点说明:数据模型中的所有外键通常基于关系表的 OID 主键。所有外键在引用主键的数据模型中都具有约束,并定义了 ON DELETE RESTRICT 语义,这样,如果仍然存在该父对象的子对象的行,则数据库可以防止您从该数据库删除行(例如,您无法删除父对象)。

5. 乐观锁定

Hibernate 为并发控制提供乐观和悲观锁定模式。为获得短时间的事务和较好的性能,大多数遗留 Hibernate 应用程序使用乐观锁定。当用户编辑屏幕上的数据时,短时间的事务会释放锁定。乐观锁定会检查对象的时间戳或版本,以确保当用户编辑数据时,没有更改该对象。如果更改,则引发乐观锁定异常,通知用户已发生异常,并且用户可以刷新导致异常的数据,并重试事务,以便下一次正常运行。通知用户乐观锁定异常通常通过应用程序的常规异常处理发生。

  • 对象模型

    并发性会导致问题,所有实体将包含版本属性,持久性提供程序使用此属性检测同一记录的并发修改。遗留应用程序只是将版本属性视为不可改变,并且在从数据库读取实体时,负责将该版本属性保存在实体中,直到将实体写回数据库。



    映射 25. 对象标识 (POJO)
                            
    // Address entity
    
    public class Address implements Serializable {
       private Long version;
        ...
    }

  • Hibernate 约定

    在 Hibernate 中,乐观锁定按照以下方式进行映射:

    • 使用版本元素定义版本属性。


    映射 26. 乐观锁定(Hibernate XML 映射)
                            
    <!-- Address class -->
    <class name="Address" table="T_ADDRESS">
        ...
        <version name="version" column="VERSION"/>
       ...
    </class>

  • OpenJPA 约定

    在 OpenJPA 中,乐观锁定按照以下方式进行映射:

    • 使用版本元素定义版本属性。


    映射 27. 乐观锁定(OpenJPA XML 映射)
                            
    <!-- Address class -->
    <entity class="Address">
       <table name="T_PHONE"/>
       <attributes>
          ...
          <version name="version">
             <column name="VERSION"/>
          </version>
          ...
       </attributes>
    </class>

    Hibernate 支持乐观和悲观锁定,但是 JPA 规范仅定义乐观锁定的概念。不过,OpenJPA 支持乐观锁定,并为悲观锁定提供扩展。因此,如果迁移使用悲观锁定的遗留 Hibernate 应用程序,请考虑在 persistence.xml 文件中设置以下 OpenJPA 配置属性:



    映射 28. 悲观锁定(OpenJPA 配置属性)
                            
    <property 
       name="openjpa.LockManager" 
       value="pessimistic">
    </property>
    <property 
       name="openjpa.ReadLockLevel" 
       value="read">
    </property>
    <property 
       name="openjpa.WriteLockLevel" 
       value="write">
    </property>
    <property 
       name="openjpa.jdbc.TransactionIsolation" 
       value="repeatable-read">
    </property>

    使用这些属性,所有读取内容都获取共享(读取)锁定并保存它们,直到事务结束。如果在同一记录具有多个并发更新版本时遇到并发性问题(死锁),则可能需要指定写入的 ReadLockLevel,以便在检索数据时生成 FOR UPDATE,并强制执行更新版本的序列。

    如果使用配置参数在 persistence.xml 文件中指定这些悲观锁定级别,则它们将会应用于所有事务。另外,您可能需要使用 org.apache.openjpa.persistence.FetchPlan 类以编程方式为单个事务设置锁定级别,如以下代码片段所示:



    映射 28. 悲观锁定(以编程方式设置的 OpenJPA 配置属性)
                            
    import org.apache.openjpa.persistence.*;
    ...
    EntityManager em = ...;
    em.getTransaction ().begin ();
    ...
    // load an object with a pessimistic read lock mode
    fetch = ( OpenJPAPersistence.cast( em ) ).getFetchPlan();
    fetch.setReadLockMode( LockModeType.WRITE );
    Address address = em.find( Address.class, addressId );
    ...
    em.getTransaction ().commit ();





回页首


迁移 Hibernate 配置参数

在 Hibernate 中,配置 SessionFactory 的最常见方法是在 hibernate.cfg.xml 文件中包括 <property> 元素,并将该文件放置在类路径的根文件夹中。另一个很少使用的等效方法是在类路径的 hibernate.properties 文件中包括属性。

在 OpenJPA 中,EntityManagerFactory 是通过以下方法配置的:在 persistence.xml 文件中包括命名的 <persistence-unit> 元素,并将该文件放置在类路径的 META-INF 文件夹中。<persistence-unit> 定义持久性提供程序、映射文件和其他属性,如数据库连接和日志记录。持久性提供程序标识实现 JPA 规范的供应商,<persistence-unit> 中的命名属性将特定于持久性提供程序(本示例中为 OpenJPA)。

将 Hibernate 应用程序迁移到 OpenJPA 时,至少会遇到三个常见配置场景:

  1. 数据库连接——该配置属性告诉 SessionFactory 如何连接到数据库。
  2. 映射位置——该属性控制对象到数据库中行的映射。
  3. 日志类别——该属性使您能够诊断问题,如设置日志记录/跟踪级别。

您可能会遇到许多配置属性,这里无法一一介绍,所以请一定要参阅参考资料,获得关于映射其他配置属性的信息。其中特别重要的参考资料是用于 org.hibernate.cfg.Environment 类和所有 Hibernate 配置属性的 Hibernate API 文档OpenJPA 用户指南

1. 数据库连接

有以下两种方法配置数据库连接:使用本地 JDBC 连接(这里将介绍它)或使用 J2EE 数据源(请参见参考资料)。

  • Hibernate 约定

    在 Hibernate 中,配置 JDBC 连接按照以下方式进行映射:

    • 使用 dialect 配置参数。
    • 使用 connection.driver_class 配置参数。
    • 使用 connection.url 配置参数。
    • 使用 connection.username 配置参数。
    • 使用 connection.password 配置参数。


    配置 1. Hibernate 数据库连接
                            
    ...
    <hibernate-configuration>    
       <session-factory>                   
    	<property 
             name="dialect">
             org.hibernate.dialect.DB2Dialect
          </property>
    	<property
             name="connection.driver_class">
             com.ibm.db2.jcc.DB2Driver
          </property>
    	<property
             name="connection.url">
             jdbc:db2://localhost:50000/HIBTEST
          </property>
    	<property 
             name="connection.username">
             db2admin
          </property>
    	<property 
             name="connection.password">
             db2admin
          </property>
       </session-factory>
    </hibernate-configuration>

  • OpenJPA 约定

    在 OpenJPA 中,配置等效 JDBC 连接按照以下方式进行映射:

    • 使用 openjpa.jdbc.DBDictionary 配置参数。(此参数是可选的,因为 OpenJPA 通常可以通过 URL 和 DriverName 属性确定正确的字典。)
    • 使用 openjpa.ConnectionDriverName 配置参数。
    • 使用 openjpa.ConnectionURL 配置参数。
    • 使用 openjpa.ConnectionUserName 配置参数。
    • 使用 openjpa.ConnectionPassword 配置参数。


    配置 2. OpenJPA 数据库连接
                            
    <persistence ...>
       <persistence-unit name="JPATEST">
          <properties>
    	   <property 
                name="openjpa.jdbc.DBDictionary" 
                value="db2(DriverVendor=db2)" >
             </property> 
             <property 
                name="openjpa.ConnectionDriverName"
                value="com.ibm.db2.jcc.DB2Driver">
             </property>
             <property 
                name="openjpa.ConnectionURL"   
                value="jdbc:db2://localhost:50000/JPATEST">
             </property>
             <property 
                name="openjpa.ConnectionUserName" 
                value="db2admin">
             </property>
    	   <property 
                name="openjpa.ConnectionPassword" 
                value="db2admin">
             </property> 
          </properties>
       </persistence-unit>
    </persistence>

    (尽管此示例不需要另一个参数 openjpa.ConnectionProperties,但它非常有用,在执行 connect() 调用时,它允许传入其他属性。)

2. 映射位置

如果您在 Hibernate 中使用基于 XML 的配置方法,则不仅可以指定配置参数,而且可以指定映射文件的位置。这是常见的场景,因为它使您能够配置 SessionFactory,而无需以编程方式指定映射文件的位置。

  • Hibernate 约定

    在 Hibernate 中,配置映射文件的位置按照以下方式进行映射:

    • 将 <mapping> 与资源属性一起使用。


    配置 3. Hibernate 映射位置
                            
    ...
    <hibernate-configuration>    
       <session-factory>                   
    	...
    	<!-- Mapping files -->
    	<mapping resource="domainmodel.hbm.xml"/>
       </session-factory>
    </hibernate-configuration>

  • OpenJPA 约定

    在 OpenJPA 中,配置映射文件的位置按照以下方式进行映射:

    • 使用 <mapping-file> 元素。


    配置 4. OpenJPA 映射位置
                            
    <persistence ...>
    <persistence-unit name="TEST">
       ...      
       <!-- Mapping files -->
       <mapping-file>META-INF/orm.xml</mapping-file>
    </persistence-unit>

3. 日志类别

Hibernate 应用程序的故障排除非常困难,因为 Hibernate 会基于映射文件中指定的元数据为您生成 SQL 命令。因此,将日志类别配置为查看 Hibernate 提交到数据库的准确 SQL 通常非常有用。

  • Hibernate 约定

    在 Hibernate 中,日志类别的配置按照以下方式进行映射:

    • 使用 show_sql 配置参数输出 SQL 命令。


    配置 5. Hibernate 日志类别
                            
    ...
    <hibernate-configuration>    
       <session-factory>                   
    	...
    	<property 
             name="show_sql">
             true
          </property>
       </session-factory>
    </hibernate-configuration>

    如果研究 SQL 不足以诊断问题,Hibernate 中还提供了其他日志类别,但是您将发现在类路径的 log4j.properties 文件中配置了日志类别。(此处不介绍该文件的配置;请参考 Hibernate 文档,获得关于配置其他 Hibernate 日志类别的信息。)

  • OpenJPA 约定

    在 OpenJPA 中,日志记录的配置按照以下方式进行映射:

    • 使用 openjpa.Log 配置属性输出 SQL。
    • 使用 openjpa.ConnectionFactoryProperties 准确地输出 SQL。


    配置 6. OpenJPA 日志类别
                            
    <persistence ...>
    <persistence-unit name="TEST">
       ...      
       <properties> 
    	<property 
             name="openjpa.Log" 
             value="SQL=TRACE">
          </property> 
          <property
             name="openjpa.ConnectionFactoryProperties"
             value="PrettyPrint=true, PrettyPrintLineLength=72">
          </property>
      </properties>
    </persistence-unit>

    您还可以使用 openjpa.Log 配置属性将 OpenJPA 配置为输出其他日志信息。(请参见 OpenJPA 用户指南。)





回页首


结束语

本文详细介绍了将使用 EJB 2.1 的专有 Hibernate 3 应用程序迁移到使用 OpenJPA 0.9.7 持久性提供程序和 EJB 3.0 的行业标准 JPA 应用程序的案例研究。通过一系列常见场景描述了迁移情况,并且在每个场景中,对 Hibernate 和 OpenJPA 的实现进行了并排比较。并排比较为那些将现有 Hibernate/EJB 2.1 应用程序迁移到 OpenJPA/EJB 3.0,以及在下一个项目中使用 OpenJPA/EJB 3.0 的用户提供了帮助。

本文介绍了如何迁移组成遗留 Hibernate 应用程序的三个主要构建块(应用程序源代码、对象关系映射和配置参数),并得出以下结论:

  • 如果 Hibernate 应用程序源代码构造良好,并封装 Hibernate 调用,则对于常见场景,源代码的迁移非常简单。对于没有封装 Hibernate 调用的程序,迁移将比较困难,但是与更改业务(会话、事务和实体管理)逻辑相比,迁移还需要较多的语法更改。

  • 由于对象关系映射已存在,所以您需要使用中间相遇迁移方法保持现有对象和数据模型。不能使用自底向上方法从数据模型生成对象模型,也不能使用自顶向下方法从对象模型生成数据模型;迁移必须保持两个模型。本文手动实现了中间相遇映射,但是 Dali JPA 工具或 IBM Design Pattern 工具包(请参见参考资料)可以自动进行 Hibernate XML 到 OpenJPA XML的大部分迁移。

  • 将常见配置参数从 Hibernate 迁移到 OpenJPA 非常容易,但是还可以使用许多其他参数来优化 OpenJPA 可能需要(也可能不需要)的遗留 Hibernate 应用程序。因此,对优化不执行并排迁移。相反,先迁移常见配置参数,然后让负载测试指示需要引入哪些 OpenJPA 优化参数。

本文没有介绍 Hibernate 查询到 OpenJPA 的迁移,因为对于 Hibernate 应用程序来说,具有满足业务需求的定义良好的域模型是比较普遍的。对于没有定义良好的域模型和需要许多特殊查询的应用程序,则解决方案(如 iBatis)比对象关系映射工具(如 Hibernate 或 OpenJPA)更合适。也就是说,如果有足够的兴趣,则后续文章可能将介绍 Hibernate 查询到 OpenJPA 的迁移。





回页首


附录:OpenJPA Java 5 注释


注释 A. 单个表继承

                
//Participant (base) class
@Entity
@Table(name="T_PRTCPNT")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="PRTCPNT_CLASS")
public class Participant implements java.io.Serializable {
   @Column(name="PRTCPNT_TID")
   @Id private Long participantId; 
   ...
}

// SalesRep subclass
@Entity
@DiscriminatorValue(value="SALES_REP")
public class SalesRep extends Participant { 
   ...
}

// Administrator subclass
@Entity
@DiscriminatorValue(value="ADMIN")
public class Administrator extends Participant { 
   ...
}


注释 B. 连接继承
                
// Participant (base) class
@Entity
@Table(name="T_PRTCPNT")
@Inheritance(strategy=InheritanceType.JOINED)
public class Participant implements Serializable { 
    @Column(name="PRTCPNT_TID")
    @Id private Long participantId;
    ... 
}

// SalesRep subclass

@Entity
@Table(name="T_SALESREP")
@PrimaryKeyJoinColumn(name="PRTCPNT_TID")
public SalesRep extends Participant { 
    ... 
}

// Administrator subclass

@Entity
@Table(name="T_ADMIN")
@PrimaryKeyJoinColumn(name="PRTCPNT_TID")
public Administrator extends Participant { 
    ... 
}


注释 C. 可嵌入对象的一对一关系
                
// Employee (parent) class
@Entity 
@Table(name="T_EMPLOYEE")
public class Employee implements Serializable {
    @Embedded private EmployeeRecord employeeRec;
    ...
}

// EmployeeRecord (child) class
@Embeddable
public class EmployeeRecord implements Serializable {
    ...
}


注释 D. 多对一关系
                
// Address (parent) class

@Entity 
@Table(name="T_ADDRESS")
public class Address implements Serializable {
    ...
    @Column(name="ADDR_TID")
    @Id private Long addressId;
}

// Phone (child) class
@Entity 
@Table(name="T_PHONE")
public class Phone implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="ADDR_TID")
    private Address address;
    ...
}


注释 E. 一对多关系
                
// Address (parent) entity

@Entity 
@Table(name="T_ADDRESS")
public class Address implements Serializable {
    ...
    @OneToMany(mappedBy="address", cascade=CascadeType.ALL)
    private Set<Phone> phones = new HashSet<Phone>();
    ... 
    @Column(name="ADDR_TID")
    @Id private Long addressId;
}        

// Phone (child) entity

@Entity 
@Table(name="T_PHONE")
public class Phone implements Serializable {
    ...
    @ManyToOne(cascade=CascadeType.ALL) 
    @JoinColumn(name="ADDR_TID")
    private Address address;
    ...
}


注释 F. 多对多关系
                
// User (owner/child) entity
@Entity 
@Table(name="T_USER")
public class User implements Serializable {
    @Column(name="USER_TID")
    @Id private Long userId;

    @ManyToMany
    @JoinTable(
        name="T_USER_GROUP",
        joinColumns=@JoinColumn(name="USER_TID"),
        inverseJoinColumns=@JoinColumn(name="GROUP_TID"))
    private Set<Group> groups = new HashSet<Group>();
    ...
}

// Group (non-owner/parent) entity
@Entity 
@Table(name="T_GROUP")
public class Group implements Serializable {
    @Column(name="GROUP_TID")
    @Id private Long groupId;

    @ManyToMany(mapppedBy="groups")
    private Set<User> users = new HashSet<User>();
    ...
}


注释 G. 延迟初始化
                
// Address (parent) entity

@Entity 
@Table(name="T_ADDRESS")
public class Address implements Serializable {
    ...
    @OneToMany(mappedBy="address", fetch=FetchType.EAGER)
    private Set<Phone> phones = new HashSet<Phone>();;
    ... 
    @Column(name="ADDR_TID")
    @Id private Long addressId;
}        

// Phone (child) entity

@Entity 
@Table(name="T_PHONE")
public class Phone implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="ADDR_TID")
    private Address address;
    ...
}


注释 H. 对象标识
                
// Address entity

@Entity 
@Table(name="T_ADDRESS")
public class Address implements Serializable {
    ... 
    @SequenceGenerator(name="AddressSeq", sequenceName="T_ADDRESS_SEQ")
    @GeneratedValue(
        strategy=GenerationType.SEQUENCE,
        generator="AddressSeq")
    @Column(name="ADDR_TID")
    @Id private Long addressId;
    ...
}


注释 I. 乐观锁定
                
@Entity
@Table(name="T_ADDRESS")
public class Address {
   ...
   @Column(name="VERSION")
   @Version private long version;
   ...
}

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章