SpringDataJpa增删改查

2019-04-14  本文已影响0人  Bertram_Wang

技术背景:
        开发工具:STS(eclipse)
        技术选择: SpringBoot, SpringDataJpa,
        数据库: MySQL, Redis

使用的是maven多模块开发,所以创建SpringBoot项目是先创建maven项目然后引入相应的依赖。(多模块项目创建参考: https://www.jianshu.com/p/ca3caa6614c1
1:创建maven子项目study-springboot-domain引入一下依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>study-springboot</groupId>
        <artifactId>study-springboot</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>study-springboot-domain</artifactId>
    <description>实体</description>
    
    <dependencies>
        <!-- 公共项目 -->
        <dependency>
            <groupId>study-springboot</groupId>
            <artifactId>study-springboot-comm</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- spring-data-jpa 启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency> 
        <!-- mysql 链接 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 自动装配 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies> 
    
    <!-- 打包 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

父级项目依赖如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>study-springboot</groupId>
    <artifactId>study-springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <description>springboot学习demo</description>
    <properties>
        <!-- 字符编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- maven打包插件 -->
        <maven-jar-plugin.version>2.6</maven-jar-plugin.version>
    </properties>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
    </parent>

    <dependencies>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 测试包,当我们使用 mvn package 的时候该包并不会被打入,因为它的生命周期只在 test 之内 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 简化简单实体getter,setter -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugin</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <modules>
        <module>study-springboot-domain</module>
        <module>study-springboot-backstage</module>
        <module>study-springboot-comm</module>
    </modules>
</project>

公共项目依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>study-springboot</groupId>
        <artifactId>study-springboot</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>study-springboot-comm</artifactId>
    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 工具包 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        
    </dependencies>
</project>

parent:引入了spring-boot-starter-parent,以及测试和lombok项目。
comm: 引入了spring-boot-starter-web,和工具包
domain:主要就是数据库操作项目(集成SpringDataJpa和Redis)

domain配置文件:app

spring:
  datasource:
    # 数据库链接--不能使用 url: xxxxxxxxxxxxxxxxx
    jdbc-url: jdbc:mysql:///db_comm_01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: Root1234
    driver-class-name: com.mysql.jdbc.Driver 
  member: 
    datasource:  
      jdbc-url: jdbc:mysql:///db_member_01?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: Root1234
      driver-class-name: com.mysql.jdbc.Driver 
  jpa: 
    database-platform: org.hibernate.dialect.MySQL5Dialect 
    show-sql: true 
    hibernate: 
      ddl-auto: none
  redis: 
    database: 0 # database name[0-15]
    host: localhost # server host
    password: Redis1234! # server password
    port: 6379 # connection port
    pool:
      max-idle: 8 # pool settings ...
      min-idle: 0
      max-active: 8
      max-wait: -1

配置文件其实主要就是配置数据库信息的这里使用了多数据源
jpa.hibernate.ddl-auto=none;不自动执行数据库模式定义语言语句
jpa.show-sql=true; 打印SQL语句

集成JPA参考: https://www.jianshu.com/p/1186134d0395

在pom文件引入依赖其实redis已经自动集成好了
注入RedisTemplate 即可以操作;但是插入的数据和key前面出现特殊字符\xac\xed\x00\x05t\x00\(没有实际影响)
解决方式是自定义序列化方式;所以添加配置类RedisConfig

package study.springboot.domain.config.redis;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class RedisConfig extends CachingConfigurerSupport{
    
    /**
     * RedisTemplate配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        //定义key序列化方式
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        //定义value的序列化方式
        Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        
        template.setKeySerializer(redisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

集成完毕;使用jpa逆向生成实体。


结构说明

测试Redis:


执行代码
执行结果

测试JPA操作(增删改-查)查是重点
表结构关系 user, role, user_role ; 多对多关系。
增:save

@Test
public void test() {
    SysUser sysUser = new SysUser();
    sysUser.setId(null);
    sysUser.setName("bertram.wang");
    sysUser.setPassword("1234567890");
    List<SysRole> roles = new ArrayList<>();
    SysRole role = new SysRole();
    role.setId(1);
    roles.add(role);
    sysUser.setRoles(roles);
    userRepository.save(sysUser);
}
执行的SQL

删除:

public void test() {
    SysUser sysUser = new SysUser();
    // 因为我数据库插入后ID=9
    sysUser.setId(9);
    userRepository.delete(sysUser);
}
执行的SQL

修改:(目标需要删除以前的关系,再添加现在的关系)

@Test
public void test() {
    SysUser sysUser = new SysUser();
    sysUser.setId(10);
    sysUser.setName("bertram");
    sysUser.setPassword("qwertyuiop");
    List<SysRole> roles = new ArrayList<>();
    SysRole role = new SysRole();
    role.setId(2);
    roles.add(role);
    sysUser.setRoles(roles);
    userRepository.save(sysUser);
}
执行的SQL

发现修改和新增都是用的save方法,只不过id有区别。

查询:
1:简单查询findOneById: 根据ID查询一条数据

@Test
public void findOneById() {
    SysUser sysUser = userRepository.findOneById(2);
    log.info("sysUser:{}",sysUser);
}
--执行的SQL:
Hibernate: select sysuser0_.id as id1_3_, sysuser0_.create_date as create_d2_3_, sysuser0_.modify_date as modify_d3_3_, sysuser0_.name as name4_3_, sysuser0_.password as password5_3_ from sys_user sysuser0_ where sysuser0_.id=?
Hibernate: select roles0_.user_id as user_id5_4_0_, roles0_.role_id as role_id4_4_0_, sysrole1_.id as id1_2_1_, sysrole1_.create_date as create_d2_2_1_, sysrole1_.modify_date as modify_d3_2_1_, sysrole1_.name as name4_2_1_, sysrole1_.parent_id as parent_i5_2_1_ from sys_user_role roles0_ inner join sys_role sysrole1_ on roles0_.role_id=sysrole1_.id where roles0_.user_id=?

自动关联查询了role信息;(实体配置如下)

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role",joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private List<SysRole> roles;

2:分页查询

@Test
public void findAllTest() {
    PageRequest pageRequest = PageRequest.of(1, 1, Direction.DESC, "id");
    SysUser sysUser = new SysUser();
    sysUser.setName("admi");
    Page<SysUser> page = userRepository.findAll(Example.of(sysUser,ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)), pageRequest);
    List<SysUser> content = page.getContent();
    log.info("content:{}", content);
}
Hibernate: select sysuser0_.id as id1_3_, sysuser0_.create_date as create_d2_3_, sysuser0_.modify_date as modify_d3_3_, sysuser0_.name as name4_3_, sysuser0_.password as password5_3_ from sys_user sysuser0_ where sysuser0_.name like ? order by sysuser0_.id desc limit ?, ?
Hibernate: select roles0_.user_id as user_id5_4_0_, roles0_.role_id as role_id4_4_0_, sysrole1_.id as id1_2_1_, sysrole1_.create_date as create_d2_2_1_, sysrole1_.modify_date as modify_d3_2_1_, sysrole1_.name as name4_2_1_, sysrole1_.parent_id as parent_i5_2_1_ from sys_user_role roles0_ inner join sys_role sysrole1_ on roles0_.role_id=sysrole1_.id where roles0_.user_id=?
Hibernate: select menus0_.role_id as role_id5_1_0_, menus0_.menu_id as menu_id4_1_0_, sysmenu1_.id as id1_0_1_, sysmenu1_.create_date as create_d2_0_1_, sysmenu1_.modify_date as modify_d3_0_1_, sysmenu1_.icon as icon4_0_1_, sysmenu1_.name as name5_0_1_, sysmenu1_.order_num as order_nu6_0_1_, sysmenu1_.parent_id as parent_i7_0_1_, sysmenu1_.permission as permissi8_0_1_, sysmenu1_.type as type9_0_1_, sysmenu1_.url as url10_0_1_ from sys_menu_role menus0_ inner join sys_menu sysmenu1_ on menus0_.menu_id=sysmenu1_.id where menus0_.role_id=?
Hibernate: select count(sysuser0_.id) as col_0_0_ from sys_user sysuser0_ where sysuser0_.name like ?

第一行SQL 语句-- xxxx where sysuser0_.name like ? order by sysuser0_.id desc limit ?, ?(模糊条件分页)

PageRequest源码

/*
 * Copyright 2008-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.domain;

import org.springframework.data.domain.Sort.Direction;
import org.springframework.lang.Nullable;

/**
 * Basic Java Bean implementation of {@code Pageable}.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 */
public class PageRequest extends AbstractPageRequest {

    private static final long serialVersionUID = -4541509938956089562L;

    private final Sort sort;

    /**
     * Creates a new {@link PageRequest}. Pages are zero indexed, thus providing 0 for {@code page} will return the first
     * page.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @deprecated use {@link #of(int, int)} instead.
     */
    @Deprecated
    public PageRequest(int page, int size) {
        this(page, size, Sort.unsorted());
    }

    /**
     * Creates a new {@link PageRequest} with sort parameters applied.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @param direction the direction of the {@link Sort} to be specified, can be {@literal null}.
     * @param properties the properties to sort by, must not be {@literal null} or empty.
     * @deprecated use {@link #of(int, int, Direction, String...)} instead.
     */
    @Deprecated
    public PageRequest(int page, int size, Direction direction, String... properties) {
        this(page, size, Sort.by(direction, properties));
    }

    /**
     * Creates a new {@link PageRequest} with sort parameters applied.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @param sort can be {@literal null}.
     * @deprecated since 2.0, use {@link #of(int, int, Sort)} instead.
     */
    @Deprecated
    public PageRequest(int page, int size, Sort sort) {

        super(page, size);

        this.sort = sort;
    }

    /**
     * Creates a new unsorted {@link PageRequest}.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @since 2.0
     */
    public static PageRequest of(int page, int size) {
        return of(page, size, Sort.unsorted());
    }

    /**
     * Creates a new {@link PageRequest} with sort parameters applied.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @param sort must not be {@literal null}.
     * @since 2.0
     */
    public static PageRequest of(int page, int size, Sort sort) {
        return new PageRequest(page, size, sort);
    }

    /**
     * Creates a new {@link PageRequest} with sort direction and properties applied.
     *
     * @param page zero-based page index.
     * @param size the size of the page to be returned.
     * @param direction must not be {@literal null}.
     * @param properties must not be {@literal null}.
     * @since 2.0
     */
    public static PageRequest of(int page, int size, Direction direction, String... properties) {
        return of(page, size, Sort.by(direction, properties));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.domain.Pageable#getSort()
     */
    public Sort getSort() {
        return sort;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.domain.Pageable#next()
     */
    public Pageable next() {
        return new PageRequest(getPageNumber() + 1, getPageSize(), getSort());
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.domain.AbstractPageRequest#previous()
     */
    public PageRequest previous() {
        return getPageNumber() == 0 ? this : new PageRequest(getPageNumber() - 1, getPageSize(), getSort());
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.domain.Pageable#first()
     */
    public Pageable first() {
        return new PageRequest(0, getPageSize(), getSort());
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(@Nullable Object obj) {

        if (this == obj) {
            return true;
        }

        if (!(obj instanceof PageRequest)) {
            return false;
        }

        PageRequest that = (PageRequest) obj;

        return super.equals(that) && this.sort.equals(that.sort);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return 31 * super.hashCode() + sort.hashCode();
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format("Page request [number: %d, size %d, sort: %s]", getPageNumber(), getPageSize(), sort);
    }
}

不难看出这个方法

public static PageRequest of(int page, int size, Direction direction, String... properties) {
    return of(page, size, Sort.by(direction, properties));
}

可以实现根据多个字段同向排序的功能。实际需求更多的可能是多个字段不通向的排序。立马看到方法:

/**
 * Creates a new {@link PageRequest} with sort parameters applied.
 *
 * @param page zero-based page index.
 * @param size the size of the page to be returned.
 * @param sort must not be {@literal null}.
 * @since 2.0
 */
public static PageRequest of(int page, int size, Sort sort) {
    return new PageRequest(page, size, sort);
}

再查看Sort源码存在静态方法

public static Sort by(List<Order> orders) {

    Assert.notNull(orders, "Orders must not be null!");

    return orders.isEmpty() ? Sort.unsorted() : new Sort(orders);
}

一直跟进Order

public static class Order implements Serializable {

    private static final long serialVersionUID = 1522511010900108987L;
    private static final boolean DEFAULT_IGNORE_CASE = false;
    private static final NullHandling DEFAULT_NULL_HANDLING = NullHandling.NATIVE;

    private final Direction direction;
    private final String property;
    private final boolean ignoreCase;
    private final NullHandling nullHandling;

    /**
     * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
     * {@link Sort#DEFAULT_DIRECTION}
     * 
     * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
     * @param property must not be {@literal null} or empty.
     */
    public Order(@Nullable Direction direction, String property) {
        this(direction, property, DEFAULT_IGNORE_CASE, DEFAULT_NULL_HANDLING);
    }

    /**
     * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
     * {@link Sort#DEFAULT_DIRECTION}
     * 
     * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
     * @param property must not be {@literal null} or empty.
     * @param nullHandling must not be {@literal null}.
     */
    public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
        this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint);
    }

    /**
     * Creates a new {@link Order} instance. Takes a single property. Direction defaults to
     * {@link Sort#DEFAULT_DIRECTION}.
     * 
     * @param property must not be {@literal null} or empty.
     * @deprecated since 2.0, use {@link Order#by(String)}.
     */
    @Deprecated
    public Order(String property) {
        this(DEFAULT_DIRECTION, property);
    }

    /**
     * Creates a new {@link Order} instance. Takes a single property. Direction defaults to
     * {@link Sort#DEFAULT_DIRECTION}.
     * 
     * @param property must not be {@literal null} or empty.
     * @since 2.0
     */
    public static Order by(String property) {
        return new Order(DEFAULT_DIRECTION, property);
    }

    /**
     * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and
     * NullHandling {@link NullHandling#NATIVE}.
     *
     * @param property must not be {@literal null} or empty.
     * @since 2.0
     */
    public static Order asc(String property) {
        return new Order(Direction.ASC, property, DEFAULT_NULL_HANDLING);
    }

    /**
     * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and
     * NullHandling {@link NullHandling#NATIVE}.
     *
     * @param property must not be {@literal null} or empty.
     * @since 2.0
     */
    public static Order desc(String property) {
        return new Order(Direction.DESC, property, DEFAULT_NULL_HANDLING);
    }

    /**
     * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
     * {@link Sort#DEFAULT_DIRECTION}
     * 
     * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
     * @param property must not be {@literal null} or empty.
     * @param ignoreCase true if sorting should be case insensitive. false if sorting should be case sensitive.
     * @param nullHandling must not be {@literal null}.
     * @since 1.7
     */
    private Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) {

        if (!StringUtils.hasText(property)) {
            throw new IllegalArgumentException("Property must not null or empty!");
        }

        this.direction = direction == null ? DEFAULT_DIRECTION : direction;
        this.property = property;
        this.ignoreCase = ignoreCase;
        this.nullHandling = nullHandling;
    }

    /**
     * Returns the order the property shall be sorted for.
     * 
     * @return
     */
    public Direction getDirection() {
        return direction;
    }

    /**
     * Returns the property to order for.
     * 
     * @return
     */
    public String getProperty() {
        return property;
    }

    /**
     * Returns whether sorting for this property shall be ascending.
     * 
     * @return
     */
    public boolean isAscending() {
        return this.direction.isAscending();
    }

    /**
     * Returns whether sorting for this property shall be descending.
     * 
     * @return
     * @since 1.13
     */
    public boolean isDescending() {
        return this.direction.isDescending();
    }

    /**
     * Returns whether or not the sort will be case sensitive.
     * 
     * @return
     */
    public boolean isIgnoreCase() {
        return ignoreCase;
    }

    /**
     * Returns a new {@link Order} with the given {@link Direction}.
     * 
     * @param direction
     * @return
     */
    public Order with(Direction direction) {
        return new Order(direction, this.property, this.ignoreCase, this.nullHandling);
    }

    /**
     * Returns a new {@link Order}
     * 
     * @param property must not be {@literal null} or empty.
     * @return
     * @since 1.13
     */
    public Order withProperty(String property) {
        return new Order(this.direction, property, this.ignoreCase, this.nullHandling);
    }

    /**
     * Returns a new {@link Sort} instance for the given properties.
     * 
     * @param properties
     * @return
     */
    public Sort withProperties(String... properties) {
        return Sort.by(this.direction, properties);
    }

    /**
     * Returns a new {@link Order} with case insensitive sorting enabled.
     * 
     * @return
     */
    public Order ignoreCase() {
        return new Order(direction, property, true, nullHandling);
    }

    /**
     * Returns a {@link Order} with the given {@link NullHandling}.
     * 
     * @param nullHandling can be {@literal null}.
     * @return
     * @since 1.8
     */
    public Order with(NullHandling nullHandling) {
        return new Order(direction, this.property, ignoreCase, nullHandling);
    }

    /**
     * Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint.
     * 
     * @return
     * @since 1.8
     */
    public Order nullsFirst() {
        return with(NullHandling.NULLS_FIRST);
    }

    /**
     * Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint.
     * 
     * @return
     * @since 1.7
     */
    public Order nullsLast() {
        return with(NullHandling.NULLS_LAST);
    }

    /**
     * Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint.
     * 
     * @return
     * @since 1.7
     */
    public Order nullsNative() {
        return with(NullHandling.NATIVE);
    }

    /**
     * Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore.
     * 
     * @return
     * @since 1.7
     */
    public NullHandling getNullHandling() {
        return nullHandling;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {

        int result = 17;

        result = 31 * result + direction.hashCode();
        result = 31 * result + property.hashCode();
        result = 31 * result + (ignoreCase ? 1 : 0);
        result = 31 * result + nullHandling.hashCode();

        return result;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(@Nullable Object obj) {

        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Order)) {
            return false;
        }

        Order that = (Order) obj;

        return this.direction.equals(that.direction) && this.property.equals(that.property)
                && this.ignoreCase == that.ignoreCase && this.nullHandling.equals(that.nullHandling);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        String result = String.format("%s: %s", property, direction);

        if (!NullHandling.NATIVE.equals(nullHandling)) {
            result += ", " + nullHandling;
        }

        if (ignoreCase) {
            result += ", ignoring case";
        }

        return result;
    }
}

一个排序字段对应一个方向。

    public Order(@Nullable Direction direction, String property) {
        this(direction, property, DEFAULT_IGNORE_CASE, DEFAULT_NULL_HANDLING);
    }

即可以是实现多个排序字段不同方法的功能
修改测试方法:

@Test
public void findAllTest() {
    Order idOrder = new Order(Direction.DESC, "id");
    Order nameOrder = new Order(Direction.ASC, "name");
    PageRequest pageRequest = PageRequest.of(1, 1, Sort.by(idOrder, nameOrder));
    SysUser sysUser = new SysUser();
    sysUser.setName("m");
    Page<SysUser> page = userRepository.findAll(Example.of(sysUser,ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)), pageRequest);
    List<SysUser> content = page.getContent();
    log.info("content:{}", content);
}
Hibernate: select sysuser0_.id as id1_3_, sysuser0_.create_date as create_d2_3_, sysuser0_.modify_date as modify_d3_3_, sysuser0_.name as name4_3_, sysuser0_.password as password5_3_ from sys_user sysuser0_ where sysuser0_.name like ? order by sysuser0_.id desc, sysuser0_.name asc limit ?, ?
Hibernate: select roles0_.user_id as user_id5_4_0_, roles0_.role_id as role_id4_4_0_, sysrole1_.id as id1_2_1_, sysrole1_.create_date as create_d2_2_1_, sysrole1_.modify_date as modify_d3_2_1_, sysrole1_.name as name4_2_1_, sysrole1_.parent_id as parent_i5_2_1_ from sys_user_role roles0_ inner join sys_role sysrole1_ on roles0_.role_id=sysrole1_.id where roles0_.user_id=?
Hibernate: select menus0_.role_id as role_id5_1_0_, menus0_.menu_id as menu_id4_1_0_, sysmenu1_.id as id1_0_1_, sysmenu1_.create_date as create_d2_0_1_, sysmenu1_.modify_date as modify_d3_0_1_, sysmenu1_.icon as icon4_0_1_, sysmenu1_.name as name5_0_1_, sysmenu1_.order_num as order_nu6_0_1_, sysmenu1_.parent_id as parent_i7_0_1_, sysmenu1_.permission as permissi8_0_1_, sysmenu1_.type as type9_0_1_, sysmenu1_.url as url10_0_1_ from sys_menu_role menus0_ inner join sys_menu sysmenu1_ on menus0_.menu_id=sysmenu1_.id where menus0_.role_id=?
Hibernate: select menus0_.role_id as role_id5_1_0_, menus0_.menu_id as menu_id4_1_0_, sysmenu1_.id as id1_0_1_, sysmenu1_.create_date as create_d2_0_1_, sysmenu1_.modify_date as modify_d3_0_1_, sysmenu1_.icon as icon4_0_1_, sysmenu1_.name as name5_0_1_, sysmenu1_.order_num as order_nu6_0_1_, sysmenu1_.parent_id as parent_i7_0_1_, sysmenu1_.permission as permissi8_0_1_, sysmenu1_.type as type9_0_1_, sysmenu1_.url as url10_0_1_ from sys_menu_role menus0_ inner join sys_menu sysmenu1_ on menus0_.menu_id=sysmenu1_.id where menus0_.role_id=?
Hibernate: select count(sysuser0_.id) as col_0_0_ from sys_user sysuser0_ where sysuser0_.name like ?

第一行:
where sysuser0_.name like ? order by sysuser0_.id desc, sysuser0_.name asc limit ?, ?

源码分页方法:

/**
 * Returns a {@link Page} of entities matching the given {@link Example}. In case no match could be found, an empty
 * {@link Page} is returned.
 *
 * @param example must not be {@literal null}.
 * @param pageable can be {@literal null}.
 * @return a {@link Page} of entities matching the given {@link Example}.
 */
<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);

第二个参数分页是知道了 ,第一个参数Example条件参数呢?
Example接口源码

/*
 * Copyright 2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.domain;

import org.springframework.data.util.ProxyUtils;

/**
 * Support for query by example (QBE). An {@link Example} takes a {@code probe} to define the example. Matching options
 * and type safety can be tuned using {@link ExampleMatcher}.
 *
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Oliver Gierke
 * @param <T> the type of the probe.
 * @since 1.12
 */
public interface Example<T> {

    /**
     * Create a new {@link Example} including all non-null properties by default.
     *
     * @param probe must not be {@literal null}.
     * @return
     */
    static <T> Example<T> of(T probe) {
        return new TypedExample<>(probe, ExampleMatcher.matching());
    }

    /**
     * Create a new {@link Example} using the given {@link ExampleMatcher}.
     *
     * @param probe must not be {@literal null}.
     * @param matcher must not be {@literal null}.
     * @return
     */
    static <T> Example<T> of(T probe, ExampleMatcher matcher) {
        return new TypedExample<>(probe, matcher);
    }

    /**
     * Get the example used.
     *
     * @return never {@literal null}.
     */
    T getProbe();

    /**
     * Get the {@link ExampleMatcher} used.
     *
     * @return never {@literal null}.
     */
    ExampleMatcher getMatcher();

    /**
     * Get the actual type for the probe used. This is usually the given class, but the original class in case of a
     * CGLIB-generated subclass.
     *
     * @return
     * @see ProxyUtils#getUserClass(Class)
     */
    @SuppressWarnings("unchecked")
    default Class<T> getProbeType() {
        return (Class<T>) ProxyUtils.getUserClass(getProbe().getClass());
    }
}

泛型接口 T其实指的就是要查询的实体,使用静态方法new TypedExample<>(probe, ExampleMatcher.matching());创建实现类,除了实体外还有一个匹配机制参数。有默认机制。

匹配机制源码

/*
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.domain;

import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Specification for property path matching to use in query by example (QBE). An {@link ExampleMatcher} can be created
 * for a {@link Class object type}. Instances of {@link ExampleMatcher} can be either {@link #matchingAll()} or
 * {@link #matchingAny()} and settings can be tuned {@code with...} methods in a fluent style. {@code with...} methods
 * return a copy of the {@link ExampleMatcher} instance with the specified setting. Null-handling defaults to
 * {@link NullHandler#IGNORE} and case-sensitive {@link StringMatcher#DEFAULT} string matching.
 * <p>
 * This class is immutable.
 *
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Oliver Gierke
 * @author Jens Schauder
 * @since 1.12
 */
public interface ExampleMatcher {

    /**
     * Create a new {@link ExampleMatcher} including all non-null properties by default matching <strong>all</strong>
     * predicates derived from the example.
     *
     * @return new instance of {@link ExampleMatcher}.
     * @see #matchingAll()
     */
    static ExampleMatcher matching() {
        return matchingAll();
    }

    /**
     * Create a new {@link ExampleMatcher} including all non-null properties by default matching <strong>any</strong>
     * predicate derived from the example.
     *
     * @return new instance of {@link ExampleMatcher}.
     */
    static ExampleMatcher matchingAny() {
        return new TypedExampleMatcher().withMode(MatchMode.ANY);
    }

    /**
     * Create a new {@link ExampleMatcher} including all non-null properties by default matching <strong>all</strong>
     * predicates derived from the example.
     *
     * @return new instance of {@link ExampleMatcher}.
     */
    static ExampleMatcher matchingAll() {
        return new TypedExampleMatcher().withMode(MatchMode.ALL);
    }

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified {@code propertyPaths}. This instance is immutable
     * and unaffected by this method call.
     *
     * @param ignoredPaths must not be {@literal null} and not empty.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withIgnorePaths(String... ignoredPaths);

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified string matching of {@code defaultStringMatcher}.
     * This instance is immutable and unaffected by this method call.
     *
     * @param defaultStringMatcher must not be {@literal null}.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher);

    /**
     * Returns a copy of this {@link ExampleMatcher} with ignoring case sensitivity by default. This instance is immutable
     * and unaffected by this method call.
     *
     * @return new instance of {@link ExampleMatcher}.
     */
    default ExampleMatcher withIgnoreCase() {
        return withIgnoreCase(true);
    }

    /**
     * Returns a copy of this {@link ExampleMatcher} with {@code defaultIgnoreCase}. This instance is immutable and
     * unaffected by this method call.
     *
     * @param defaultIgnoreCase
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase);

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the
     * {@code propertyPath}. This instance is immutable and unaffected by this method call.
     *
     * @param propertyPath must not be {@literal null}.
     * @param matcherConfigurer callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
     * @return new instance of {@link ExampleMatcher}.
     */
    default ExampleMatcher withMatcher(String propertyPath, MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {

        Assert.hasText(propertyPath, "PropertyPath must not be empty!");
        Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!");

        GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher();
        matcherConfigurer.configureMatcher(genericPropertyMatcher);

        return withMatcher(propertyPath, genericPropertyMatcher);
    }

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified {@code GenericPropertyMatcher} for the
     * {@code propertyPath}. This instance is immutable and unaffected by this method call.
     *
     * @param propertyPath must not be {@literal null}.
     * @param genericPropertyMatcher callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified {@code PropertyValueTransformer} for the
     * {@code propertyPath}.
     *
     * @param propertyPath must not be {@literal null}.
     * @param propertyValueTransformer must not be {@literal null}.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer);

    /**
     * Returns a copy of this {@link ExampleMatcher} with ignore case sensitivity for the {@code propertyPaths}. This
     * instance is immutable and unaffected by this method call.
     *
     * @param propertyPaths must not be {@literal null} and not empty.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withIgnoreCase(String... propertyPaths);

    /**
     * Returns a copy of this {@link ExampleMatcher} with treatment for {@literal null} values of
     * {@link NullHandler#INCLUDE} . This instance is immutable and unaffected by this method call.
     *
     * @return new instance of {@link ExampleMatcher}.
     */
    default ExampleMatcher withIncludeNullValues() {
        return withNullHandler(NullHandler.INCLUDE);
    }

    /**
     * Returns a copy of this {@link ExampleMatcher} with treatment for {@literal null} values of
     * {@link NullHandler#IGNORE}. This instance is immutable and unaffected by this method call.
     *
     * @return new instance of {@link ExampleMatcher}.
     */
    default ExampleMatcher withIgnoreNullValues() {
        return withNullHandler(NullHandler.IGNORE);
    }

    /**
     * Returns a copy of this {@link ExampleMatcher} with the specified {@code nullHandler}. This instance is immutable
     * and unaffected by this method call.
     *
     * @param nullHandler must not be {@literal null}.
     * @return new instance of {@link ExampleMatcher}.
     */
    ExampleMatcher withNullHandler(NullHandler nullHandler);

    /**
     * Get defined null handling.
     *
     * @return never {@literal null}
     */
    NullHandler getNullHandler();

    /**
     * Get defined {@link ExampleMatcher.StringMatcher}.
     *
     * @return never {@literal null}.
     */
    StringMatcher getDefaultStringMatcher();

    /**
     * @return {@literal true} if {@link String} should be matched with ignore case option.
     */
    boolean isIgnoreCaseEnabled();

    /**
     * @param path must not be {@literal null}.
     * @return return {@literal true} if path was set to be ignored.
     */
    default boolean isIgnoredPath(String path) {
        return getIgnoredPaths().contains(path);
    }

    /**
     * @return unmodifiable {@link Set} of ignored paths.
     */
    Set<String> getIgnoredPaths();

    /**
     * @return the {@link PropertySpecifiers} within the {@link ExampleMatcher}.
     */
    PropertySpecifiers getPropertySpecifiers();

    /**
     * Returns whether all of the predicates of the {@link Example} are supposed to match. If {@literal false} is
     * returned, it's sufficient if any of the predicates derived from the {@link Example} match.
     *
     * @return whether all of the predicates of the {@link Example} are supposed to match or any of them is sufficient.
     */
    default boolean isAllMatching() {
        return getMatchMode().equals(MatchMode.ALL);
    }

    /**
     * Returns whether it's sufficient that any of the predicates of the {@link Example} match. If {@literal false} is
     * returned, all predicates derived from the example need to match to produce results.
     *
     * @return whether it's sufficient that any of the predicates of the {@link Example} match or all need to match.
     */
    default boolean isAnyMatching() {
        return getMatchMode().equals(MatchMode.ANY);
    }

    /**
     * Get the match mode of the {@link ExampleMatcher}.
     *
     * @return never {@literal null}.
     * @since 2.0
     */
    MatchMode getMatchMode();

    /**
     * Null handling for creating criterion out of an {@link Example}.
     *
     * @author Christoph Strobl
     */
    enum NullHandler {

        INCLUDE, IGNORE
    }

    /**
     * Callback to configure a matcher.
     *
     * @author Mark Paluch
     * @param <T>
     */
    interface MatcherConfigurer<T> {
        void configureMatcher(T matcher);
    }

    /**
     * A generic property matcher that specifies {@link StringMatcher string matching} and case sensitivity.
     *
     * @author Mark Paluch
     */
    @EqualsAndHashCode
    class GenericPropertyMatcher {

        @Nullable StringMatcher stringMatcher = null;
        @Nullable Boolean ignoreCase = null;
        PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE;

        /**
         * Creates an unconfigured {@link GenericPropertyMatcher}.
         */
        public GenericPropertyMatcher() {}

        /**
         * Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
         *
         * @param stringMatcher must not be {@literal null}.
         * @param ignoreCase
         * @return
         */
        public static GenericPropertyMatcher of(StringMatcher stringMatcher, boolean ignoreCase) {
            return new GenericPropertyMatcher().stringMatcher(stringMatcher).ignoreCase(ignoreCase);
        }

        /**
         * Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
         *
         * @param stringMatcher must not be {@literal null}.
         * @return
         */
        public static GenericPropertyMatcher of(StringMatcher stringMatcher) {
            return new GenericPropertyMatcher().stringMatcher(stringMatcher);
        }

        /**
         * Sets ignores case to {@literal true}.
         *
         * @return
         */
        public GenericPropertyMatcher ignoreCase() {

            this.ignoreCase = true;
            return this;
        }

        /**
         * Sets ignores case to {@code ignoreCase}.
         *
         * @param ignoreCase
         * @return
         */
        public GenericPropertyMatcher ignoreCase(boolean ignoreCase) {

            this.ignoreCase = ignoreCase;
            return this;
        }

        /**
         * Sets ignores case to {@literal false}.
         *
         * @return
         */
        public GenericPropertyMatcher caseSensitive() {

            this.ignoreCase = false;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#CONTAINING}.
         *
         * @return
         */
        public GenericPropertyMatcher contains() {

            this.stringMatcher = StringMatcher.CONTAINING;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#ENDING}.
         *
         * @return
         */
        public GenericPropertyMatcher endsWith() {

            this.stringMatcher = StringMatcher.ENDING;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#STARTING}.
         *
         * @return
         */
        public GenericPropertyMatcher startsWith() {

            this.stringMatcher = StringMatcher.STARTING;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#EXACT}.
         *
         * @return
         */
        public GenericPropertyMatcher exact() {

            this.stringMatcher = StringMatcher.EXACT;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#DEFAULT}.
         *
         * @return
         */
        public GenericPropertyMatcher storeDefaultMatching() {

            this.stringMatcher = StringMatcher.DEFAULT;
            return this;
        }

        /**
         * Sets string matcher to {@link StringMatcher#REGEX}.
         *
         * @return
         */
        public GenericPropertyMatcher regex() {

            this.stringMatcher = StringMatcher.REGEX;
            return this;
        }

        /**
         * Sets string matcher to {@code stringMatcher}.
         *
         * @param stringMatcher must not be {@literal null}.
         * @return
         */
        public GenericPropertyMatcher stringMatcher(StringMatcher stringMatcher) {

            Assert.notNull(stringMatcher, "StringMatcher must not be null!");
            this.stringMatcher = stringMatcher;
            return this;
        }

        /**
         * Sets the {@link PropertyValueTransformer} to {@code propertyValueTransformer}.
         *
         * @param propertyValueTransformer must not be {@literal null}.
         * @return
         */
        public GenericPropertyMatcher transform(PropertyValueTransformer propertyValueTransformer) {

            Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be null!");
            this.valueTransformer = propertyValueTransformer;
            return this;
        }
    }

    /**
     * Predefined property matchers to create a {@link GenericPropertyMatcher}.
     *
     * @author Mark Paluch
     */
    class GenericPropertyMatchers {

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string case insensitive.
         *
         * @return
         */
        public static GenericPropertyMatcher ignoreCase() {
            return new GenericPropertyMatcher().ignoreCase();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string case sensitive.
         *
         * @return
         */
        public static GenericPropertyMatcher caseSensitive() {
            return new GenericPropertyMatcher().caseSensitive();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#CONTAINING}.
         *
         * @return
         */
        public static GenericPropertyMatcher contains() {
            return new GenericPropertyMatcher().contains();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#ENDING}.
         *
         * @return
         */
        public static GenericPropertyMatcher endsWith() {
            return new GenericPropertyMatcher().endsWith();

        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#STARTING}.
         *
         * @return
         */
        public static GenericPropertyMatcher startsWith() {
            return new GenericPropertyMatcher().startsWith();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#EXACT}.
         *
         * @return
         */
        public static GenericPropertyMatcher exact() {
            return new GenericPropertyMatcher().exact();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#DEFAULT}.
         *
         * @return
         */
        public static GenericPropertyMatcher storeDefaultMatching() {
            return new GenericPropertyMatcher().storeDefaultMatching();
        }

        /**
         * Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#REGEX}.
         *
         * @return
         */
        public static GenericPropertyMatcher regex() {
            return new GenericPropertyMatcher().regex();
        }
    }

    /**
     * Match modes for treatment of {@link String} values.
     *
     * @author Christoph Strobl
     * @author Jens Schauder
     */
    enum StringMatcher {

        /**
         * Store specific default.
         */
        DEFAULT,
        /**
         * Matches the exact string
         */
        EXACT,
        /**
         * Matches string starting with pattern
         */
        STARTING,
        /**
         * Matches string ending with pattern
         */
        ENDING,
        /**
         * Matches string containing pattern
         */
        CONTAINING,
        /**
         * Treats strings as regular expression patterns
         */
        REGEX;
    }

    /**
     * Allows to transform the property value before it is used in the query.
     */
    interface PropertyValueTransformer extends Function<Optional<Object>, Optional<Object>> {

        /**
         * For backwards compatibility of clients used to invoke Spring's Converter interface.
         *
         * @param source
         * @return
         */
        @Deprecated
        default Object convert(Object source) {
            return apply(Optional.ofNullable(source)).orElse(null);
        }
    }

    /**
     * @author Christoph Strobl
     * @author Oliver Gierke
     * @since 1.12
     */
    enum NoOpPropertyValueTransformer implements ExampleMatcher.PropertyValueTransformer {

        INSTANCE;

        /*
         * (non-Javadoc)
         * @see java.util.function.Function#apply(java.lang.Object)
         */
        @Override
        @SuppressWarnings("null")
        public Optional<Object> apply(Optional<Object> source) {
            return source;
        }
    }

    /**
     * Define specific property handling for a Dot-Path.
     *
     * @author Christoph Strobl
     * @author Mark Paluch
     * @since 1.12
     */
    @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    @EqualsAndHashCode
    class PropertySpecifier {

        String path;
        @Nullable StringMatcher stringMatcher;
        @Nullable Boolean ignoreCase;
        PropertyValueTransformer valueTransformer;

        /**
         * Creates new {@link PropertySpecifier} for given path.
         *
         * @param path Dot-Path to the property. Must not be {@literal null}.
         */
        PropertySpecifier(String path) {

            Assert.hasText(path, "Path must not be null/empty!");
            this.path = path;

            this.stringMatcher = null;
            this.ignoreCase = null;
            this.valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@link StringMatcher} in the returned instance.
         *
         * @param stringMatcher must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withStringMatcher(StringMatcher stringMatcher) {

            Assert.notNull(stringMatcher, "StringMatcher must not be null!");
            return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer);
        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@code ignoreCase}.
         *
         * @param ignoreCase must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withIgnoreCase(boolean ignoreCase) {
            return new PropertySpecifier(this.path, this.stringMatcher, ignoreCase, this.valueTransformer);
        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@link PropertyValueTransformer} in the returned instance.
         *
         * @param valueTransformer must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withValueTransformer(PropertyValueTransformer valueTransformer) {

            Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!");
            return new PropertySpecifier(this.path, this.stringMatcher, this.ignoreCase, valueTransformer);
        }

        /**
         * Get the properties Dot-Path.
         *
         * @return never {@literal null}.
         */
        public String getPath() {
            return path;
        }

        /**
         * Get the {@link StringMatcher}.
         *
         * @return can be {@literal null}.
         */
        @Nullable
        public StringMatcher getStringMatcher() {
            return stringMatcher;
        }

        /**
         * @return {@literal null} if not set.
         */
        @Nullable
        public Boolean getIgnoreCase() {
            return ignoreCase;
        }

        /**
         * Get the property transformer to be applied.
         *
         * @return never {@literal null}.
         */
        public PropertyValueTransformer getPropertyValueTransformer() {
            return valueTransformer == null ? NoOpPropertyValueTransformer.INSTANCE : valueTransformer;
        }

        /**
         * Transforms a given source using the {@link PropertyValueTransformer}.
         *
         * @param source
         * @return
         * @deprecated since 2.0, use {@link #transformValue(Optional)} instead.
         */
        @Deprecated
        public Object transformValue(Object source) {
            return transformValue(Optional.ofNullable(source)).orElse(null);
        }

        /**
         * Transforms a given source using the {@link PropertyValueTransformer}.
         *
         * @param source
         * @return
         */
        public Optional<Object> transformValue(Optional<Object> source) {
            return getPropertyValueTransformer().apply(source);
        }
    }

    /**
     * Define specific property handling for Dot-Paths.
     *
     * @author Christoph Strobl
     * @author Mark Paluch
     * @since 1.12
     */
    @EqualsAndHashCode
    class PropertySpecifiers {

        private final Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<>();

        PropertySpecifiers() {}

        PropertySpecifiers(PropertySpecifiers propertySpecifiers) {
            this.propertySpecifiers.putAll(propertySpecifiers.propertySpecifiers);
        }

        public void add(PropertySpecifier specifier) {

            Assert.notNull(specifier, "PropertySpecifier must not be null!");
            propertySpecifiers.put(specifier.getPath(), specifier);
        }

        public boolean hasSpecifierForPath(String path) {
            return propertySpecifiers.containsKey(path);
        }

        public PropertySpecifier getForPath(String path) {
            return propertySpecifiers.get(path);
        }

        public boolean hasValues() {
            return !propertySpecifiers.isEmpty();
        }

        public Collection<PropertySpecifier> getSpecifiers() {
            return propertySpecifiers.values();
        }
    }

    /**
     * The match modes to expose so that clients can find about how to concatenate the predicates.
     *
     * @author Oliver Gierke
     * @since 1.13
     * @see ExampleMatcher#isAllMatching()
     */
    enum MatchMode {
        ALL, ANY;
    }
}

ExampleMatcher.matching()的配置机制是:new TypedExampleMatcher().withMode(MatchMode.ALL);(全部匹配)
修改测试方法:
Page<SysUser> page = userRepository.findAll(Example.of(sysUser), pageRequest);

执行的SQL将不再是like ;
where sysuser0_.name=? order by sysuser0_.id desc, sysuser0_.name asc limit ?, ?
实际开发需求更多的使用了模糊查询

/**
 * Returns a copy of this {@link ExampleMatcher} with the specified string matching of {@code defaultStringMatcher}.
 * This instance is immutable and unaffected by this method call.
 *
 * @param defaultStringMatcher must not be {@literal null}.
 * @return new instance of {@link ExampleMatcher}.
 */
ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher);

找到这个方法设置字符串的匹配规则; 有如下规则

enum StringMatcher {

    /**
     * Store specific default.
     */
    DEFAULT,
    /**
     * Matches the exact string
     */
    EXACT,
    /**
     * Matches string starting with pattern
     */
    STARTING,
    /**
     * Matches string ending with pattern
     */
    ENDING,
    /**
     * Matches string containing pattern
     */
    CONTAINING,
    /**
     * Treats strings as regular expression patterns
     */
    REGEX;
}

选择匹配方法 :CONTAINING --包含字符串即可
测试方法:

Page<SysUser> page = userRepository.findAll(Example.of(sysUser, ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)), pageRequest);

优化一下:
自定义枚举

package study.springboot.domain.enums.jpa;

import org.springframework.data.domain.ExampleMatcher;

/**
 * @Date 2019年4月11日
 * @Sgin SelectFlagEnum
 * @Author Bertram.Wang
 */
public enum SelectFlagEnum {
    LIKE(ExampleMatcher.matching().withStringMatcher( ExampleMatcher.StringMatcher.CONTAINING));
    public ExampleMatcher exampleMatcher;

    private SelectFlagEnum(ExampleMatcher exampleMatcher) {
        this.exampleMatcher = exampleMatcher;
    }
}

测试方法修改为:

@Test
public void findAllTest() {
    Order idOrder = new Order(Direction.DESC, "id");
    Order nameOrder = new Order(Direction.ASC, "name");
    PageRequest pageRequest = PageRequest.of(1, 1, Sort.by(idOrder, nameOrder));
    SysUser sysUser = new SysUser();
    sysUser.setName("m");
    Page<SysUser> page = userRepository.findAll(Example.of(sysUser, SelectFlagEnum.LIKE.exampleMatcher), pageRequest);
    List<SysUser> content = page.getContent();
    log.info("content:{}", content);
}

公用字段自动填充的问题:

/**
 * @Date 2019年4月11日
 * @Sgin BaseEntity
 * @Author Bertram.Wang
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@MappedSuperclass 
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="create_date")
    @CreatedDate
    private Date createDate;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="modify_date")
    @LastModifiedDate
    private Date modifyDate;

}  

创建时间和修改时间自动填充的问题?
加上注解: @LastModifiedDate , @CreatedDate
实体类加上监听 : @EntityListeners(AuditingEntityListener.class)
启动类上: @EnableJpaAuditing
加上这些后新增方法确实能自动填充创建时间和修改时间。

Hibernate: insert into sys_user (create_date, modify_date, name, password) values (?, ?, ?, ?)
Hibernate: insert into sys_user_role (user_id, role_id) values (?, ?)

第一行自动填充了,但是级联插入的关系表却没有填充创建时间和修改时间,导致抛出错误o.h.engine.jdbc.spi.SqlExceptionHelper : Field 'create_date' doesn't have a default value

没有找到代码解决方法。
最后处理方式是:数据库设置默认值。

上一篇下一篇

猜你喜欢

热点阅读