最近总想写点什么,正好研究了一下OpenJPA,大概通读了一下新出的1.2版本的官方文档,然后自己做了一个小例子。因为Spring2.5的推出,增加了许多新特性,市面上关于怎样整合Spring和OpenJPA的书、包括网上文章也特别少。这里打算分享一下自己的例子,希望不足之处大家见谅,欢迎指正切磋技术。
简单介绍一下OpenJPA,它是Apache对于Java Persistence API(即JPA)的一套实现,一个ORM思想下的优秀框架。之前一致用Hibernate,甚至还用过Apache早期对ORM的实现框架OJB,然后随着J2EE 5的规范出台,JPA也正式进入数据库映射框架的大舞台,EJB3、OpenJPA就是其倡导者。感觉今后的数据库持久化层,将很可能被Hibernate3、OpenJPA、EJB3.0瓜分。
首先我们定义实体类,使用J2EE 5推荐的Annotation方式来配置映射,相对于之前Xml方式的字段~属性映射方式来说,这种Annotation方式早期是基于JavaDoc的思想,称之为元数据。元数据的作用是创建文档、跟踪代码中的依赖性。甚至执行基本的编译时检查。最大的好处就是可以使用额外数据来分析代码,是未来编程的一个新趋势。
工程使用Jar包:geronimo-jpa_3.0_spec.jar、jta.jar、openjpa.jar、spring.jar、spring-agent.jar、serp.jar、log4j.jar以及几个常用Apache Common工程的包。使用的数据库是Apache的文件型数据库Derby,访问方式嵌入式。首先我定义一个实体类Customer,映射数据库table是tb_customer,这里可以选择性的定义DB Schema,可以使得定义的表分属不同的table space。主键映射使用AUTO方式,uuid-hex生成器,原理可见JDK自带的java.util.UUID类。注意我使用了自定义的枚举类型来映射一些固定范围内的type值,比如性别、种类、状态相关的字段都可以使用自定义枚举类,ORDINAL表示在数据库相应字段存取的是int,或者指定SPRING表示数据相应字段存取的是枚举变量字符串,根据个人需求而定
@Entity @Table(name="tb_customer",schema="DerbyDB") public class Customer { @Id @GeneratedValue(strategy=GenerationType.AUTO,generator="uuid-hex") @Column(name="col_id",length=32) private String id; @Column(name="col_name",unique=true,length=32) private String name; @Column(name="col_address",length=128) private String address; @Column(name="col_email",length=64) private String email; @Enumerated(EnumType.ORDINAL) @Column(name="col_sex") private SexType gender; @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="owner") private Set<Good> goods = new HashSet<Good>(); public enum SexType{MALE,FEMALE;} //All getter and setter .... ignore code }
注意那个OneToMany,使用的是懒加载,级联操作定义为ALL,mappedBy表示在映射的另外一端由哪一个字段来维护这种OneToMany关系,owner表示在映射类的另外一端即Good类中有一个字段名字叫做owner,是Customer类型。指定mappedBy实际是想双向维护这种对应关系。至于Good类的代码我就不粘贴了,大家很容易参照着写出来,重点是Spring2.5版本的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver"/> <property name="url" value="jdbc:derby:DerbyDB;create=true"/> <property name="username" value=""/> <property name="password" value=""/> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="txManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> </property> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter"> <property name="showSql" value="true"/> <property name="generateDdl" value="true"/> <property name="databasePlatform" value="org.apache.openjpa.jdbc.sql.DerbyDictionary"/> </bean> </property> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property> </bean> <tx:annotation-driven transaction-manager="txManager"/> <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> <property name="dataSource" ref="dataSource"/> </bean> <bean id="goodDao" class="com.openjpa.demo.dao.GoodDaoImpl"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="customerDao" class="com.openjpa.demo.dao.CustomerDaoImpl"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="dbService" class="com.openjpa.demo.service.DBServiceImpl"> <property name="goodDao" ref="goodDao"/> <property name="customerDao" ref="customerDao"/> </bean> </beans>
注意2.5版本的配置文件Root节点beans改用Xml Schema来验证格式,以往我们都习惯写上一行DTD的声明来验证。这里改成Xml Schema主要是为了引入tx标签,比如在事务管理的时候其作用的<tx:annotation-driven transaction-manager="txManager"/>,或者在插入一些声明式的AOP代码时候也会用到tx标签。
解释一下配置需要注意的细节:我采用@Transactional方式来管理事务,所以会注入PersistenceAnnotationBeanPostProcessor这个类,事务管理利用AOP拦截数据库操作代码来启动、提交、回滚事务,需要一个事务拦截器TransactionInterceptor,它里面要包含事务管理组件transactionManager和事务传播属性定义组件transactionAttributeSource。这样配置之后就可以在自定义的任何Service类或者相应的方法中启动事务管理功能(即当获得数据库连接时管理事务)。看一下我的DBServiceImpl片段就明白了:
@Transactional(readOnly = true) public class DBServiceImpl implements DBService{ private CustomerDao customerDao; private GoodDao goodDao; private TradeDao tradeDao; public void setCustomerDao(CustomerDao customerDao) { this.customerDao = customerDao; } public void setGoodDao(GoodDao goodDao) { this.goodDao = goodDao; } @Override @Transactional(readOnly = false, propagation=Propagation.REQUIRES_NEW) public void addUser(Customer user) throws Exception{ customerDao.create(user); } @Override public List<Customer> getAllUser() throws Exception{ return customerDao.findAll(); } //....ignore other code }
我使用的@Transactional标签并且定义了默认readOnly属性,这对于提高只读查询功能的效率是很有帮助的。只是在非只读操作上我需要把readOnly开关给关闭并且定义事务传播属性(之前这一些列的配置都是需要在Xml文件中指定),现在都可以使用元数据来完成。只不过这里一致怀疑Xml配置的好处是修改不需要重新编译,那么元数据的指定一旦要发生修改的话,必然需要重新编译替换class文件,这样做一定是最好的么?
最后需要说明的是JPA配置的核心组件类entityManager,这里注意我对entityManager注入InstrumentationLoadTimeWeaver,这是先说明JDK5.0之后引入的一个接口java.lang.instrument,允许提供Java代理检测运行在JVM上程序的服务,检测机制是对方法的字节码进行修改。而JPA允许通过LoadTimeWeaver属性注入一个JPA ClassTransformer实例到特定环境中。注入的这个代理挂钩通过修改方法字节码来协助JPA容器管理实体类和数据库的同步,检测实体类的各种状态,比如detach、managed、persistence等。典型的应用是在配置懒加载的时候对实体类的get方法做一些手脚。
还有一点是JPA规范要求配置JPA应用的时候必须在classpath能找到persistence.xml或者META-INF/persistence.xml,在里面配置JPA容器的各种属性,这里由于整合Spring而使得大部分的属性都可以移植到Spring的配置文件中。使得我的persistence.xml就是一个简单的声明而已,当然用到更多专属OpenJPA特性的时候,Spring不一定能全部整合配置,毕竟Spring支持的是J2EE 5推崇的JPA,而不是特指OpenJPA。
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="derbyDemo" transaction-type="RESOURCE_LOCAL"> </persistence-unit> </persistence>
今天大概就说道这里吧,总的体会感觉OpenJPA提供的功能比JPA规范中定义的要多的多,很强大,值得好好研究一番。