Spring学习笔记之使用对象-关系映射持久化数据

ORM(object-relational mapping)——对象/关系 映射。
Spring对多个ORM框架提供了支持。下面分别介绍Spring对Hibernate和JPA(Java持久化API,java Persistence API)的支持。
maven:

<dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-ormartifactId>
        <version>4.3.8.RELEASEversion>
    dependency>

1.在Spring中集成Hibernate

1.1声明Hibernate的Session工厂

使用Hibernate所需的主要接口是org.hibernate.Session。Session接口提供了基本的数据访问功能。获取Hibernate Session对象的标准方式是借助于Hibernate Session Factory接口的实现类。SessionFactory主要负责Hibernate Session的打开、关闭以及管理。
从3.1版本开始,Spring提供了三个Session工厂bean供我们选择:

  • org.springframework.orm.hibernate3.LocalSessionFactoryBean
  • org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
  • org.springframework.orm.hibernate4.LocalSessionFactoryBean

至于选择哪一个Session工厂,取决于使用哪一个版本的Hibernate以及使用XML还是使用注解定义对象-数据库之间的映射关系。XML——LocalSessionFactoryBean
,注解——AnnotationSessionFactoryBean。
org.springframework.orm.hibernate4.LocalSessionFactoryBean
类似于前俩个的结合体,能够支持基于XML的映射和基于注解的映射。

/**
     * org.springframework.orm.hibernate3.LocalSessionFactoryBean
     * @param dataSource
     * @return
     */
    @Bean
    @Autowired
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource){
        LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
        sfb.setDataSource(dataSource);
        sfb.setMappingResources(new String[] {"test.hnm.xml"});//设置Hibernate映射文件
        Properties prop = new Properties();
        prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
        sfb.setHibernateProperties(prop);
        return sfb;
    }

    /**
     * org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean

     * @param dataSource
     * @return
     */
    @Bean
    @Autowired
    public AnnotationSessionFactoryBean sessionFactory(DataSource dataSource){
        AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setPackagesToScan(new String[] {"com.sh.domin"});
        Properties prop = new Properties();
        prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
        bean.setHibernateProperties(prop);
        return bean;
    }

/**
     * org.springframework.orm.hibernate4.LocalSessionFactoryBean
     * 基于注解的方式
     * @param dataSource
     * @return
     */
    @Bean
    @Autowired
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource){
        LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
        sfb.setDataSource(dataSource);
        //使用PackagesToScan属性告诉Spring要扫描一个或多个包以查找域类,这些类通过主机的方式表明
        //要使用Hibernate进行持久化,这些类可以使用的注解包括JPA的@Entity或@MappedSuperclass以及Hibernate的@Entity。
        sfb.setPackagesToScan(new String[] {"com.sh.domin"});
        Properties properties = new Properties();
        properties.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
        sfb.setHibernateProperties(properties);
        return sfb;
    }

1.2构建不依赖于Spring 的hibernate代码

使用上下文Session.

@Repository
public class HibernateRepository {

    @Autowired
    private SessionFactory sessionFactory;//注入SessionFactory

    public Session currentSession(){
        return sessionFactory.getCurrentSession();//从SessionFactory众获取当前Session
    }

    @Transactional
    public List list(){
        //使用当前Session查询user表中的所有数据方法list集合中
        return (List)currentSession().createCriteria(User.class).list();
    }
}

实体User:

@Entity()
@Table(name="user")
public class User implements Serializable {

    private static final long serialVersionUID = -4869287368065710953L;

    @Id
    private int id;
    private String username;
    private String password;
    private int sex;

    @Column(name="id",nullable=false)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="username")
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Column(name="password")
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Column(name="sex")
    public int getSex() {
        return sex;
    }
    public void setSex(int sex) {
        this.sex = sex;
    }

}

@Repository是Spring的另一种构造性注解,它能够像其他注解一样被Spring的组件扫描所扫描到,只要这个Repository类在组件扫描所涵盖的包中即可。它还有一项任务就是捕获平台相关的异常,然后使用Spring统一非检查型异常的形式重新抛出。因此,我们需要在Spring应用上下文中添加一个PersistenceExceptionTranslationPostProcessor Bean:

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

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

1.3 遇见的异常

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
原因:Hibernate4 No Session found for current thread原因
此demo的解决办法:
1.启用TransactionManagement:

@Configuration
@ComponentScan(basePackages={"com.sh.*"},
excludeFilters={
        @Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
})
@EnableTransactionManagement
public class RootConfig {
...
}

2.配置Bean:

@Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory){
        HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(sessionFactory);
        return transactionManager;
    }

3.访问持久层时加注解@Transactional

@Transactional
    public List list(){
        //使用当前Session查询user表中的所有数据方法list集合中
        return (List)currentSession().createCriteria(User.class).list();
    }

Connection was closed in SingleConnectionDataSource
解决办法:设置SuppressClose为true

/**
     * 配置数据源
     * @return
     */
    @Bean
    public DataSource dataSource(){
        SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/ceshi");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setSuppressClose(true);
        return dataSource;
    }

org.hibernate.AnnotationException: No identifier specified for entity异常
原因:在使用Hibernate的映射表的时候Entity类是必须要有主键的,否则就会报这个异常。
解决办法:
主键上加上: @Id
@Column(name = “id”, unique = false, nullable = false)
例如上面User类写的那样。

demo源码:SpringMVC4+Hibernate4 Demo

2 Spring与Java持久化API

Java持久化API(Java Persistence API,JPA)诞生在EJB2实体Bean的废墟之上,并成为下一代Java持久化标准。JPA是基于POJO的持久化机制,它从Hibernate和Java数据对象(Java Data Object,JDO)上借鉴了很多理念并加入了Java5注解的特性。

2.1配置实体管理器工厂

在Spring中使用JPA的第一步是要在Spring应用上下文中将实体类管理器工厂(entity manager factory)按照bean的形式进行配置。
基于JPA的应用程序需要使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:

  • 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事物中对其进行控制。这种方式的实体管理器适合于不运行在Java EE容器的独立应用程序。
  • 容器管理类型(Container-managed):实体管理器由Java EE创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或JNDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适合与Java EE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。

对于使用JPA的Spring开发者来说,不管你使用哪种EntityManagerFactory。Spring都负责管理EntityManager。对于应用程序管理类型的实体管理器,Spring承担了应用程序的角色并以透明的方式处理EntityManager。在容器管理的场景下,Spring会担当容器的角色。
这两种实体管理器工厂分别有对应的Spring工厂Bean创建:

  • LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManagerFactory
  • LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory。

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

  

<persistence version="1.0"  
xmlns:persistence="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 persistence_1_0.xsd ">  

  
<persistence-unit name="unitName" transaction-type="JTA">  

     
   <description> description>  

     
   <provider>   provider>  

     
   <jta-data-source>java:/MySqlDSjta-data-source>  
   <non-jta-data-source> non-jta-data-source>  

     
   <mapping-file>product.xmlmapping-file>  

     
   <jar-file>../lib/model.jarjar-file>  

     
   <class>com.domain.Userclass>  
   <class>com.domain.Productclass>  

     
   <exclude-unlisted-classes/>  

     
   <properties>  
      
    <property name="hibernate.hbm2ddl.auto" value="update" />  
    <property name="hibernate.show_sql" value="true" />  
   properties>  

persistence-unit>  

persistence>

Bean:

/**
     * 配置应用程序管理类型的JPA
     * @return
     */
    @Bean
    public LocalEntityManagerFactoryBean entityManagerFactoryBean(){
        LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean();
        emfb.setPersistenceUnitName("unitName");
        return emfb;
    }

使用容器管理类型的JPA
Bean:


    /**
     * 配置容器管理类型的JPA
     */
    @Bean
    @Autowired
    public LocalContainerEntityManagerFactoryBean containerEntityManagerFactoryBean(DataSource dataSource,JpaVendorAdapter jpaVendorAdapter){
        LocalContainerEntityManagerFactoryBean lcemf = new LocalContainerEntityManagerFactoryBean();
        lcemf.setDataSource(dataSource);
        //指定使用哪一个厂商的JPA实现
        lcemf.setJpaVendorAdapter(jpaVendorAdapter);
        //扫描com.jpa.domin包下带有@Entity注解的实体类
        lcemf.setPackagesToScan("com.jpa.domin");
        return lcemf;
    }

jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:

  • EclipseLinkJpaVendorAdapter
  • HibernateJpaVendorAdapter
  • OpenJpaVendorAdapter
  • TopLinkJpaVendorAdapter(在spring3.1的版本中已经将其废弃)

这里使用Hibernate作为JPA实现

/**
     * 指明使用哪一个厂商的JPA实现
     * @return
     */
    @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;
    }

database属性指定的使用的数据库。
Hibernate的JPA适配器支持多种数据库,可以通过database属性配置使用哪一个数据库:

数据库平台 属性database的值
IBM DB2 DB2
Apache Derby DERBY
H2 H2
Hypersonic HSQL
Informix INFORMIX
MySQL MYSQL
Oracle ORACLE
PostgresQL POSTGRESQL
Microsoft SQL Server SQLSERVER
Sybase SYBASE

2.2编写基于JPA的Repository

@Repository
@Transactional
public class TestRepository {

    @PersistenceUnit
    private EntityManagerFactory emf;

    public User findById(int id){
        return this.emf.createEntityManager().find(User.class, id);
    }
}

@PersistenceUnit注解会将EntityManagerFactory 注入到Repository中。
这里麻烦的是每个方法都会调用this.emf.createEntityManager().来创建EntityManager。因为EntityManager并不是线程安全的,所以我们不能预先准备好它。但是我们可以借助@PersistenceContext注解为Repository设置EntityManager:

@Repository
@Transactional
public class JpaRepository {

    @PersistenceContext
    private EntityManager em;

    public User getUserById(int id){
        return em.find(User.class, id);
    }

}

真相是@PersistenceContext并不会真正注入EntityManager——精确来讲是这样的。他没有将真正的EntityManager设置给Repository,而是给了他一个EntityManager的代理。真正的EntityManager是与当前事务相关联的哪一个,如果不存在这样的EntityManager的话,就会创建一个新的。
@Transactional表明这Repository中的持久化方法是在事务上下文中执行。
最后,因为我们没有使用spring 模板,所有要配置异常转换的bean给@Repository注解:

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

不过,不论是JPA还是Hibernate,这个是可以省略的,如果你希望在Repository中抛出特定的JPA或Hibernate异常,只需将PersistenceExceptionTranslationPostProcessor省略掉即可,这样原来的异常就会正常的处理。不会转换成spring的异常体系。
本节demo:Spring MVC+JPA Demo

3.借助Spring Data实现自动化的JPA Repository

Spring Data 能够让我们之编写Repository接口就可以了,而不需要实现类。

public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{

}

编写Spring Data JPA Repository的关键在于要从一组接口中挑选一个进行扩展。这里我们扩展了Spring data JPA的JpaRepository。后面会介绍其他几个接口。通过这种方式,JpaRepository进行了参数化,所有它就能知道这是一个用来持久化User对象的Repository,并且User的ID类型为Integer。另外它还会继承18个执行持久化的通用方法。
我们不需要创建ISpringDataJpaRepository 的实现,它的实现是由Spring Data完成的。为了让Spring Data 创建ISpringDataJpaRepository 的实现,我们需要在Spring配置中添加一个元素:
XML配置:


<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
       default-lazy-init="true">

    <description>SpringJpa配置description>
    ...
 
    <jpa:repositories base-package="com.jpa.dao"  transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>

    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    bean>
    ...
bean>

java配置:

@EnableJpaRepositories(basePackages="com.jpa.dao")
public class RootConfig {
    ...
    /**
     * JPA事物管理器
     * @param emf
     * @return
     */
    @Bean
    @Autowired
    public JpaTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager jtm = new JpaTransactionManager(emf);
        return jtm;
    }
}

这里我们指定了一个基础包,会扫描它的基础包来查找扩展自Spring Data JPA Repository接口的所有接口。如果发现了扩展自Repository的接口,它会自动生成(在应用启动的时候)这个接口的实现。

配置好了这些以后就可以使用了,我们之前说过,现在的这个接口已经继承了18个方法,所以我们用其中的一个findone():

@Controller
public class TestController {

    @Autowired
    private ISpringDataJpaRepository sdjr;

    @RequestMapping("findone")
    public String findOne(){
        User user = this.sdjr.findOne(1);
        System.out.println(user.getUsername());
        return "index";
    }

}

3.1定义查询方法

public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{

    User findByUsername(String username);

}

当创建Repository实现的时候,Spring Data会检查Repository接口的所有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的目的。本质上,Spring Data定义了一组小型的领域特定语言(domin-specific language,DSL),在这里,持久化的细节都是通过Repository方法的签名来描述的。
Repository方法是一个由动词、一个可选的主题(subject)关键词By以及一个断言所组成的。在上面那个方法中,动词是find,断言是Username,主题并没有指定,暗含的主题是User。
又比如这个:readUserByFirstnameOrLastname()。
Spring Data 允许在方法名中使用四种动词:get,read,find和count,其中,动词get,read和find是同一的,这三个动词对应的Repository方法都会查询数据并返回对象。而动词count则会返回匹配对象的数量,而不是对象本身。
Repository方法的主题是可选的。它的主要目的是让你在命名方法的时候,有更多的灵活性。
要查询的对象类型是通过如何参数化JpaRepository接口来确定的,而不是方法名称中的主题。
在省略主题的时候,有一种例外情况。如果主题的名称以Distinct开头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。
Spring学习笔记之使用对象-关系映射持久化数据_第1张图片
在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。不过,我们也可以选择其他的比较操作,包括如下的种类:

  • IsAfter,After,IsGreaterThan,GreaterThan
  • IsGreaterThanEQual,GreaterThanEqual
  • IsBefore,Before,IsLessThan,LessThan
  • IsLessThanEqual,LessThanEqual
  • IdBetween,Between
  • IdNull,Null
  • IsNotNull,NotNull
  • IsIn,In
  • IsNotIn,NotIn
  • IsStartingWith,StartingWith,StartsWith
  • IsEndingWith,EndingWith,EndsWith
  • IsContaining,Containing,Contains
  • IsLike,Like
  • IsNotLike,NotLike
  • IsTrue,True
  • IsFalse,False
  • Is,Equals
  • IsNot,Not
    要处理String类型的属性时,条件中可能还会包含IgnoringCase或IgnoresCase,这样在执行对比的时候就会不在考虑字符是大写还是小写。
    例如:
List readByFirstnameIgnoringCaseOrLastnameIgnoringCase(String first, String last);

或者这样,结果是一样的:

List readByFirstnameOrLastnameAllIgnoringCase(String first, String last);

排序:

List readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first,String last);

可以看到条件部分可以通过And或者Or进行分割。
如下给出了几个符合方法命名约定的方法签名:

  • List findPetsByBreedIn(List breed)
  • int countProductsByDisContinuedTrue()
  • List findByShippingDateBetween(Date start, Date end)

3.2声明自定义查询

如果所需的数据无法通过方法名称进行恰当的描述,那么我们可以使用@Query注解,为Spring Data提供要执行的查询。

//这里的from User指的是User对象,换成user则无法运行
    @Query("select u from User u where u.email like '%qq.com'")
    List findAllQQEmailUser();

3.3混合自定义的功能

当Spring Data JPA为Repository接口生成实现的时候,它还会查找名字与接口相同,并且添加了Impl后缀的一个类。如果这个类存在的话,Spring Data JPA将会把它的方法与Spring Data JPA所生成的方法合并在一起。对于我们的接口ISpringDataJpaRepository来说,要查找ISpringDataJpaRepositoryImpl类。
所以,在使用Spring Data JPA的同时,我们也可以使用原始的EntityManager。
这是我们的ISpringDataJpaRepositoryImpl类:

@Transactional
public class ISpringDataJpaRepositoryImpl implements UserSweeper{

    @PersistenceContext
    private EntityManager em;

    public int sexSweep() {

        String update = "UPDATE User user set user.sex = 1 WHERE user.sex = 0";
        return em.createQuery(update).executeUpdate();
    }

}

这里,我们没有实现ISpringDataJpaRepository。ISpringDataJpaRepository将由Spring Data JPA实现。ISpringDataJpaRepositoryImpl实现了另一个接口UserSweeper:

public interface UserSweeper {

    int sexSweep();
}

最后,让ISpringDataJpaRepository继承一下UserSweeper,或者只要确保UserSweeper中的方法在ISpringDataJpaRepository中出现,就能把ISpringDataJpaRepositoryImpl中的方法和Spring Data JPA为ISpringDataJpaRepository生成的方法绑定在一起。然后通过ISpringDataJpaRepository来使用它:

public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>,UserSweeper{...}

使用:

@Controller
public class TestController {

    @Autowired
    private ISpringDataJpaRepository sdjr;

    @RequestMapping("sex")
    public String countSex(){

        int count = this.sdjr.sexSweep();
        System.out.println(count);
        return "index";
    }

}

如前所述,Spring Data JPA将实现类与接口关联起来是基于接口的名称。但是,Impl后缀只是默认的做法,如果你想使用其他后缀的话,只需要在配置@EnableJpaRepositories的时候设置repositoryImplementationPostfix属性即可。下面把后缀改为Helper:
java配置:

@EnableJpaRepositories(basePackages="com.jpa.dao",
        repositoryImplementationPostfix="Helper")

XML配置:

<jpa:repositories base-packages="com.jpa.dao" 
    repository-impl-postfix="Helper" />

本节Demo:Spring Data JPA Demo

你可能感兴趣的:(Spring学习笔记)