Spring Data JPA 基本用法笔记整理

写在前面:

之前一直写MyBatis,去年开始做的这个新项目用的是JPA,整理了一些基本使用方法

1、 集成方法:

1.1 引入依赖

<!--spring data 依赖-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<!--数据库连接-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

1.2 配置文件

配置数据库信息:

//数据库连接信息
spring.datasource.url= url
spring.datasource.username=username
spring.datasource.password=<password>

//Java代码实体字段命名与数据库表结构字段之间的名称映射策略
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
//下面配置开启后,会禁止将驼峰转为下划线
//spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
//是否打印运行时的sql
spring.jpa.show-sql=false

//控制是否可以基于程序中Entity的定义自动创建或者修改DB中表结构 
//create -> 重启后删除上一次的表,重新生成
//create-drop -> sessionFactory关闭时,创建的表就自动删除,服务启动后重新创建
//validate -> 验证创建数据库表结构,不同就报错
//update none
//等价于spring.jpa.hibernate.ddl-auto
spring.jpa.properties.hibernate.hbm2ddl.auto=update

1.3 入口注解

没有特殊需求可以什么都不加

@SpringBootApplication
@EntityScan("path") //指定实体的目录
@EnbaleJpaRepositories(basePackages = {"com.veezean.demo.repository"}) //指定扫描的表repository目录
@EnableJpaAuditing//开启JPA auditing能力,可以自动赋值一些字段,比如创建时间、最后一次修改时间etc
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

2、基本用法

2.1 实体映射类 Entity

实体类编写比较简单,只需要在普通的Java数据类上添加一些注解,用来描述字段的一些附加信息

@Data
@Entity
@Table(name = "user")
@EntityListeners(value = AuditingEntityListener.class)//对实体属性变化的跟踪
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String workId;
    private String userName;
    @ManyToOne(optional = false)
    @JoinColumn(name = "department")
    private DepartmentEntity department;
    @CreatedDate
    private Date createTime;
    @LastModifiedDate
    private Date updateTime;

}

2.2 常用注解

 

 

2.3 Repository

2.3.1 三种 Repository

Spring Data 扩展了Repository接口,提供了一些便于操作数据库的子类,对主体repository层级提供的主要方法进行简单的梳理:

  • CrudRepository: 提供基本的CRUD操作。
  • PagingAndSortingRepository: 在父类的基础上提供分页、排序的能力
  • JpaRepository: 在父类的基础上,提供了查询列表、批量删除、强制同步以及Example查询等能力

3.3.2 自定义Repository类

 

 

自定义Repository时,继承JpaRepository需要传入两个泛型:

1、需要操作的具体Entity对象;

2、Entity的主键数据类型

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    List<UserEntity> findAllByDepartment(DepartmentEntity department);
    UserEntity findFirstByWorkId(String workId);
    List<UserEntity> findAllByDepartmentInAndUserNameLike(List<DepartmentEntity> departmentIds, String userName);
}

3.4 查询

如上所述,简单的操作只需要基于SpringData JPA的命名规范进行接口方法的命名即可,无需关注具体实现,也不需要提供实现类

但是面对复杂的操作,这样的定义方式就不再适用了,那么应该如何做呢?

不固定查询字段的场景(Example)

例:需要做一个用户搜索的能力,要求支持根据用户名、工号、部门、性别、年龄、职务等等若干个字段中的1个或者多个的组合来查询符合条件的用户信息。

🌟 使用 Example查询

public Page<UserEntity> queryUsers(Request request, UserEntity queryParams) {
	// 查询条件构造出对应Entity对象,转为Example查询条件
	Example<UserEntity> example = Example.of(queryParams);
	// 构造分页参数
  Pageable pageable = PageHelper.buildPageable(request);
	// 按照条件查询,并分页返回结果
  return userRepository.findAll(example, pageable);
}

🔸查询条件复杂时为了不使方法名称过长也可以使用 Example

public List<User> findUsersByNameAndAge(String name, Integer age) {  
        User user = new User();  
        user.setName(name);  
        user.setAge(age);  
  
        ExampleMatcher matcher = ExampleMatcher.matching()  
                .withMatcher("name", match -> match.contains())  
                .withMatcher("age", match -> match.exact());  
  
        Example<User> example = Example.of(user, matcher);  
  
        return userRepository.findByExample(example);  
    }  

❗example只能针对字符串进行条件设置

复杂场景(SQL, Specification)

🌟定制SQL

遇到非常复杂的查询场景也可以通过定制SQL的方式实现

@Query(
        value = "select t.*,(select group_concat(a.assigner_name) from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id) deal_person,"
            + " (select a.task_name from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id limit 1) cur_step "
            + "   from workflow_info t where t.state='R'  and t.type in (?1) "
            + "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) order by t.create_time desc",
        countQuery = "select count(1) from workflow_info t where t.state='R'  and t.type in (?1) "
            + "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) ",
        nativeQuery = true)
    Page<FlowResource> queryResource(List<String> type, String workId, Pageable pageable);

🌟Specification 构建动态查询

1️⃣ 需要定义一个Specification,用来构建查询条件:

Specification<User> spec = (root, query, criteriaBuilder) -> {
        Path<Integer> type = root.get("verified");
        // verified == "1"
        Predicate verifiedPredicate = criteriaBuilder.equal(type, "1");
	      // email like "%qq%"
        Path<String> email = root.get("email");
        Predicate emailPredicate = criteriaBuilder.like(email, "%qq%");
        // and 条件 verified == "1" and email like "%qq%"
        Predicate predicate = criteriaBuilder.and(verifiedPredicate, emailPredicate);
        return predicate;
    };

lambda表达式中传入的3个参数分别为:

Root:查询哪个表(关联查询) = from CriteriaQuery:查询哪些字段,排序是什么 =组合(order by . where ) CriteriaBuilder:条件之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么类型(> between in...) = where

Specification接口中只定义了如下一个方法:

//构造查询条件
    /**
    *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
    *	query	:代表一个顶层查询对象,用来自定义查询
    *	cb		:用来构建查询,此对象里有很多条件方法
		* Predicate(Expression): 每一条查询条件的详细描述
    **/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

因此也可以通过匿名内部类的方式:

Specification <User> spec = new Specification<User>() {
			public Predicate toPredicate(Root<User> root, 
                                        CriteriaQuery<?> query, CriteriaBuilder cb) {
				//cb:构建查询,添加查询方式   like:模糊匹配
				//root:从实体Customer对象中按照custName属性进行查询
				return cb.like(root.get("name").as(String.class), "111%");
			}
		};

2️⃣ 自定义一个repository接口,继承JpaRepository和JpaSpecificationExecutor

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

JpaSpecificationExecutor 接口中定义了如下方法:

 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);
}

3️⃣ 使用时只需调用repository中的findAll方法,并传入相应参数即可:

userRepository.findAll(specification);  

3.5 分页、排序

Pageable

分页,排序使用Pageable对象进行传递,其中包含Page和Sort参数对象。查询时直接传递

List<User> findAllByDepartment(Department dept, Pageable pageable);

** Specification 查询分页也只需要构造Pageable参数并传入

Slice结果对象

**还有一种特殊的分页场景。比如,DB表中有100w条记录,然后现在需要将这些数据全量的加载到ES中。如果逐条查询然后插入ES,显然效率太慢;如果一次性全部查询出来然后直接往ES写,服务端内存可能会爆掉。

🌟 这种场景可以基于Slice结果对象实现

Slice的作用是,只知道是否有下一个Slice可用,不会执行count

private <T extends EsDocument, F> void fullLoadToEs(IESLoadService<T, F> esLoadService) {
    try {
        final int batchHandleSize = 10000;
        Pageable pageable = PageRequest.of(0, batchHandleSize);
        do {
            // 批量加载数据,返回Slice类型结果
            Slice<F> entitySilce = esLoadService.slicePageQueryData(pageable);
            // 具体业务处理逻辑
            List<T> esDocumentData = esLoadService.buildEsDocumentData(entitySilce);
            esUtil.batchSaveOrUpdateAsync(esDocumentData);
            // 获取本次实际上加载到的具体数据量
            int pageLoadedCount = entitySilce.getNumberOfElements();
            if (!entitySilce.hasNext()) {
                break;
            }
            // 自动重置page分页参数,继续拉取下一批数据
            pageable = entitySilce.nextPageable();
        } while (true);
    } catch (Exception e) {
        log.error("error occurred when load data into es", e);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值