写在前面:
之前一直写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;@CreatedDateprivate Date createTime;@LastModifiedDateprivate 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);}
}