Spring Data repository的目标是显著减少各种持久化存储在数据访问层的模板代码量。
简化一点的人话就是:减少数据库读写相关的代码量。
关于这一点Spring Data repository的确做到了,很多简单的操作甚至可以不用写实现代码,甚至不需要写SQL。
Spring Data套装中有很多工具,基本都是为了数据访问层需要。
这篇文章主要介绍一下核心的通用的概念相关的东西。
Repository接口:
CrudRepository中定义了一些常见的增删改查接口。
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
}
Repository接口有2个类型参数:
T当前需要映射实体
ID当前映射的实体中的主键类型
save方法保存对象
findById根据id获取对象
findAll查询所有对象
返回索引对象数量
删除指定对象
检查指定id对象是否存在
如果Spring Data repository只是提供了一些接口,那它就没什么值得好说。
重要的是它为我们提供了不写代码,或者少写代码就能实现数据存储访问的方式。
下面,我们通过一个简洁的小例子来说明怎样简单使用。
假设我们使用SpringBoot、MySQL、Spring Data JPA。
首先引入JPA依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
@Configuration
@EnableJpaRepositories(basePackages = {"vip.mycollege.mysql.jpa.repository"})
public class SpringDataJPAConfig {
}
这个配置主要是告诉Spring Repository接口在那些包下面。
Spring会去扫描这些包,找到继承了Repository的接口,为它们生成代理类。
application.properties文件配置数据库:
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=tim
spring.datasource.password=123456
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 = "user")
public class User {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)//主键生成策略
@Column(name="id")//数据库字段名
private Integer id;
@Column(name = "username", unique = true, nullable = false, length = 30)
private String username;
@Column(name="age")
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}
}
主要是几个注解:
import org.springframework.data.repository.CrudRepository;
import vip.mycollege.mysql.jpa.entity.User;
public interface UserCrudRepository extends CrudRepository<User,Integer> {
}
如果只是简单的增删改查,就这样就可以了,什么方法都不用添加,也不需要自己去实现接口。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import vip.mycollege.mysql.jpa.entity.User;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserCrudRepositoryTest {
@Resource
private UserCrudRepository userCrudRepository;
@Test
public void save(){
User user = new User();
user.setAge(20);
user.setUsername("allen");
userCrudRepository.save(user);
}
}
不需要创建表,直接就可以使用。
除了save方法,CrudRepository中其他的方法也都可以直接使用,不用再去写实现类SQL。
如果要分页排序怎么办?
Spring Data Repository也考虑到了,实现PagingAndSortingRepository接口就可以了。
PagingAndSortingRepository继承了CrudRepository。
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
还是只需要继承接口就可以了。
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}
然后,就可以直接使用了:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import vip.mycollege.mysql.jpa.entity.User;
import javax.annotation.Resource;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserPagingAndSortingRepositoryTest {
@Resource
private UserPagingAndSortingRepository userPagingAndSortingRepository;
@Test
public void testPagingAndSortingRepositorySort() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<User> list = (List<User>) userPagingAndSortingRepository.findAll(sort);
for (User users : list) {
System.out.println(users);
}
}
@Test
public void testPagingAndSortingRepositoryPaging() {
Pageable pageable = PageRequest.of(1, 2);
Page<User> page = userPagingAndSortingRepository.findAll(pageable);
System.out.println("数据的总条数:" + page.getTotalElements());
System.out.println("总页数:" + page.getTotalPages());
List<User> list = page.getContent();
for (User users : list) {
System.out.println(users);
}
}
@Test
public void testPagingAndSortingRepositorySortAndPaging() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(0, 10, sort);
Page<User> page = userPagingAndSortingRepository.findAll(pageable);
System.out.println("数据的总条数:" + page.getTotalElements());
System.out.println("总页数:" + page.getTotalPages());
List<User> list = page.getContent();
for (User users : list) {
System.out.println(users);
}
}
}
如果觉得CrudRepository方法比较多,不想暴露那么多接口,那可以直接继承Repository。
@NoRepositoryBean
interface BaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends BaseRepository<User, Long> {
User findByUsername(String username);
}
@NoRepositoryBean注解告诉Spring这个接口不用创建代理对象实例,主要是用来继承的。
除了已经定好的接口方法,Spring Data Repository还可以通过解析方法名来执行查询,如:
public interface UserRepository extends Repository<User,Integer> {
List<User> findByUsernameAndAge(String name,Integer age);
}
然后,也不需要实现方法就可以直接使用:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import vip.mycollege.mysql.jpa.entity.User;
import javax.annotation.Resource;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UsersRepositoryTest {
@Resource
private UserRepository userRepository;
@Test
public void findByUsernameAndAge(){
List<User> users = userRepository.findByUsernameAndAge("tim", 20);
users.forEach(System.out::println);
}
}
// Page findByAge(Integer age,Pageable pageable);
Slice<User> findByAge(Integer age, Pageable pageable);
List<User> findByAge(Integer age,Sort sort);
@Test
public void findAgeSlice(){
Pageable pageable = PageRequest.of(0,3);
while(true){
Slice<User> slice = userRepository.findByAge(20,pageable);
List<User> users = slice.getContent();
users.forEach(System.out::println);
if (!slice.hasNext()){
break;
}
pageable = slice.nextPageable();
}
}
// @Test
// public void findAgePage(){
// Pageable pageable = PageRequest.of(1, 3);
// Page page = userRepository.findByAge(20,pageable);
// List users = page.getContent();
// users.forEach(System.out::println);
// }
@Test
public void findSort(){
Sort sort = Sort.by(Sort.Direction.DESC, "username");
List<User> users = userRepository.findByAge(20,sort);
users.forEach(System.out::println);
}
Spring Data Repository还支持异步查询:
@Async
Future<User> findByUsername(String username);
@Async
CompletableFuture<User> findOneByUsername(String username);
@Async
ListenableFuture<User> findOneByAge(Integer age);
然后,可以这样调用:
@Test
public void findByUsername() throws ExecutionException, InterruptedException {
Future<User> tim = userRepository.findByUsername("tim");
System.out.println(tim.get());
}
List<User> findFirst10ByUsername(String username, Sort sort);
List<User> findTop10ByUsername(String username);
interface UserRepository extends CrudRepository<User, Long> {
long countByUsername(String username);
long deleteByUsername(String username);
List<User> removeByUsername(String username);
}
Spring Data Repository可以返回Stream。
Stream<User> readAllByAgeAfter(Integer age);
@Test
@Transactional(readOnly = true)
public void readAllByAgeAfter() {
try (Stream<User> userStream = userRepository.readAllByAgeAfter(20)) {
userStream.forEach(System.out::println);
}
}
我们已经知道Spring Data Repository非常强大的功能就是根据方法名解析查询,不用自己写SQL或者查询逻辑之类,只需要Repository中的方法名遵循相关规范就可以。
findBy + 属性名 + 关键字(And、Or…) + 属性名 + 关键字(And、Or…)
Spring Data Jpa会对方法进行解析,会先把方法中find…By、read…By、query…By、count…By、get…By等去掉,并且根据剩下的关键字and、or等解析生成SQL。
感兴趣的朋友可以看一下:PartTree、Part类
关键字 | 方法名称示例 | 查询提示 |
---|---|---|
Is | findByFirstname,findByFirstnameIs | = |
Or | findByLastnameOrFirstname | or |
In | findByIdIn(Collection ids) | in |
And | findByLastnameAndFirstname | and |
Not | findByLastnameNot | not |
Asc | findByLastnameAsc | asc |
Top | findTop10ByUsername | top |
True | findByActiveTrue | true |
Like | findByFirstnameLike | like |
NotIn | findByIdNotIn(Collection ids) | not in |
False | findByActiveFalse | false |
After | findByStartDateAfter | > |
Before | findByStartDateBefore | < |
Equals | findByFirstname,findByFirstnameEquals | = |
IsNull | findByAgeIsNull | is null |
NotNull | findByAgeNotNull | is not null |
Between | findByStartDateBetween | between and |
NotLike | findByFirstnameNotLike | like |
OrderBy | findByAgeOrderByLastnameDesc | order by |
LessThan | findByAgeLessThan | <= |
Distinct | findByUsernameDistinct | distinct |
IsNotNull | findByAgeIsNotNull | is not null |
EndingWith | findByFirstnameEndingWith | like |
Containing | findByFirstnameContaining | like |
IgnoreCase | findByFirstnameIgnoreCase | = |
GreaterThan | findByAgeGreaterThan | > |
StartingWith | findByFirstnameStartingWith | like |
LessThanEqual | findByAgeLessThanEqual | <= |
GreaterThanEqual | findByAgeGreaterThanEqual | >= |
@Query可以直接指定SQL语句,只需要将nativeQuery设置为true
@Query(value = "select * from user where username like ?1",nativeQuery = true)
List<User> queryByUsername(String username);
在SQL语句中使用?1,?2…?n这样的占位符和方法参数对应。
默认,@Query指定的是JPQL:
@Query("select u from User u where u.username like ?1%")
Page<User> findByUsernameLikePageable(String username, Pageable pageable);
@Test
public void findByUsernameLikePageable(){
PageRequest pageRequest = PageRequest.of(0, 5);
Page<User> page = userJpaRepository.findByUsernameLikePageable("t", pageRequest);
System.out.println(page.getTotalPages());
page.getContent().forEach(System.out::println);
}
注意:在JPQL查询使用的是类名User而不是表名user,使用的是别名而不是字段,如果写成:
@Query(“select id,name from User u where u.username like ?1%”)
查出来的就是Object对象。
Spring Data Repositories
查询方法解析