Giter VIP home page Giter VIP logo

jpa-spec's Introduction

Build Status codecov Quality Gate Status FOSSA Status codebeat badge CII Best Practices MIT

jpa-spec

Inspired by Legacy Hibernate Criteria Queries, while this should be considered deprecated vs JPA APIs,

but it still productive and easily understandable. Build on Spring Data JPA and simplify the dynamic query process.

Features

  • Compatible with Spring Data JPA and JPA 2.1 interface.
  • Equal/NotEqual/Like/NotLike/In/NotIn support multiple values, Equal/NotEqual support Null value.
  • Each specification support join query(left joiner).
  • Support custom specification.
  • Builder style specification creator.
  • Support pagination and sort builder.

Docs

English Version Chinese Version
Latest 最新
3.2.5 3.2.5_cn
3.2.1 3.2.1_cn
3.1.0 3.1.0_cn
3.0.0 3.0.0_cn

Gradle

repositories {
    jcenter()
}

dependencies {
    implementation 'com.github.wenhao:jpa-spec:3.2.5'
}

Maven

<dependency>
    <groupId>com.github.wenhao</groupId>
    <artifactId>jpa-spec</artifactId>
    <version>3.2.5</version>
</dependency>

Build

./gradlew clean build

Specification By Examples:

Each specification support three parameters:

  1. condition: if true(default), apply this specification.
  2. property: field name.
  3. values: compare value with model, eq/ne/like support multiple values.

General Example

each Repository class should extends from two super class JpaRepository and JpaSpecificationExecutor.

public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
}    
public Page<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
            .gt(Objects.nonNull(request.getAge()), "age", 18)
            .between("birthday", new Date(), new Date())
            .like("nickName", "%og%", "%me")
            .build();

    return personRepository.findAll(specification, new PageRequest(0, 15));
}

Equal/NotEqual Example

find any person nickName equals to "dog" and name equals to "Jack"/"Eric" or null value, and company is null.

Test: EqualTest.java and NotEqualTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .eq("nickName", "dog")
            .eq(StringUtils.isNotBlank(request.getName()), "name", "Jack", "Eric", null)
            .eq("company", null) //or eq("company", (Object) null)
            .build();

    return personRepository.findAll(specification);
}

In/NotIn Example

find any person name in "Jack" or "Eric" and company not in "ThoughtWorks" or "IBM".

Test: InTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .in("name", request.getNames())
            .notIn("company", Arrays.asList("ThoughtWorks", "IBM"))
            .build();

    return personRepository.findAll(specification);
}

Comparison Example

Support any comparison class which implements Comparable interface, find any people age bigger than 18.

Test: GreatThanTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .gt(Objects.nonNull(request.getAge()), "age", 18)
            .lt("birthday", new Date())
            .build();

    return personRepository.findAll(specification);
}

Between Example

find any person age between 18 and 25, birthday between someday and someday.

Test: BetweenTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .between(Objects.nonNull(request.getAge(), "age", 18, 25)
            .between("birthday", new Date(), new Date())
            .build();

    return personRepository.findAll(specification);
}  

Like/NotLike Example

find any person name like %ac% or %og%, company not like %ec%.

Test: LikeTest.java and NotLikeTest.java

public Page<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .like("name", "ac", "%og%")
            .notLike("company", "ec")
            .build();

    return personRepository.findAll(specification);
}

Or

support or specifications.

Test: OrTest.java

public List<Phone> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>or()
                    .like("name", "%ac%")
                    .gt("age", 19)
                    .build();

    return phoneRepository.findAll(specification);
}

Mixed And and Or

support mixed and and or specifications.

Test: AndOrTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
                    .like("name", "%ac%")
                    .predicate(Specifications.or()
                            .lt("age", 19)
                            .gt("age", 25)
                            .build())
                    .build();

    return personRepository.findAll(specification);
}

Join

each specification support association query as left join.

Test: JoinTest.java

@ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei".

public List<Phone> findAll(SearchRequest request) {
    Specification<Phone> specification = Specifications.<Phone>and()
        .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei")
        .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack")
        .build();

    return phoneRepository.findAll(specification);
}

@ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street.

public List<Phone> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
        .between("age", 10, 35)
        .eq(StringUtils.isNotBlank(jack.getName()), "addresses.street", "Chengdu")
        .build();

    return phoneRepository.findAll(specification);
}

Custom Specification

You can custom specification to do the @ManyToOne and @ManyToMany as well.

@ManyToOne association query, find person name equals to "Jack" and phone brand equals to "HuaWei".

Test: PredicateTest.java

public List<Phone> findAll(SearchRequest request) {
    Specification<Phone> specification = Specifications.<Phone>and()
        .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei")
        .predicate(StringUtils.isNotBlank(request.getPersonName()), (root, query, cb) -> {
            Path<Person> person = root.get("person");
            return cb.equal(person.get("name"), "Jack");
        })
        .build();

    return phoneRepository.findAll(specification);
}

@ManyToMany association query, find person age between 10 and 35, live in "Chengdu" street.

Test: PredicateTest.java

public List<Phone> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
        .between("age", 10, 35)
        .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
            Join address = root.join("addresses", JoinType.LEFT);
            return cb.equal(address.get("street"), "Chengdu");
        }))
        .build();

    return phoneRepository.findAll(specification);
}

Sort

Test: SortTest.java

public List<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
            .gt("age", 18)
            .between("birthday", new Date(), new Date())
            .like("nickName", "%og%")
            .build();

    Sort sort = Sorts.builder()
        .desc(StringUtils.isNotBlank(request.getName()), "name")
        .asc("birthday")
        .build();

    return personRepository.findAll(specification, sort);
}

Pagination

find person by pagination and sort by name desc and birthday asc.

public Page<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
            .gt("age", 18)
            .between("birthday", new Date(), new Date())
            .like("nickName", "%og%")
            .build();

    Sort sort = Sorts.builder()
        .desc(StringUtils.isNotBlank(request.getName()), "name")
        .asc("birthday")
        .build();

    return personRepository.findAll(specification, PageRequest.of(0, 15, sort));
}

Virtual View

Using @org.hibernate.annotations.Subselect to define a virtual view if you don't want a database table view.

There is no difference between a view and a database table for a Hibernate mapping.

Test: VirtualViewTest.java

@Entity
@Immutable
@Subselect("SELECT p.id, p.name, p.age, ic.number " +
           "FROM person p " +
           "LEFT JOIN id_card ic " +
           "ON p.id_card_id=ic.id")
public class PersonIdCard {
    @Id
    private Long id;
    private String name;
    private Integer age;
    private String number;

    // Getters and setters are omitted for brevity
}    
public List<PersonIdCard> findAll(SearchRequest request) {
    Specification<PersonIdCard> specification = Specifications.<PersonIdCard>and()
            .gt(Objects.nonNull(request.getAge()), "age", 18)
            .build();

    return personIdCardRepository.findAll(specification);
}

Projection, GroupBy, Aggregation

Spring Data JPA doesn't support Projection(a little but trick), GroupBy and Aggregation,

furthermore, Projection/GroupBy/Aggregation are often used for complex statistics report, it might seem like overkill to use Hibernate/JPA ORM to solve it.

Alternatively, using virtual view and give a readable/significant class name to against your problem domain may be a better option.

Copyright and license

Copyright © 2016-2019 Wen Hao

Licensed under MIT License

FOSSA Status

jpa-spec's People

Contributors

fossabot avatar marcinsoja avatar souvc avatar wenhao avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jpa-spec's Issues

OR条件查询的问题

	Specification<UserInfo> spec = Specifications.<UserInfo>or()
			.eq(StringUtils.isNotBlank(userInfo.getPhoneNo()), "phoneNo", userInfo.getPhoneNo())
			.like(StringUtils.isNotBlank(userInfo.getName()), "name", "%" + userInfo.getName() + "%")
			.build();

当我phoneNo和name都为空时,查询的后台sql,显示的where 0 = 1
这是个bug吗?

mysql json

mysql 查询json格式字段的支持吗?

or操作报错

error

java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name

code

Specification specification = Specifications.or().
eq(request.getMerchantMarketingId(),"merchantMarketingId",request.getMerchantMarketingId()).
eq(StringUtils.isNotEmpty(request.getParam()),"name",request.getParam()).
eq(StringUtils.isNotEmpty(request.getParam()),"phone",request.getParam()).
build();

确定name属性是存在的

增加 boolean 值类型 Specification

class User {
    private long id;
    private boolean off = false;
    // getter、setter
}

希望可以这样

PredicateBuilder<User> builder = Specifications.<User>and()
                .eq("off", true);
// 或者增加一个支持
/*
PredicateBuilder<User> builder = Specifications.<User>and()
                .true("off");
PredicateBuilder<User> builder = Specifications.<User>and()
                .false("off");
*/

实际需要这样

PredicateBuilder<User> builder = Specifications.<User>and()
                .predicate((root, query, cb) -> cb.isTrue(root.get("off")));

OR条件查询的问题

// when
Specification specification = Specifications.and()
.eq(isNotBlank(EMPTY), "name", jack.getName())
.like(isNotBlank(EMPTY), "name", "%" + jack.getName() + "%")
.build();

    List<Person> persons = personRepository.findAll(specification);

    // then
    assertThat(persons.size()).isEqualTo(2);

亲,我邮件回复不了你,不支持回复。
你这里是用的 and 条件连接符,我说的是 or 条件连接符哦!

在判断日期eq的时候 存在问题

如果数据库中的日期是date类型的 使用eq的时候需要将日期的时分秒毫秒设置为0 希望加上

@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME, pattern = "yyyy-MM-dd HH:mm:ss")

这样的注解进行判断

JPA specification - filerting on joining table's embedded object

你好,

我们有个场景需要对join表的embedded属性字段做过滤。假设我们有如下2个@entity类和一个@embeddable类:

`
@entity
@DaTa
public class Product {
@id
@column(columnDefinition = "bigint(10)", name = "id", unique = true, nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(length = 100)
String name;

@JsonManagedReference
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product")
protected List<Variant> variants;

}

@entity
@DaTa
public class Variant {

@Id
@Column(columnDefinition = "bigint(10)", name = "id", unique = true, nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "sku", length = 50)
protected String sku;

@Embedded
protected Money price;

@JsonBackReference
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "product_id")
protected Product product;

}

@embeddable
@DaTa
public class Money {

@Column(length = 10)
private String currency;

@Column(columnDefinition = "bigint(13)")
private Integer amount;

}
`

当我创建如下specification进行查询时候,会报错。specification如下:

`
public Page findAllProducts(Pageable pageable) {
Specification specification = Specifications.and()
.eq("name", "product-111")
.ge("variants.price.amount", 2000)
.build();

    return productRepository.findAll(specification, pageable);
}

`
Java报错信息如下:

java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [price.amount] on this ManagedType [com.example.sample.model.Variant] at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:128) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:113) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] at org.hibernate.jpa.criteria.path.AbstractFromImpl.locateAttributeInternal(AbstractFromImpl.java:116) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] at org.hibernate.jpa.criteria.path.AbstractPathImpl.locateAttribute(AbstractPathImpl.java:204) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] at org.hibernate.jpa.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:177) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] at com.github.wenhao.jpa.specification.GeSpecification.toPredicate(GeSpecification.java:22) ~[classes/:na] at com.github.wenhao.jpa.PredicateBuilder.lambda$build$0(PredicateBuilder.java:131) ~[classes/:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:718) ~[spring-data-jpa-1.11.8.RELEASE.jar:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:649) ~[spring-data-jpa-1.11.8.RELEASE.jar:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:608) ~[spring-data-jpa-1.11.8.RELEASE.jar:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:407) ~[spring-data-jpa-1.11.8.RELEASE.jar:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_152] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_152] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_152] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_152] at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) ~[spring-data-commons-1.13.8.RELEASE.jar:na] at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) ~[spring-data-commons-1.13.8.RELEASE.jar:na] at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.8.RELEASE.jar:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.8.RELEASE.jar:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.11.8.RELEASE.jar:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) ~[spring-data-commons-1.13.8.RELEASE.jar:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.12.RELEASE.jar:4.3.12.RELEASE] at com.sun.proxy.$Proxy104.findAll(Unknown Source) ~[na:na] at com.example.sample.controller.ProductController.findAllProducts(ProductController.java:52) ~[classes/:na] at com.example.sample.controller.ProductController.findProducts(ProductController.java:43) ~[classes/:na]
请问这个问题该如何处理?谢谢帮忙看一下。

枚举属性

当类中存在枚举属性时,用一些EQ,NEQ等查询时,就会映射失败

貌似不支持multiselect这种玩法

请问是不支持下面这种玩法吗?
criteriaQuery.multiselect(root.get("appId"), criteriaBuilder.sum(root.get("callCount")), criteriaBuilder.sum(root.get("successCount")));

spring boot 2.0.0 + spring data jpa 2.0.5报错

BetweenSpecification.java中的第25行
return cb.between(from.get(field), range.getLowerBound(), range.getUpperBound());
报如下错误:
The method between(Expression<? extends Y>, Expression<? extends Y>, Expression<? extends Y>) in the type CriteriaBuilder is not applicable for the arguments (Path, Range.Bound, Range.Bound)

关联查询时,每使用关联表的一个条件,就会多一个join

比如下面的例子
Specification specification = Specifications.and()
.between("age", 10, 35)
.eq("addresses.street", "Chengdu")
.eq("addresses.city", "china")
.build();
用到了addresses的两个条件:street,city,在生成Sql时addresses关联了两次:
如:select * from person left join addresses on x=x left join addresses on x=x ******
这是什么问题呢

如何设置cache

在Repository和Entity中都可以设置cache,在用jpa-spec如何设置cache呢?

findAll with empty query parameter,get total num but no data

     Page<Departments> departmentss = departmentsService.findAll(
                Specifications.<Departments>and()
                        //.eq("deptNo","d001")
                        .build(),
                new PageRequest(1,20)
        );
        if (!CollectionUtils.isEmpty(departmentss.getContent())){
            logger.info("departments num:"+departmentss.getTotalElements()+".");
            List<Departments> list = departmentss.getContent();
            for (Departments dpt : list) {
                logger.info(dpt.toString());
            }
        }else
            logger.error("departments's info is not exist");

but if I use default findAll,all rows will be found.

between条件下Range的bug

Specification spec = Specifications.or()
.eq(StringUtils.isNotBlank(mobileBasic.getCity()), "city", mobileBasic.getCity())
.between(StringUtils.isNotBlank(dateInfo.getDateRange()), "createTime", Range.of(Range.Bound.inclusive(dateInfo.getStartDate()), Range.Bound.inclusive(dateInfo.getEndDate())))
.build();

您好,StringUtils.isNotBlank(dateInfo.getDateRange())这一句我已经判空了,却还是进入后面Range.of(Range.Bound.inclusive(dateInfo.getStartDate())导致程序一直报 must not be null,
这个是什么原因呢!

in的体验不是特别号

用in构建Specification时,入参是可变参数。但是入参类型为set,list等Collection时 通过了编译,运行时却抛出了转换异常。
希望构建Specification时可以接收Collection入参

希望增加group by功能

问题重现:
当一对多自关联时,如果限定状态为某个值 ,同时限定子类集合里面的一个状态为某个值,则生成的sql是左连接,此时会导致重复数据,希望可以增加group by 功能去重。

top 和 count

top或者是count怎么做,比如我想top创建时间>n的x条,或者count创建时间>n的数据

read lock

您好 jpa - spec如何实现read lock呢?

建议查询条件构建里面的values 提供Supplier重载

.in(CollectionUtils.isNotEmpty(statusSet), "status", statusSet.toArray())
比如这个条件, 尽管前面条件里判空了,但是如果statusSet是null仍然会抛出空指针

一般解决这个问题可以用 ==null?null: 这种三元符来解决:
.in(CollectionUtils.isNotEmpty(statusSet), "status", statusSet==null?null:statusSet.toArray())

但是更优雅的方式是提供Supplier参数重载. 这样扩展性也更好:
.in(CollectionUtils.isNotEmpty(statusSet), "status", ()-> statusSet.toArray())

and和or条件混合

如何在Specifications的and谓词增加一个或多个or条件
形成这样的sql效果:
where (a=1) and (b=2 or c=3)and (d=4)

ony to many , many to many 重复join

查询语句里多次用到many端的属性时,会重复join。
下面代码能修复这个问题

public From getRoot(String property, Root<T> root) {
    if (property.contains(".")) {
        String joinProperty = StringUtils.split(property, ".")[0];
        //防止重复join
        Optional<Join<T, ?>> oldJoin = root.getJoins().stream().filter(j -> j.getAttribute().getName().equals(joinProperty)).findAny();
        return oldJoin.orElseGet(() -> root.join(joinProperty, JoinType.LEFT));
    }
    return root;
}

PredicateBuilder.eq("fieldName") 不能按预期实现对字段值为null的判断

com.github.wenhao.jpa.specification.EqualSpecification中的第44行,对变长参数的 values == null 的比较结果永远为 false,因为在 调用 PredicateBuilder.eq(boolean condition, String property, Object... values) 方法时,省略变长参数 values后,其值为一个长度为0的 Object[], 并不为 null。因此,对于字段为 null的条件,无法用 eq("fieldName") 这种简洁的调用方式实现,而要写成 eq("fieldName", new Object[]{null}) 这种别扭的方式才可以。

所以在 44 行处的判断除了判断 values == null, 还要加一个 values.length == 0,即改为 if (values == null || values.length == 0) 即可。

左外联查询找不到字段

eq(tutorStudno != null, "course.tutor.studno", tutorStudno)

当执行上面eq进行查询时,会出现下面的错误

java.lang.IllegalArgumentException:
Unable to locate Attribute with the the given name [tutor.studno]
on this ManagedType [com.jss.app.model.base.RowBase]

我确定字段名没有写错,而且使用JPA的简单查询是能够查出来的

JPA简单查询的代码

// 下面的代码可以执行,并正确返回结果
List<StudentCourse> findByCourse_Tutor_Studno(String tutorStudno);

具体的实现细节

// 省略一些属性的读取
String studentStudno = jsonObject.getString("studentStudno");
String tutorStudno = jsonObject.getString("tutorStudno");
Specification<StudentCourse> specification = Specifications.<StudentCourse>and()
				.eq(studentId != null, "student.id", studentId)
				.like(!StringUtils.isEmpty(name), "course.name", "%" + name + "%")
				.eq(academicYear != null, "course.academicYear", academicYear)
                                .eq(term != null, "course.term", term)
				.eq(courseTyoe != null, "course.courseTyoe", courseTyoe)
				.eq(!StringUtils.isEmpty(studentStudno), "student.studno", studentStudno)
				.eq(!StringUtils.isEmpty(tutorStudno), "course.tutor.studno", tutorStudno).build();

但是,student.studnocourse.courseTyoe 等查询没有问题;

下面是实体类

StudentCourse

@Entity
@Table(name = "T_STUDENT_COURSE")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = false)
public class 	StudentCourse  extends RowBase {
	private static final long serialVersionUID = -164015745985836181L;
	@ManyToOne(cascade = CascadeType.REMOVE)
	private Student student;
	@ManyToOne(cascade = CascadeType.REMOVE)
	private Course course;
	@ColumnDefault(value = "0")
	private Integer status;
	private Integer score;
}

Course

@Proxy(lazy = false)
@Entity
@Table(name = "T_COURSE")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = false)
@Accessors(chain = true)
public class Course extends CommonDictionary implements Serializable {
	private static final long serialVersionUID = -840690077388663221L;
	@ManyToOne
	private Tutor tutor;
	@ManyToMany
	@JoinTable(name = "T_COURSE_GROUP")
	private List<Group> group;
	private Integer academicYear;
	@Enumerated
	private Term term;
	@Enumerated
	@ColumnDefault(value = "0")
	private CourseTyoe courseType;
	@ColumnDefault(value = "0")
	private Integer amount;
	@ColumnDefault(value = "0")
	private Integer currentNum;

}

Tutor

@Entity
@Table(name = "T_TUTOR")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Tutor extends RowBase implements Serializable {

	private static final long serialVersionUID = 281326835164141801L;
	private String name;

	@Enumerated
	private Sex sex;
	private String studno;
	private String title;
	@ManyToOne
	private Institute institute;
}

RowBase

@MappedSuperclass
@Data
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert(true)
@DynamicUpdate
@Accessors(chain = true)
public class RowBase implements Serializable {

	private static final long serialVersionUID = -8386266958781724931L;
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	@Column(updatable = false)
	@CreationTimestamp
	private Date createTime;
	@UpdateTimestamp
	private Date updateTime;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.