Querydsl 入門

蘇建誠 Connor Su 2020/10/26 10:04:52
4580

QueryDsl是一種增強JPA的開源框架,使JPA使用起來更加靈活方便,而且只要會一點SQL的寫法,基本上就可以無痛入門

而且也不需要複雜的設定就可以使用,接下來就讓我們進入QueryDsl的世界!

 

pom.xml


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web-services</artifactId>
		</dependency>
		<!-- H2資料庫依賴 -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>
		<!-- 自動生成GET/SET等...套件 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<!-- Apache 工具類別-->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

 

<!-- query dsl 核心套件 -->
		<dependency>
			<groupId>com.querydsl</groupId>
			<artifactId>querydsl-apt</artifactId>
			<scope>provided</scope>
		</dependency>

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

 

<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>

			<!-- dsl Maven Apt 自動生成Q類實體-->
			<plugin>
				<groupId>com.mysema.maven</groupId>
				<artifactId>apt-maven-plugin</artifactId>
				<version>1.1.3</version>
				<executions>
					<execution>
						<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>

 

本次使用H2之資料庫,H2是一種以純java語言所構成的資料庫,本次使用嵌入式的方式建置H2資料庫

採用啟動時便自動載入SQL建立Table方式

附件--SQL.txt (因篇幅關係  此為本文範例所使用之資料)

application.properties設定

#此為設定H2連線資訊
spring.datasource.url =jdbc:h2:mem:test
spring.datasource.username = root
spring.datasource.password = 
spring.datasource.driverClassName = org.h2.Driver

#此為設定開啟H2控制台
spring.h2.console.enabled=true

#此為設定自動更新Table 必要!  沒設定只會創立Table不會插入data
spring.jpa.hibernate.ddl-auto = update
#啟動程式後,自動到resources底下匯入Table  此處設定到/resource/db撈取
spring.datasource.schema=classpath:/db/schema.sql
#啟動程式後,自動到resources底下插入data  此處設定到/resource/db撈取
spring.datasource.data=classpath:/db/data.sql

 

在Application.java所在Package底下新增config,此處放置配置文件

此java為H2資料庫進行註冊配置,spring boot 啟動時才能辨識到H2連線

@Configuration
public class WebConfiguration {
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Bean
	ServletRegistrationBean h2servletRegistration() {
		ServletRegistrationBean servletRegistrationBean = 
				new ServletRegistrationBean(new WebServlet());
		servletRegistrationBean.addUrlMappings("/console/*");
		return servletRegistrationBean;
	}
}

 

目前到這邊已可以嘗試啟動傳案  並在瀏覽器輸入http://localhost:8080/console

便會出現H2控制台

按下Connect 即可進入Table(application.properties設定帳號為 root 無密碼)

從左側便可看到預先載入的Table已成功創立,點下Table名稱且按下RUN鍵就可以看到Data也一起新增成功了!

(如果發現Table有誤可先試試將專案Clean完再重啟專案)

 

接下來就是針對QueryDsl作配置了!

在config下新增配置文件如下

因為後續會使用@Autowired做自動注入所以需要先做好配置

@Component
public class QueryDslConfiguration implements WebMvcConfigurer{
	@Bean
    public JPAQueryFactory jpaQuery(EntityManager entityManager) {
        return new JPAQueryFactory(entityManager);
    }
}

 

以下為範例架構

 

 

Customer.java內容

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Customer {
	@Id
	private String seq;
	private String memberId;
	private String firstName;
	private String lastName;
}

Orderform.java內容

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Orderform {
	@Id
	private String seq;
	private String orderId;
	private String memberId;
	private String petId;
}

Pet.java內容

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Pet {
	@Id
	private String seq;
	private String petId;
	private String petName;
	private String species;
	private Integer age;
}

新增完Entity物件後,會發現target/generated-sources/java同時出現了Entity類別

還記得之前在pom.xml裡有一段便是生成Q類別

/**
 * QCustomer is a Querydsl query type for Customer
 */
@Generated("com.querydsl.codegen.EntitySerializer")
public class QCustomer extends EntityPathBase<Customer> {

    private static final long serialVersionUID = 1209675926L;

    public static final QCustomer customer = new QCustomer("customer");

    public final StringPath firstName = createString("firstName");

    public final StringPath lastName = createString("lastName");

    public final StringPath memberId = createString("memberId");

    public final StringPath seq = createString("seq");

    public QCustomer(String variable) {
        super(Customer.class, forVariable(variable));
    }

    public QCustomer(Path<? extends Customer> path) {
        super(path.getType(), path.getMetadata());
    }

    public QCustomer(PathMetadata metadata) {
        super(Customer.class, metadata);
    }

}

 

Dto組成

@Data
@NoArgsConstructor
@AllArgsConstructor

public class DetailDto {
	
	@JsonProperty("ORDER_ID")
	private String orderId;
	
	@JsonProperty("MEMBER_ID")
	private String memberId;
	
	@JsonProperty("PET_ID")
	private String petId;
	
	@JsonProperty("FIRST_NAME")
	private String firstName;
	
	@JsonProperty("LAST_NAME")
	private String lastName;
	
	@JsonProperty("PET_NAME")
	private String petName;
	
	@JsonProperty("SPECIES")
	private String species;
	
	@JsonProperty("AGE")
	private Integer age;
}
本範例採取簡單的網址傳值 使用GET

@RestController
@RequestMapping(value="/customer")
public class CustomerController {
	
	@Autowired
	private CustomerDao customerDao;
	
	@GetMapping(value="/getCustomer")
	public List<Customer> getCustomer(@RequestParam String firstName, @RequestParam String lastName) {
		List<Customer> list = customerDao.findCustomer(firstName, lastName);
		return list;
	}
	
	@GetMapping(value="/getOrder")
	public List<DetailDto> getOrder(@RequestParam String firstName, @RequestParam String lastName) {
		List<DetailDto> dtos = customerDao.findOrder(firstName, lastName);
		return dtos;
	}
}

 

先來看看如果要以customer的FirstName LastName 做搜尋的Dao處理方式

看到這你可能會想  為什麼不用JpaRepository  寫完方法就結束了 還要寫這麼多Code

的確簡單的查詢用JpaRepository就足夠了   可是當出現多表查詢  或是 動態查詢 JpaRepository就顯得有點吃力了

@Service
public class CustomerDao {
	
	@Autowired
	private JPAQueryFactory queryFactory;
	
	public List<Customer> findCustomer(String firstName, String lastName) {
		QCustomer qcustomer = QCustomer.customer;
		
		return queryFactory.selectFrom(qcustomer)
				.where(qcustomer.firstName.eq(firstName)
						.or(qcustomer.lastName.eq(lastName)))
				.fetch();
	}
}

http://localhost:8080/customer/getCustomer?firstName=Randy&lastName=

http://localhost:8080/customer/getCustomer?firstName=&lastName=Marsh

http://localhost:8080/customer/getCustomer?firstName=Randy&lastName=Marsh

看到這個結果,與預想的有出入,怎麼連不叫Randy Marsh也出現了,哦!原來是Dao是使用 "or",當你要改成and時就會發現

當你給了兩個參數的時候正確了,可是上面只給一個參數兩個情形又不對了

那怎麼辦呢? 拆成兩個方法,一個處理1個參數用"or"查詢 一個處理兩個參數 用"and"查詢

這時候Querydsl就派上用場啦!

 

只要將需要動態查詢的條件使用BooleanBuilder依照不同情況進行篩選,便可達到目的!

	public List<Customer> findCustomer(String firstName, String lastName) {
		QCustomer qcustomer = QCustomer.customer;
		BooleanBuilder condition = new BooleanBuilder();
		if (StringUtils.isBlank(firstName) || StringUtils.isBlank(lastName)) {
			condition.and(qcustomer.firstName.eq(firstName)
							.or(qcustomer.lastName.eq(lastName)));
		} else {
			condition.and(qcustomer.firstName.eq(firstName)
							.and(qcustomer.lastName.eq(lastName)));
		}
		
		return queryFactory.selectFrom(qcustomer)
				.where(condition)
				.fetch();
	}

http://localhost:8080/customer/getCustomer?firstName=Randy&lastName=Marsh

目前介紹的只是簡單的動態查詢,是否感覺到好用呢! ,實務上一定有碰過這種情形,如果使用本來的JpaRepository就會出現許多方法只差了某些條件卻要新增許多重複

的方法,造成重複的Code過多,不好維護也不易開發

 

接下來來看看如果多表查詢!

有一種在寫SQL的感覺吧! 一看就很明確知道資料是使用哪個Table哪個欄位資料所組成

在實務上JpaRepository的多表查詢所用的SQL相信大家應該有點心得了吧,看到眼睛都花了也看不太懂到底是哪個資料是對應哪個表

看都看老半天去了,再看看以QueryDsl的寫法高下立判!

public List<DetailDto> findOrder(String firstName, String lastName) {
		QCustomer qcustomer = QCustomer.customer;
		QPet qpet = QPet.pet;
		QOrderform qorderform = QOrderform.orderform;
		
		return queryFactory.select(
				Projections.bean(DetailDto.class, 
						qorderform.orderId,
						qorderform.memberId,
						qorderform.petId,
						qcustomer.firstName,
						qcustomer.lastName,
						qpet.petName,
						qpet.species,
						qpet.age))
				.from(qorderform)
				.leftJoin(qpet).on(qpet.petId.eq(qorderform.petId))
				.leftJoin(qcustomer).on(qcustomer.memberId.eq(qorderform.memberId))
				.where(qcustomer.firstName.eq(firstName)
						.or(qcustomer.lastName.eq(lastName)))
				.fetch();
	}

http://localhost:8080/customer/getOrder?firstName=Randy&lastName=Marsh

回傳的資料以Dto的資料組成

 

本文就到這裡結束了,雖然只有粗淺的介紹,但是應該讓大家感受到這個工具的強大了!

還有許多功能沒介紹到,如果想了解更多,可至官網看使用文件

 

參考資料:

https://openhome.cc/Gossip/Spring/H2.html

http://www.querydsl.com/static/querydsl/4.4.0/reference/html_single/

 

 

 

蘇建誠 Connor Su