likes
comments
collection
share

spring boot整合jpa注意点之QueryDsl框架使用

作者站长头像
站长
· 阅读数 14

yaml 的配置

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 密码
      url: 数据库地址
  jpa:
    # 控制台显示sql语句,一般很难看
    show-sql: true
    hibernate:
      # 项目启动时,根据实体类怎么变更数据库的表
      ddl-auto: update

其中:spring.jpa.hibernate.ddl-auto,可选参数:

  1. create:每次运行程序时,都会重新创建表,故而数据会丢失
  2. create-drop:每次运行程序时会先创建表结构,然后程序结束时清空表
  3. update:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)
  4. validate:运行程序会校验数据于数据库的字段类型是否相同,字段不同会报错
  5. none:禁用DDL处理

实体类注解配置讲解

  1. @Entity

注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名

  • 指定实体名称(表名):

  • 没有指定name属性且没有使用@Table,命名为类名生成

  • 指定name属性且没有使用@Table,命名为name属性value值

  • 指定name属性且使用了@Table指定name,命名以@Table的name的value

  1. @Table

注解用来标识实体类与数据表的对应关系。

  • name:表示该实体类映射的表名。
  • catalog:指定数据库名称,默认为当前连接url配置的数据库。
  • schema:指定数据库的用户名 ,默认为当前配置的用户。
  • uniqueConstraints:用于批量设置唯一约束。
  1. @Id

所有的实体类都要有的主键,@Id注解表示该属性是一个主键

  1. @GeneratedValue

注解表示主键自动生成,strategy则表示主键的生成策略

JPA自带的几种主键生成策略:

  • TABLE:使用一个特定的数据库表格来保存主键
  • SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator指定生成主键的生成器
  • IDENTITY:主键由数据库自动生成(主要支持自动增长的数据库,如mysql)
  • AUTO:主键由程序控制,也是GenerationType的默认值,mysql不支持,会报错:test.hibernate_sequence不存在
  1. @GenericGenerator

自定义主键生成策略,由@GenericGenerator实现。

  • name属性指定生成器名称。 与@GeneratorValue中 generator 的值对应。
  • strategy属性指定具体生成器的类名。
  • parameters得到strategy指定的具体生成器所用到的参数。

*Hibernate主键生成策略和各自的具体生成器之间的关系,在IdentifierGeneratorFactory接口中已经定义了。由DefaultIdentifierGeneratorFactory工厂去实现的

spring boot整合jpa注意点之QueryDsl框架使用 几种常见的主键策略生成器:

native:对于 oracle 采用 Sequence 方式;对于 MySQL 和 SQL Server 采用 Identity 方式。native就是将主键的生成工作交由数据库完成,hibernate不管。

uuid:采用128位的uuid算法生成主键,uuid被编码为一个32位16进制数字的字符串,占用空间大。

guid:采用数据库底层的guid算法机制,对应MYSQL的uuid()函数,SQL Server的newid()函数,ORACLE的rawtohex(sys_guid())函数等。

assigned: 在插入数据的时候主键由程序处理,等同于JPA中的AUTO,这是@GenericGenerator的默认设置。

6.@Column

@Column注解来标识实体类中属性与数据表中字段的对应关系。

查看@Column源码发现@Column注解共有10个属性,且均为可选属性。

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    //name属性定义了被标注字段在数据库表中所对应字段的名称。
    String name() default "";
    
    //unique属性表示该字段是否为唯一标识,默认为false.
    boolean unique() default false;
    
    //nullable属性表示该字段是否可以为null值,默认为true。
    boolean nullable() default true;
    
    //insertable属性表示在使用“INSERT”脚本插入数据时,是否需要插入该字段的值。
    //insertable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的。
    boolean insertable() default true;
    
    //updatable属性表示在使用“UPDATE”脚本插入数据时,是否需要更新该字段的值。
    //updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的。
    boolean updatable() default true;
    
    //columnDefinition属性表示创建表时,该字段创建的SQL语句
    String columnDefinition() default "";
    
    //table属性定义了包含当前字段的表名。
    String table() default "";
    
    //length属性表示字段的长度,当字段的类型为varchar时,该属性才有效。
    int length() default 255;
    
    //precision属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。
    int precision() default 0;
    int scale() default 0;
}
  1. @Transient:注解表示在生成数据库的表时,该属性被忽略,即不生成对应的字段
  2. 代码展示
@Data
@Entity
@Table(name = "sys_user")
public class SysUser {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "user-id")
    @GenericGenerator(name = "user-id",strategy = "uuid")
    private String id;

    @Column(name = "user_name")
    private String name;

    @Column(name = "user_author")
    private String author;

    private Float price;

    @Transient
    private String description;
}

dao层接口实现

通过集成jpa的接口实现自己项目中的持久层接口,继承JpaRepository接口

import com.yc.pojo.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{


}

其中JpaRepository又继承了PagingAndSortingRepositoryQueryByExampleExecutor这两个接口。

在这几个接口中已经实现了我们常用的几种增删改查方法。并且实现这个接口后可以编写派生的方法去做sql的增删改查。

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    // 查询等于name的所有数据
    List<SysUser> findByName(String name);
    
    // 查询price不为空的数据
    List<SysUser> findByPriceNotNull();
    
    //查询既有name,又有author相等的数据
    List<SysUser> findByNameAndAuthor(String name,String author);

}

注意:如果我们使用这种派生的方法去做sql的操作,必须遵循jpa的模板。例如下面

关键词样本JPQL 片段
DistinctfindDistinctByLastnameAndFirstnameselect distinct …​ where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnamefindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull,NullfindByAge(Is)Null… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(参数绑定附加%
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(参数绑定在前面%
ContainingfindByFirstnameContaining… where x.firstname like ?1(参数绑定在 中%
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)

特殊参数处理

在自定义从查询分页或排序的情况下,我们通常可以自己使用PageableSliceSort

Page<User> findByLastname(String lastname, Pageable pageable); 

Slice<User> findByLastname(String lastname, Pageable pageable); 

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

注意SortPageable不能传入null值,如果针对这个方法,你不想进行分页或者排序,请使用Sort.unsorted()Pageable.unpaged()

分页和排序

您可以使用属性名称定义简单的排序表达式。您可以连接表达式以将多个条件收集到一个表达式中。

  1. 第一种
Sort sort = Sort.by("firstname").ascending()
                .and(Sort.by("lastname").descending());
  1. 要以更类型安全的方式定义排序表达式,请从要定义排序表达式的类型开始,并使用方法引用来定义要排序的属性。
Sort.TypedSort<SysUser> sort = Sort.sort(SysUser.class);
Sort and = sort.by(SysUser::getId).ascending()
        .and(sort.by(SysUser::getName).descending());

注意 TypedSort.by(…)(通常)使用 CGlib 来使用运行时代理,这在使用 Graal VM Native 等工具时可能会干扰本机映像编译。

  1. 如果你在项目中使用到了Querydsl框架,可使用元模型来定义
QSort sort = QSort.by(QPerson.firstname.asc()) 
            .and(QSort.by(QPerson.lastname.desc()));

限制查询结果

first您可以使用或关键字来限制查询方法的结果top,这两个关键字可以互换使用。您可以附加一个可选的数值topfirst指定要返回的最大结果大小。如果省略该数字,则假定结果大小为 1。以下示例显示如何限制查询大小:

User findFirstByOrderByLastnameAsc(); 

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表达式还支持Distinct支持不同查询的数据存储的关键字。Optional另外,对于将结果集限制为一个实例的查询,支持使用关键字将结果包装起来。

如果将分页或切片应用于限制查询分页(以及可用页面数量的计算),则它将应用于有限结果内。

通过使用参数与动态排序相结合来限制结果,Sort您可以表达“K”个最小元素和“K”个最大元素的查询方法。

返回集合或可迭代对象的方法

返回多个结果的查询方法可以使用标准 Java IterableListSet. 除此之外,我们支持返回 Spring Data 的Streamable、 的自定义扩展Iterable以及Vavr提供的集合类型。请参阅解释所有可能的查询方法返回类型的附录。

使用 Streamable 作为查询方法返回类型

您可以用作任何集合类型Streamable的替代。Iterable它提供了访问非并行Stream(缺少)的便捷方法以及直接遍历元素并将 与其他元素连接的Iterable能力:….filter(…)``….map(…)``Streamable

例如下面:

interface PersonRepository extends Repository<Person, Long> { 
    Streamable<Person> findByFirstnameContaining(String firstname);
    Streamable<Person> findByLastnameContaining(String lastname); 
} 

Streamable<Person> result = repository.findByFirstnameContaining("av") 
                             .and(repository.findByLastnameContaining("ea"));

注意 Streamable中的and方法表示拼接的意思,并不是两个查询出来的相交,全部查询出来放在一起。

在spring官网中还有两种自定义的集合迭代类型,这里就不介绍了,上面这一种也已经够用了

方法的空处理

从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8Optional来指示可能缺少值。除此之外,Spring Data 支持在查询方法上返回以下包装类型:

  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option

或者,查询方法可以选择根本不使用包装类型。然后通过返回 来指示不存在查询结果null。返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回,null而是返回相应的空表示。有关详细信息,请参阅“存储库查询返回类型”。

流式查询结果

Stream<T>您可以使用 Java 8作为返回类型来增量处理查询方法的结果。不是将查询结果包装在Stream,而是使用数据存储特定的方法来执行流处理。

Stream<SysUser> findByNameLike(String name);

Stream可能会包装底层数据存储特定的资源,因此必须在使用后关闭。您可以Stream使用close()方法或使用 Java 7try-with-resources块手动关闭,如下例所示:

前并非所有 Spring Data 模块都支持Stream<T>返回类型。spring官网提示

异步查询结果

您可以使用Spring 的异步方法运行功能来异步运行存储库查询。这意味着该方法在调用后立即返回,而实际查询发生在已提交给 Spring 的任务中TaskExecutor。异步查询与反应式查询不同,不应混合使用。有关反应式支持的更多详细信息,请参阅特定的文档。以下示例显示了许多异步查询:

@Async
Future<User> findByFirstname(String firstname);

@Async
CompletableFuture<User> findOneByFirstname(String firstname); 

@Async 
ListenableFuture<User> findOneByLastname(String lastname);

上面这几个方法的返回类型,都是用于异步返回接收,属于异步编程工具

自定义查询

要实现自定义查询,也首先需要去对应的dao层接口下定义方法,这个方法也就不需要遵循什么派生规则,但是需要使用到注解指名sql。

使用@Query

使用命名查询来声明实体的查询是一种有效的方法,并且对于少量查询来说效果很好。由于查询本身与运行它们的 Java 方法相关联,因此您实际上可以使用 Spring Data JPA 注释直接绑定它们,@Query而不是将它们注释到域类。这将域类从持久性特定信息中解放出来,并将查询共同定位到存储库接口。

注释到查询方法的查询优先于使用定义的查询@NamedQuery或在 中声明的命名查询orm.xml

以下示例显示了使用@Query注释创建的查询:

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Query(value = "select s from SysUser  s where s.name=?1")
    List<SysUser> getByName(String name);
}

注意: 这里的?+数字代表一个占位符,比较特殊的是数字从1开始,而非从0开始。?1代表顺序中的第一个参数。

使用命名参数绑定查询名称的方法

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Query(value = "select s from SysUser  s where s.name=:name")
    List<SysUser> getByName(@Param("name") String name);
}

注意: =:加上变量名的方式来绑定参数,这里是与方法参数中有@Param的值匹配的,而不是与实际参数匹配的,且@Param的顺序可以随意放置。

利用@Query进行修改

当使用@Query时,如果想要修改,需要加入@Modifying进行修饰,相当于 @Update 注解。

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Transactional
    @Modifying
    @Query(value = "update SysUser s set s.price=?1 where s.name=?2")
    Integer update(Float price,String name);
}

这样做会触发注释到该方法的查询作为更新查询而不是选择查询。由于EntityManager在执行修改查询后 可能包含过时的实体,因此我们不会自动清除它(有关详细信息,请参阅的JavaDocEntityManager.clear()),因为这实际上会删除EntityManager. 如果您希望EntityManager自动清除,可以将@Modifying注释的clearAutomatically属性设置为true。其实在高版本中,可以不管了,加不加无所谓

注释@Modifying仅与注释结合使用才相关@Query。派生查询方法或自定义方法不需要此注释。

注意: 再进行insert , update , delete 操作时,必须配置事务。

原生sql查询

nativeQuery=true时,指可以用原生的sql运行。例如: 所谓本地查询,就是使用原生的sql语句(根据数据库的不同,在sql的语法或结构方面可能有所区别)进行查询数据库的操作。

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Transactional
    @Modifying
    @Query(value = "update sys_user s set s.price=?1 where s.user_name=?2",nativeQuery = true)
    Integer update(Float price,String name);
}

如果没有这个属性,那就是用springdata jpa的对象来进行查询。

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Transactional
    @Modifying
    @Query(value = "update SysUser s set s.price=?1 where s.name=?2")
    Integer update(Float price,String name);
}

与分页和排序一起使用

PageRequest排序可以通过提供或直接使用来完成SortOrder实例中实际使用的属性Sort需要匹配您的域模型,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。

使用任何不可引用的路径表达式都会导致Exception.

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{
    
    @Query(value = "select s from SysUser s  where s.name=?1")
    Page<SysUser> select(String name, Pageable pageable);
}

Sort但是,与 with 一起使用@Query可以让您潜入子句中包含函数的非路径检查Order实例ORDER BY。这是可能的,因为Order附加到给定的查询字符串。默认情况下,Spring Data JPA 拒绝任何Order包含函数调用的实例,但您可以使用它JpaSort.unsafe来添加可能不安全的排序。

以下示例使用SortJpaSort,包括 上的不安全选项JpaSort

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("select u from User u where u.lastname like ?1%")
    List<User> findByAndSort(String lastname, Sort sort);

    @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
    List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}


repo.findByAndSort("lannister", Sort.by("firstname")); //`Sort`指向域模型中属性的有效表达式
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)")); //`ort`包含函数调用无效。抛出异常。
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); //有效`Sort`包含明确*不安全的* `Order`
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len")); //`Sort`指向别名函数的有效表达式。

使用SpEl表达式

从 Spring Data JPA 版本 1.4 开始,我们支持在使用@Query. 运行查询时,将根据一组预定义的变量来评估这些表达式。Spring Data JPA 支持一个名为 的变量entityName。它的用法是select x from #{#entityName} x. 它插入entityName与给定存储库关联的域类型。解决方法entityName如下:如果域类型在@Entity注释上设置了 name 属性,则使用它。否则,将使用域类型的简单类名。

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Query(value = "select s from #{#entityName} s  where s.name=?1")
    Page<SysUser> select(String name, Pageable pageable);
}

为了避免在注释的查询字符串中声明实际的实体名称@Query,您可以使用该#{#entityName}变量。

可以entityName使用@Entity注释进行自定义。orm.xmlSpEL 表达式不支持 自定义

用于操作参数的 SpEL 表达式也可用于操作方法参数。在这些 SpEL 表达式中,实体名称不可用,但参数可用。可以通过名称或索引来访问它们,如以下示例所示。

通过索引访问

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Query(value = "select s from #{#entityName} s  where s.name=?#{escape([0])}")
    Page<SysUser> select(String name, Pageable pageable);
}

escape: 专门用来检索特殊字符的。

通过名称访问也就是命名查询

清理输入值

@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);

鉴于存储库接口中的此方法声明findContainingEscaped("Peter_")会找到Peter_Parker,但不会找到Peter Parker。可以通过设置注释escapeCharacter的来配置使用的转义字符@EnableJpaRepositories。请注意,SpEL 上下文中可用的方法escape(String)只会转义 SQL 和 JPQL 标准通配符_%。如果底层数据库或 JPA 实现支持其他通配符,这些通配符将不会被转义。

动态类型

希望选择在调用时使用的类型(这使其成为动态的)。要应用动态投影,请使用如下例所示的查询方法:

@Repository
public interface SysUserDao extends JpaRepository<SysUser,Integer>{

    @Query(value = "select s from #{#entityName} s  where s.name=?1")
    <T> Page<T> select(String name, Pageable pageable,Class<T> Class);
}

@Test
public void Test1(){
    Page<SysUser> select = sysUserDao.select("张杰 1", PageRequest.of(1, 3), SysUser.class);
    select.forEach(System.out::println);
}

检查类型的查询参数Class是否有资格作为动态投影参数。如果查询的实际返回类型等于参数的通用参数类型Class,则匹配的Class参数不可再查询或 SpEL 表达式中使用。如果您想使用Class参数作为查询参数,请确保使用不同的通用参数,例如Class<?>

QueryDsl框架(推荐与jpa一起)

QueryDsl作用

  1. Querydsl支持代码自动完成,因为是纯Java API编写查询,因此主流Java IDE对其的代码自动完成功能支持几乎可以发挥到极致(因为是纯Java代码,所以支持很好)

  2. Querydsl几乎可以避免所有的SQL语法错误(当然用错了Querydsl API除外,因为不写SQL了,因此想用错也难)

  3. Querydsl采用Domain类型的对象和属性来构建查询,因此查询绝对是类型安全的,不会因为条件类型而出现问题

  4. Querydsl采用纯Java API的作为SQL构建的实现可以让代码重构发挥到另一个高度

  5. Querydsl的另一个优势就是可以更轻松的进行增量查询的定义

使用

在Spring环境下,可以通过两种风格来使用QueryDSL。

一种是使用JPAQueryFactory的原生QueryDSL风格, 另一种是基于Spring Data提供的QueryDslPredicateExecutor<T>的Spring-data风格。

使用QueryDslPredicateExecutor<T>可以简化一些代码,使得查询更加优雅。 而JPAQueryFactory的优势则体现在其功能的强大,支持更复杂的查询业务。甚至可以用来进行更新和删除操作。

依赖和插件

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <scope>provided</scope>
</dependency>

在高版本中 spring家族帮我们整理了版本,不要指定版本,出现问题再去修改版本号

<build>
    <plugins>
         <plugin>
             <groupId>com.mysema.maven</groupId>
             <artifactId>apt-maven-plugin</artifactId>
             <version>1.1.3</version>
             <executions>
                 <execution>
                     <phase>generate-resources</phase>
                     <goals>
                         <goal>process</goal>
                     </goals>
                     <configuration>
                         <outputDirectory>target/generated-sources/java</outputDirectory>
                         <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                     </configuration>
                 </execution>
             </executions>
         </plugin>
    </plugins>
</build>

添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。

上文引入的依赖中querydsl-apt即是为此插件服务的。

注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。

QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。

若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的query type。


<project>
	<build>
		<plugins>
			<plugin>
                <groupId>com.querydsl</groupId>
                <artifactId>querydsl-maven-plugin</artifactId>
                <version>${querydsl.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>export</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <jdbcDriver>org.apache.derby.jdbc.EmbeddedDriver</jdbcDriver>
                    <jdbcUrl>jdbc:derby:target/demoDB;create=true</jdbcUrl>
                    <packageName>com.mycompany.mydomain</packageName>
                    <targetFolder>${project.basedir}/target/generated-sources/java</targetFolder>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.apache.derby</groupId>
                        <artifactId>derby</artifactId>
                        <version>${derby.version}</version>
                    </dependency>
                </dependencies>
			</plugin>
		</plugins>
	</build>
</project>

生成Q类的实体类

spring boot整合jpa注意点之QueryDsl框架使用

完成之后就会在target下生成对应的Q类

spring boot整合jpa注意点之QueryDsl框架使用

JPAQueryFactory风格

QueryDSL 在支持JPA的同时,也提供了对 Hibernate 的支持。可以通过 HibernateQueryFactory 来使用。

装配注入

EntityManager是完成持久化操作的核心对象,需要在JPAQueryFactory工厂中配置

  1. 使用配置类
@Configuration
public class QueryDslConfig {

    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager){
        return new JPAQueryFactory(entityManager);
    }
}

单表查询操作

@SpringBootTest
public class MainTest {

    @Autowired
    private JPAQueryFactory jpaQueryFactory;

    private  QSysUser sysUser = QSysUser.sysUser;

    @Test
    public void Test1(){
        // 查询全部
        List<SysUser> fetch = jpaQueryFactory.select(sysUser)
                .from(sysUser)
                .fetch();
        List<SysUser> fetch1 = jpaQueryFactory.selectFrom(sysUser)
                .fetch();
        // 查询某一个字段
        List<String> fetch2 = jpaQueryFactory.select(sysUser.name)
                .from(sysUser)
                .fetch();
        // 去重查询
        List<String> fetch3 = jpaQueryFactory.selectDistinct(sysUser.name)
                .from(sysUser)
                .fetch();

        // 获取首个查询结果
        SysUser sysUser1 = jpaQueryFactory.selectFrom(sysUser)
                .fetchFirst();

        // 获取唯一的查询结果
        // 当fetchOne查询出来是一个多个数据集合,将会抛出 ‘com.querydsl.core.NonUniqueResultException’  错误
        SysUser sysUser2 = jpaQueryFactory.select(sysUser)
                .from(sysUser)
                .where(sysUser.name.eq("张杰 2"))
                .fetchOne();
    }
}

where条件查询

List<SysUser> fetch = jpaQueryFactory.selectFrom(sysUser)
        .where(sysUser.name.eq("张杰 2")
                .and(sysUser.author.like("%你好 0%"))
                .and(sysUser.price.between(2.0f,4.4f)))
        .where(sysUser.createTime.isNull())
        .fetch();
fetch.forEach(System.out::println);

注意:and连接是在where中的,如果你的where中判断条件太多,你可以在外部再进行where的拼接

BooleanBuilder条件管理(动态语句)

BooleanBuilder booleanBuilder = new BooleanBuilder();
booleanBuilder.and(sysUser.name.eq("张杰 2"));
booleanBuilder.and(sysUser.author.like("%你好 0%"));
booleanBuilder.and(sysUser.price.between(1.3F,3.3F));

List<SysUser> fetch = jpaQueryFactory.selectFrom(sysUser).where(booleanBuilder).fetch();
fetch.forEach(System.out::println);

这种方式我感觉最方便,并且最大优点就是可以实现动态的。

@Test
public void Test1(){
   // 1. 注意,and连接是在where中
    sel("张杰 1","你好",1.2f,3.3f);
}

public void sel(String name,String author,Float left,Float right){
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    if (StringUtils.hasText(name)){
        booleanBuilder.and(sysUser.name.eq(name));
    }
    if (StringUtils.hasText(author)){
        booleanBuilder.and(sysUser.author.like("%"+author+"%"));
    }
    if (left<right && left!=0 && right !=0){
        booleanBuilder.and(sysUser.price.between(left,right));
    }
    List<SysUser> fetch = jpaQueryFactory.selectFrom(sysUser).where(booleanBuilder).fetch();
    fetch.forEach(System.out::println);
}

这种写法的确很爽!,但是sql语句就会慢慢生疏了,所以在余下时间就需要再去练习sql

复杂的查询关系

BooleanBuilder booleanBuilder = new BooleanBuilder();
booleanBuilder.and(sysUser.name.like("%张杰%"));

BooleanBuilder booleanBuilder1 = new BooleanBuilder();
booleanBuilder1.and(sysUser.price.between(2.3F,6.6F));

// 在第一个条件管理中在添加第二个管理条件
booleanBuilder.and(booleanBuilder1);


List<SysUser> fetch = jpaQueryFactory.selectFrom(sysUser).where(booleanBuilder).fetch();
System.out.println(fetch.size());
fetch.forEach(System.out::println);
转载自:https://juejin.cn/post/7258445326913880125
评论
请登录