1. 项目地址
gitee地址: https://gitee.com/allenzones/allen-spring-data-jpa.git
2. 前言
本片文章仅讨论spring data jpa的单表查询,不讨论hibernate各种join关联的骚操作,原因看下面4结语,也不讨论mybatis。
建表
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `test`;
CREATE TABLE `test` (
`test_id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`test_name` varchar(255) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`test_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `test` VALUES ('1', '1', 'allen', '2017-12-13 14:07:07');
INSERT INTO `test` VALUES ('2', '1', 'jack', '2017-12-21 01:13:34');
INSERT INTO `test` VALUES ('3', '1', 'tom', '2017-11-16 01:14:13');
INSERT INTO `test` VALUES ('4', '4', 'annie', '2017-12-06 01:14:30');
DROP TABLE IF EXISTS `test_two`;
CREATE TABLE `test_two` (
`test_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`test_name` varchar(255) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`test_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `test_two` VALUES ('1', '1', 'allen', '2017-12-04 01:10:32');
INSERT INTO `test_two` VALUES ('1', '2', 'jack', '2017-12-02 01:11:09');
INSERT INTO `test_two` VALUES ('2', '1', 'tom', '2017-03-12 01:11:25');
INSERT INTO `test_two` VALUES ('2', '2', 'annie', '2017-11-01 01:12:07');
3. 查询
3.1. 简单查询
- findOne
Test test = testRepository.findOne(testId);
通过findOne 可以通过主键索引将主键对应的行查出来。
对于联合主键查询如下:
首先定义主键类TestTwoKey,加入@Embeddable注解
@Embeddable
@Getter
@Setter
public class TestTwoKey implements Serializable{
@Column(name = "test_id")
private Long testId;
@Column(name = "user_id")
private Long userId;
public TestTwoKey() {
}
public TestTwoKey(Long testId, Long userId) {
this.testId = testId;
this.userId = userId;
}
}
在TestTwo类中使用TestTwoKey作为主键
@Entity
@Table(name = "test_two")
@Getter
@Setter
public class TestTwo implements Serializable{
@EmbeddedId
private TestTwoKey key;
@Column(name = "test_name")
private String testName;
@Column(name = "create_time")
private Date createTime;
public TestTwo() {
}
public TestTwo(Long testId,Long userId) {
TestTwoKey testTwoKey = new TestTwoKey(testId,userId);
this.key = testTwoKey;
}
}
在Repository中集成的JpaRepository将主键类型改为TestTwoKey
public interface TestTwoRepository extends JpaRepository{
}
最后查询
TestTwoKey testTwoKey = new TestTwoKey(testId,userId);
TestTwo testTwo = testTwoRepository.findOne(testTwoKey);
2.findAll
查询全部
List testList = testRepository.findAll();
也可以通过主键idList查询
public List findAllTwo(List testIdList) {
List testList = testRepository.findAll(testIdList);
return testList;
}
3.2. 条件查询
通过Example查询满足条件
Test test = new Test();
test.setTestId(testId);
test.setUserId(userId);
test.setTestName(testName);
ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING);
Example example= Example.of(test,matcher);
List testList = testRepository.findAll(example);
通过Specification查询
使用Specification,在对应的Repository中,得继承 JpaSpecificationExecutor
public interface TestRepository extends JpaRepository, JpaSpecificationExecutor{
}
查询代码
public List findByTermTwo(Long testId, List userIdList, String testName) {
List testList = testRepository.findAll(new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb) {
Predicate p1 = null;
if (testId != null) {
p1 = cb.equal(root.get("testId"), testId);
}
if (testName != null) {
Predicate p2 = cb.like(root.get("testName"), "%" + testName + "%");
p1 = p1 == null ? p2 : cb.and(p1, p2);
}
CriteriaBuilder.In userIdIn = cb.in(root.get("userId"));
if (userIdList.size() != 0) {
for (Long id : userIdList) {
userIdIn.value(id);
}
}
List predicates = new ArrayList<>();
if (p1 != null) {
predicates.add(p1);
}
if (userIdList.size() != 0) {
predicates.add(userIdIn);
}
if (predicates.size() != 0) {
query.where(predicates.toArray(new Predicate[]{}));
} else {
query.where();
}
return null;
}
});
return testList;
}
sql的in对应一个CriteriaBuilder.In对象,此外还要注意的是like,模糊查询的%如果放在前面很影响性能,在全文搜索中,还是用solr或者elasticsearch比较合适。
对于Example和Specification用法还有很多,详情请看Api文档,对比之下Specification更加灵活。
3.3. 分页查询
1.分页查询
1.1.构建PageRequest
通过构建PageRequest查询,第一个参数是第几页,第二个参数是页面大小,第三个是排序。
需要注意的是构建排序Sort时,紧跟升降序之后的字段是JavaBean的字段名,而不是Mysql的字段名。
Sort sort = new Sort(Sort.Direction.DESC,"createTime");
PageRequest pageRequest = new PageRequest(0,2,sort);
Page page = testRepository.findAll(pageRequest);
返回结果是用Page包装的beanList,里面包含了分页信息。
totalPages总共多少页
totalElements总共的元素个数
first第一页
last最后一页
{
"content": [
{
"testId": 4,
"userId": 4,
"testName": "annie",
"createTime": 1512494070000
}
],
"totalPages": 1,
"totalElements": 1,
"last": true,
"number": 0,
"size": 10,
"sort": [
{
"direction": "DESC",
"property": "createTime",
"ignoreCase": false,
"nullHandling": "NATIVE",
"ascending": false,
"descending": true
}
],
"first": true,
"numberOfElements": 1
}
1.2. 在controller传入page参数
PageableDefault初始化分页条件,然后可以通过传参来修改分页条件
传参
page
size
sort
在controller中可以配置默认的排序方式,如下
public Object getByPageTwo(@PageableDefault(sort = {"createTime"}, direction = Sort.Direction.DESC)Pageable pageable){
return testService.findByPageTwo(pageable);
}
在service中
Page page = testRepository.findAll(pageable);
结果如上Json数据
2.条件分页查询
2.1 通过Example
Sort sort = new Sort(Sort.Direction.DESC,"createTime");
PageRequest pageRequest = new PageRequest(0,2,sort);
Test test = new Test();
test.setTestId(testId);
test.setUserId(userId);
test.setTestName(testName);
ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING);
Example example= Example.of(test,matcher);
Page page = testRepository.findAll(example,pageRequest);
2.2 通过Specification
@Override
public Page findByPageAndTermTwo(Long testId, List userIdList, String testName, Pageable pageable) {
Page page = testRepository.findAll(new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb) {
Predicate p1 = null;
if (testId != null) {
p1 = cb.equal(root.get("testId"), testId);
}
if (testName != null) {
Predicate p2 = cb.like(root.get("testName"), "%" + testName + "%");
p1 = p1 == null ? p2 : cb.and(p1, p2);
}
CriteriaBuilder.In userIdIn = cb.in(root.get("userId"));
if (userIdList.size() != 0) {
for (Long id : userIdList) {
userIdIn.value(id);
}
}
List predicates = new ArrayList<>();
if (p1 != null) {
predicates.add(p1);
}
if (userIdList.size() != 0) {
predicates.add(userIdIn);
}
if (predicates.size() != 0) {
query.where(predicates.toArray(new Predicate[]{}));
} else {
query.where();
}
return null;
}
}, pageable);
return page;
}
3.4. 自定义查询
1.通过Repository的@Query
在@Query语句中,可以作为增删改查,在增删改中还要添加@Modifying注解,在调用此Repository方法中的Service方法还要加入@Transactional事务注解。如果要插入数据的化必须得将@Query定义为nativeQuery=true,由于此次只讨论查询,不讲太多。
使用Query来查询,注意的是字段名为javaBean的名字。
查询testId为x的结果
@Query("select t from Test t where t.testId = ?1")
Test getTestById(Long testId);
查询userId为x的结果,带上分页
@Query("select t from Test t where t.userId = ?1")
List getTestByUserId(Long userId, Pageable pageable);
虽然支持不写@Query注解,通过方法名的规范也可以实现结果,但是可读性不是特别强,不建议通过方法名的命名规范来查询,比如说下面一句化可以达到查询testId为x的结果,可以不用写@Query
Test getTestByTestId(Long testId);
使用nativeQuery来查询,注意的是字段名为sql列的名字
@Query(nativeQuery = true,value = "select * from test t where t.test_id = ?1")
Test getTestByNativeQuery(Long testId);
2.通过自定义sql语句
通过custom,要注意类名的命名规范,接口为xxxRepositoryCustom,继承此接口的为xxxRepositoryImpl,如果不按照此作为类名,在启动时会报错。
然后再xxxRepository接口中继承xxxRepositoryCustom接口。
public interface TestRepositoryCustom {
}
public class TestRepositoryImpl implements TestRepositoryCustom {
@PersistenceContext
private EntityManager em;
}
public interface TestRepository extends JpaRepository,TestRepositoryCustom{
}
然后查询testId为x的结果
public JSONArray getTestListByCustom(Long userId) {
JSONArray jsonArray = new JSONArray();
String sql = "select t.test_id ,t.user_id,t.test_name,t.create_time from test t where t.user_id = ?";
Query query = em.createNativeQuery(sql);
query.setParameter(1, userId);
List result = query.getResultList();
Iterator iterator = result.iterator();
while (iterator.hasNext()) {
Object[] row = (Object[]) iterator.next();
JSONObject jsonObject = new JSONObject();
jsonObject.put("testId", row[0]);
jsonObject.put("userId", row[1]);
jsonObject.put("testName", row[2]);
jsonObject.put("createTime", row[3]);
jsonArray.add(jsonObject);
}
return jsonArray;
}
这种方法是通过EntityManager来执行query。
4. 结语
当然spring data jpa 查询有很多,这里不一一细讲,毕竟常用的就这些。虽然数据库支持各种join联表查询,但是在目前Spring Cloud微服务框架中,很多业务逻辑对应模块的存储是分库的,并且在大表中执行一次join操作是很耗费时间以及空间的。微服务的宗旨是服务细化,所以可以说,可以进行join操作,但是不推荐,在spring data jpa 的文档中,也不细讲hibernate的join关联。
个人博客
Allen的博客:blog.allenszone.tech
我的知乎
Allen同学 的知乎主页
我的微信