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


    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的体系下,这样以后如果切换持久化机制会更容易一些。

  • 相关阅读:
    图像滤波与OpenCV中的图像平滑处理
    OpenCV创建轨迹条,图片像素的访问
    模板类和友元的总结和实例验证
    C++中运算符重载
    C++之Stack模板类
    C++中explicit关键字的作用
    #ifdef-#endif的作用及其使用技巧
    ZOJ 3170 Friends
    ZOJ 3713 In 7-bit
    HDU 1421 搬寝室
  • 原文地址:https://www.cnblogs.com/lz2017/p/9066854.html
Copyright © 2020-2023  润新知