hibernate-使用data jpa

简介
笔者在前面的文章中提及到过 spring-data-jpa ,什么是 jpa ,什么是 spring-data-jpa 。jpa 是 Java Persistence API的简称,是 javaEE 的 orm 规范,spring-data-jpa 是依照 jap 规范的关于数据持久层的一系列接口,在 spring 中是这样介绍 data-jpa 的:spring-data-jpa 是 spring 数据持久层的一部分,能够更轻松方便实现基于 JPA 的库,更容易构建出 spring 应用。说白了,spring-data-jpa 让我们更方便地操作持久层。

使用
1.使用 data-jpa 主要是要使用 spring 提供的一系列 Repository 接口。

// 接口族
Repository<T, ID extends Serializable>
    CrudRepository<T, ID extends Serializable>
        PagingAndSortingRepository<T, ID extends Serializable>
            JpaRepository<T, ID extends Serializable>

// 用于动态查询            
JpaSpecificationExecutor<T>

2.先来看看这些接口声明了那些方法

空接口

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

CrudRepository 声明了基本的 CRUD 方法

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
    // 保存或更新
    <S extends T> S save(S var1);
    // 批量保存或更新
    <S extends T> Iterable<S> save(Iterable<S> var1);

    // 根据主键查找
    T findOne(ID var1);
    // 根据主键判断是否存在
    boolean exists(ID var1);

    // 查找所有符合条件的
    Iterable<T> findAll();
    Iterable<T> findAll(Iterable<ID> var1);

    // 统计总量
    long count();

    // 根据主键或其他条件删除
    void delete(ID var1);
    void delete(T var1);
    void delete(Iterable<? extends T> var1);
    void deleteAll();
}

PagingAndSortingRepository 接口继承 CrudRepository ,主要增加了分页和排序功能

public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
    // 排序查找
    Iterable<T> findAll(Sort var1);
    // 分页查找
    Page<T> findAll(Pageable var1);
}

JpaRepository 接口继承 PagingAndSortingRepository 接口,扩展了一些返回参数类型为 List 的方法

public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAll(Iterable<ID> var1);

    <S extends T> List<S> save(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

JpaSpecificationExecutor 是一个高级动态查询接口,不是 Repository 系列中的子接口

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> var1);

    List<T> findAll(Specification<T> var1);

    Page<T> findAll(Specification<T> var1, Pageable var2);

    List<T> findAll(Specification<T> var1, Sort var2);

    long count(Specification<T> var1);
}

3.如何使用这些接口,你仅需要继承这些接口即可,具体实现由 data-jpa 库帮你实现,和使用 mybatis 差不过。举个栗子:

@Repository
public interface UserRepository extends JpaRepository<User,Long> {}

这样就可以通过 spring 自动注入到类中使用了,你可以调用上述接口中声明的任意方法完成持久化操作。一般上述方法就可以满足一般的需求,但是我们可不可以扩展这些接口呢,答案是可以的,不过在扩展接口时的方法命名需要遵循 data-jpa 的规范约束,例如:

@Repository
public interface UserRepository extends JpaRepository<User,Long> {

    // 根据 userId 查找 user
    User findByUserId(String userId);
    // 根据 username 查找 user
    User findByUsername(String username);
    User findByUsernameAndAge(String username,int age);
}

基本是使用:findBy,deleteBy,countBy,existsBy,orderBy,between ,distinct,and,or…,实体类属性名 等这些关键字拼接起来。当这些都不满足需求时,就可以通过使用 @Query 注解自己拼 hql 了。例如:

@Repository
public interface UserRepository extends JpaRepository<User,Long> {

    User findByUserId(String userId);
    User findUserByUsername(String username);

    @Query("select u from User u join u.department dept where dept.departmentId = :deptId")
    Page<User> findByDeptId(@Param("deptId") String deptId, Pageable pageable);

    @Query("select u from User u join  u.roles role where role.roleId = :roleId")
    Page<User> findByRoleId(@Param("roleId") Long roleId, Pageable pageable);

    @Query("select count(u.userId) from User u join u.department dept where dept.departmentId = :deptId")
    int countByDeptId(@Param("deptId") String deptId);

    /*注意update需要modifying*/
    @Modifying
    @Query("update User u set u.flag = :flag where u.userId = :userId")
    int updateUserFlag(@Param("flag") Integer flag, @Param("userId") String userId);

}

一般在做联表查询是就需要使用 hql 了,hql 也是面向对象的,实体类等价于对应的表,通过打点访问类中的关联实体类属性即可做到联表。此外,很多时候我们需要动态查询,以上方案 sql 都是固定的,怎样才能动态拼接 sql 实现动态查询呢,这就要使用到 JpaSpecificationExecutor 了。再来看看这个接口的方法:

 public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> var1);

    List<T> findAll(Specification<T> var1);

    Page<T> findAll(Specification<T> var1, Pageable var2);

    List<T> findAll(Specification<T> var1, Sort var2);

    long count(Specification<T> var1);
}

在声明的方法中有三个参数:Sort,Pageable ,Specification ,关键是最后的这个。Specification 就是动态拼接的 sql 的数据结构,在使用动态查询,我们需要做的是如何创建 Specification 对象。

public interface Specification<T> {
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}

Specification 也是一个接口,其中仅有一个方法 toPredicate , 实际上 Predicate 才真正是动态拼接 sql 的数据结构。在组装 sql 时,使用的是 root,criteriaQuery,criteriaBuilder 这三个对象。例如:

@Repository
public interface UserRepository extends JpaRepository<User,Long>,JpaSpecificationExecutor<User> {


    public static class Specs{

        /**
         * 根据查询条件构造动态查询sql
         * username
         * userId
         * deptId
         * roleId
         *
         *root : 可以获取实体类中的属性名(等价数据库中的表,列)
         *criteriaQuery:构建子查询(等价于使用 orderBy,groupBy 等)
         *criteriaBuilder:构建 predicate 对象,构建 sql 片段
         *
         */
        public static Specification<User> newQueryParams(Map<String,String> paramsMap){

            return new Specification<User>() {
                @Override
                public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                    // 创建 Predicate
                    Predicate predicate = criteriaBuilder.conjunction();

                    // 组装条件
                    if(StringUtils.isNotBlank(paramsMap.get("username"))){
                        predicate.getExpressions().add(criteriaBuilder.like(root.<String>get("username")
                                ,"%"+ paramsMap.get("username") + "%"));
                    }
                    if(StringUtils.isNotBlank(paramsMap.get("userId"))){
                        predicate.getExpressions().add(criteriaBuilder.equal(root.<String>get("userId"),paramsMap.get("userId")));
                    }
                    if(StringUtils.isNotBlank(paramsMap.get("deptId"))){
                        // 注意联表的问题
                        predicate.getExpressions().add(criteriaBuilder.equal(root.get("department").get("departmentId"),paramsMap.get("deptId")));
                    }
                    if(StringUtils.isNotBlank(paramsMap.get("roleId"))){
                        predicate.getExpressions().add(criteriaBuilder.equal(root.join("roles", JoinType.LEFT).get("roleId").as(String.class), paramsMap.get("roleId")));
                    }

                    return predicate;
                }
            };

        }
    }
}

PS: 在 hibernate 中的级联注解中,一般使用 CascadeType.ALL,如@OneToMany(cascade={CascadeType.ALL}) , 但是往往不需要级联删除,这是可以使用 hibernate 提供的注解:@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})

PS : spring-data-example 多看官方栗子,多看官方栗子,多看官方栗子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值