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

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并不是线程安全的,不适合注入到iRepository这种单例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的体系下,这样以后如果切换持久化机制会更容易一些。

原文地址:https://www.cnblogs.com/lz2017/p/9066854.html

时间: 2024-10-09 23:54:06

使用对象-关系映射持久化数据的相关文章

ASP.NET5实践02:EF7迁移-连接字符串读取-增删改查-关系映射

1.概述 本章重点本应该先从实体关系映射介绍,就像做网页设计先从整体布局开始一样. 最好先基本搞明白实体关系映射到数据表关联关系之后,再研究实体属性或表字段细节. EF7.x和EF6.x区别是很大的.EF7为了迎合NoSql,与以前单一处理关系型数据库映射有一些不同的理念. 在讲这之前,我们先学习EF7迁移和数据库字符串配置读写. 这算准备工作,虽然有些啰嗦,但这是写这篇博客的思路.既然是实践系列,就边体验便写博客! 2.手动迁移 实体类: public class Role { public

持久化和对象关系映射

持久化:持久(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘文件.XML文件等).持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中.XML数据文件中等等. 持久层:主要实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联. 对象数据映射(ORM):Object/Relational Mapper,即“对象-关系型数据映射组件”.对于O/R,即 Object(对象)和 Relational(关系

解析大型.NET ERP系统数据访问 对象关系映射框架LLBL Gen Pro

LLBL Gen Pro是一个为.NET开发人员设计的的对象关系映射(ORM)框架,与NHibernate,Entity Framework等框架一样,通过实体与数据表的映射,实现关系数据库持久化. 1  LLBL Gen Pro 入门  LLBL Gen Pro Basic 打开LLBL Gen Pro程序,在右边的数据库浏览器(Catelog Explorer)中根结点右键选择从关系数据库创建关系模型( Add Relational Model Data from a Database),然

持久化API(JPA)系列(六)实体关系映射(ORM)之映射类型

ORM实体关系映射,即将数据库中的数据表及表之间的关系,通过实体Bean及实体Bean之间的关系表现出来,实现通过操作实体Bean来操作数据库. ORM(Object-Relation-Map),其中Object表示实体Bean,Relation表示数据表,Map表示实体Bean与数据表的映射. 由于EJB3中的实体Bean采用JPA框架,因此这里的ORM就是指JPA映射.它的作用也类似于Hibernate.iBATIS.JDO.TopLink等持久化层框架中的实体关系映射. 根据表与表之间的关

持久化API(JPA)系列(八)实体关系映射(ORM)之单表映射@EmbeddedId

接上文<持久化API(JPA)系列(七)实体关系映射(ORM)之单表映射@IdClass> 本文将介绍<联合主键:使用@EmbeddedId嵌入外部主键> 上文是通过@IdClass引用外部主键,联合主键也可以采用嵌入式主键替代. 1)新建外部主键类Family2.java 设置联合主键man和woman,因此外部主键也需要定义两个相同的变量,并添加一个以两个变量为输入的构造函数,同时添加getter/setter函数. 主键类必须满足: 1.必须实现Serializable接口,

第54篇ORM对象关系映射 如何使用ORM与数据可建立连接

1. ORM(对象关系映射) 很多语言的很多web框架中都有这个概念 2. 为什么要有ORM? 1. 写程序离不开数据. 2. 在Python程序中要用到数据库中的数据,怎么办? 1. 使用pymysql连接MySQL数据库的步骤 1. import pymysql 2. 建立连接 conn = pymysql.connect( host='127.0.0.1', port=3306, database='day43', user='root', password='123', charset=

Hibernate(开放源代码的对象关系映射框架)

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库. Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久

Hibernate (开放源代码的对象关系映射框架)

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库. Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久

Hibernate基础学习(四)&mdash;对象-关系映射(上)

一.映射对象标识符      Java语言按内存地址来识别或区分同一个类的不同对象,而关系数据库按主键值来识别或区分同一个表的不同记录.Hibernate使用对象标识符(OID)来建立内存中的对象和数据库表中的记录的对应关系,对象的OID和数据库表的主键对应,为了保证OID的唯一性和不可变性,应该让Hibernate,而不是应用程序来为OID赋值.     Hibernate推荐在数据表中使用代理主键,即不具备业务含义的字段.代理主键通常为整型,因为整型比字符串要节省更多数据库空间.     在