说老实话,Spring Data JPA很好用,上次使用还是2013年,当时只是完成Java Bean和数据库中表的映射。
最近想起来用Spring Data JPA的起因是手头有一个项目,源代码是用原生SQL+JDBC实现的,在第一次部署时要初始化数据库,还hardcode了很多数据库配置参数。正好最近有空,就打算用Spring Boot Data JPA(spring-boot-starter-data-jpa)改造一下,仔细看了一下源代码发现和几年前已经天差地别,如果你的业务逻辑不是特别复杂或者表结构设计合理,一行真正SQL逻辑实现都不要写,良心啊,真是为我这样的懒人着想啊。
首先开始写Repository,常见的用法有两种,一个是写一个接口继承JpaRepository,代码如下:
package com.company.inventory.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import comcompany.inventory.model.Device;
public interface DeviceRepository extends JpaRepository<Device, Long> {
List<Device> findBySnOrderByGmtCreatedDesc(String sn);
}
另外一个是继承CrudRepository,代码如下:
package com.company.inventory.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import comcompany.inventory.model.Device;
public interface DeviceRepository extends CrudRepository<Device, Long> {
List<Device> findBySnOrderByGmtCreatedDesc(String sn);
}
JpaRepository和CrudRepository关系如下:
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID>
public interface CrudRepository<T, ID> extends Repository<T, ID>
从上面的关系就能看出JpaRepository除了能做CrudRepository能做的所有事外,还多了分页和排序功能以及QueryByExampleExecutor提供的QueryByExample功能。但是JpaRepository也和JPA持久化技术进行了绑定。http://jtuts.com/2014/08/26/difference-between-crudrepository-and-jparepository-in-spring-data-jpa/
所以建议尽量使用CrudRepository或者PagingAndSortingRepository
Supported keywords inside method names
Keyword
|
Sample
|
JPQL snippet
|
And
|
findByLastnameAndFirstname
|
… where x.lastname = ?1 and x.firstname = ?2
|
Or
|
findByLastnameOrFirstname
|
… where x.lastname = ?1 or x.firstname = ?2
|
Is,Equals
|
findByFirstname ,
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<Age> ages)
|
… where x.age in ?1
|
NotIn
|
findByAgeNotIn(Collection<Age> ages)
|
… 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)
|
接下来是定义Entity,遇到了第一个坑,代码如下:
package com.company.inventory.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table( name = "device" )
public class Device implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String sn;
@Column(name = "gmtCreated", nullable = false)
private Date gmtCreated = new Date();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getGmtCreated() {
return gmtCreated;
}
public void setGmtCreated(Date gmtCreated) {
this.gmtCreated = gmtCreated;
}
}
第一个坑,该类必须是实体类,不能继承于任何基类,除非其中的参数不需要映射到数据库中。
例如有一个基类如下:
public class BaseModel{
protected String name;
//set, get方法忽略
}
public class Person extends BaseModel implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
}
name将不能映射到数据库中。
第二个坑,如果Field类似gmtCreated,有大写字母的,默认映射到数据库字段名会变为gmt_created。如果你的设计不是这样,而是直接映射不做修改。需要在application.properties中指定Naming strategy,否则默认是org.hibernate.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy,在进行领域映射时,首字母小写,大写字母变为下划线加小写。添加代码如下:
spring.jpa.hibernate.naming.physical-strategy = org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
第三个坑,如果使用like,代码如下:
public Page<User> findUserByKey(String key, int page, int size){
Page<User> result = userRepository.findAll(new Specification<User> () {
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<User, Role> join = root.join("role", JoinType.INNER);
Path<String> namePath = join.get("name");
Path<String> statusPath = root.get("status");
Path<String> usernamePath = root.get("username");
Path<String> nicknamePath = root.get("nickname");
Predicate or = cb.or(cb.like(namePath, key), cb.like(usernamePath, key), cb.like(nicknamePath, key));
return cb.and(or, cb.equal(statusPath, 0));
}
}, PageRequest.of(page - 1, size));
return result;
}
其中的key,必须是类似%abc%,而不是'%abc%'。
'