SpringInAction笔记(十一)—— 使用对象-关系映射持久化数据(中)

Spring与Java持久化API


       Java持久化API(Java Persistence API,JPA)诞生在EJB 2实体Bean的 废墟之上,并成为下一代Java持久化标准。JPA是基于POJO的持久化机制,它从Hibernate和Java数据对象(Java Data Object,JDO)上借鉴了很多理念并加入了Java 5注解的特性。
       在Spring中使用JPA的第一步是要在Spring应用上下文中将实体管理器工厂(entity manager factory)按照bean的形式来进行配置。

11.2.1 配置实体管理器工厂
       基于JPA的应用程序需要使用EntityManagerFactory 的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:

 

  • 应用程序管理类型(Application-managed):当应用程序向实体 管理器工厂直接请求实体管理器时,工厂会创建一个实体管理 器。在这种模式下,程序要负责打开或关闭实体管理器并在事务 中对其进行控制。这种方式的实体管理器适合于不运行在Java EE容器中的独立应用程序。

 

  • 容器管理类型(Container-managed):实体管理器由Java EE创 建和管理。应用程序根本不与实体管理器工厂打交道。相反,实 体管理器直接通过注入或JNDI来获取。容器负责配置实体管理器 工厂。这种类型的实体管理器最适用于Java EE容器,在这种情 况下会希望在persistence.xml指定的JPA配置之外保持一些自己对 JPA的控制。

       以上的两种实体管理器实现了同一个EntityManager接口。
   应用程序管理类型的EntityManager是由EntityManagerFactory创建的,而EntityManagerFactory是通过 PersistenceProvider的createEntityManagerFactory() 方法得到的。与此相对,容器管理类型的EntityManagerFactory是通过PersistenceProvider的 createContainerEntityManager Factory()方法获得的。
       不管使用哪种EntityManagerFactory,Spring 都会负责管理EntityManager。如果你使用的是应用程序管理类型的实体管理器,Spring承担了应用程序的角色并以透明的方式处理EntityManager。在容器管理的场景下,Spring会担当容器的角色。
       这两种实体管理器工厂分别由对应的Spring工厂Bean创建:

  • LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManager-Factory;
  • LocalContainerEntityManagerFactoryBean生成容器管理类型的Entity-ManagerFactory。

       不管选择应用程序管理类型,还是容器管理类型的 EntityManager Factory,对于基于Spring的应用程序来讲是完
全透明的。当组合使用Spring和JPA时,处 理EntityManagerFactory的复杂细节被隐藏了起来,数据访问代码只需关注它们的真正目标即可,也就是数据访问。

配置应用程序管理类型的JPA
      对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息 来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。 persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲, persistence.xml列出了一个或多个的持久化类以及一些其他的配置如 数据源和基于XML的配置文件。
        如下是一个典型的persistence.xml文件,它是用于Spittr应用程序的:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	version="1.0">
	<persistence-unit name="spitterPU">
		<class>com.habuma.spittr.domain.Spitter</class>
		<class>com.habuma.spittr.domain.Spittle</class>
		<properties>
			<property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver" />
			<property name="toplink.jdbc.url"
				value="jdbc:hsqldb:hsql://localhost/spitter/spitter" />
			<property name="toplink.jdbc.user" value="sa" />
			<property name="toplink.jdbc.password" value="" />
		</properties>
	</persistence-unit>
</persistence>

 

通过以下的@Bean注解方法在Spring中声明LocalEntityManagerFactoryBean:

@Bean
public LocalEntityManagerFactoryBean entityManagerFactoryBean() {
    LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean();
    emfb.setPersistenceUnitName("spitterPU");
    return emfb;
}

赋给persistenceUnitName属性的值就是persistence.xml中持久化单元的名称。       

       创建应用程序管理类型的EntityManagerFactory都是在 persistence.xml中进行的,而这正是应用程序管理的本意。在应用程序管理的场景下(不考虑Spring时),完全由应用程序本身来负责获 取EntityManagerFactory,这是通过JPA实现的 PersistenceProvider做到的。如果每次请求EntityManagerFactory时都需要定义持久化单元,那代码将会迅速膨胀。通过将其配置在persistence.xml中,JPA就能够在这个特定的位置查找持久化单元定义了。       

       关于在应用程序管理中使用JPA的示例,请参考使用 Spring Data JPA 简化 JPA 开发

 

使用容器管理类型的JPA        

       容器管理的JPA采取了一个不同的方式。当运行在容器中时,可以使用容器(在我们的场景下是Spring)提供的信息来生 成EntityManagerFactory。       

       如下的@Bean注解方法声明了在Spring中如何使用LocalContainerEntity-ManagerFactoryBean来配置容器管理类型的JPA:

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory(JpaVendorAdapter jpaVendorAdapter) {
		LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
		emfb.setDataSource(dataSource);
		emfb.setJpaVendorAdapter(jpaVendorAdapter);
		emfb.setJpaProperties(hibernateProperties());
		emfb.setPackagesToScan("spittr");
		return emfb;
	}

       private Properties hibernateProperties() {  
		Properties props = new Properties();
		//输出格式化后的sql,更方便查看
		props.setProperty("hibernate.format_sql", "false");
		//指定Hibernate启动的时候自动创建或更新表结构
		props.setProperty("hibernate.hbm2ddl.auto", "update");
		//关闭beanvalitionFactory自动验证
		props.setProperty("javax.persistence.validation.mode", "none");
                return props;  
       } 


       @Bean  
       public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {  
               JpaTransactionManager manager = new JpaTransactionManager();   
               manager.setEntityManagerFactory(emf);
               return manager;  
       } 

       LocalContainerEntityManagerFactoryBean 会扫描spittr包,查找带有@Entity注解的类。因此,没有必要在persistence.xml文件中进行声明了。同时,因为DataSource也是注入到LocalContainerEntityManagerFactoryBean中的,所以也没有必要在 persistence.xml文件中配置数据库信息了。那么结论就是,persistence.xml文件完全没有必要存在了!你尽可以将其删除,让 LocalContainerEntityManagerFactoryBean来处理这些事情。


jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:

 

  • EclipseLinkJpaVendorAdapter
  • HibernateJpaVendorAdapter
  • OpenJpaVendorAdapter

在本例中,我们使用Hibernate作为JPA实现,所以将其配置 为Hibernate-JpaVendorAdapter:

	@Bean 
	public JpaVendorAdapter jpaVendorAdapter() {
		HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
		//设置所使用的数据库为MySQL
		adapter.setDatabase(Database.MYSQL);
		//是否在控制台打印生成的SQL语句
		adapter.setShowSql(true);
		//是否在控制台打印生成的SQL语句
		adapter.setGenerateDdl(false);
		adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
		return adapter;
	}


从JNDI获取实体管理器工厂
        使用如下的Java配置来获取EntityManagerFactory:

	@Bean(destroyMethod="")
	@Profile("jndi")
	public JndiObjectFactoryBean jndiDataSource() throws IllegalArgumentException, NamingException {
		JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
		//jndiObjectFB.setJndiName("jndi/spittrDB");
		jndiObjectFB.setJndiName("java:comp/env/jndi/spittrDB");
		jndiObjectFB.setResourceRef(true);
		jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
		return jndiObjectFB;	
	}

尽管这种方法没有返回EntityManagerFactory,但是它的结果就 是一个EntityManagerFactory bean。这是因为它所返回的 JndiObjectFactoryBean是FactoryBean接口的实现,它能够创建EntityManagerFactory。 不管你采用何种方式得到EntityManagerFactory,一旦得到这样的对象,接下来就可以编写Repository了。

11.2.2 编写基于JPA的Repository
      正如Spring对其他持久化方案的集成一样,Spring对JPA集成也提供了JpaTemplate模板以及对应的支持类JpaDaoSupport。但是,为了实现更纯粹的JPA方式,基于模板的JPA已经被弃用了。
  程序清单11.2 不使用Spring模板的纯JPA Repository

JpaSpittleRepository:

package spittr.data;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import spittr.Spittle;

@Repository
@Transactional
public class JpaSpittleRepository implements SpittleRepository {
	  
	@PersistenceContext	  
	private EntityManager entityManager;

	@Override
	public List<Spittle> findSpittles(int count) {
		// TODO Auto-generated method stub
	    return (List<Spittle>) entityManager.createQuery("select s from Spittle s order by s.postedTime desc")
	            .setMaxResults(count)
	            .getResultList();
	}

	@Override
	public Spittle findOne(long id) {
		// TODO Auto-generated method stub
		return entityManager.find(Spittle.class, id);
	}

	@Override
	public void save(Spittle spittle) {
		// TODO Auto-generated method stub
		entityManager.persist(spittle);
	}

	@Override
	public List<Spittle> findBySpitterId(long spitterId) {
		// TODO Auto-generated method stub
		return (List<Spittle>) entityManager.createQuery("select s from Spittle s, Spitter sp where s.spitter = sp and sp.id=? order by s.postedTime desc")
		        .setParameter(1, spitterId)
		        .getResultList();
	}

	@Override
	public void delete(long id) {
		// TODO Auto-generated method stub
		entityManager.remove(findOne(id));
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return entityManager.createQuery("select s from Spittle s").getResultList().size();
	}

}

 

JpaSpitterRepository:

package spittr.data;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import spittr.Spitter;

@Repository
@Transactional
public class JpaSpitterRepository implements SpitterRepository {
	
	@PersistenceContext	  
	private EntityManager entityManager;

	@Override
	public void save(Spitter spitter) {
		// TODO Auto-generated method stub
		entityManager.persist(spitter);
	}

	@Override
	public Spitter findByUsername(String username) {
		// TODO Auto-generated method stub
		return (Spitter) entityManager.createQuery("select s from Spitter s where s.username=?").setParameter(1, username).getSingleResult();
	}

	@Override
	public Spitter findOne(long id) {
		// TODO Auto-generated method stub
		return entityManager.find(Spitter.class, id);
	}

	@Override
	public List<Spitter> findAll() {
		// TODO Auto-generated method stub
		return (List<Spitter>) entityManager.createQuery("select s from Spitter s").getResultList();
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return findAll().size();
	}

}

        借助@PersistentContext注解为JpaSpitterRepository 设置EntityManager。这样的话,在每个方法中就没有必要再通过 EntityManagerFactory创建EntityManager了。@PersistenceContext并不会真正注入EntityManager——至少,精确来讲不是这样的。它没有将真正的EntityManager设置给Repository,而是给了它一 个EntityManager的代理。真正的EntityManager是与当前事务相关联的那一个,如果不存在这样的EntityManager的话,就会创 建一个新的。这样的话,我们就能始终以线程安全的方式使用实体管 理器。       

      JpaSpitterRepository使用了@Repository 和@Transactional注解。@Transactional表明这个Repository 中的持久化方法是在事务上下文中执行的。 对于@Repository注解,它的作用与开发Hibernate上下文Session版 本的Repository时是一致的。由于没有使用模板类来处理异常,所以我们需要为Repository添加@Repository注解,这 样PersistenceExceptionTranslationPostProcessor就会知道要将这个bean产生的异常转换成Spring的统一数据访问异常。          

       既然提到了 PersistenceExceptionTranslationPostProcessor,要记住的是我们需要将其作为一个bean装配到Spring中,就像我们在 Hibernate样例中所做的那样:

    @Bean
    public BeanPostProcessor persistenceTranceslation() {
    	return new PersistenceExceptionTranslationPostProcessor();
    }

提醒一下,不管对于JPA还是Hibernate,异常转换都不是强制要求的。如果你希望在Repository中抛出特定的JPA或Hibernate异常,只需将PersistenceException-TranslationPostProcessor省略掉即可,这样原来的异常就会正常地处理。但是,如果使用了Spring 的异常转换,你会将所有的数据访问异常置于Spring的体系之下,这样以后切换持久化机制的话会更容易。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值