SpingData的学习

Spring Data : Spring 的一个子项目,类似于Sping MVC 一样是Spring的另一个模块,所以还需要下载其jar ,它需要的jar有:

spring-data-jpa-1.11.8.RELEASE.jar

spring-data-commons-1.13.8.RELEASE.jar

slf4j-api-1.7.5.jar

没有添加slf4j会报错,Spring Data的版本不兼容也会报错,我在 使用SpringData出现java.lang.AbstractMethodError 这篇笔记中记载了,这里就不复述了。

Spring Data是用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷

SpringData 项目所支持 NoSQL 存储:

  • MongoDB (文档数据库)
  • Neo4j(图形数据库)
  • Redis(键/值存储)
  • Hbase(列族数据库)

SpringData 项目所支持的关系数据存储技术:

  • JDBC
  • JPA(我们一般将Spring Data与JPA配合使用)

JPA +Spring Data : 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成,很类似于Mybatis的mapper接口吧,但是我们一般使用的JPA实现产品都是Hibernate,所以并不像Mybatis那样还需要写SQL,最多写的就是JPQL(面向对象的写法)。

框架怎么会知道要使用什么样的业务逻辑呢?

比如:当有一个 UserDao.findUserById()  这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User  对象。没错,Spring Data可以通过规范方法名字的方式,来确定方法具体需要实现什么样的逻辑。

准备工作

使用 Spring Data JPA 进行持久层开发需要的四个步骤:

1.配置 Spring 整合 JPA,添加jar包

2.在 Spring 配置文件中配置 Spring Data,让 Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后(不要忘了在applicationContext.xml文件中添加jpa的约束),Spring 初始化容器时将会扫描 base-package  指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动注入的特性来直接使用该对象。

        <!-- 配置SpringDate -->
        <!-- 记得要加入jpa的约束 -->
        <!-- base-package: 扫描 Repository Bean 所在的 package
              自定义Repository中的方法是要注意Spring Data 会在 base-package 中查找 "接口名Impl" 作为实现类.
         entity-manager-factory-ref:引用EntityManagerFactory
         transaction-manager-ref:引用transactionManager-->
        <jpa:repositories base-package="cn.lynu.repository"
                   entity-manager-factory-ref="entityManagerFactory"
                   transaction-manager-ref="transactionManager"></jpa:repositories>

3.声明持久层的接口,该接口继承  Repository,Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。 在接口中声明需要的方法,在实际使用中我们让自定义的repository接口继承Repository的子接口,可以方便我们开发。

4.Spring Data 将根据给定的策略(也就是根据方法名)来生成实现代码。

Repository 接口

Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法。

public interface Repository<T, ID extends Serializable> { } 

Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。

与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的:

1.
@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepository{}


2.
public interface PersonRepository extends Repository<Person, Integer>{}

Repository 的子接口

我们刚才说了,Repository接口中没有任何方法,所以我们可以继承其子接口,其子接口中提供了CRUD和分页的操作,开始了解一些这些子接口吧:

  • Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
  • CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法  
  • PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法  
  • JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法 

这些repository自下而上是一种继承关系。

CrudRepository接口

  • T save(T entity);//保存或更新单个实体(保存还是更新区别于是否有id)  
  • Iterable<T> save(Iterable<? extends T> entities);//保存集合        
  • T findOne(ID id);//根据id查找实体        
  • boolean exists(ID id);//根据id判断实体是否存在          
  • Iterable<T> findAll();//查询所有实体,不用或慎用!          
  • long count();//查询实体数量          
  • void delete(ID id);//根据Id删除实体          
  • void delete(T entity);//删除一个实体  
  • void delete(Iterable<? extends T> entities);//删除一个实体的集合          
  • void deleteAll();//删除所有实体,不用或慎用! 

PagingAndSortingRepository接口

该接口再上一个接口的基础上还提供了分页与排序功能 :

  • Iterable<T> findAll(Sort sort); //排序  
  • Page<T> findAll(Pageable pageable); //分页查询(含排序功能) 
    //使用继承了PagingAndSortingRepository的Repository
    @Test
    public void testPgae() {
        int pageNo=2-1;   //注意  这里是当前页要减1(从0开始)
        int size=5;
        Sort sort=new Sort(Direction.DESC, "age");   //指定排序方式和排序的属性
        //PageRequest是从Pageable接口的实现类
//        PageRequest pr=new PageRequest(pageNo, size);
        PageRequest pr=new PageRequest(pageNo, size,sort);  //还可以指定排序
        Page<Person> page = personRepository.findAll(pr);
        System.out.println("当前页:"+(page.getNumber()+1));  //而显示当前页时需要加1
        System.out.println("每页大小:"+page.getSize());
        System.out.println("总记录数:"+page.getTotalElements());
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("当前页的数据:"+page.getContent());
    }

JpaRepository接口

该接口在上一个接口的基础上还提供了这样的方法:

  • List<T> findAll(); //查找所有实体  
  • List<T> findAll(Sort sort); //排序、查找所有实体  
  • List<T> save(Iterable<? extends T> entities);//保存集合  
  • void flush();//执行缓存与数据库同步  
  • T saveAndFlush(T entity);//强制执行持久化  (可以执行插入或更新操作)
  • void deleteInBatch(Iterable<T> entities);//删除一个实体集合 
    //使用继承了JpaRepository的Repository
    //类似于JPA的merge方法  Hibernate的saveOrUpdate
    @Test
    public void testJPARepository() {
        Person person=new Person();
        person.setAge(21);
        person.setBirth(new Date());
        person.setpName("lz");
        
        Person person2 = personRepository.saveAndFlush(person);
        System.out.println(person==person2);
    }

我们自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。还有一个接口,它不属于Repository的体系中,但是它提供带查询条件的分页,很强大,它就是 JpaSpecificationExecutor 接口。普通分页我们使用继承了JpaRepository接口中的findAll方法进行开发即可,如果要进行带查询条件的分页,就还需要继承JpaSpecificationExecutor 接口,使用其内部的的findAll方法:

public interface PersonRepository extends JpaRepository<Person, Integer>,JpaSpecificationExecutor<Person>{}

 

   //实现带查询条件的分页要使用继承了JpaSpecificationExecutor的Repository
    @Test
    public void testCirPage() {
        int pageNo=2-1;   //注意  这里是当前页要减1(从0开始)
        int size=5;
        PageRequest pageable=new PageRequest(pageNo, size);

        //通常使用 Specification 的匿名内部类
        Specification<Person> specification=new Specification<Person>() {
            /**
             * @param *root: 代表查询的实体类. 
             * @param query: 可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
             * 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询 的 TypedQuery 对象. 
             * @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
             * @return: *Predicate 类型, 代表一个查询条件. 
             */
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path path = root.get("id");
                Predicate predicate=cb.gt(path, 5);   //gt表示> 大于  lt表示< 小于
                return predicate;
            }
        };
        
        Page<Person> page = personRepository.findAll(specification, pageable);
        System.out.println("当前页:"+(page.getNumber()+1));  //而显示当前页时需要加1
        System.out.println("每页大小:"+page.getSize());
        System.out.println("总记录数:"+page.getTotalElements());
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("当前页的数据:"+page.getContent());
        
    }

SpringData 方法规范

简单条件查询: 查询某一个实体类或者集合  按照 Spring Data 的规范,查询方法以 findBy | readBy | getBy 开头,注意By关键字也不要丢失哦。 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。 

例如:

//根据id查用户
    Person findById(Integer id);

    Person findById(Integer id);
    
    Person readById(Integer id);

如果有多个查询添加的话,需要使用And关键字连接:

//根据名称或年龄查询
    List<Person> findBypNameOrAge(String pName,Integer a);

对了,放心吧,方法的参数名可以自定义,但是其数据类型要与Entity的属性类型一致,不然会类型转换异常。

直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,目前支持的关键字写法如下:

 

可能存在的一种特殊(很极端)情况:一个Emp实体类中有Dept 对象,Dept对象有Id属性,而Emp中有一个名为DeptId的属性,如何根据Dept的Id来查询?

如果写成  findByDeptId ,因为存在名为DeptId这个属性,所以就会按照这个属性来查询,如何区分这种同名的问题呢?这时可以明确在属性之间加上 "_" 以显式表达意图:findByDept_Id  表明是根据Emp实体中的Dept对象属性下的Id来查询 

这种根据方法名称查询的方法存在一定的命名约束,如果不按照该约束命名,还会以编译时异常的形式提示开发者,如何才能随心所欲的命名方法,而可以完成各种操作呢?这就需要@Query注解了。

@Query

@Query注解使用的是 org.springframework.data.jpa.repository.Query 包下的,注意不要导错了。我们就可以在Query注解中写JPQL语句了,这样既可以摆脱方法命名约束,还可以较为灵活的创建Dao方法。

    //使用@Query注解(可以不受命名约束)
    //使用占位符 方法的参数顺序需要与占位符一致(?号后必须指定当前参数位置,且从1开始)
    @Query("SELECT p from Person p where p.id=?1")
    Person testPersonById(Integer id);

了解过JPA的就知道了,这是使用?占位符的方式,可以指定?开始的顺序是从0还是1开始。当然,也可以使用名称占位符的形式:

    @Query("select p from Person p where p.id=:id or p.age=:age")
    List<Person> getIdOrAge(@Param("age")Integer age,@Param("id")Integer id);

使用名称占位符的时候,需要在方法的参数上使用@Param注解来指明该参数使用的是哪一个名称占位。

如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %:

    @Query("select p from Person p where p.pName like %?1%")
    List<Person> likepName(String name);
    
    @Query("select p from Person p where p.pName like %:name%")
    List<Person> likepName2(@Param("name")String name);

@Modifying 注解和事务

JPQL默认情况下只支持查询,不支持UPDATE和DELETE,这也是与HQL的不同之处,但是我们可以通过添加@Modifying注解来进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作。注意:不支持INSERT操作。

    //可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
    //在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
    //UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作. 
    //默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
    @Modifying
    @Query("update Person set age=:age where id=:id")
    void updatePerson(@Param("age")Integer age,@Param("id")Integer id);

Spring Data默认给每个方法添加一个只读事务,注意是只读的事务,所以对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明 (这里说的就是在Service层加上事务).

为所有的 Repository 都添加自定义实现的方法

步骤:

  1. 定义一个接口: 声明要添加的并自实现的方法
  2. 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法
  3. 声明 Repository 接口, 并继承 1) 声明的接口 使用.  注意: 默认情况下, Spring Data 会在 base-package 中查找 "接口名Impl" 作为实现类,所以Impl的实现类需要和我们的Repository在同一个包中;如果不想以Impl结尾, 也可以通过 repository-impl-postfix 声明后缀。

 第一步:定义一个接口

package cn.lynu.dao;

/**
 * 自定义Repository使用(自定义方法的仓库接口)
 *在这个接口中写需要自定义的仓库方法
 */
public interface PersonDao {
    void test();
}

第二步:添加一个以Impl结尾的实现类

package cn.lynu.repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import cn.lynu.dao.PersonDao;
import cn.lynu.entity.Person;

//自定义repository使用(命名规范:仓库名+Impl)
//在这里实现自定义方法的仓库接口中的方法
public class PersonRepositoryImpl implements PersonDao {
    
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void test() {
        Person person = entityManager.find(Person.class, 11);
        System.out.println("--->>"+person.getpName()+"--->"+person.getAge());
    }

}

第三步:我们的repository也需要继承这个PersonDao接口

public interface PersonRepository extends 
            JpaRepository<Person, Integer>,JpaSpecificationExecutor<Person>,PersonDao{}

这样我们就可以在所有继承了PersonDao的repository中使用我们自定义的test方法了:

    //使用自定义的repository的方法
    @Test
    public void testMyRepository() {
        personRepository.test();
    }

Spring MVC Spring Data Spring JPA整合

配置:

在web.xml中的配置:

基本上都是Spring和Spring MVC的配置, 还需要配置一个OpenEntityManagerInViewFilter 来处理no session 的懒加载问题,类似于在SSH整合中配的OpenSessionInView:

    <!-- 处理no session(懒加载异常)问题 -->
    <filter>
      <filter-name>OpenEntityManagerInViewFilter</filter-name>
      <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>OpenEntityManagerInViewFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

applicationContext.xml中的配置:

其实在  JPA的学习  中 与Spring的整合都已经说过了,现在就是再添加一行整合Spring Data的代码,我在这里再贴一份吧:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

        <!-- 配置数据源 -->
        <context:property-placeholder location="classpath:db.properties"/>
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="user" value="${jdbc.user}"></property>
          <property name="password" value="${jdbc.password}"></property>
          <property name="driverClass" value="${jdbc.driverClass}"></property>
          <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        </bean>
        
        <!-- 配置entitymanagerFactory -->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
          <property name="dataSource" ref="dataSource"></property>
          <property name="jpaVendorAdapter">
             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
          </property>
          <property name="packagesToScan" value="cn.lynu.entity"></property>
          <property name="jpaProperties">
            <props>
                  <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <!-- 二级缓存和查询缓存 -->
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
                <prop key="hibernate.cache.use_query_cache">true</prop>
            </props>
          </property>
          <!-- 只有JPA的实体类上使用@Cacheable(true)才使用二级缓存-->
          <property name="sharedCacheMode" value="ENABLE_SELECTIVE"></property>
        </bean>
        
        <!-- 配置组件扫描 -->
        <context:component-scan base-package="cn.lynu">
          <!-- 不在扫描@Controller 和@ControllerAdvice.因为已经在springmvc.xml中配置 -->
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
          <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
        </context:component-scan>
        
        <!-- JPA事务管理器 -->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
          <property name="entityManagerFactory" ref="entityManagerFactory"></property>
        </bean>
        
        <!-- 事务注解 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        
        <!-- 配置springData -->
        <jpa:repositories base-package="cn.lynu.repository"></jpa:repositories>
        
</beans>

整合过程使用二级缓存

在整合过程中,使用ehcache做二级缓存(还是Hibernate作为JPA的实现产品)的时候,使用Repository中自带的方法无法使用二级缓存,只有使用自定义的方法才可以,这是值得注意的。

  1. applicationContext.xml中配置二级缓存
  2. 实体类上使用@Cacheable(true) 表明该实体类使用二级缓存
  3. 自定义Repository中的方法,并配置@QueryHints
    @QueryHints({@QueryHint(name=org.hibernate.jpa.QueryHints.HINT_CACHEABLE,value="true")})
    @Query("select dept from Department dept")
    public List<Department> getAll();

 

posted @ 2017-11-04 15:16  OverZeal  阅读(329)  评论(0编辑  收藏  举报