使用对象-关系映射持久化数据

JDBC可以比较好的完成数据持久化的工作,并在一些特定的场景下表现出色。但随着应用越来越复杂,对于持久化的需求也越来越复杂:例如,在每次操作数据库的时候,都可以自动的完成属性与字段的对应关系,而不是每次自己去封装对象或指定列名;对于易错的SQL,无休止的问号字符串,我们希望可以自动生成语句和查询;此外,我们还需要一些更复杂的特性:

  • 延时加载(Lazy loading):随着对象变得越来越复杂,有的时候我们不希望也没必要立即获取完成的对象间关系。延迟加载允许我们只在需要i的时候获取数据。
  • 预先抓取(Eager fetching):这是与延迟加载相对应的。借助于预先抓取,我们可以使用一个查询获得完整对象的信息。这样在一次查询中,把所有的数据都得到,节省多次查询的成本。
  • 级联(Cascading):有时候修改数据库中表的时候会同时修改其他表,这个时候就需要使用级联。

在持久层使用ORM工具,可以节省数千行的代码和大量的时间。ORM工具能够把我们的注意力从容易出错的SQL中转向如何实现应用程序的真正需求。

这里记录一下使用Spring整合Hibernate和JPA的方式,因为在之前的学习中,这两项技术与Spring的整合都实现过,所以这里只记录一些觉得重要的东西。

在Spring中集成Hibernate

声明Hibernate的Session工厂

使用Hibernate所需要的主要接口是org.hibernate.Session。Session接口提供了基本的数据库访问功能。获取Hibernate的 Session对象的方式是借助Hibernate Sessionfactory接口的实现类。SessionFactory主要负责Hibernate session的打开,关闭以及管理。

Spring中提供了两个SessionFactory的bean供我们使用:

  • LocalSessionFactoryBean(优先使用)
  • AnnotationSessionFactoryBean

这些Session工厂bean都是Spring FactoryBean接口的实现,它们会产生一个Hibernate SessionFactory,它能够装配进任何SessionFactory 类型的属性中。

LocalSessionFactoryBean之前是处理XML映射Hibernate所需要的工厂bean,而 AnnotationSessionFactoryBean 则是处理注解映射HIbernate所需的工厂bean,但是在Hibernate4及之后,LocalSessionFactoryBean能够支持基于XML和注解的映射配置,所以我们优先使用这个工厂bean,注意使用与当前Hibernate版本一致的工厂bean。

配置DataSource不用再多说了,配置LocalSessionFactoryBean时,属性dataSource需要装配一个DataSource bean的应用;属性packagesToScan告诉Spring扫描一个或多个包以查找域对象,这些域对象通过使用Hibernate的@Entity或JPA 的@Entity注解表明要进行持久化;属性hibernateProperty指定一些配置信息,例如方言之类的;属性mappingResources可以指定一个或多个Hibernate映射文件(使用XML而不是注解配置映射,就不使用packagesToScan而通过mappingResources指定映射文件;如果还有Hibernate的主配置文件,使用configLocations属性指定主配置文件位置,取消主配置文件就使用hibernateProperty添加配置设置。

  @Bean
  public SessionFactory sessionFactoryBean() {
    try {
      LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
      lsfb.setDataSource(dataSource());
      lsfb.setPackagesToScan("cn.lynu.domain");
      Properties props = new Properties();
      props.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
      lsfb.setHibernateProperties(props);
      return lsfb;
    } catch (IOException e) {
      return null;
    }
  }
}

在Spring应用上下文中配置完Hibernate的Session工厂bean后,就可以创建Repository。

构建不依赖Spring的Hibernate代码

在早期Spring与Hibernate整合的时候,编写Repository将会涉及Spring的HibernateTemplate,现在比较好的做法是不再使用HibernateTemplate,而是使用上下文Session,通过将Hibernate SessionFactory装配到Repository中,并使用它来获取Session。

@Repository
public class HibernateSpitterRepositoryImpl implements SpitterRepository {

    private SessionFactory sessionFactory;

    @Inject
    public HibernateSpitterRepository(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;         
    }
    
    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }
    
    public long count() {
        return findAll().size();
    }

    public Spitter save(Spitter spitter) {
        Serializable id = currentSession().save(spitter);  
        return new Spitter((Long) id, 
                spitter.getUsername(), 
                spitter.getPassword(), 
                spitter.getFullName(), 
                spitter.getEmail(), 
                spitter.isUpdateByEmail());
    }

    public Spitter findOne(long id) {
        return (Spitter) currentSession().get(Spitter.class, id); 
    }

    public Spitter findByUsername(String username) {        
        return (Spitter) currentSession() 
                .createCriteria(Spitter.class) 
                .add(Restrictions.eq("username", username))
                .list().get(0);
    }

    public List<Spitter> findAll() {
        return (List<Spitter>) currentSession() 
                .createCriteria(Spitter.class).list(); 
    }
    
}

首先使用@Inject组件让Spring自动将一个SessionFactory注入到 HibernateSpitterRepositoryImpl 的sessionFactory属性中。接下来,我们使用currectSession获得当前事务的Session。现在唯一与Spring有关的应该就只剩下@Repostiory注解了,如果需要进一步不与Spring耦合,可以不用这个注解,手动将Repository声明为一个bean。

因为我们这里没有使用HibernateTemplate,所以对于异常也没有转换为Spring中数据访问的通用异常,如果需要转换,只需在Spring引用上下文中添加PersistenExceptionTranslationProcessor bean:

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

PersistenExceptionTranslationProcessor 是一个bean后置处理器,它会在所有拥有@Repostiory注解的类上添加一个通知,这样就会捕获任何平台相关的一场并以Spring非检查型数据访问异常重新抛出。

Spring与Java 持久层 API

JPA是基于POJO的持久化机制,它是Java用于统一ORM框架的标准,既然是标准就需要具体实现,Hibernate经常作为JPA的实现产品,当然还可以使用其他的ORM框架 。在Spring中使用JPA的第一步要在Spring应用上下文中将实体管理器工厂(entityManagerFactory)按照bean的形式进行配置。

配置实体管理器工厂

基于JPA的应用程序需要使用EntityManagerFactory的实现类获取EntityManager实例。Spring中有两种实体管理器工厂:

LocalEntityManagerFactoryBean 生成应用是程序管理的EntityManagerFactory,应用程序向实体管理器工厂直接请求实体管理器,工厂非常创建一个实体管理器,这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制(persistence.xml文件中进行JPA配置)。这种方式适合于不运行在JavaEE容器中的独立应用程序。

LocalContainerEntityManagerFactoryBean (优先使用)生成容器管理的EntityManagerFactory,实体管理器的创建和管理,应用程序根本不需要与实体管理器工厂打交道,相反,容器来管理工厂,实体管理器直接通过注入或JNDI的方式获取,最适用于JavaEE容器中,这种情况下会希望在persistence.xml指定的JPA配置之外可以有些自己对JPA的控制,或者是完全不需要persistence.xml配置文件。

配置应用程序管理的JPA

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

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
    <persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
        <!-- 
        配置使用什么 ORM 产品来作为 JPA 的实现 
        1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
        2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
        -->
        <!-- <provider>org.hibernate.ejb.HibernatePersistence</provider> -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    
        <!-- 添加持久化类 (推荐配置)-->
        <class>cn.lynu.model.User</class>

        <properties>
            <!-- 连接数据库的基本信息 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            
            <!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>

        </properties>
    </persistence-unit>
</persistence>

例如这里的配置文件:指定了一个持久化类,JPA实现产品,数据源以及一些属性配置。

因为在persistence.xml文件中包含了大量的配置信息,所以Spring中就很少了,只需要通过@Bean注解在Spring中声明LocalEntityManagerFactoryBean :

@Bean
public LocalEntityManagerFactoryBena entityManagerFactoryBean(){
   LocalEntityManagerFactoryBean emfb=new LocalEntityManagerFactoryBena();
   emfb.setPersistenceUnitName("jpa-1");
   return emfb;
}

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

在应用程序管理的场景下(不考虑Spring时),创建实体管理器工厂的配置都是在persistence.xml文件中,完全由程序去控制,其实就是由配置的JPA实现产品 (provider属性指定的值)去做到的。如果借助Spring,我们不再需要直接处理JPA实现产品,也不用在persistence.xml文件中配置数据源(因为一般数据源都是交给Spring管理的),甚至我们可以取消这个XML文件。

使用容器管理JPA(推荐)

在这里的容器就是Spring了,我们把数据源的配置放在Spring应用上下文,而不是persistence.xml文件中。首先是DataSource的配置,这里不再多说了。然后来配置JPA的实现产品,将其声明为一个Bean:

  @Bean
  public JpaVendorAdapter jpaVendorAdapter() {
    HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
    adapter.setDatabase(Database.MYSQL);
    adapter.setShowSql(true);
    adapter.setGenerateDdl(false);
    // 设置方言   
    adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
    return adapter;
  }

最后,使用@Bean配置使用LocalContainerEntityManagerFactoryBean,使用packageToScan属性指定持久化单元中的实体类所在位置:

  @Bean
  public LocalContainerEntityManagerFactoryBean emf(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setDataSource(dataSource);
    emf.setJpaVendorAdapter(jpaVendorAdapter);
    emf.setPackagesToScan("cn.lynu.domain");
    return emf;
  }

packageToScan属性配置会去扫描对应包下带有@Entity注解的实体类。persistence.xml文件完全没有存在的必要了。

从JNDI获取实体类管理工厂

如果EntityManagerFactory可能已经创建好并位于JNDI中等待使用,在这种情况下,可以使用Spring jee命名空间下的<jee:jndi-lookup>元素获取对EntityManagerFactory的引用:

<jee:jndi-lookup id="emf" jndi-name="persistence/jpa-1">

或者使用Java配置来获取EntityManagerFactory:

@Bean
public JndiObjectFactoryBean entityManagerFactory(){
   JndiObjectFactoryBean jndiObjectFB=new JndiObjectFactoryBean();
   jndiObjectFB.setJndiName("persistence/jpa-1");
   return jndiObjectFB;
} 

使用Java配置JNDI的方式虽然没有直接返回一个EntityManagerFactory,但是它所返回的 JndiObjectFactoryBean 是FactoryBean接口的实现,只要指定的JNDIName是一个EntityManagerFactory,就可以正确创建实体管理工厂。

编写基于JPA的Repository

为了实现更为纯粹的JPA方式,与Hibernate类似,我们避免与Spring耦合。有两种方式:

  1. 给Repository中使用@PersistenceUnit注入EntityManagerFactory,每次操作通过这个工厂的createEntityManager()方法去拿EntityManager,再通过EntityManager进行数据访问操作,这意味着每次调用Repository中的方法,都会创建一个新的EntityManager。
  2. 我们希望可以预先创建好EntityManager,但是EntityManager并不是线程安全的,不适合注入到Repository这种单例bean(Spring中的bean默认都是单例)中。我们需要通过@PersistenceContext注解来为Repository设置EntityManager。推荐使用这种方式。
@Repository
public class JpaSpitterRepositoryImpl implements SpitterRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public Spitter save(Spitter spitter) {
        entityManager.persist(spitter);
        return spitter;
    }

}

刚才说了EntityManager不是线程安全的,所以会不会有线程安全的问题呢?是不会的,因为@PersistenceContext并不会真正注入EntityManager给Repository,而是给一个EntityManager的代理,真正的EntityManager是与当前事务相关联的,如果不存在就会新创建一个。这样的话,我们就能始终以线程安全的方式使用实体管理器。

注意@PersistenceUnit和@PersistenceContext并不是Spring中的注解,而是JPA规格提供的。为了让Spring理解这些注解,需要配置PersistenceAnnotationBeanPostProcessor这个bean后置处理器,如果使用了<context:annotation-config>或<context:component-scan>  /@ComponentScan 就不用担心了,因为这些配置会自动注册这个后置处理器,否则的话,我们就需要显示地注册这个bean:

@Bean
public PersistenceAnnotationBeanPostProcessor paPostProcessor(){
    return new PersistenceAnnotationNeamPostProcessor();  
}

由于也没有使用Spring中的模板类,所以需要转换为Spring统一的数据访问异常,就需要我们手动注册PersistenceExceptionTranslationPostProcessor这个bean将产生的异常转换为Spring的统一数据访问异常。

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

其实不管是JPA还是Hibernate,异常的转换并不是必须的。如果我们允许在Repository中抛出特定的JPA或Hibernate异常,只需要将PersistenceExceptionTranslationPostProcessor省略掉即可。如果使用了Spring的异常转换,就可以将所有的数据访问异常置于Spring的体系下,这样以后如果切换持久化机制会更容易一些。

 

posted @ 2018-05-21 18:22  OverZeal  阅读(320)  评论(0编辑  收藏  举报