Spring Data JPA之快速入门

一、核心概念
  Spring Data repository抽象的中心接口是Repository,该接口中没有任何方法,它将域类以及域类的ID类型作为类型参数进行管理。此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。该CrudRepository规定对于正在管理的实体类复杂的CRUD功能。
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
  <S extends T> S save(S entity);     //保存或更新给定的实体,当实体的ID在数据库中存在时执行更新操作
  Optional<T> findById(ID primaryKey); //    返回由给定ID标识的实体。
  Iterable<T> findAll();               //返回所有实体。
  long count();                        //返回实体数量。
  void delete(T entity);               //    删除给定的实体。
  boolean existsById(ID primaryKey);   //指示是否存在具有给定ID的实体。
  // … more functionality omitted.
}

  除此CrudRepository接口之外,JPA还提供了其他的特定的接口,如JpaRepository、PagingAndSortingRepository、MongoRepository等,这些接口都扩展了CrudRepository。以PagingAndSortingRepository为例,该接口扩充的分页、和排序的的功能。

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {
  Iterable<T> findAll(Sort sort);
  Page<T> findAll(Pageable pageable);
}
//使用方式,要访问User页面大小为20 的第二页,您可以执行以下操作:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));//PageRequest是Pageable接口的实现类

  另外,在实际开发中,我们也常常使用JpaSpecificationExecutor接口来实现更为复杂的分页、排序查询(动态条件查询),Specification接口就是用来构建查询条件的。

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> spec);
    List<T> findAll(Specification<T> spec);
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    List<T> findAll(Specification<T> spec, Sort sort);
    long count(Specification<T> spec);
}

  上面是Specification接口的内部方法,下面则是Specification的具体使用:

Sort sort=new Sort(Sort.Direction.DESC,"createTime");
Pageable pageable=new PageRequest(page-1,pageSize,sort);
Page<ScanLog> pageList=scanLogRepo.findAll(new MySpec(),pageable);//具体方法调用
//下面是通过 内部类封装的 条件查询类
/**
 * 建立查询条件
 */
private class MySpec implements Specification<ScanLog> {
     @Override
     public Predicate toPredicate(Root<ScanLog> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Join<ScanLog,ProjectInfo> join = root.join("projectInfo", JoinType.INNER);//关联查询
            Path<Integer> param1=root.get("scanType");
            Path<Date> param2=root.get("createTime");
            Path<String> param3 = join.get("projectName");
            Predicate predicate=null;
            if(projectName==null||projectName.equals("")){
                predicate = cb.and(cb.equal(param1,scanType),cb.between(param2,beginTime,endTime));
            }else {
                predicate = cb.and(cb.equal(param1,scanType),cb.like(param3,projectName),cb.between(param2,beginTime,endTime));
            }
            return predicate;
     }
}

二、配置JPA的repositories支持

  我们创建了基于JPA的实体类和repository接口,正如Spring管理service、controller中的类(bean),基于JPA创建的类和接口我们也需要在Spring容器中进行注册。可以采用以下几种方式进行配置:

  1. XML配置

    每一个Spring Data模块都包含repositories元素能够让你简单的基于base-package定义来进行Spring扫描。示例1。 通过XML来开启Spring Data repositories

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="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/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

  <repositories base-package="com.acme.repositories" />
</beans:beans>

  2.javaConfig配置(注解配置)

    你也可以在一个JavaConfig类中使用@Enable${store}Repositories声明来触发repository的构建。 一个简单的开启Spring Data repositories的配置看上去是这样的:

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
  @Bean
  public EntityManagerFactory entityManagerFactory() {
    //
  }
}

    如果是多数据源,我们也可以如下配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= {"com.johnson.one.repository","com.johnson.two.repository" })//设置dao(repo)所在位置
public class RepositoryPrimaryConfig {

    @Autowired
    private JpaProperties jpaProperties;

    @Autowired
    @Qualifier("primaryDS")
    private DataSource primaryDS;

    @Bean(name = "entityManagerPrimary")
    @Primary
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactoryPrimary")
    @Primary
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        /*.packages("com.johnson.one.model","com.johnson.two.model") //设置实体类所在位置*/
        return builder
                .dataSource(primaryDS)
                .properties(getVendorProperties(primaryDS))
                .packages("com.johnson.one.model","com.johnson.two.model") //设置实体类所在位置
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

    private Map<String, String> getVendorProperties(DataSource dataSource) {
        return jpaProperties.getHibernateProperties(dataSource);
    }

    @Bean(name = "transactionManagerPrimary")
    @Primary
    PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }
}

  3.独立使用

RepositoryFactorySupport factory =new JpaRepositoryFactory(entityManager);
UserRepository repository = factory.getRepository(UserRepository.class);

三、定义查询方法

   Repository代理有两种方法可以从方法名称派生于store的查询:

    (1)通过直接从方法名称派生查询,如:

List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastnameAndAge(String lastname, int age);
//从以上的方法的定义中,我们可以发现通过方法名派生查询,对于多条件,长属性名的派生查询,会生成很长的方法名,因此具有一定的局限性,对于一些简单的单表条件查询,比较适合这种方式

     (2)通过使用手动定义的查询----通过@Query注解使用自定义sql或hql查询

      对于以上通过方法派生查询的不足,很多时候,我们可以通过@Query自定义sql或hql语句的方式去解决。

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select u from User u where u.emailAddress = ?1")
    User findByEmailAddress(String emailAddress);
    
    @Query("select u from User u where u.firstname like %?1")
    List<User> findByFirstnameEndsWith(String firstname);//使用Like表达式
    
    @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
    User findByEmailAddress(String emailAddress);//使用本地Sql即原生sql查询时,需设置nativeQuery=true
    
    //使用@Modifying、@Query注解可以派生出 增删改操作
    @Modifying
    @Query("update User u set u.firstname = ?1 where u.lastname = ?2")
    int setFixedFirstnameFor(String firstname, String lastname);//修改查询
    
    @Modifying
    @Query("delete from User u where user.role.id = ?1")
    void deleteInBulkByRoleId(long roleId);//删除查询
    
    @Modifying
    @Query(value = "insert into orders(name,uid) value(?1,?2)", nativeQuery = true)
    public void insertOrder(String name,int uid);//利用原生的SQL进行插入操作
}

    但是,请注意:Spring Data JPA目前不支持对原生sql查询进行动态排序,因为它必须操纵声明的实际查询,这对于本机SQL无法可靠地执行但是,您可以通过自己指定计数查询来使用本机查询进行分页,如下例所示:

 

public interface UserRepository extends JpaRepository<User, Long> {

  @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
    nativeQuery = true)
  Page<User> findByLastname(String lastname, Pageable pageable);
}

 

    如果要在@Query定义的接口中使用排序,可以通过提供PageRequestSort直接使用来进行排序。在需要匹配域模型的Order实例中实际使用的属性Sort,这意味着它们需要解析为查询中使用的属性或别名。默认情况下,Spring Data JPA拒绝任何Order包含函数调用的实例,但您可以使用它JpaSort.unsafe来添加可能不安全的排序。

//使用Sort和JpaSort
public interface UserRepository extends JpaRepository<User, Long> {
  @Query("select u from User u where u.lastname like ?1%")
  List<User> findByAndSort(String lastname, Sort sort);

  @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
  List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}

repo.findByAndSort("lannister", new Sort("firstname"));               //Sort指向域模型中的属性的有效表达式
repo.findByAndSort("stark", new Sort("LENGTH(firstname)"));           //Sort包含函数调用无效。有例外
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); //有效Sort包含明确不安全 Order
repo.findByAsArrayAndSort("bolton", new Sort("fn_len"));              //Sort指向别名函数的有效表达式

    上面的 @Query查询语句中,基本上都是使用的是占位符的方式传参并对参数赋值的,除此之外,还可以使用命名参数的方式对参数赋值,示例如下所示:

public interface UserRepository extends JpaRepository<User, Long> {
  //使用命名参数的最大好处就是当方法参数(包括位置)发生变化时,不用修改查询语句,其次从版本4开始,@Param可以省略
  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
  User findByLastnameOrFirstname(@Param("lastname") String lastname,@Param("firstname") String firstname);
}

  (3)直接通过entityManager来实现数据库的操作

    有些时候我们需要的Repository接口提供的功能是无法用SpringData的方法命名约定来描述,甚至无法用@Query注解来设置查询来实现,此时的SpringData对于我们来说具有一定的局限性,为了实现我们的功能,我们只能在更低的层级上(较SpringData低一级)使用JPA,即使用传统的JPA的方式(操作entityManager)去实现。

@RunWith(SpringRunner.class)
@SpringBootTest
public class JpademoApplicationTests {
    
    @PersistenceContext
    private EntityManager entityManager;//使用@PersistenceContext注解注入一个entityManager代理对象,如果该对象为null,创建新的对象
    @Test
    public void contextLoads() {
        String sql="select * from orders where user_id=10";
        List<Orders> list= entityManager.createNativeQuery(sql,Orders.class).getResultList();
        for (int i=0;i<list.size();i++){
            System.out.println(list.get(i).getCreateTime());
        }
    }
}

四、SpringData扩展

  1、Querydsl扩展

     Querydsl是一个框架,可以通过其流畅的API构建静态类型的SQL式查询。几个Spring数据模块通过QuerydslPredicateExecutor提供与Querydsl的集成,如下例所示:

public interface QuerydslPredicateExecutor<T> {

  Optional<T> findById(Predicate predicate);  //查找并返回与之匹配的单个实体Predicate

  Iterable<T> findAll(Predicate predicate);   //查找并返回与之匹配的所有实体Predicate

  long count(Predicate predicate);            //返回匹配的实体数量Predicate

  boolean exists(Predicate predicate);        //返回与Predicate 匹配的实体
  // … more functionality omitted.
}

    要使用Querydsl支持,请QuerydslPredicateExecutor在repository接口上进行扩展,如以下示例所示:

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
//以下是具体调用
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
    .and(user.lastname.startsWithIgnoreCase("mathews"));//查询firstname为dave并且lastname以mathews开头的用户
userRepository.findAll(predicate);

  2、web支持

     由于JPA主要用于持久层的开发,所以对于web支持这儿不做介绍。

 

 

 
posted @ 2019-07-13 18:37  预见方能遇见  阅读(317)  评论(0)    收藏  举报