网上关于到底是使用mybatis还是JPA的争论较大,但与我无关,新公司用的JPA,那我就得学。
翻看了一些网上的文章,发现国内确实用的人很少,大部分文章都是好几年前的,而且错误很多,也没有一个统一的大而全的版本,那我也只能自己来写了。
什么是JPA、Spring Data JPA?
Jpa (Java Persistence API) 是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象/关联映射工具来管理 Java 应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术,结束现在 Hibernate,TopLink,JDO 等 ORM 框架各自为营的局面。
总的来说JPA是ORM规范,Hibernate、TopLink等是JPA规范的具体实现。而Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现),基于JPA的标准数据进行操作。简化了操作持久层的代码,它提供了包括增删改查等在内的常用功能,且易于扩展,也就是说它可以在实现了JPA规范的ORM框架下方便切换。
如何使用?
学习任何新东西,先会用,对它有个大概的了解,再去深究原理和细节,有的文章搞得本末倒置,不知所云,看起来很是头疼。
都2021年了,肯定得用springboot
核心jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
yml配置文件
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: mysql123
jpa:
database: MySQL
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
#每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新
ddl-auto: update
这里注意: jpa:hibernate:ddl-auto: update是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
1.create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
2.create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
3.update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
4.validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,字段不同会报错。不会创建新表,但是会插入新值。
5.none: 禁用ddl处理
使用JPA创建一个简单的查询
实体类
//@Entity说明这个class是实体类,并且使用默认的orm规则,即class名对应数据库表中表名,class字段名即表中的字段名。
(如果想改变这种默认的orm规则,就要使用@Table来改变class名与数据库中表名的映射规则,@Column来改变class中字段名与db中表的字段名的映射规则)
@Entity
//Table用来定义entity主表的name,catalog,schema等属性。
@Table(name = "tb_user")
@Data
public class User {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", length = 64)
private String email;
}
一般情况下,我们通过@id和@GeneratedValue来指定id主键和id策略,@GeneratedValue的strategy属性有四种策略,分别是:
- TABLE:使用一个特定的数据库表格来保存主键。
- SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
- IDENTITY:主键由数据库自动生成(主要是自动增长型,如Mysql)
- AUTO:主键由程序控制(也是默认的,在指定主键时,如果不指定主键生成策略,默认为AUTO)
这里主键采用了UUID策略 @GenericGenerator是Hibernate提供的主键生成策略注解,注意下面的@GeneratedValue(JPA注解)使用generator = "idGenerator"引用了上面的name = "idGenerator"主键生成策略
uuid是Hibernate的拓展id策略,除uuid以外还有不少其它策略,用法和上面类似,具体可以看这篇文章: blog.csdn.net/qq_34531925…
Repository接口
public interface UserRepository extends JpaRepository<User, String> {
}
可以用IDEA去看相关的继承树,如图所示
我们继承的是JpaRepository,从源码不难看出,从CrudReposity开始,这些接口内都定义了一些基本的CRUD方法,根据方法名很容易看出其用法
jpa还可以自定义简单查询
以下简单地机翻自spring data jpa的文档,英语好可以自己去看文档
解析查询方法名称分为主语和谓语。第一部分 ( find…By, exists…By) 定义查询的主题,第二部分构成谓词。介绍从句(主语)可以包含进一步的表达。find(或其他引入关键字)和之间的任何文本都By被认为是描述性的,除非使用结果限制关键字之一,例如Distinct在要创建的查询上设置不同的标志或Top/First以限制查询结果。
JPA在这里遵循Convention over configuration(约定大约配置)的原则,遵循spring 以及JPQL定义的方法命名。Spring提供了一套可以通过命名规则进行查询构建的机制。这套机制会把方法名首先过滤一些关键字,比如 find…By, read…By, query…By, count…By 和 get…By 。系统会根据关键字将命名解析成2个子语句,第一个 By 是区分这两个子语句的关键词。这个 By 之前的子语句是查询子语句(指明返回要查询的对象),后面的部分是条件子语句。如果直接就是 findBy… 返回的就是定义Respository时指定的领域对象集合,同时JPQL中也定义了丰富的关键字:and、or、Between等等,下面我们来看一下JPQL中有哪些关键字:
And----findByLastnameAndFirstname----where x.lastname = ?1 and
Or----findByLastnameOrFirstname----where x.lastname = ?1 or x.firstname = ?2
Is,Equals----findByFirstnameIs,findByFirstnameEquals----where x.firstname = ?1
Between----findByStartDateBetween----where x.startDate between ?1 and ?2
LessThan----findByAgeLessThan----where x.age < ?1
LessThanEqual----findByAgeLessThanEqual----where x.age ⇐ ?1
GreaterThan----findByAgeGreaterThan----where x.age > ?1
GreaterThanEqual----findByAgeGreaterThanEqual----where x.age >= ?1
After----findByStartDateAfter----where x.startDate > ?1
Before----findByStartDateBefore----where x.startDate < ?1
IsNull----findByAgeIsNull----where x.age is null
IsNotNull,NotNull----findByAge(Is)NotNull----where x.age not null
Like----findByFirstnameLike----where x.firstname like ?1
NotLike----findByFirstnameNotLike----where x.firstname not like ?1
StartingWith----findByFirstnameStartingWith----where x.firstname like ?1 (parameter bound with appended %)
EndingWith----findByFirstnameEndingWith----where x.firstname like ?1 (parameter bound with prepended %)
Containing----findByFirstnameContaining----where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy----findByAgeOrderByLastnameDesc----where x.age = ?1 order by x.lastname desc
Not----findByLastnameNot----where x.lastname <> ?1
In----findByAgeIn(Collection ages)----where x.age in ?1
NotIn----findByAgeNotIn(Collection age)----where x.age not in ?1
TRUE----findByActiveTrue()----where x.active = true
FALSE----findByActiveFalse()----where x.active = false
IgnoreCase----findByFirstnameIgnoreCase----where UPPER(x.firstame) = UPPER(?1)
And----findByLastnameAndFirstname----where x.lastname = ?1 and
Or----findByLastnameOrFirstname----where x.lastname = ?1 or x.firstname = ?2
Is,Equals----findByFirstnameIs,findByFirstnameEquals----where x.firstname = ?1
Between----findByStartDateBetween----where x.startDate between ?1 and ?2
LessThan----findByAgeLessThan----where x.age < ?1
LessThanEqual----findByAgeLessThanEqual----where x.age ⇐ ?1
GreaterThan----findByAgeGreaterThan----where x.age > ?1
GreaterThanEqual----findByAgeGreaterThanEqual----where x.age >= ?1
After----findByStartDateAfter----where x.startDate > ?1
Before----findByStartDateBefore----where x.startDate < ?1
IsNull----findByAgeIsNull----where x.age is null
IsNotNull,NotNull----findByAge(Is)NotNull----where x.age not null
Like----findByFirstnameLike----where x.firstname like ?1
NotLike----findByFirstnameNotLike----where x.firstname not like ?1
StartingWith----findByFirstnameStartingWith----where x.firstname like ?1 (parameter bound with appended %)
EndingWith----findByFirstnameEndingWith----where x.firstname like ?1 (parameter bound with prepended %)
Containing----findByFirstnameContaining----where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy----findByAgeOrderByLastnameDesc----where x.age = ?1 order by x.lastname desc
Not----findByLastnameNot----where x.lastname <> ?1
In----findByAgeIn(Collection ages)----where x.age in ?1
NotIn----findByAgeNotIn(Collection age)----where x.age not in ?1
TRUE----findByActiveTrue()----where x.active = true
FALSE----findByActiveFalse()----where x.active = false
IgnoreCase----findByFirstnameIgnoreCase----where UPPER(x.firstame) = UPPER(?1)
当然我们也可以自己编写SQL,但我觉得既然用了jpa就不该这么做
/**
* 查询根据参数位置
* @param name
* @return
*/
@Query(value = "select * from person where name = ?1",nativeQuery = true)
Person findPersonByName(String Name);
/**
* 查询根据Param注解
* @param name
* @return
*/
@Query(value = "select p from person p where p.uname = :name")
Person findPersonByNameTwo(@Param("name") String name);
根据参数位置,?号后的数字就代表第几个参数,和底下的形参对应 根据@param注解,直接引用:加注解内定义的名字即可
@Query有nativeQuery=true,表示可执行的原生sql,原生sql指可以直接复制sql语句给参数赋值就能运行 @Query无nativeQuery=true, 表示不是原生sql,查询语句中的表名则是对应的项目中实体类的类名
注意:在@Query注解中编写JPQL实现DELETE和UPDATE操作的时候必须加上@modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作,且需加上@Transactional以表明事务操作
复杂查询
通过上面的继承树我们可以看到有个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);
}
方法中的Specification就是需要我们传进去的参数,它是一个接口,也是我们实现复杂查询的关键,其中只有一个方法toPredicate
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
也就是说我们只需要继承Specification接口,重写这个toPredicate方法即可,具体实现需要按照JPA 2.0 criteria api写好查询条件即可
这里找了个例子,参考 segmentfault.com/a/119000003…
public List<Flow> queryFlows(int pageNo, int pageSize, String status, String userName, Date createTimeStart, Date createTimeEnd) {
List<Flow> result = null;
// 构造自定义查询条件
Specification<Flow> queryCondition = new Specification<Flow>() {
@Override
public Predicate toPredicate(Root<Flow> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicateList = new ArrayList<>();
if (userName != null) {
predicateList.add(criteriaBuilder.equal(root.get("currentOperator"), userName));
}
if (status != null) {
predicateList.add(criteriaBuilder.equal(root.get("status"), status));
}
if (createTimeStart != null && createTimeEnd != null) {
predicateList.add(criteriaBuilder.between(root.get("createTime"), createTimeStart, createTimeEnd));
}
if (orderId!= null) {
predicateList.add(criteriaBuilder.like(root.get("orderId"), "%" + orderId+ "%"));}
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
}
};
// 分页和不分页,这里按起始页和每页展示条数为0时默认为不分页,分页的话按创建时间降序
if (pageNo == 0 && pageSize == 0) {
result = flowRepository.findAll(queryCondition);
} else {
result = flowRepository.findAll(queryCondition, PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Direction.DESC, "createTime"))).getContent();
}
return result;
}
其实仔细看看也很简单,Specification的泛型就是你要查询的类,predicateList装的是查询条件,就是常用的equals、between、like这种,然后就是加了个分页,分页传入PageRequest对象(注意用的是PageRequest.of来构造对象)。分页中有一个getContent(),可以不加,不加的话还会返回页数/总条数等一些分页的参数,加这个方法就只返回list集合.
到这里一些基本的查询都可以实现了,一些深入的用法后面再慢慢写。