自从邂逅了spring-data-jpa + querydsl 这个组合后,我再也没用过mybatis。

QueryDsl简单了解

QueryDSL可以在任何支持的ORM框架或者SQL平台上以一种通用的API方式来构建查询。目前QueryDSL支持的平台包括JPA,JDO,SQL,Mongodb 等等。

官网

http://www.querydsl.com

Gitthub

https://github.com/querydsl/querydsl

入门到会用

这个教程是演示querydsl + spring-data-jpa整合使用(其他的我也不会)。对于spring-data-jpa,如果你不熟悉也没太大的关系,不影响使用querydsl。

开始之前,这里要做一些说明。

这里不会涉及太多JPA相关的东西

QueryDsl和jpa整合其实很简单(就几行代码)。但是jap和springboot的整合会涉及很多的配置属性,代码。这里不会过多的去解释它们。

实体类建模不采用面向对象的思想建模

JPA让人讨厌的很大一个原因是因为那一堆 @OnToOne, @OneToMany 。。。等关系描述。并且由此带来诸多的概念:延迟加载,级联删除,级联保存,孤儿删除。。。更是头疼不已。代码是死的,人是活的,不一定非要墨守成规。不用面向对象思想建模,就彻底解决了这些关联问题。

演示工程的创建

在编辑器初始化工程的过程,省略。

Maven完整的核心依赖

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

io.springboot.querydsl

springboot-querydsl

0.0.1-SNAPSHOT

org.springframework.boot

spring-boot-starter-parent

2.3.3.RELEASE

UTF-8

UTF-8

1.8

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

org.springframework.boot

spring-boot-starter-undertow

org.springframework.boot

spring-boot-starter-data-jpa

mysql

mysql-connector-java

com.zaxxer

HikariCP

com.querydsl

querydsl-jpa

com.querydsl

querydsl-apt

provided

org.springframework.boot

spring-boot-maven-plugin

true

true

com.mysema.maven

apt-maven-plugin

1.1.3

process

target/generated-sources/java

com.querydsl.apt.jpa.JPAAnnotationProcessor

除了jpa和必须的依赖(驱动,连接池)以外,querydsl只有3个组件。俩依赖,一个插件。插件的功能在于,在maven打包的时候,根据实体类生成查询对象。

配置文件

server:

port: 80

logging:

level:

root: DEBUG

# 在日志中输SQL参数的绑定信息

'org.hibernate.type.descriptor.sql.BasicBinder': TRACE

spring:

datasource:

type: com.zaxxer.hikari.HikariDataSource

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://127.0.0.1:3306/querydsl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true

username: root

password: root

data:

jpa:

repositories:

enabled: true

bootstrap-mode: default

jpa:

# 指定关系型数据库的方言

database-platform: org.hibernate.dialect.MySQL57Dialect

# 不在视图层维护连接

open-in-view: false

# 日志中输出SQL

show-sql: false

properties:

# 格式化日志中输出的SQL

hibernate.format_sql: false

hibernate:

# SQL建表策略: UPDATE

ddl-auto: update

这基本都是spring-data-jpa相关的一些配置,特别的地方,都写上了注释。很好理解。

JPAQueryFactoryConfiguration

JPA整合QueryDsl,其实就这点代码

package io.springboot.querydsl.configuration;

import javax.persistence.EntityManager;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

@Configuration

public class JPAQueryFactoryConfiguration {

@Bean

public JPAQueryFactory jpaQueryFactory(@Autowired EntityManager entityManager) {

return new JPAQueryFactory(entityManager);

}

}

实体类

建模,这里定义了4个实体类,描述了常见的一对多,多对多关系。

BaseEntity 抽象出公共的字段

User 用户

Email 用户邮箱一对多关联

Role 角色

UserRole 用户角色多对多关联

这里省略了Getter/Setter 方法

BaseEntity

package io.springboot.querydsl.entity;

import java.io.Serializable;

import java.time.LocalDateTime;

import javax.persistence.Column;

import javax.persistence.MappedSuperclass;

@MappedSuperclass

public abstract class BaseEntity implements Serializable {

/**

*

*/

private static final long serialVersionUID = 7054150882445633369L;

// 创建时间

@Column(columnDefinition = "timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'", nullable = false)

private LocalDateTime createdDate;

// 最后修改时间

@Column(columnDefinition = "timestamp NULL DEFAULT NULL COMMENT '最后一次修改时间'")

private LocalDateTime lastModifiedDate;

}

User

package io.springboot.querydsl.entity;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Index;

import javax.persistence.Table;

@Entity

@Table(name = "user", indexes = {

@Index(name = "name", columnList = "name", unique = true),

})

@org.hibernate.annotations.Table(appliesTo = "user", comment = "用户")

public class User extends BaseEntity {

/**

*

*/

private static final long serialVersionUID = -5342379801159855228L;

@Id

@Column(columnDefinition = "INT(11) UNSIGNED COMMENT 'id'")

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Integer id;

// 昵称

@Column(columnDefinition = "VARCHAR(20) COMMENT '昵称'", nullable = false)

private String name;

// 性别

@Column(columnDefinition = "TINYINT(1) unsigned COMMENT '性别。0:女,1:男,2:未知'", nullable = false)

private Gender gender;

// 账户是否已经验证

@Column(columnDefinition = "TINYINT(1) unsigned COMMENT '账户是否已经验证'", nullable = false)

private Boolean validated;

// 性别枚举

public static enum Gender {

GIRL, BOY, UNKNOWN

}

}

Email

package io.springboot.querydsl.entity;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Index;

import javax.persistence.Table;

@Entity

@Table(name = "email", indexes = {

@Index(name = "userEmailAccount", columnList = "user_id,account", unique = true),

@Index(name = "account", columnList = "account")

})

@org.hibernate.annotations.Table(appliesTo = "email", comment = "用户邮箱")

public class Email extends BaseEntity {

/**

*

*/

private static final long serialVersionUID = -730436482990380359L;

@Id

@Column(columnDefinition = "INT(11) UNSIGNED COMMENT 'id'")

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Integer id;

// 用户id

@Column(name = "user_id", columnDefinition = "INT(11) UNSIGNED COMMENT '用户id'")

private Integer userId;

// 邮箱账户

@Column(name = "account", columnDefinition = "VARCHAR(20) COMMENT '昵称'", nullable = false)

private String account;

}

Role

package io.springboot.querydsl.entity;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Index;

import javax.persistence.Table;

@Entity

@Table(name = "role", indexes = {

@Index(name = "name", columnList = "name", unique = true),

})

@org.hibernate.annotations.Table(appliesTo = "role", comment = "角色")

public class Role extends BaseEntity {

/**

*

*/

private static final long serialVersionUID = 1749885146919803064L;

@Id

@Column(columnDefinition = "INT(11) UNSIGNED COMMENT 'id'")

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Integer id;

// 名称

@Column(columnDefinition = "VARCHAR(20) COMMENT '名称'", nullable = false)

private String name;

}

UserRole

package io.springboot.querydsl.entity;

import java.io.Serializable;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.IdClass;

import javax.persistence.Index;

import javax.persistence.Table;

@Entity

@Table(name = "user_role", indexes = {

@Index(name = "roleId", columnList = "role_id"),

})

@org.hibernate.annotations.Table(appliesTo = "user_role", comment = "用户角色关联")

@IdClass(UserRole.Id.class)

public class UserRole extends BaseEntity {

/**

*

*/

private static final long serialVersionUID = 1782979029236838525L;

@Column(name = "user_id", columnDefinition = "INT(11) UNSIGNED COMMENT '用户ID'", nullable = false)

@javax.persistence.Id

private Integer userId;

@javax.persistence.Id

@Column(name = "role_id", columnDefinition = "INT(11) UNSIGNED COMMENT '角色ID'", nullable = false)

private Integer roleId;

public static class Id implements Serializable {

private static final long serialVersionUID = 2751217704686895162L;

private Integer userId;

private Integer roleId;

public Id() {

}

public Id(Integer userId, Integer roleId) {

super();

this.userId = userId;

this.roleId = roleId;

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((roleId == null) ? 0 : roleId.hashCode());

result = prime * result + ((userId == null) ? 0 : userId.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

Id other = (Id) obj;

if (roleId == null) {

if (other.roleId != null)

return false;

} else if (!roleId.equals(other.roleId))

return false;

if (userId == null) {

if (other.userId != null)

return false;

} else if (!userId.equals(other.userId))

return false;

return true;

}

}

}

Repository

BaseRepository

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean

public interface BaseRepository extends JpaRepository, JpaSpecificationExecutor, QuerydslPredicateExecutor {

}

其他的几个Repository

package io.springboot.querydsl.repository;

import io.springboot.querydsl.entity.User;

public interface UserRepository extends BaseRepository{

}

package io.springboot.querydsl.repository;

import io.springboot.querydsl.entity.Email;

public interface EmailRepository extends BaseRepository {

}

package io.springboot.querydsl.repository;

import io.springboot.querydsl.entity.Role;

public interface RoleRepository extends BaseRepository {

}

package io.springboot.querydsl.repository;

import io.springboot.querydsl.entity.UserRole;

public interface UserRoleRepository extends BaseRepository {

}

Service

BaseService

package io.springboot.querydsl.service;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import org.springframework.data.querydsl.QuerydslPredicateExecutor;

public interface BaseService extends JpaRepository, JpaSpecificationExecutor , QuerydslPredicateExecutor {

}

AbstractService

package io.springboot.querydsl.service;

import java.util.List;

import java.util.Optional;

import java.util.function.Function;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.Example;

import org.springframework.data.domain.Page;

import org.springframework.data.domain.Pageable;

import org.springframework.data.domain.Sort;

import org.springframework.data.jpa.domain.Specification;

import org.springframework.transaction.annotation.Transactional;

import com.querydsl.core.types.OrderSpecifier;

import com.querydsl.core.types.Predicate;

import com.querydsl.jpa.impl.JPAQueryFactory;

import io.springboot.querydsl.repository.BaseRepository;

public class AbstractService implements BaseService {

@Autowired

protected BaseRepository baseRepository;

@Autowired

protected JPAQueryFactory jpaQueryFactory;

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll() {

return this.baseRepository.findAll();

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll(Sort sort) {

return this.baseRepository.findAll(sort);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAllById(Iterable ids) {

return this.baseRepository.findAllById(ids);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public List saveAll(Iterable entities) {

return this.baseRepository.saveAll(entities);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void flush() {

this.baseRepository.flush();

}

@Transactional(rollbackFor = Throwable.class)

public S saveAndFlush(S entity) {

return this.baseRepository.saveAndFlush(entity);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void deleteInBatch(Iterable entities) {

this.baseRepository.deleteInBatch(entities);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void deleteAllInBatch() {

this.baseRepository.deleteAllInBatch();

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public T getOne(ID id) {

return this.baseRepository.getOne(id);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll(Example example) {

return this.baseRepository.findAll(example);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll(Example example, Sort sort) {

return this.baseRepository.findAll(example, sort);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Page findAll(Pageable pageable) {

return this.baseRepository.findAll(pageable);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public S save(S entity) {

return this.baseRepository.save(entity);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Optional findById(ID id) {

return this.baseRepository.findById(id);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public boolean existsById(ID id) {

return this.baseRepository.existsById(id);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public long count() {

return this.baseRepository.count();

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void deleteById(ID id) {

this.baseRepository.deleteById(id);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void delete(T entity) {

this.baseRepository.delete(entity);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void deleteAll(Iterable entities) {

this.baseRepository.deleteAll(entities);

}

@Override

@Transactional(rollbackFor = Throwable.class)

public void deleteAll() {

this.baseRepository.deleteAll();

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Optional findOne(Example example) {

return this.baseRepository.findOne(example);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Page findAll(Example example, Pageable pageable) {

return this.baseRepository.findAll(example, pageable);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public long count(Example example) {

return this.baseRepository.count(example);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public boolean exists(Example example) {

return this.baseRepository.exists(example);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Optional findOne(Specification spec) {

return this.baseRepository.findOne(spec);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll(Specification spec) {

return this.baseRepository.findAll(spec);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Page findAll(Specification spec, Pageable pageable) {

return this.baseRepository.findAll(spec, pageable);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public List findAll(Specification spec, Sort sort) {

return this.baseRepository.findAll(spec, sort);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public long count(Specification spec) {

return this.baseRepository.count(spec);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Optional findOne(Predicate predicate) {

return this.baseRepository.findOne(predicate);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Iterable findAll(Predicate predicate) {

return this.baseRepository.findAll(predicate);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Iterable findAll(Predicate predicate, Sort sort) {

return this.baseRepository.findAll(predicate, sort);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Iterable findAll(Predicate predicate, OrderSpecifier... orders) {

return this.baseRepository.findAll(predicate, orders);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Iterable findAll(OrderSpecifier... orders) {

return this.baseRepository.findAll(orders);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public Page findAll(Predicate predicate, Pageable pageable) {

return this.baseRepository.findAll(predicate, pageable);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public long count(Predicate predicate) {

return this.baseRepository.count(predicate);

}

@Override

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public boolean exists(Predicate predicate) {

return this.baseRepository.exists(predicate);

}

// 自定义的2个方法,用于在事务中获取到JPAQueryFactory,执行自定义的查询逻辑

@Transactional(rollbackFor = Throwable.class)

public R apply(Function function) {

return function.apply(this.jpaQueryFactory);

}

@Transactional(readOnly = true, rollbackFor = Throwable.class)

public R applyReadOnly(Function function) {

return function.apply(this.jpaQueryFactory);

}

}

其他的几个Service

package io.springboot.querydsl.service;

import org.springframework.stereotype.Service;

import io.springboot.querydsl.entity.User;

@Service

public class UserService extends AbstractService {

}

package io.springboot.querydsl.service;

import org.springframework.stereotype.Service;

import io.springboot.querydsl.entity.Email;

@Service

public class EmailService extends AbstractService {

}

package io.springboot.querydsl.service;

import org.springframework.stereotype.Service;

import io.springboot.querydsl.entity.Role;

@Service

public class RoleService extends AbstractService{

}

package io.springboot.querydsl.service;

import org.springframework.stereotype.Service;

import io.springboot.querydsl.entity.UserRole;

@Service

public class UserRoleService extends AbstractService {

}

Application启动类

package io.springboot.querydsl;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.autoconfigure.domain.EntityScan;

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication

@EnableJpaRepositories(basePackages = { "io.springboot.querydsl.repository" })

@EntityScan(basePackages = { "io.springboot.querydsl.entity" })

public class QueryDslAppliccation {

public static void main(String[] args) {

SpringApplication.run(QueryDslAppliccation.class, args);

}

}

Maven打包

执行MAVEN打包,插件会在指定目录下生成查询对象,以Q开头。

启动工程自动创建数据表

如果启动过程没有异常的话,就会根据实体类在数据库自动的创建数据表(因为配置了自动建表策略)。

CREATE TABLE `email` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',

`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`last_modified_date` timestamp NULL DEFAULT NULL COMMENT '最后一次修改时间',

`account` varchar(20) NOT NULL COMMENT '昵称',

`user_id` int(11) unsigned DEFAULT NULL COMMENT '用户id',

PRIMARY KEY (`id`),

UNIQUE KEY `userEmailAccount` (`user_id`,`account`),

KEY `account` (`account`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户邮箱';

CREATE TABLE `role` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',

`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`last_modified_date` timestamp NULL DEFAULT NULL COMMENT '最后一次修改时间',

`name` varchar(20) NOT NULL COMMENT '名称',

PRIMARY KEY (`id`),

UNIQUE KEY `name` (`name`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色';

CREATE TABLE `user` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',

`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`last_modified_date` timestamp NULL DEFAULT NULL COMMENT '最后一次修改时间',

`gender` tinyint(1) unsigned NOT NULL COMMENT '性别。0:女,1:男,2:未知',

`name` varchar(20) NOT NULL COMMENT '昵称',

`validated` tinyint(1) unsigned NOT NULL COMMENT '账户是否已经验证',

PRIMARY KEY (`id`),

UNIQUE KEY `name` (`name`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户';

CREATE TABLE `user_role` (

`role_id` int(11) unsigned NOT NULL COMMENT '角色ID',

`user_id` int(11) unsigned NOT NULL COMMENT '用户ID',

`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`last_modified_date` timestamp NULL DEFAULT NULL COMMENT '最后一次修改时间',

PRIMARY KEY (`role_id`,`user_id`),

KEY `roleId` (`role_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联';

本篇总结

这里这里大篇幅的东西 都是 spring-data-jpa相关的,QueryDsl的整合其实就几个东西

导入相关依赖

把JPAQueryFactory添加倒IOC(学QueryDsl,本质上就是学怎么用它)

完整的工程源码

https://github.com/springboot-community/springboot-querydsl