君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

一、       概述

对于我们以前实现数据操作层,我们大部分是通过手工来创建,创建的过程一般分为下面几个步骤:

  1. 创建一个具有公共增、删、改、查及分页的基类dao接口
  2. 创建实现基类dao接口及各个方法的抽象类
  3. 创建具体的dao层,并继承基类dao接口
  4. 实现具体的dao层,继承基类的抽象类

现在我们有更好的方法替代这些工作了,spring Data家族给我们提供了一个现成的dao层框架,这里面有不同的项目,如Spring Data JPASpring Data Neo4j and Spring Data MongoDB,他们的共同特点是他们给我们提供了框架代码,不再需要我们自己去实现了。

而且,spring Data能自动创建实体dao的实现类和自定义查询。因此基于以前写得程序,我们几乎不再需要第四步骤了。

在这一章我们会学到如何利用spring Datajpa来调整我们的dao层。我们的项目是基于maven实现的,所以首先我们将配置pom,然后我们再进入实际的仓库代码。

 

二、       配置maven

  在maven中的pom中加入如下的依赖

 <dependency>

            <groupId>org.springframework.data</groupId>

            <artifactId>spring-data-jpa</artifactId>

            <version>${spring.data.version}</version>

        </dependency>

      

三、       配置持久化单元

  在类路径底下新建META-INF,在META-INF中建一个persistence.xml,其内容如下:

  <?xml version="1.0"?>

<persistence 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_1_0.xsd" version="1.0">

<!-- file:///D:\hibernate\hibernate-entitymanager-3.3.2.CR1\resources\org\hibernate\ejb\persistence_1_0.xsd-->

  <persistence-unit name="spring" transaction-type="RESOURCE_LOCAL">

    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <properties>

       

         <property name="hibernate.max_fetch_depth" value="3"/>

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

         <property name="hibernate.jdbc.fetch_size" value="18"/>

         <property name="hibernate.jdbc.batch_size" value="10"/>

         <property name="hibernate.show_sql" value="true"/>

         <property name="hibernate.format_sql" value="true"/>

         <!--

         <property name="hibernate.cache.provider_class" value="org.hibernate.cache.SingletonEhCacheProvider"/>

        <property name="hibernate.cache.use_second_level_cache" value="true"/>

        <property name="hibernate.cache.use_query_cache" value="true"/>

         -->

        

      </properties>

  </persistence-unit>

</persistence>

 

四、       配置applicationContext.xml文件

  在resources加入spring的配置文件applicationContext.xml,日志配置和jdbc配置详情请参考源码。applicationContext.xml中的内容如下

  <beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="

        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd

        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd

        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

 

   <bean

       class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

       <property name="locations">

           <list> 

              <value>classpath:jdbc.properties</value> 

        </list> 

       </property>

    </bean>

   

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

      <property name="driverClassName" value="${jdbc.driverClassName}" />

      <property name="url" value="${jdbc.url}" />

      <property name="username" value="${jdbc.username}" />

      <property name="password" value="${jdbc.password}" />

    </bean>

   

 

    <bean id="entityManagerFactory"

        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"

        p:persistenceUnitName="spring"

        p:dataSource-ref="dataSource" />

 

    <bean id="transactionManager"

        class="org.springframework.orm.jpa.JpaTransactionManager"

        p:entityManagerFactory-ref="entityManagerFactory" />

 

    <jpa:repositories base-package="com.example.runbooks.repo" />

 

    <tx:annotation-driven />

    <jpa:repositories base-package="com.pango.spring.dao"/>

 

</beans>

其中: <jpa:repositories base-package="com.pango.spring.dao"/> 在服务启动时,将dao层接口通过动态代理加入到容器管理中。即类似于我们以前配置的@service然后通过组建扫描机制,加入到容器管理中。

 

五、   spring data jpa详解

    接下去我们来写spring data jpa的代码,让 Spring Data JPA 来帮助我们完成业务逻辑。在着手写代码之前,开发者需要先 下载Spring Data JPA 的发布包(需要同时下载 Spring Data Commons 和 Spring Data JPA 两个发布包,Commons 是 Spring Data 的公共基础包),并把相关的依赖 JAR 文件加入到 CLASSPATH 中。

首先我们来看看Spring data jpa执行过程如下:

 

在看上面Bean定义的时候,其实已经明白了执行过程:
1. 将JPA CRUD规范相关的方法交给SimpleJpaRepository这个类执行
2. 将特殊查询相关的交给QueryExecutorMethodInterceptor执行。主要做自定义实现的部分,method query部分和named query部分。
具体查询类详见下图。

 

第二、我们来看接口类

public interface UserDao extends JpaRepository<User, Integer> {

    public void findByUserName(String userName);

}

}

通过上面的列子,我们来总结一下使用spring Data jpa 进行持久化开发大致需要的三个步骤:

  1. 声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定义了一些常用的增删改查(CrudRepository),以及分页相关的方法(PagingAndSortingRepository),当然在我们的列子中我们实现了JpaRepository。
  1. 在接口中声明需要的业务方法。Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。
  2. 在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

   对于上面的接口我们还可以有一种写法,即通过注解

@RepositoryDefinition(domainClass = User.class, idClass = Integer.class)

public interface UserDao{

    public void findByUserName(String userName);

}

效果与上面通过接口实现一样。

前面提到,Spring Data JPA 在后台为持久层接口创建代理对象时,会解析方法名字,并实现相应的功能。除了通过方法名字以外,它还可以通过如下两种方式指定查询语句:

  1. Spring Data JPA 可以访问 JPA 命名查询语句。开发者只需要在定义命名查询语句时,为其指定一个符合给定格式的名字,Spring Data JPA 便会在创建代理对象时,使用该命名查询语句来实现其功能。
  2. 开发者还可以直接在声明的方法上面使用 @Query 注解,并提供一个查询语句作为参数,Spring Data JPA 在创建代理对象时,便以提供的查询语句来实现其功能。

下面我们分别讲述三种创建查询的方式。

  • 根据方法名来查询,即我们刚开始给出的列子

public List<User> findByUserName(String userName);

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):

  • 先判断 userAddressZip (根据 POJO 规范,首字母变为小写,下同)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
  • 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
  • 接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 "AccountInfo.user.addressZip" 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 "AccountInfo.user.address.zip" 的值进行查询。

可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上 "_" 以显式表达意图,比如 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

  • And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
  • Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
  • Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
  • LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
  • GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
  • IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
  • IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
  • NotNull --- 与 IsNotNull 等价;
  • Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);
  • NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
  • OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
  • Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user);
  • In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
  • NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
  • 使用 @Query 创建查询

  @Query("select u from User u where u.userName=?1 ")

public List<User> findByQuery(String userName);

我们可以看到使用@Query非常的简单,就通常写jpql语句一样。

  • JPA 命名查询语句创建查询

首先创建命名查询,如下

@Entity

@NamedQuery(name="User.findName",query="select u from User u where u.userName=?")

public class User implements Serializable {

    /**

}

这里需要注意的名称,名称需要满足”DomainClass.methodName()”的命名规则,或者出现异常。

@Query("select u from User u where u.userName=?1 ")

    public List<User> findByQuery(String userName);

  • 创建查询的顺序

Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此,<jpa:repositories> 提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:

  • create --- 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query 指定的查询语句,都将会被忽略。
  • create-if-not-found --- 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方 法名字来创建查询。这是 query-lookup-strategy 属性的默认值。

use-declared-query --- 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常

六、       spring data jpa 1.1新特性及分页介绍

 

  • 在spring data jpa1.1中加入了对原生sql的支持,但是这个功能还不是很完善,具有一定的局限性。对于动态的分页和排序查询还不支持。个人感觉这个功能并不是很好用,spring data jpa有待完善。

@Query(value="select * from user t where t.userName=?",nativeQuery=true)

需要在后面加入nativeQuery=true 即表示原生查询

  • 用于查询生成的新关键字: LessThanEqual, GreaterThanEqual, Before, After, StartsWith, EndsWith, Contains
  • CDI 集成 (see here for details)

CDI上下文依赖注入,在javaEE 6引入了这个概念,即JSR 299 (Contexts and Dependency Injection for the Java EE platform)规范,简称CDI。CDI规范吸收了来自Spring IoC容器JBoss SeamGoogle Guice的 最佳实践,并与Java EE开发的实际需要相结合。正如CDI的字面含义一样,CDI中的两个核心功能是上下文信息(context)和依赖注入。这两个功能的结合点是Java 中基本的组件模型bean。在CDI中,bean 定义了应用的状态和逻辑,并由容器来进行管理。每个被管理的bean都有定义好的绑定到特定上下文的作用域和生命周期。当需要注入或访问bean时,容器 会从作用域对应的上下文中获取。当作用域失效时,对应上下文中所有的对象都会被删除。CDI中的每个bean都可以作为依赖注入时的目标。

CDI中预定义了一些常用的作用域。默认的作用域是Dependent,表示只对被注入的对象生效。作用域ApplicationScoped表示 应用的全局作用域,用来创建全局唯一的对象。RequestScoped和SessionScoped则与HTTP相关,分别表示HTTP请求和HTTP 会话。ConversationScoped是由应用自定义生命周期长短的作用域,可以用来实现跨多页面的工作流。

  • 分页查询

Spring data jpa 对分页做了封装,主要的类有Pageable和Page

  @Query("select a from User a where a.balance > ?1")

     public Page<User> findByBalanceGreaterThan(

     Integer balance,Pageable pageable);

需要传入的参数

PageRequest request =

                new PageRequest(1, 5, Sort.Direction.DESC, "id");

       Page<User> users = userDao.findByBalanceGreaterThan(100, request);

多值排序可以使用下面这个pageRequest

PageRequest request2 = new PageRequest(1, 5, new Sort(

               new Order(Direction.ASC, "lastName"),

               new Order(Direction.DESC, "salary")

             ));

  ---转http://wenku.baidu.com/view/319d1162783e0912a2162abf.html

posted on 2013-05-22 04:55  刺猬的温驯  阅读(365)  评论(0)    收藏  举报