🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 【第八章】 对ORM的支持 之 8.4 集成JPA ——跟我学spring3 ## 8.4  集成JPA JPA全称为Java持久性API(Java Persistence API),JPA是Java EE 5标准之一,是一个ORM规范,由厂商来实现该规范,目前有Hibernate、OpenJPA、TopLink、EclipseJPA等实现。 ### 8.4.1  如何集成 Spring目前提供集成Hibernate、OpenJPA、TopLink、EclipseJPA四个JPA标准实现。 Spring通过使用如下Bean进行集成JPA(EntityManagerFactory): * **LocalEntityManagerFactoryBean:**适用于那些仅使用JPA进行数据访问的项目,该FactoryBean将根据JPA PersistenceProvider自动检测配置文件进行工作,一般从“META-INF/persistence.xml”读取配置信息,这种方式最简单,但不能设置Spring中定义的DataSource,且不支持Spring管理的全局事务,而且JPA 实现商可能在JVM启动时依赖于VM agent从而允许它们进行持久化类字节码转换(不同的实现厂商要求不同,需要时阅读其文档),不建议使用这种方式; persistenceUnitName:指定持久化单元的名称; 使用方式: ``` <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="persistenceUnit"/> </bean> ``` * **从JNDI中获取:**用于从Java EE服务器获取指定的EntityManagerFactory,这种方式在进行Spring事务管理时一般要使用JTA事务管理; 使用方式: ``` <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd"> <jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/persistenceUnit"/> </beans> ``` 此处需要使用“jee”命名标签,且使用&lt;jee:jndi-lookup&gt;标签进行JNDI查找,“jndi-name”属性用于指定JNDI名字。 * **LocalContainerEntityManagerFactoryBean:**适用于所有环境的FactoryBean,能全面控制EntityManagerFactory配置,如指定Spring定义的DataSource等等。 **persistenceUnitManager**:用于获取JPA持久化单元,默认实现DefaultPersistenceUnitManager用于解决多配置文件情况 **dataSource**:用于指定Spring定义的数据源; **persistenceXmlLocation**:用于指定JPA配置文件,对于对配置文件情况请选择设置persistenceUnitManager属性来解决; **persistenceUnitName**:用于指定持久化单元名字; **persistenceProvider**:用于指定持久化实现厂商类;如Hibernate为org.hibernate.ejb.HibernatePersistence类; **jpaVendorAdapter**:用于设置实现厂商JPA实现的特定属性,如设置Hibernate的是否自动生成DDL的属性generateDdl;这些属性是厂商特定的,因此最好在这里设置;目前Spring提供HibernateJpaVendorAdapter、OpenJpaVendorAdapter、EclipseLinkJpaVendorAdapter、TopLinkJpaVendorAdapter、OpenJpaVendorAdapter四个实现。其中最重要的属性是“**database**”,用来指定使用的数据库类型,从而能根据数据库类型来决定比如如何将数据库特定异常转换为Spring的一致性异常,目前支持如下数据库(**DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE**)。 **jpaDialect**:用于指定一些高级特性,如事务管理,获取具有事务功能的连接对象等,目前Spring提供HibernateJpaDialect、OpenJpaDialect 、EclipseLinkJpaDialect、TopLinkJpaDialect、和DefaultJpaDialect实现,注意DefaultJpaDialect不提供任何功能,因此在使用特定实现厂商JPA实现时需要指定JpaDialect实现,如使用Hibernate就使用HibernateJpaDialect。当指定**jpaVendorAdapter属性时可以不指定jpaDialect,会自动设置相应的JpaDialect实现;** **jpaProperties和jpaPropertyMap**:指定JPA属性;如Hibernate中指定是否显示SQL的“hibernate.show_sql”属性,对于jpaProperties设置的属性自动会放进jpaPropertyMap中; loadTimeWeaver:用于指定LoadTimeWeaver实现,从而允许JPA 加载时修改相应的类文件。具体使用得参考相应的JPA规范实现厂商文档,如Hibernate就不需要指定loadTimeWeaver。 接下来学习一下Spring如何集成JPA吧: **1、准备jar包,从下载的hibernate-distribution-3.6.0.Final包中获取如下Hibernate需要的jar包从而支持JPA:** + lib\jpa\hibernate-jpa-2.0-api-1.0.0.Final.jar //用于支持JPA **2、对象模型定义,此处使用UserModel2:** ``` package cn.javass.spring.chapter8; //省略import @Entity @Table(name = "test") public class UserModel2 { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name = "name") private String myName; //省略getter和setter } ``` 注意此处使用的所有注解都是位于javax.persistence包下,如使用@org.hibernate.annotations.Entity 而非@javax.persistence. Entity将导致JPA不能正常工作。 **1、 JPA配置定义(chapter8/persistence.xml),定义对象和数据库之间的映射:** ``` <?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL"/> </persistence> ``` 在JPA配置文件中,我们指定要持久化单元名字,和事务类型,其他都将在Spring中配置。 **2、 数据源定义,此处使用第7章的配置文件,即“chapter7/applicationContext-resources.xml”文件。** **3、 EntityManagerFactory配置定义(chapter8/applicationContext-jpa.xml):** ``` <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="persistenceXmlLocation" value="chapter8/persistence.xml"/> <property name="persistenceUnitName" value="persistenceUnit"/> <property name="persistenceProvider" ref="persistenceProvider"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaDialect" ref="jpaDialect"/> <property name="jpaProperties"> <props> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <bean id="persistenceProvider" class="org.hibernate.ejb.HibernatePersistence"/> ``` ``` <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="generateDdl" value="false" /> <property name="database" value="HSQL"/> </bean> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> ``` * **LocalContainerEntityManagerFactoryBean:**指定使用本地容器管理EntityManagerFactory,从而进行细粒度控制; * **dataSource**属性指定使用Spring定义的数据源; * **persistenceXmlLocation**指定JPA配置文件为chapter8/persistence.xml,且该配置文件非常简单,具体配置完全在Spring中进行; * **persistenceUnitName**指定持久化单元名字,即JPA配置文件中指定的; * **persistenceProvider**:指定JPA持久化提供商,此处使用Hibernate实现HibernatePersistence类; * **jpaVendorAdapter**:指定实现厂商专用特性,即generateDdl= false表示不自动生成DDL,database= HSQL表示使用hsqldb数据库; * **jpaDialect**:如果指定jpaVendorAdapter此属性可选,此处为HibernateJpaDialect; * **jpaProperties**:此处指定“hibernate.show_sql =true”表示在日志系统debug级别下将打印所有生成的SQL。 **4、 获取EntityManagerFactory:** ``` package cn.javass.spring.chapter8; //省略import public class JPATest { private static EntityManagerFactory entityManagerFactory; @BeforeClass public static void setUpClass() { String[] configLocations = new String[] { "classpath:chapter7/applicationContext-resources.xml", "classpath:chapter8/applicationContext-jpa.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations); entityManagerFactory = ctx.getBean(EntityManagerFactory.class); } } ``` 此处我们使用了chapter7/applicationContext-resources.xml定义的“dataSource”数据源,通过ctx.getBean(EntityManagerFactory.class)获取EntityManagerFactory。 5、  通过EntityManagerFactory获取EntityManager进行创建和删除表: ``` @Before public void setUp() throws SQLException { //id自增主键从0开始 String createTableSql = "create memory table test" + "(id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " + "name varchar(100))"; executeSql(createTableSql); } @After public void tearDown() throws SQLException { String dropTableSql = "drop table test"; executeSql(dropTableSql); } private void executeSql(String sql) throws SQLException { EntityManager em = entityManagerFactory.createEntityManager(); beginTransaction(em); em.createNativeQuery(sql).executeUpdate(); commitTransaction(em); closeEntityManager(em); } private void closeEntityManager(EntityManager em) { em.close(); } private void rollbackTransacrion(EntityManager em) throws SQLException { if(em != null) { em.getTransaction().rollback(); } } private void commitTransaction(EntityManager em) throws SQLException { em.getTransaction().commit(); } private void beginTransaction(EntityManager em) throws SQLException { em.getTransaction().begin(); } ``` 使用EntityManagerFactory创建EntityManager,然后通过EntityManager对象的createNativeQuery创建本地SQL执行创建和删除表。 **6、 使用EntityManagerFactory获取EntityManager对象进行持久化数据:** ``` @Test public void testFirst() throws SQLException { UserModel2 model = new UserModel2(); model.setMyName("test"); EntityManager em = null; try { em = entityManagerFactory.createEntityManager(); beginTransaction(em); em.persist(model); commitTransaction(em); } catch (SQLException e) { rollbackTransacrion(em); throw e; } finally { closeEntityManager(em); } } ``` 使用**EntityManagerFactory**获取**EntityManager**进行操作,看到这还能忍受冗长的代码和事务管理吗?Spring同样提供JpaTemplate模板类来简化这些操作。 大家有没有注意到此处的模型对象能自动映射到数据库,这是因为Hibernate JPA实现默认自动扫描类路径中的@Entity注解类及*.hbm.xml映射文件,可以通过更改Hibernate JPA属性“hibernate.ejb.resource_scanner”,并指定org.hibernate.ejb.packaging.Scanner接口实现来定制新的扫描策略。 ### 8.4.2  使用JpaTemplate JpaTemplate模板类用于简化事务管理及常见操作,类似于JdbcTemplate模板类,对于复杂操作通过提供JpaCallback回调接口来允许更复杂的操作。 接下来示例一下JpaTemplate的使用: **1、修改Spring配置文件(chapter8/applicationContext-jpa.xml),添加JPA事务管理器:** ``` <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> ``` * txManager:指定事务管理器,JPA使用JpaTransactionManager事务管理器实现,通过entityManagerFactory指定EntityManagerFactory;用于支持Java SE环境的**JPA扩展的持久化上下文(EXTENDED Persistence Context)**。 **2、修改JPATest类,添加类变量ctx,用于后边使用其获取事务管理器使用:** ``` package cn.javass.spring.chapter8; public class JPATest { private static EntityManagerFactory entityManagerFactory; private static ApplicationContext ctx; @BeforeClass public static void beforeClass() { String[] configLocations = new String[] { "classpath:chapter7/applicationContext-resources.xml", "classpath:chapter8/applicationContext-jpa.xml"}; ctx = new ClassPathXmlApplicationContext(configLocations); entityManagerFactory = ctx.getBean(EntityManagerFactory.class); } } ``` **3)JpaTemplate模板类使用:** ``` @Test public void testJpaTemplate() { final JpaTemplate jpaTemplate = new JpaTemplate(entityManagerFactory); final UserModel2 model = new UserModel2(); model.setMyName("test1"); PlatformTransactionManager txManager = ctx.getBean(PlatformTransactionManager.class); new TransactionTemplate(txManager).execute( new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { jpaTemplate.persist(model); return null; } }); String COUNT_ALL = "select count(*) from UserModel"; Number count = (Number) jpaTemplate.find(COUNT_ALL).get(0); Assert.assertEquals(1, count.intValue()); } ``` * **jpaTemplate**:可通过new JpaTemplate(entityManagerFactory)方式创建; * **txManager:**通过ctx.getBean(PlatformTransactionManager.class)获取事务管理器; * **TransactionTemplate:**通过new TransactionTemplate(txManager)创建事务模板对象,并通过execute方法执行TransactionCallback回调中的doInTransaction方法中定义需要执行的操作,从而将由模板类通过txManager事务管理器来进行事务管理,此处是调用jpaTemplate对象的persist方法进行持久化; * **jpaTemplate.persist()**:根据JPA规范,在JPA扩展的持久化上下文,该操作必须运行在事务环境,还有persist()、 merge()、remove()操作也必须运行在事务环境; * **jpaTemplate.find():**根据JPA规范,该操作无需运行在事务环境,还有find()、getReference()、 refresh()、detach()和查询操作都无需运行在事务环境。 此实例与Hibernate和Ibatis有所区别,通过JpaTemplate模板类进行如持久化等操作时必须有运行在事务环境中,否则可能抛出如下异常或警告: * **“javax.persistence.TransactionRequiredException:Executing an update/delete query”**:表示没有事务支持,不能执行更新或删除操作; * **警告“delaying identity-insert due to no transaction in progress”:**需要在日志系统启动debug模式才能看到,表示在无事务环境中无法进行持久化,而选择了延迟标识插入。 以上异常和警告是没有事务造成的,也是最让人困惑的问题,需要大家注意。 ### 8.4.3  集成JPA及最佳实践 类似于JdbcDaoSupport类,Spring对JPA也提供了JpaDaoSupport类来支持一致的数据库访问。JpaDaoSupport也是DaoSupport实现: 接下来示例一下Spring集成JPA的最佳实践: **1、 定义Dao接口,此处使用cn.javass.spring.chapter7.dao. IUserDao:** **2、 定义Dao接口实现,此处是JPA实现:** ``` package cn.javass.spring.chapter8.dao.jpa; //省略import @Transactional(propagation = Propagation.REQUIRED) public class UserJpaDaoImpl extends JpaDaoSupport implements IUserDao { private static final String COUNT_ALL_JPAQL = "select count(*) from UserModel"; @Override public void save(UserModel model) { getJpaTemplate().persist(model); } @Override public int countAll() { Number count = (Number) getJpaTemplate().find(COUNT_ALL_JPAQL).get(0); return count.intValue(); } } ``` 此处注意首先JPA实现放在dao.jpa包里,其次实现类命名如UserJpaDaoImpl,即×××JpaDaoImpl,当然如果自己有更好的命名规范可以遵循自己的,此处只是提个建议。 另外在类上添加了**@Transactional**注解表示该类的所有方法将在调用时需要事务支持,propagation传播属性为Propagation.REQUIRED表示事务是必需的,如果执行该类的方法没有开启事务,将开启一个新的事务。 **3、进行资源配置,使用resources/chapter7/applicationContext-resources.xml:** **4、dao定义配置,在chapter8/applicationContext-jpa.xml中添加如下配置:** **4.1、**首先添加tx命名空间用于支持事务: ``` <?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> ``` 4.2、为**@Transactional**注解事务开启事务支持: ``` <tx:annotation-driven transaction-manager="txManager"/> ``` 只为类添加**@Transactional** 注解是不能支持事务的,需要通过&lt;tx:annotation-driven&gt;标签来开启事务支持,其中txManager属性指定事务管理器。 4.3、配置DAO Bean: ``` <bean id="abstractDao" abstract="true"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="userDao" class="cn.javass.spring.chapter8.dao.jpa.UserJpaDaoImpl" parent="abstractDao"/> ``` 首先定义抽象的abstractDao,其有一个entityManagerFactory属性,从而可以让继承的子类自动继承entityManagerFactory属性注入;然后定义userDao,且继承abstractDao,从而继承entityManagerFactory注入;我们在此给配置文件命名为applicationContext-jpa.xml表示JPA实现。 **5、最后测试一下吧(cn.javass.spring.chapter8\. JPATest):** ``` @Test public void testBestPractice() { String[] configLocations = new String[] { "classpath:chapter7/applicationContext-resources.xml", "classpath:chapter8/applicationContext-jpa.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations); IUserDao userDao = ctx.getBean(IUserDao.class); UserModel model = new UserModel(); model.setMyName("test"); userDao.save(model); Assert.assertEquals(1, userDao.countAll()); } ``` 和Spring JDBC框架的最佳实践完全一样,除了使用applicationContext-jpa.xml代替了applicationContext-jdbc.xml,其他完全一样。也就是说,DAO层的实现替换可以透明化。 还有与集成其他ORM框架不同的是JPA在进行持久化或更新数据库操作时需要事务支持。 ### 8.4.4  Spring+JPA的CRUD Spring+JPA CRUD(增删改查)也相当简单,让我们直接看具体示例吧: ``` @Test public void testCRUD() { PlatformTransactionManager txManager = ctx.getBean(PlatformTransactionManager.class); final JpaTemplate jpaTemplate = new JpaTemplate(entityManagerFactory); TransactionTemplate tansactionTemplate = new TransactionTemplate(txManager); tansactionTemplate.execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { UserModel model = new UserModel(); model.setMyName("test"); //新增 jpaTemplate.persist(model); //修改 model.setMyName("test2"); jpaTemplate.flush();//可选 //查询 String sql = "from UserModel where myName=?"; List result = jpaTemplate.find(sql, "test2"); Assert.assertEquals(1, result.size()); //删除 jpaTemplate.remove(model); return null; } }); } ``` * 对于增删改必须运行在事务环境,因此我们使用TransactionTemplate事务模板类来支持事务。 * 持久化:使用JpaTemplate 类的persist方法持久化模型对象; * 更新:对于持久化状态的模型对象直接修改属性,调用flush方法即可更新到数据库,在一些场合时flush方法调用可选,如执行一个查询操作等,具体请参考相关文档; * 查询:可以使用find方法执行JPA QL查询; * 删除:使用remove方法删除一个持久化状态的模型对象。 Spring集成JPA进行增删改查也相当简单,但本文介绍的稍微复杂一点,因为牵扯到编程式事务,如果采用声明式事务将和集成Hibernate方式一样简洁。 原创内容,转载请注明出处【[http://sishuok.com/forum/blogPost/list/0/2500.html](http://sishuok.com/forum/blogPost/list/0/2500.html#7218)】