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耦合。有两种方式:
- 给Repository中使用@PersistenceUnit注入EntityManagerFactory,每次操作通过这个工厂的createEntityManager()方法去拿EntityManager,再通过EntityManager进行数据访问操作,这意味着每次调用Repository中的方法,都会创建一个新的EntityManager。
- 我们希望可以预先创建好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