Spring Data Jpa 单表查询

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. 简单查询

  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同学 的知乎主页
我的微信

Spring Data Jpa 单表查询_第1张图片
Allen同学

你可能感兴趣的:(Spring Data Jpa 单表查询)