Skip to content

Commit c426a8b

Browse files
committed
initial implementation
1 parent 4673009 commit c426a8b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2901
-0
lines changed

.gitignore

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
HELP.md
2+
/target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
5+
### STS ###
6+
.apt_generated
7+
.classpath
8+
.factorypath
9+
.project
10+
.settings
11+
.springBeans
12+
.sts4-cache
13+
14+
### IntelliJ IDEA ###
15+
.idea
16+
*.iws
17+
*.iml
18+
*.ipr
19+
20+
### NetBeans ###
21+
/nbproject/private/
22+
/nbbuild/
23+
/dist/
24+
/nbdist/
25+
/.nb-gradle/
26+
/build/
27+
28+
### VS Code ###
29+
.vscode/

.travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: java
2+
jdk:
3+
- openjdk8
4+
script: mvn test -f pom.xml
5+
sudo: false
6+
7+
after_success:
8+
- bash <(curl -s https://codecov.io/bash)

CHANGELOG.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- 1.0
2+
- first release

README.md

+308
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# QueryDSL EntityQL - bridge beteen JPA and Native Queries
2+
3+
[![Build Status](https://travis-ci.org/eXsio/querydsl-entityql.svg?branch=master)](https://travis-ci.org/eXsio/querydsl-entityql)
4+
[![codecov](https://codecov.io/gh/eXsio/querydsl-entityql/branch/master/graph/badge.svg)](https://codecov.io/gh/eXsio/querydsl-entityql)
5+
6+
## Overview
7+
8+
EntityQL is a tool that is able to use JPA Entity mappings and create QueryDSL-SQL meta models on the fly.
9+
Those Models can be then used to construct Native SQL Queries based on JPA mappings, using QueryDSL fluent API.
10+
11+
12+
It works with QueryDSL-SQL, not QueryDSL-JPA. I will use the term QueryDSL in the context of QueryDSL-SQL.
13+
14+
## How it works
15+
16+
There is a special method ```EntityQL::qEntity``` that uses Reflection to gather all DDL information required to construct
17+
QueryDSL metamodel and to sucessfully perform all operations supported by QueryDSL. The scan occurs once per Entity class -
18+
the Annotation metadata is cached in memory for further reuse.
19+
20+
The metamodels are also created in memory, there is no code generation during compilation or runtime. The resulting instance
21+
of ```Q``` class contains ```Maps``` containing the mappings between the Entity's field names and the corresponding
22+
QueryDSL-specific models that are used for constructing SQL Queries.
23+
24+
Once we've obtained an instance of ```Q``` class, everything down the line is just plain QueryDSL API in motion.
25+
Please see the examples section to see how easy it is in practice.
26+
27+
## Use Cases
28+
29+
There are 2 primary use cases for EntityQL:
30+
31+
1) Supplementary add-on on top of existing JPA/Hibernate usage.
32+
33+
The main strength of EntityQL is that it is capable of taking the preexisting JPA Entity mappings and construct Native Queries
34+
using QueryDSL API. JPQL and Criteria API are sufficient for most mundane data related tasks, but they fail miserably every time
35+
we need to perform some more complex SQL statemet (select from select and window functions being a good example). Using plain JPA
36+
would force us to either
37+
- use String - based Native SQL Queries
38+
- move the logic to the Database and use Stored Procedure
39+
- create inefficient workarounds in Java
40+
41+
None of the above solutions are convenient and safe. EntityQL provides a way to circumvent those kinds of issues in a
42+
modern, safe and readable way.
43+
44+
QueryDSL (when properly configured) is able to work in the scope of the same transaction as the Entity Manager, so we can
45+
even mix and match the usages within the same transactions.
46+
47+
2) Lightweight alternative to JPA/Hibernate.
48+
49+
EntityQL is a perfect fit for persistence layer for users who:
50+
- like the JPA's style of code-first database schema management
51+
- would like to retain the abstraction layer making the persistence code more portable (QueryDSL supports all major databases)
52+
- likes the easy testing in in-memory databases thanks to Hibernate's schema generation features
53+
- doesn't need/want all the fireworks offered by Hibernate (like dirty checking, auto flushing, cascades etc)
54+
- would like to have 100% control over the executed SQL statements
55+
- needs SQL features unavailable in JPA, but supported by QueryDSL (like window functions)
56+
- wants to have unbeatable persistence performance (QueryDSL is orders of magnitude faster than Hibernate as it has minimal abstractions and works directly on JDBC level)
57+
58+
EntityQL is just a translation layer between JPA mappings and QueryDSL. QueryDSL is perfectly capable to handle all DML statements.
59+
60+
## Examples
61+
62+
Using the following Entities:
63+
64+
```java
65+
66+
@Entity
67+
@Table(name = "BOOKS")
68+
public class Book {
69+
70+
@Id
71+
@Column(name = "BOOK_ID")
72+
@GeneratedValue
73+
private Long id;
74+
75+
@Column(name = "NAME", unique = true)
76+
private String name;
77+
78+
79+
@Column(name = "DESC", nullable = true, columnDefinition = "CLOB")
80+
private String desc;
81+
82+
@Column(name = "PRICE")
83+
private BigDecimal price;
84+
}
85+
86+
@Entity
87+
@Table(name = "ORDERS")
88+
public class Order implements Serializable {
89+
90+
@Id
91+
@Column(name = "ORDER_ID")
92+
@GeneratedValue
93+
private Long id;
94+
95+
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
96+
private List<OrderItem> items = new ArrayList<>();
97+
}
98+
99+
@Entity
100+
@Table(name = "ORDER_ITEMS")
101+
public class OrderItem implements Serializable {
102+
103+
@Id
104+
@Column(name = "ORDER_ITEM_ID")
105+
@GeneratedValue
106+
private Long id;
107+
108+
@ManyToOne
109+
@JoinColumn(name = "BOOK_ID", nullable = false)
110+
private Book book;
111+
112+
@ManyToOne
113+
@JoinColumn(name = "ORDER_ID", nullable = false)
114+
private Order order;
115+
116+
@Column(name = "QTY", nullable = false)
117+
private Long quantity;
118+
}
119+
120+
```
121+
122+
We can construct execute the following example SQL Queries:
123+
124+
- simple select with projection:
125+
126+
```java
127+
Q<Book> book = qEntity(Book.class);
128+
129+
List<BookDto> books = queryFactory.query()
130+
.select(
131+
constructor(
132+
BookDto,
133+
book.longNumber("id"),
134+
book.string("name"),
135+
book.string("desc"),
136+
book.decimalNumber("price")
137+
))
138+
.from(book).fetch();
139+
140+
```
141+
142+
- joins with 'on' clause:
143+
```java
144+
Q<Book> book = qEntity(Book.class);
145+
Q<Order> order = qEntity(Order.class);
146+
Q<OrderItem> orderItem = qEntity(OrderItem.class);
147+
148+
List<BookDto> books = queryFactory.query()
149+
.select(
150+
constructor(
151+
BookDto,
152+
book.longNumber("id"),
153+
book.string("name"),
154+
book.string("desc"),
155+
book.decimalNumber("price")
156+
))
157+
.from(book)
158+
.innerJoin(orderItem).on(orderItem.longNumber("book").eq(book.longNumber("id")))
159+
.innerJoin(order).on(orderItem.longNumber("order").eq(order.longNumber("id")))
160+
.where(order.longNumber("id").eq(1L))
161+
.fetch()
162+
163+
```
164+
165+
- joins with Foreign Keys:
166+
```java
167+
Q<Book> book = qEntity(Book.class);
168+
Q<Order> order = qEntity(Order.class);
169+
Q<OrderItem> orderItem = qEntity(OrderItem.class);
170+
171+
List<BookDto> books = queryFactory.query()
172+
.select(
173+
constructor(
174+
BookDto,
175+
book.longNumber("id"),
176+
book.string("name"),
177+
book.string("desc"),
178+
book.decimalNumber("price")
179+
))
180+
.from(orderItem)
181+
.innerJoin(orderItem.<Book> joinColumn("book"), book)
182+
.innerJoin(orderItem.<Order> joinColumn("order"), order)
183+
.where(order.longNumber("id").eq(2L))
184+
.fetch()
185+
```
186+
187+
- DTO Projections with simple Column list:
188+
```java
189+
Q<Book> book = qEntity(Book.class);
190+
191+
List<BookDto> books = queryFactory.query()
192+
.select(
193+
dto(BookDto, book.columns("id", "name", "desc", "price"))
194+
)
195+
.from(book)
196+
.fetch();
197+
198+
```
199+
200+
- nested Select clauses:
201+
```java
202+
Q<Book> book = qEntity(Book.class);
203+
Q<Order> order = qEntity(Order.class);
204+
Q<OrderItem> orderItem = qEntity(OrderItem.class);
205+
206+
Long count = queryFactory.select(count())
207+
.from(
208+
select(order.longNumber("user"))
209+
.from(orderItem)
210+
.innerJoin(orderItem.<Book> joinColumn("book"), book)
211+
.innerJoin(orderItem.<Order> joinColumn("order"), order)
212+
.where(book.decimalNumber("price").gt(new BigDecimal("80")))
213+
).fetchOne();
214+
215+
```
216+
217+
- the usual DML statements:
218+
219+
```java
220+
Q<Book> book = qEntity(Book.class);
221+
222+
queryFactory.insert(book)
223+
.set(book.longNumber("id"), 10L)
224+
.set(book.string("name"), "newBook")
225+
.set(book.decimalNumber("price"), BigDecimal.ONE)
226+
.execute();
227+
228+
queryFactory.update(book)
229+
.set(book.string("name"), "updatedBook")
230+
.set(book.decimalNumber("price"), BigDecimal.ONE)
231+
.where(book.longNumber("id").eq(9L))
232+
.execute();
233+
```
234+
235+
- Simplified DML statements:
236+
237+
```java
238+
Q<Book> book = qEntity(Book.class);
239+
240+
book.set(
241+
queryFactory.insert(book),
242+
"id", 11L,
243+
"name", "newBook2",
244+
"price", BigDecimal.ONE
245+
).execute();
246+
247+
248+
SQLUpdateClause update = queryFactory.update(book)
249+
.where(book.longNumber("id").eq(9L))
250+
251+
book.set(update,
252+
"name", "updatedBook",
253+
"price", BigDecimal.ONE
254+
).execute()
255+
256+
```
257+
258+
If you want to see more examples, please explore the integration test suite.
259+
260+
## Limitations and restrictions
261+
262+
All the limitations revolve around wheter we have all the data needed to construct the metamodels.
263+
Hibernate contains a log of magical features like autogeneration of table and column names, mapping columns to ```Map``` etc.
264+
To work properly, EntityQL needs to work with well-formatted and completely described Entites.
265+
266+
- Entity must have a valid ```@Table``` Annotation containing the Table name and (optionally) Schema name
267+
- Only fields containing ```@Column``` or ```@JoinColumn``` Annotations will be visible to EntityQL
268+
- When dealing with bidirectional mappings, only the sides that actually contain columns (```@JoinColumn```) will be supported,
269+
other sides will be ignored (```@OneToMany``` and the reversed ```@OneToOne```)
270+
- ```@JoinTable``` Annotation is not supported. If you want to use EntityQL with ```@ManyToMany``` mapping,
271+
you can create an ```@Immutable @Entity``` that matches the table configured in ```@JoinTable```, for example:
272+
273+
274+
```java
275+
276+
@ManyToMany
277+
@JoinTable(
278+
name = "USERS_GROUPS",
279+
joinColumns = @JoinColumn(name = "GROUP_ID"),
280+
inverseJoinColumns = @JoinColumn(name = "USER_ID")
281+
)
282+
private Set<User> users;
283+
```
284+
can be supported by:
285+
```java
286+
@Entity
287+
@Immutable
288+
@Table(name = "USERS_GROUPS")
289+
public class UserGroup implements Serializable {
290+
291+
@Id
292+
@Column(name = "GROUP_ID", nullable = false)
293+
private Long groupId;
294+
295+
@Id
296+
@Column(name = "USER_ID", nullable = false)
297+
private Long userId;
298+
}
299+
300+
```
301+
302+
## Support
303+
304+
Although this is a project I'm working on in my spare time, I try to fix any issues as soon as I can. If you nave a feature request that could prove useful I will also consider adding it in the shortest possible time.
305+
306+
## BUGS
307+
308+
If You find any bugs, feel free to submit PR or create an issue on GitHub: https://github.com/eXsio/querydsl-entityql

0 commit comments

Comments
 (0)