先让咱们来巩固一下 Java 的基础知识。Java 是面向对象的语言,目的是解决现实生活中的问题,能够经过使用类来包装现实生活中的事物成对象、使用属性来描述对象的特色并使用方法来控制对象的行为。咱们在 Java 里一切的操做都是针对对象自己,这也是为何咱们须要 ORM 来操做存储在数据库里面的“对象”。题外话,对于相似于 Redis 这种基于 Key-Value 存储的 NoSQL(非关系型数据库)来讲,咱们并不能很显然地观察到存储在里面的“对象”,由于这种“对象”不一样于关系型数据库里的对象,好比 MySQL。在 MySQL 中,其 Table 就对应 Java 中的类,每一条 Column 对应的就是对象,每个字段对应的就是属性,固然 MySQL 也有本身的方法,不过不在本文讨论范围内。咱们能够很直观地经过 SQL 的查询语句来观察“对象”,但在 NoSQL 中,咱们只能经过序列化和反序列化来写和读对象。相信我,您宁愿看汇编也不想看序列化以后的对象……html
为了节省时间,关于用什么和怎么来构建基本环境不在此赘述,这里提供 SDJ 的官方指南之一供您参考,只要您能引用 SDJ 的包(org.springframework.data:spring-data-jpa:1.11.6.RELEAS)就行。java
SDJ 对于应用来讲最应该关注的就是如下几个核心接口(按照从子类到父类的继承顺序):mysql
JpaRepository —— JPA 协议的具体实现的接口。git
package org.springframework.data.jpa.repository; public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {}
PagingAndSortingRepository ——分页和排序的接口。QueryByExampleExecutor ——范例查询的接口。github
package org.springframework.data.repository; public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {} package org.springframework.data.repository.query; public interface QueryByExampleExecutor<T> {}
CrudRepository ——通用CRUD操做的接口。redis
package org.springframework.data.repository; public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {}
就如同名称所示,咱们能够根据状况继承特定的接口,不一样的接口提供不一样的功能,若是咱们须要分页和排序,就继承 PagingAndSortingRepository
接口。但为了全方位地了解 SDJ,本文使用 JpaRepository
接口,因为其位于继承树的最底端,能够理解成二叉树里面的树叶,因此可使用包括其父类的全部未被重写方法。固然这样也有一些不影响功能实现的矛盾点,咱们一会会见到。spring
如同以前所说,咱们须要针对性地经过类来构建一个对象。这里建立一个用户类(推荐使用 Lombok 来简化代码)。sql
// SDJ 使用 @Entity,NoSQL 使用 @Document。(暂未验证) @Entity public class User { // 声明主键。 @Id // 声明由程序控制主键生成策略。 @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // 约束、不可为空、长度16。 @Column(unique = true, nullable = false, length = 16) private String username; @Column(unique = true, nullable = false) private String email; // 设定默认值。 private Double balance = 0.0; // 不容许更新。 @Column(updatable = false) private Date createTime = new Date(); // 为 SDJ 提供一个默认的构造方法。 public User() { } // 提供一个不可为 null 的属性的构造方法以防止出错。 public User(String username, String email) { this.username = username; this.email = email; } // 省略一大堆 Getter、Setter 和 ToString 方法。也能够经过 Lombok 插件以注解的方式大幅度简化,各大 IDE 均提供! }
而后建立一个简单到可能怀疑本身人生接口,继承自 JpaRepository
。数据库
import org.springframework.data.jpa.repository.JpaRepository; import top.cciradih.spring.data.jpa.entity.User; // <User, Long> 泛型指定了能接受的对象类型和其主键类型,主键类型在一些方法里颇有用。 public interface UserRepository extends JpaRepository<User, Long> { }
惊不惊喜?意不意外?咱们已经可使用它了!什么都不用写!segmentfault
// 保存单个对象并返回。 User savedUser = userRepository.save(new User("Cciradih", "hidarichaochen@gmail.com"));
这是调用 CrudRepository<T, ID extends Serializable>
接口的 <S extends T> S save(S entity)
方法来保存并返回存储的对象。
// 保存多个对象并返回。 List<User> userList = new ArrayList<>(); User newUser; for (int i = 0; i < 10; i++) { newUser = new User("Cciradih" + i, "hidarichaochen@gmail.com" + i); userList.add(newUser); } List<User> savedUserList = userRepository.save(userList);
这是调用 JpaRepository<T, ID extends Serializable>
接口的 <S extends T> List<S> save(Iterable<S> entities)
方法来保存多个对象。
// 根据主键查询单个对象。 User foundUser = userRepository.findOne(1L);
这是调用 CrudRepository<T, ID extends Serializable>
接口的 T findOne(ID id)
方法来查询单个对象。
// 查询所有对象。 List<User> foundUserList = userRepository.findAll();
这是调用 JpaRepository<T, ID extends Serializable>
接口的 List<T> findAll()
方法来查所有对象。
import org.springframework.data.domain.Sort; // 根据 id 字段查询并排序,默认是顺序(ASC)。 List<User> foundASCSortedUserList = userRepository.findAll(new Sort("id")); // 根据 id 字段倒序查询(DESC)。 List<User> foundDESCSortedUserList = userRepository.findAll(new Sort(Sort.Direction.DESC, "id"));
这里涉及到新的对象—— new Sort("id")
。从名字能够看得出来是为了排序的,其内部的具体逻辑在本文就不赘述。经过 JpaRepository<T, ID extends Serializable>
接口的 List<T> findAll(Sort sort)
方法来查询并排序。
import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; User user = new User("Cciradih", "hidarichaochen@gmail.com"); // 1.使用 Example。 // 建立 Example。 Example<User> userExample = Example.of(user); User foundExampleUser = userRepository.findOne(userExample); // 2.使用 ExampleMatcher。 // 建立 ExampleMatcher。 ExampleMatcher exampleMatcher = ExampleMatcher.matching() // 忽略 id 和 createTime 字段。 .withIgnorePaths("id", "createTime") // 忽略大小写。 .withIgnoreCase() // 忽略为空字段。 .withIgnoreNullValues(); // 携带 ExampleMatcher。 userExample = Example.of(user, exampleMatcher); User foundExampleWithExampleMatcherUser = userRepository.findOne(userExample);
这里涉及到 Example 和 ExampleMatcher 的使用。
Example 经过其静态方法 <T> Example<T> of(T probe)
来引入一个 Example<T>
对象。而后就经过 QueryByExampleExecutor<T>
接口的 <S extends T> S findOne(Example<S> example)
方法来查询符合范例的对象。固然这里在一般状况下是查询不到任何对象的,由于 User 有不一样的 Id 和某种意义上讲不会相同的 createTime。
ExampleMatcher 经过静态方法 <T> Example<T> of(T probe, ExampleMatcher matcher)
携带一个符合的泛型对象和 ExampleMatcher
对象,这里忽略了 id 和 createTime 字段,因此可以查找到符合条件的对象。
ExampleMatcher 是对 Example 的拓展,这也是为何我说 SDJ 的控制细腻度依然很高的缘由。
关于 ExampleMatcher 更多的过滤方法能够自行参照源码。
import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; // 分页查询,从 0 页开始查询 5 个。 Page<User> foundUserPage = userRepository.findAll(new PageRequest(0, 5)); // 分页表。 List<User> content = foundUserPage.getContent(); // 总数量。 long totalElements = foundUserPage.getTotalElements(); // 总页数。 long totalPages = foundUserPage.getTotalPages(); // 分页表大小。 int size = foundUserPage.getSize();
这里涉及到新的对象—— new PageRequest(0, 5)
。从名字能够看得出来是为了分页的,其内部的具体逻辑在本文就不赘述。经过 PagingAndSortingRepository<T, ID extends Serializable>
接口的 Page<T> findAll(Pageable pageable)
方法来查询 Page<T>
对象。Page 包含了全部分页须要的属性,能够经过其方法来获取。
// 根据主键删除单个对象 userRepository.delete(1L);
这是调用 CrudRepository<T, ID extends Serializable>
接口的 void delete(ID id)
方法来根据主键删除单个对象。
// 根据对象删除单个对象 User user = new User("Cciradih", "hidarichaochen@gmail.com"); userRepository.delete(user);
这是调用 CrudRepository<T, ID extends Serializable>
接口的 void delete(T entity)
方法来根据对象删除单个对象。和 Example 的例子同样,也不会删除任何对象,请自行思考。
// 删除所有 userRepository.deleteAll();
这是调用 CrudRepository<T, ID extends Serializable>
接口的 void deleteAll()
方法来根据对象删除单个对象。
以前提到过的一些不影响功能实现的矛盾点,在这里体现。因为 JpaRepository<T, ID extends Serializable>
间接继承自 CrudRepository<T, ID extends Serializable>
因此咱们能够同时使用 void deleteAllInBatch()
和 void deleteAll()
方法,二者的功能是同样的,能够两者取其一。想要避免这样的事,只能在不影响实际业务逻辑的状况下往上继承接口。
// 删除多个对象 List<User> userList = new ArrayList<>(); User user; for (int i = 0; i < 10; i++) { user = new User("Cciradih" + i, "hidarichaochen@gmail.com" + i); userList.add(user); } userRepository.delete(userList);
这是调用 CrudRepository<T, ID extends Serializable>
接口的 void delete(Iterable<? extends T> entities)
方法来删除多个对象。和 Example 的例子同样,也不会删除任何对象,请自行思考。
// 统计对象数量 long count = userRepository.count();
这是调用 CrudRepository<T, ID extends Serializable>
接口的 long count()
方法来统计对象数量。
// 判断对象是否存在 boolean exists = userRepository.exists(1L);
这是调用 CrudRepository<T, ID extends Serializable>
接口的 boolean exists(ID id)
方法来根据主键判断对象是否存在。
我尽量地列举了默认方法,但为了行文方便,其中有些方法是能够配合使用的,好比 JpaRepository<T, ID extends Serializable>
接口的 <S extends T> List<S> findAll(Example<S> example, Sort sort)
,能够同时传入 Example 和 Sort 对象进行查询,请自行参照源码使用。
我但愿您可以经过此文对 SDJ 的默认方法达到“知其然”的程度,在软件开发中,我认为须要从“知其然”到”知其因此然“再到”造其因此然“的地步,但路须要一步一步走。
源码仓库
系列目录
参考
个人咖啡馆
若是您对本文有什么建议或者问题,欢迎您来个人咖啡馆坐坐338147322。