Jpa规范之hibernate的一对一关联查询操作学习

北城以北 提交于 2020-01-28 04:00:55

我们搭建一个maven项目,然后我们加载数据,数据我已经准备好了。

-- truncate all
delete from jpa_test.oo_t_idcard where 1=1;
delete from jpa_test.oo_t_person where 1=1;

-- oo_t_person
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (1, 4, '2019-12-12 02:55:14.786000000', 4, '2019-12-12 02:55:14.786000000', 'green', 62.00, 75.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (2, 4, '2019-12-12 02:55:14.841000000', 4, '2019-12-12 02:55:14.841000000', 'blue', 97.00, 23.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (3, 4, '2019-12-12 02:55:14.843000000', 4, '2019-12-12 02:55:14.843000000', 'yellow', 20.00, 9.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (4, 4, '2019-12-12 02:55:14.845000000', 4, '2019-12-12 02:55:14.845000000', 'blue', 102.00, 35.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (5, 4, '2019-12-12 02:55:14.847000000', 4, '2019-12-12 02:55:14.847000000', 'blue', 120.00, 45.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (6, 4, '2019-12-12 02:55:14.848000000', 4, '2019-12-12 02:55:14.848000000', 'blue', 98.00, 42.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (7, 4, '2019-12-12 02:55:14.851000000', 4, '2019-12-12 02:55:14.851000000', 'green', 156.00, 17.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (8, 4, '2019-12-12 02:55:14.854000000', 4, '2019-12-12 02:55:14.854000000', 'black', 72.00, 8.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (9, 4, '2019-12-12 02:55:14.856000000', 4, '2019-12-12 02:55:14.856000000', 'black', 105.00, 5.00);
INSERT INTO jpa_test.oo_t_person (id, created_by, created_date, last_modified_by, last_modified_date, hair_color, height, weight) VALUES (10, 4, '2019-12-12 02:55:14.858000000', 4, '2019-12-12 02:55:14.858000000', 'red', 172.00, 16.00);

-- oo_t_idcard
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (11, 4, '2019-12-12 02:55:15.716000000', 4, '2019-12-12 02:55:15.716000000', '6c398cd7-8ed9-4c6c-8080-120a1a826f89', '张三1', 0, 2);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (12, 4, '2019-12-12 02:55:15.719000000', 4, '2019-12-12 02:55:15.719000000', '87b5feff-8255-4ac6-aa69-6a9990048dcd', '孙七', 0, 3);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (13, 4, '2019-12-12 02:55:15.720000000', 4, '2019-12-12 02:55:15.720000000', '307dc084-a241-452b-b609-c824eb96bc44', '李四', 0, 4);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (14, 4, '2019-12-12 02:55:15.722000000', 4, '2019-12-12 02:55:15.722000000', '5e704f89-2d13-4794-9494-29dfa7c446ec', '赵六', 0, 5);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (15, 4, '2019-12-12 02:55:15.724000000', 4, '2019-12-12 02:55:15.724000000', 'b0ade2b0-a350-4536-90a0-4b09736493ec', '李四', 1, 6);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (16, 4, '2019-12-12 02:55:15.727000000', 4, '2019-12-12 02:55:15.727000000', 'a9c1531e-8719-4912-8bc7-ba3f7a5da05f', '李四', 1, 7);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (17, 4, '2019-12-12 02:55:15.729000000', 4, '2019-12-12 02:55:15.729000000', '1ad27d7b-0208-4adb-a856-d6a87dc4a227', '钱八', 0, 8);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (18, 4, '2019-12-12 02:55:15.733000000', 4, '2019-12-12 02:55:15.733000000', '08fd002e-bce7-4931-acb0-b18cf134880b', '王五', 1, 9);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (19, 4, '2019-12-12 02:55:15.740000000', 4, '2019-12-12 02:55:15.740000000', '34f09eef-c0f5-4418-936c-b58c2a82b256', '钱八', 1, 10);
INSERT INTO jpa_test.oo_t_idcard (id, created_by, created_date, last_modified_by, last_modified_date, card_no, name, sex, person_id) VALUES (20, 4, '2019-12-12 02:55:16.213000000', 4, '2019-12-12 02:55:16.213000000', 'a9963fd3-be45-483f-b5e1-e409de9c9c84', '赵六', 0, 1);

然后我们编写一个人和身份证两个实体类之间的一对一的关系:

@Entity
@Table(name = "oo_t_idcard")
@Data
@ToString(exclude = "person", callSuper = true)
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class IdCard extends DefaultEntity {

    private static final long serialVersionUID = -4315695680043696544L;
    /**
     * 姓名
     */
    private String name;

    /**
     * 身份证号
     */
    private String cardNo;

    /**
     * 性别
     */
    private Integer sex;

    /**
     * 外键
     */
    @OneToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_person"))
    private Person person;

}
@Entity
@Table(name = "oo_t_person")
@Setter
@Getter
@ToString(exclude = "idCard", callSuper = true)
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Person extends DefaultEntity {

    private static final long serialVersionUID = 4051419685118041233L;

    /**
     * 身高
     */
    private BigDecimal height;

    /**
     * 体重
     */
    private BigDecimal weight;

    /**
     * 头发颜色
     */
    private String hairColor;

    @OneToOne(mappedBy = "person", cascade = {CascadeType.MERGE, CascadeType.REMOVE})
    private IdCard idCard;
}

然后就是定义接口继承hibernate提供的仓库JpaRepository如下代码:

/**
 * 身份证 .
 *
 * @className: IDCardRepository
 * @author: ws
 * @date: 19-11-25 下午2:18
 * @version: 1.0.0
 */
public interface IDCardRepository extends JpaRepository<IdCard, Long> {

}
/**
 * 人 数据访问层 .
 *
 * @className: PersonRepository
 * @author: ws
 * @date: 19-11-25 下午2:12
 * @version: 1.0.0
 */
public interface PersonRepository extends BaseRepository<Person, Long> {

    Optional<Person> findByWeightAndHeight(BigDecimal weight, BigDecimal height);

    int countByHairColor(String hairColor);

    @Query("from Person p left join fetch p.idCard i where i.name = ?1")
    Person findSomePersonByNameAtJPQL(String name);

    @Query(value = "select * from oo_t_person p left join oo_t_idcard oti on p.id = oti.person_id where oti.name = ?1"
            , nativeQuery = true)
    Person findSomePersonByNameAtNative(String name);

    @Transactional
    long deleteByHeight(BigDecimal height);
}

最后我们边测试边理解hibernate的各种api方法,和查询方法和规则:

public class One2OneRepositoryTest extends BaseTest {

    /**
     * 测试数据数量
     */
    private int dataCount = 10;

    /**
     * 姓名数组
     */
    private String[] nameArr = {"张三", "李四", "王五", "赵六", "孙七", "钱八"};

    /**
     * 性别数组
     */
    private Integer[] sexArr = {0, 1};

    private String[] hairColorArr = {"red", "green", "black", "blue", "yellow", "pink", "gray"};

    /**
     * 随机数
     */
    private Random random = new Random();

    @Autowired
    private DynamicLeftJoinQuery dynamicLeftJoinQuery;

    @Autowired
    private EntityManager entityManager;

    @Test
    @Order(1)
    @Disabled
    @DisplayName("批量数据保存(Person -> IdCard)")
    void testSave1() {

        // Person表保存所有生成出来的数据
        System.out.println("=== Person表保存所有生成出来的数据 =========================================");
        Collection<Person> persons = initPersonData(dataCount);
        personRepository.saveAll(persons);

        // 将第一个Person数据从persons集合移出,并将其他Person数据生成对应的idCard数据
        Person firstPerson = persons.iterator().next();
        persons.remove(firstPerson);
        System.out.println("=== 将第一个Person数据从persons集合移出,并将其他Person数据生成对应的idCard数据 =========================================");
        Collection<IdCard> idCards = initIDCardData(persons);

        // 保存idCards集合的所有数据,除第一个Person
        System.out.println("=== 保存idCards集合的所有数据,除第一个Person =========================================");
        idCardRepository.saveAll(idCards);

        // 用自由态的方式关联赋值给idCard,并保存
        System.out.println("=== 用自由态的方式关联赋值给idCard,并保存 =========================================");
        idCards = initIDCardData(Collections.singleton((Person) new Person().setId(firstPerson.getId())));
        idCardRepository.saveAll(idCards);

        assertAll(
                // 随机检查fk有没有生成
                () -> assertThat(assertThat(idCardRepository.getOne()
                            .getPerson()
                            .getId())
                        .isNotNull()),
                // 检查第一个Person的fk关联有没有建立成功
                () -> assertThat(assertThat(idCardRepository
                        .count(Example.of(new IdCard()
                                .setPerson((Person) new Person()
                                        .setId(firstPerson.getId())))))
                        .isEqualTo(1))
        );
    }

    @Test
    @Order(2)
    @Disabled
    @DisplayName("批量数据保存(IdCard -> Person)")
    void testSave2() {
        Person person = initPersonData(1).get(0);
        IdCard idCard = initIDCardData(Collections.singleton(person)).get(0);
        idCard.setPerson(null);

        // 先分别保存无关联的idCard和person
        System.out.println("=== 先分别保存无关联的idCard和person =========================================");
        idCardRepository.save(idCard);
        personRepository.save(person);

        // 检查这个idCard确无person关联
        System.out.println("=== 检查这个idCard确无person关联 =========================================");
        assertThat(idCardRepository.getOne(idCard.getId()).getPerson()).isNull();

        // 关联赋值person后,再次保存
        System.out.println("=== 关联赋值person后,再次保存 =========================================");
        idCard.setPerson(person);
        idCardRepository.save(idCard);

        // 再次检查这个idCard应有person对象内容
        System.out.println("=== 再次检查这个idCard应有person对象内容 =========================================");
        // 关于fetch
        // 注意:在IdCard里配置了fetch=lazy时,调用person#getHeight()时会触发1 + N查询。而fetch=eager时,会先join查询出来,这里能直接get到
        assertThat(idCardRepository.getOne(idCard.getId()).getPerson().getHeight()).isNotNull();
    }

    /**
     * 单表删除测试删除(IdCard)
     * 注:依赖testSave1()
     */
    @Test
    @Order(3)
    @Disabled
    @DisplayName("单表删除(IdCard)")
    void testDelete1() {

        // 默认无任何Cascade.REMOVE配置时开始测试
        // 只删除IdCard(从待久态里删除)
        IdCard idCard = idCardRepository.getOne();
        System.out.println("=== 只删除IdCard(从待久态里删除) =========================================");
        idCardRepository.delete(idCard);

        assertAll(
                // 检查该idCard是否真删除
                () -> assertThat(idCardRepository.findById(idCard.getId()).orElse(null)).isNull(),
                // 检查idCard对应的person数据没受影响
                () -> assertThat(personRepository.findById(idCard.getPerson().getId()).orElse(null)).isNotNull()
        );

//        // 只删除IdCard(从自由态里删除)
//        IdCard temp = idCardRepository.getOne();
//        IdCard nextIdCard = (IdCard) new IdCard().setId(temp.getId());
//        System.out.println(nextIdCard.getId());
//        System.out.println("=== 只删除IdCard(从自由态里删除) =========================================");
//        idCardRepository.delete(nextIdCard);
//
//        assertAll(
//                // 检查该idCard是否真删除
//                () -> assertThat(idCardRepository.findById(nextIdCard.getId()).orElse(null)).isNull(),
//                // 检查idCard对应的person数据没受影响
//                () -> assertThat(personRepository.findById(temp.getPerson().getId()).orElse(null)).isNotNull()
//        );
    }

    /**
     * 单表删除测试删除(IdCard)
     * 结论:在有fk关联时不存在单删Person的情况。要么接受删除失败,要么修改设置成cascade=REMOVE
     * 注:依赖testSave1()
     */
    @Test
    @Order(4)
    @Disabled
    @DisplayName("单表删除(Person)")
    void testDelete2() {

        // 默认无任何cascade配置时开始测试
        // 只删除Person(从待久态里删除)
        Person person = personRepository.getOne();
        // 这里实验了delete()方法实为byId删除
        // 如果故意将id赋null,会报"The given id must not be null!"异常
//        person.setId(null);
        personRepository.delete(person);

        // 如果故意单传person#id来删除,会因idCard的id没找到,触发fk约束,致删除失败。
        // 异常信息:Cannot delete or update a parent row: a foreign key constraint fails
//        personRepository.delete((Person) new Person().setId(person.getId()));
        // 所以,单传id删除的正确姿势如下
//        personRepository.deleteById(person.getId());
        // 或直接简单调用delete(Entity)
//        personRepository.delete(person);
        // 或将2个对象的id单独提供出来delete
//        personRepository.delete((Person) new Person().setIdCard((IdCard) new IdCard().setId(person.getIdCard().getId())).setId(person.getId()));
        // 以上3选一

        assertAll("单表删除(Person)结果验证",
                // 检查该person是否真删除
                // 由于person有idCard表里有fk约束,所以,直接删除person会报"Cannot delete or update a parent row: a foreign key constraint fails"异常
                // 有2个方法解决
                // 1. 在IdCard#person上加@OnDelete(action=OnDeleteAction.CASCADE)。此时会在建fk时加上"ON DELETE CASCADE"级联关系
                // 2. 在Person#idCard上加@OneToOne(cascade = CascadeType.REMOVE)
                // 所以,在有fk关联时不存在单删Person的情况
                () -> assertThat(personRepository.findById(person.getId()).orElse(null)).isNotNull(),
                // 因此,此检查在这个case里为false
                () -> assertThat(idCardRepository.findById(person.getIdCard().getId()).orElse(null)).isNotNull()
        );

//        // 只删除Person(从自由态里删除)
//        Person nextPerson = (Person) new Person().setId(personRepository.getOne().getId());
//        personRepository.delete(nextPerson);
//
//        assertAll(
//                // 检查该person是否真删除
//                () -> assertThat(personRepository.findById(nextPerson.getId()).orElse(null)).isNull(),
//                // 检查person对应的idCard数据没受影响
//                () -> assertThat(idCardRepository.findOne(Example.of(new IdCard()
//                        .setPerson((Person) new Person()
//                                .setId(nextPerson.getId())))).orElse(null))
//                        .isNotNull()
//        );
    }

    /**
     * 级联更新(Person)
     *
     */
    @Test
    @Order(5)
    @Disabled
    @DisplayName("级联更新(Person)")
    void testCascadeUpdate1() {

        Person person = personRepository.getOne();

        person.setHairColor(hairColorArr[0]);
        // 这里height=null后,会将该列数据update成null。即save()里的merge方法是全量更新
        person.setHeight(null);
        // Person#idCard上加cascade = CascadeType.MERGE后可达到级联更新idCard的效果
        person.getIdCard().setName("Elsa");

        System.out.println("=== 更新Person和级联更新 IdCard =========================================");
        personRepository.save(person);

        Person result = personRepository.getOne(person.getId());

        // 结论:person能正常update自己和idCard上的属性
        assertAll("级联更新结果验证",
                () -> assertThat(result.getHairColor()).isEqualTo(hairColorArr[0]),
                () -> assertThat(result.getIdCard().getName()).isEqualTo("Elsa")
        );

        person = personRepository.getOne(1);
        // 故意将IdCard赋null
        person.setIdCard(null);
        person.setHairColor("yellow1");
        System.out.println("=== 级联更新 IdCard =========================================");
        personRepository.save(person);

        // 检查在把Person#idCard赋null后,会不会在save()后删除该idCard?
        // 结论:不会。实际上@OneToOne两边都不会
        assertThat(idCardRepository.findOne(Example.of(new IdCard()
                .setPerson((Person) new Person()
                        .setId(person.getId())))).orElse(null))
                .isNotNull();
    }

    /**
     * 级联更新(IdCard)
     *
     */
    @Test
    @Order(6)
    @Disabled
    @DisplayName("级联更新(IdCard)")
    void testCascadeUpdate2() {
        System.out.println("IdCard是从表,不建议(或不允许)级联更新Person!");
    }

    @Test
    @Order(7)
    @Disabled
    @DisplayName("JPA默认查询接口(by单Id)")
    void testQuery1() {
        Long personId = personRepository.getOne().getId();

        // find系列方法强调“找”,所以,在有可能找不到的情况下用Optional来判断存在性
        System.out.println("=== 可正常获取到person的find方法 ========================");
        assertThat(personRepository.findById(personId).orElse(null)).isNotNull();
        System.out.println("=== 不能正常获取到person的find方法 ========================");
        assertThat(personRepository.findById(-1L).orElse(null)).isNull();

        // get系列方法强调“取”,所以,在取不到的情况下会抛EntityNotFoundException异常
        System.out.println("=== 可正常获取到person的get方法 ========================");
        assertThat(personRepository.getOne(personId)).isNotNull();
        System.out.println("=== 不能正常获取到person的get方法 ========================");
        // 此时再判断isNull()已无意义,get不到会直接抛EntityNotFoundException异常
        assertThrows(EntityNotFoundException.class, () -> personRepository.getOne(-1L));
    }

    @Test
    @Order(8)
    @Disabled
    @DisplayName("JPA默认查询接口(by多Id)")
    void testQuery2() {
        int count = 3;
        Iterable<Long> ids = personRepository.getMany(count)
                .stream()
                .map(Person::getId)
                .collect(Collectors.toList());

        // 注意:JPA的语法是方法名要对应field name。所以,不存在所谓的getByIds这样的写法,除非field name真是的ids
        // “多”的定义体现在介词前面。例如:findAllById

        System.out.println("=== 可正常获取到多个person的findAll方法 ========================");
        assertThat(personRepository.findAllById(ids).size()).isEqualTo(count);

        // 查不到也会返回一个empty list
        assertThat(personRepository.findAllById(Arrays.asList(-1L, -2L, -3L))).isNotNull().isEmpty();
    }

    @Test
    @Order(9)
    @Disabled
    @DisplayName("JPA默认查询接口(分页查询)")
    void testQuery3() {
        int pageNo = 0; // 从0开始
        int pageSize = dataCount / 2;
        Page<Person> page = personRepository.page(pageNo, pageSize);

        System.out.println("=== 分页结果 ===========================");
        System.out.println("=== page size: ===========================");
        System.out.println(pageSize);
        System.out.println("=== total rows: ===========================");
        System.out.println(page.getTotalElements());
        System.out.println("=== total page: ===========================");
        System.out.println(page.getTotalPages());
        System.out.println("=== contents: ===========================");
        System.out.println(page.getContent());
    }

    @Test
    @Order(10)
    @Disabled
    @DisplayName("JPA默认查询接口(分页 + 排序)")
    void testQuery4() {
        int pageNo = 0;
        int pageSize = dataCount / 2;

        // 这里的height是field name
        // 多列排序用and()
        // 升序用ascending()
        // 降序用descending()
        Sort sort = Sort.by("height").ascending();

        // 另外,javax.persistence还提供了@OrderBy注解,可定义在field name上。每次查询都会带上。可酌情使用

        Page<Person> page = personRepository.findAll(PageRequest.of(pageNo, pageSize));
        Page<Person> pageAndSort = personRepository.findAll(PageRequest.of(pageNo, pageSize, sort));

        System.out.println("=== 分页 + 排序结果 ===========================");
        System.out.println("=== 未排序数据: ===========================");
        System.out.println(page.stream().map(Person::getHeight).collect(Collectors.toList()));
        System.out.println("=== 已排序数据: ===========================");
        System.out.println(pageAndSort.stream().map(Person::getHeight).collect(Collectors.toList()));
    }


    @Test
    @Order(11)
    @Disabled
    @DisplayName("JPA默认查询接口(Example)")
    void testQuery5() {
        // Example查询会把Entity的field names作为返回值。field非null值作为where条件
        // 等同于原脚手架里提供的getByEntity(Entity entity)方法
        // JPA的find系列用Example还可以追加Page和Sort
        Person person = personRepository.getOne();

        // byId查询的另一种写法
        System.out.println("=== byId查询的另一种写法 ===========================");
        assertThat(personRepository.findOne(Example.of((Person) new Person().setId(person.getId()))).orElse(null)).isNotNull();

        // byHeight查询
        System.out.println("=== byHeight查询 ===========================");
        assertThat(personRepository.findAll(Example.of(new Person().setHeight(person.getHeight())))).isNotEmpty();

        // JPA还提供了ExampleMatcher查询条件匹配器,用于和Example配合生成动态查询条件。如忽略大小写、模糊匹配等。
        // 相关说明和使用可参考:
        // Spring官方文档:5.6.3. Example Matchers
        // https://blog.csdn.net/moshowgame/article/details/80282813
        // https://www.cnblogs.com/powerwu/articles/10716070.html
        // 示例略
    }

    @Test
    @Order(12)
    @Disabled
    @DisplayName("JPA默认查询接口(Others)")
    void testQuery6() {
        // count查询
        System.out.println("=== count查询 ===========================");
        long count = personRepository.count(Example.of(new Person().setHairColor(hairColorArr[0])));
        assertThat(count).isGreaterThan(0);

        // existsByExample查询
        System.out.println("=== existsByExample查询 ===========================");
        boolean exists = personRepository.exists(Example.of(new Person().setHairColor(hairColorArr[0])));
        assertThat(exists).isTrue();

        // existsById查询
        System.out.println("=== existsById查询 ===========================");
        Person person = personRepository.getOne();
        assertThat(personRepository.existsById(person.getId())).isTrue();
    }

    @Test
    @Order(13)
//    @Disabled
    @DisplayName("用方法命名规则查询")
    void testQuery7() {
        // JPA还提供一种“Query Methods”的查询方式。即对method进行一套基于规则要求的命名后可直接用于查询。大量减少了简单查询的sql开发要求
        // 命名规则说明请参见Spring官方文档
        // 5.3 Query Methods
        // Table 3. Supported keywords inside method names

        // 另外,JPA除find前缀的查询方式,还支持delete(remove), count, exists前缀方式
        // 注意:delete前缀需要再补充@Transactional

        Person person = personRepository.getOne();

        System.out.println("=== Query Methods命名规则find查询(可正常返回) ===========================");
        assertThat(personRepository.findByWeightAndHeight(person.getWeight(), person.getHeight())
                .orElse(null)).isNotNull();

        // find查询支持对返回值的Optional、Stream、Future、等包装
        System.out.println("=== Query Methods命名规则find查询(不可正常返回) ===========================");
        assertThat(personRepository.findByWeightAndHeight(new BigDecimal(-1), new BigDecimal(-1))
                .orElse(null)).isNull();

        System.out.println("=== Query Methods命名规则count查询 ===========================");
        assertThat(personRepository.countByHairColor(hairColorArr[0])).isNotNull();
    }

    @Test
    @Order(14)
    @Disabled
    @DisplayName("方法级自定义JPQL")
    void testQuery8() {
        // 如mybatis一样,JPA也提供了方法上直接自定义JPQL的方式
        // 即在方法定义上注明@Query
        // JPQL=Java持久化查询语言
        // 同时,@Query也提供对原始sql的支持。即设置nativeQuery=true
        // @Query也支持对delete, count等语法支持
        // 注意:在用delete时,还要加上@Modifying

        System.out.println("=== 方法级自定义JPQL ===========================");
        assertThat(personRepository.findSomePersonByNameAtJPQL(nameArr[0])).isNotNull();

        System.out.println("=== 方法级自定义native sql ===========================");
        assertThat(personRepository.findSomePersonByNameAtNative(nameArr[0])).isNotNull();

        // 更多的JPQL语法及查询示例可参考:
        // https://blog.csdn.net/czp11210/article/details/50799489
    }

    @Test
    @Order(15)
    @Disabled
    @DisplayName("Wrapper式查询")
    void testQuery9() {
        // Hibernate提供能像mybatis-plus里Wrapper类似的用Java语句拼装sql的方式
        // 核心对象:EntityManager


        // 完成如下sql查询的写法:
        // select count(*) from Person
        System.out.println("=== countAllPerson查询 ===========================");
        CriteriaBuilder builder1 = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery1 = builder1.createQuery(Long.class);
        Root<Person> root1 = criteriaQuery1.from(Person.class);
        criteriaQuery1.select(builder1.count(root1));
        Long count = entityManager.createQuery(criteriaQuery1).getSingleResult();
        assertThat(count).isEqualTo(dataCount);

        System.out.println("===================================================");

        // 完成如下sql查询的写法:
        // select * from Person p where p.weight = ?
        Person person = personRepository.getOne();
        System.out.println("=== countAllPerson查询 ===========================");
        CriteriaBuilder builder2 = entityManager.getCriteriaBuilder();
        CriteriaQuery<Person> criteriaQuery2 = builder2.createQuery(Person.class);
        Root<Person> root2 = criteriaQuery2.from(Person.class);
        criteriaQuery2.where(builder2.equal(root2.get("weight"), person.getWeight()));
        assertThat(entityManager.createQuery(criteriaQuery2).getResultList()).isNotEmpty();

        // 更多CriteriaBuilder的用法参考:
        // Hibernate官方文档:16. Criteria
        // 动态构造查询简单介绍:https://blog.csdn.net/zhaoruda/article/details/80157975
        // 一个复杂查询的例子:https://www.cnblogs.com/g-smile/p/9177841.html
    }

    @Test
    @Order(16)
    @Disabled
    @DisplayName("EntityManager手写JPQL")
    void testQuery10() {

        // 此方式也支持对原始SQL的手写。调用createNativeQuery()方法即可
        TypedQuery<Person> query = entityManager.createQuery("from Person p where p.hairColor = ?1", Person.class);
        query.setParameter(1, hairColorArr[0]);

        assertThat(query.getResultList()).isNotEmpty();
    }

    @Test
    @Order(17)
    @Disabled
    @DisplayName("动态JPQL")
    public void testDynamicQuery() {
//        Person person = personRepository
//                .findAll(PageRequest.of(1, 1))
//                .stream()
//                .findAny()
//                .orElse(new Person());
//
//        System.out.println("Person: " + person);
//
//        System.out.println(dynamicLeftJoinQuery.queryByEntity(new Person().setIdCard(new IdCard().setName(person.getIdCard().getName())).setWeight(person.getWeight())));
    }

        /**
         * 初始化一定数量的 Person .
         * @param count
         * @author ws
         * @date 19-11-25 下午3:11
         * @return java.util.List<com.maxnerva.test.maxbase.common.jpa.one2one.entity.Person>
         **/
    private List<Person> initPersonData(int count) {
        return Stream.iterate(0, i -> i + 1).limit(count)
                .map(i -> new Person()
                        .setHeight(new BigDecimal(random.nextInt(200)))
                        .setWeight(new BigDecimal(random.nextInt(100)))
                        .setHairColor(hairColorArr[random.nextInt(hairColorArr.length)]))
                .collect(Collectors.toList());
    }

    /**
     * 初始化 身份证 数据 .
     * @param persons
     * @author ws
     * @date 19-11-26 上午10:26
     * @return java.util.List<com.maxnerva.test.maxbase.common.jpa.one2one.entity.IDCard>
     **/
    private List<IdCard> initIDCardData(Collection<Person> persons) {
        return persons.stream()
                .map(person -> {
                    IdCard idCard = new IdCard();
                    idCard.setName(nameArr[random.nextInt(nameArr.length)]);
                    idCard.setSex(sexArr[random.nextInt(sexArr.length)]);
                    idCard.setCardNo(StrUtil.uuid());
                    idCard.setPerson(person);

                    return idCard;
                })
                .collect(Collectors.toList());
    }


    @Autowired
    private PersonRepository personRepository;

    @Autowired
    private IDCardRepository idCardRepository;

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!