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

来源:互联网 发布:淘宝退款在哪里看 编辑:程序博客网 时间:2024/05/16 18:30

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

<dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-orm</artifactId>        <version>4.3.8.RELEASE</version>    </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.

@Repositorypublic class HibernateRepository {    @Autowired    private SessionFactory sessionFactory;//注入SessionFactory    public Session currentSession(){        return sessionFactory.getCurrentSession();//从SessionFactory众获取当前Session    }    @Transactional    public List<User> list(){        //使用当前Session查询user表中的所有数据方法list集合中        return (List<User>)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)})@EnableTransactionManagementpublic 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<User> list(){        //使用当前Session查询user表中的所有数据方法list集合中        return (List<User>)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

<?xml version="1.0" encoding="UTF-8"?>  <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 ">  <!--         Name属性用于定义持久化单元的名字 (name必选,空值也合法);        transaction-type 指定事务类型(可选)    -->  <persistence-unit name="unitName" transaction-type="JTA">     <!-- 描述信息.(可选) -->     <description> </description>     <!-- javax.persistence.PersistenceProvider接口的一个实现类(可选) -->     <provider>   </provider>     <!-- Jta-data-source和 non-jta-data-source用于分别指定持久化提供商使用的JTA和/或non-JTA数据源的全局JNDI名称(可选) -->     <jta-data-source>java:/MySqlDS</jta-data-source>     <non-jta-data-source> </non-jta-data-source>     <!-- 声明orm.xml所在位置.(可选) -->     <mapping-file>product.xml</mapping-file>     <!-- 以包含persistence.xml的jar文件为基准的相对路径,添加额外的jar文件.(可选) -->     <jar-file>../lib/model.jar</jar-file>     <!-- 显式列出实体类,在Java SE 环境中应该显式列出.(可选) -->     <class>com.domain.User</class>     <class>com.domain.Product</class>     <!-- 声明是否扫描jar文件中标注了@Enity类加入到上下文.若不扫描,则如下:(可选) -->     <exclude-unlisted-classes/>     <!--   厂商专有属性(可选)   -->     <properties>      <!-- hibernate.hbm2ddl.auto= create-drop / create / update -->      <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@Transactionalpublic 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@Transactionalpublic 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配置:

<?xml version="1.0" encoding="UTF-8"?><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"/>    <!-- Jpa 事务管理器  -->    <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;    }}

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

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

@Controllerpublic 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开头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。
Repository方法命名
在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。不过,我们也可以选择其他的比较操作,包括如下的种类:

  • 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<User> readByFirstnameIgnoringCaseOrLastnameIgnoringCase(String first, String last);

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

List<User> readByFirstnameOrLastnameAllIgnoringCase(String first, String last);

排序:

List<User> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first,String last);

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

  • List<Pet> findPetsByBreedIn(List<String> breed)
  • int countProductsByDisContinuedTrue()
  • List<Order> 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<User> findAllQQEmailUser();

3.3混合自定义的功能

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

@Transactionalpublic 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{...}

使用:

@Controllerpublic 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

0 0
原创粉丝点击