zoukankan      html  css  js  c++  java
  • 使用对象-关系映射持久化数据

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

  • 相关阅读:
    python中的编码问题
    CVPR2018 Tutorial 之 Visual Recognition and Beyond
    hdu 1376 Octal Fractions
    hdu 1329 Hanoi Tower Troubles Again!
    hdu 1309 Loansome Car Buyer
    hdu 1333 Smith Numbers
    hdu 1288 Hat's Tea
    hdu 1284 钱币兑换问题
    hdu 1275 两车追及或相遇问题
    hdu 1270 小希的数表
  • 原文地址:https://www.cnblogs.com/lz2017/p/9066854.html
Copyright © 2011-2022 走看看