diff --git a/persistence-modules/java-jpa-2/pom.xml b/persistence-modules/java-jpa-2/pom.xml
index f79f6f1633..b41cdccb07 100644
--- a/persistence-modules/java-jpa-2/pom.xml
+++ b/persistence-modules/java-jpa-2/pom.xml
@@ -48,6 +48,17 @@
${postgres.version}
runtime
+
+ com.querydsl
+ querydsl-apt
+ ${querydsl.version}
+ provided
+
+
+ com.querydsl
+ querydsl-jpa
+ ${querydsl.version}
+
org.assertj
@@ -101,11 +112,28 @@
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
+
@@ -118,6 +146,7 @@
3.5.1
3.3.3
3.0.0
+ 4.2.2
-
\ No newline at end of file
+
diff --git a/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Cocktail.java b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Cocktail.java
new file mode 100644
index 0000000000..b957ec2ed5
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Cocktail.java
@@ -0,0 +1,84 @@
+package com.baeldung.jpa.unrelated.entities;
+
+import org.hibernate.annotations.NotFound;
+import org.hibernate.annotations.NotFoundAction;
+
+import javax.persistence.*;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "cocktails")
+public class Cocktail {
+ @Id
+ @Column(name="cocktail_name")
+ private String name;
+
+ @Column
+ private double price;
+
+ @Column(name="category")
+ private String category;
+
+ @OneToOne
+ @NotFound(action = NotFoundAction.IGNORE)
+ @JoinColumn(name = "cocktail_name",
+ referencedColumnName = "cocktail",
+ insertable = false,
+ updatable = false,
+ foreignKey = @javax.persistence.ForeignKey(value= ConstraintMode.NO_CONSTRAINT))
+ private Recipe recipe;
+
+ @OneToMany
+ @NotFound(action=NotFoundAction.IGNORE)
+ @JoinColumn(name = "cocktail",
+ referencedColumnName = "cocktail_name",
+ insertable = false,
+ updatable = false,
+ foreignKey = @javax.persistence.ForeignKey(value= ConstraintMode.NO_CONSTRAINT))
+ private List recipeList;
+
+ public Cocktail() {
+ }
+
+ public Cocktail(String name, double price, String baseIngredient) {
+ this.name = name;
+ this.price = price;
+ this.category = baseIngredient;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public Recipe getRecipe() {
+ return recipe;
+ }
+
+ public List getRecipeList() {
+ return recipeList;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Cocktail cocktail = (Cocktail) o;
+ return Double.compare(cocktail.price, price) == 0 &&
+ Objects.equals(name, cocktail.name) &&
+ Objects.equals(category, cocktail.category);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, price, category);
+ }
+}
diff --git a/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/MultipleRecipe.java b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/MultipleRecipe.java
new file mode 100644
index 0000000000..e04df150f3
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/MultipleRecipe.java
@@ -0,0 +1,65 @@
+package com.baeldung.jpa.unrelated.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Objects;
+
+@Entity
+@Table(name="multiple_recipes")
+public class MultipleRecipe {
+ @Id
+ @Column(name="id")
+ private Long id;
+
+ @Column(name="cocktail")
+ private String cocktail;
+
+ @Column(name="instructions")
+ private String instructions;
+
+ @Column(name="base_ingredient")
+ private String baseIngredient;
+
+ public MultipleRecipe() {}
+
+ public MultipleRecipe(Long id, String cocktail, String instructions, String baseIngredient) {
+ this.baseIngredient = baseIngredient;
+ this.cocktail = cocktail;
+ this.id = id;
+ this.instructions = instructions;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getCocktail() {
+ return cocktail;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+
+ public String getBaseIngredient() {
+ return baseIngredient;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MultipleRecipe that = (MultipleRecipe) o;
+ return Objects.equals(id, that.id) &&
+ Objects.equals(cocktail, that.cocktail) &&
+ Objects.equals(instructions, that.instructions) &&
+ Objects.equals(baseIngredient, that.baseIngredient);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, cocktail, instructions, baseIngredient);
+ }
+}
diff --git a/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Recipe.java b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Recipe.java
new file mode 100644
index 0000000000..585c6abec3
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Recipe.java
@@ -0,0 +1,48 @@
+package com.baeldung.jpa.unrelated.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Objects;
+
+@Entity
+@Table(name="recipes")
+public class Recipe {
+ @Id
+ @Column(name = "cocktail")
+ private String cocktail;
+
+ @Column
+ private String instructions;
+
+ public Recipe() {
+ }
+
+ public Recipe(String cocktail, String instructions) {
+ this.cocktail = cocktail;
+ this.instructions = instructions;
+ }
+
+ public String getCocktail() {
+ return cocktail;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Recipe recipe = (Recipe) o;
+ return Objects.equals(cocktail, recipe.cocktail) &&
+ Objects.equals(instructions, recipe.instructions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cocktail, instructions);
+ }
+}
diff --git a/persistence-modules/java-jpa-2/src/main/resources/META-INF/persistence.xml b/persistence-modules/java-jpa-2/src/main/resources/META-INF/persistence.xml
index 11e007973f..eec7f7cf6e 100644
--- a/persistence-modules/java-jpa-2/src/main/resources/META-INF/persistence.xml
+++ b/persistence-modules/java-jpa-2/src/main/resources/META-INF/persistence.xml
@@ -162,5 +162,26 @@
value="false" />
-
-
\ No newline at end of file
+
+
+ org.hibernate.jpa.HibernatePersistenceProvider
+ com.baeldung.jpa.unrelated.entities.Cocktail
+ com.baeldung.jpa.unrelated.entities.Recipe
+ com.baeldung.jpa.unrelated.entities.MultipleRecipe
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/persistence-modules/java-jpa-2/src/test/java/com/baeldung/jpa/unrelated/entities/UnrelatedEntitiesUnitTest.java b/persistence-modules/java-jpa-2/src/test/java/com/baeldung/jpa/unrelated/entities/UnrelatedEntitiesUnitTest.java
new file mode 100644
index 0000000000..540811c409
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/test/java/com/baeldung/jpa/unrelated/entities/UnrelatedEntitiesUnitTest.java
@@ -0,0 +1,187 @@
+package com.baeldung.jpa.unrelated.entities;
+
+import javax.persistence.*;
+
+import com.querydsl.jpa.impl.JPAQuery;
+import org.junit.jupiter.api.*;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UnrelatedEntitiesUnitTest {
+ private static EntityManagerFactory entityManagerFactory;
+ private static EntityManager entityManager;
+ private static Cocktail mojito;
+ private static Cocktail ginTonic;
+
+ @BeforeAll
+ public static void setup() {
+ entityManagerFactory = Persistence.createEntityManagerFactory("jpa-h2-unrelated-entities");
+ entityManager = entityManagerFactory.createEntityManager();
+ mojito = new Cocktail("Mojito", 11, "Rum");
+ ginTonic = new Cocktail("Gin tonic", 8.50, "Gin");
+ entityManager.getTransaction().begin();
+ entityManager.persist(mojito);
+ entityManager.persist(ginTonic);
+ entityManager.persist(new Recipe(mojito.getName(), "Some instructions"));
+ entityManager.persist(new MultipleRecipe(1L, mojito.getName(), "some instructions", mojito.getCategory()));
+ entityManager.persist(new MultipleRecipe(2L, mojito.getName(), "some other instructions", mojito.getCategory()));
+ entityManager.getTransaction().commit();
+ }
+
+ @AfterAll
+ public static void closeSession() {
+ entityManager.close();
+ }
+
+ @Test
+ public void whenQueryingForCocktailThatHasRecipe_thenTheExpectedCocktailReturned() {
+ // JPA
+ Cocktail cocktail = entityManager.createQuery(
+ "select c from Cocktail c join c.recipe", Cocktail.class)
+ .getSingleResult();
+ verifyResult(mojito, cocktail);
+
+ cocktail = entityManager.createQuery(
+ "select c from Cocktail c join Recipe r on c.name = r.cocktail", Cocktail.class)
+ .getSingleResult();
+ verifyResult(mojito, cocktail);
+
+ // QueryDSL
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail)
+ .join(QCocktail.cocktail.recipe)
+ .fetchOne();
+ verifyResult(mojito, cocktail);
+
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail)
+ .join(QRecipe.recipe)
+ .on(QCocktail.cocktail.name.eq(QRecipe.recipe.cocktail))
+ .fetchOne();
+ verifyResult(mojito, cocktail);
+ }
+
+ @Test
+ public void whenQueryingForCocktailThatHasNotARecipe_thenTheExpectedCocktailReturned() {
+ Cocktail cocktail = entityManager
+ .createQuery("select c from Cocktail c left join c.recipe r " +
+ "where r is null",
+ Cocktail.class)
+ .getSingleResult();
+ verifyResult(ginTonic, cocktail);
+
+ cocktail = entityManager
+ .createQuery("select c from Cocktail c left join Recipe r " +
+ "on c.name = r.cocktail " +
+ "where r is null",
+ Cocktail.class)
+ .getSingleResult();
+ verifyResult(ginTonic, cocktail);
+
+ QRecipe recipe = new QRecipe("alias");
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail)
+ .leftJoin(QCocktail.cocktail.recipe, recipe)
+ .where(recipe.isNull()).fetchOne();
+ verifyResult(ginTonic, cocktail);
+
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail)
+ .leftJoin(QRecipe.recipe)
+ .on(QCocktail.cocktail.name.eq(QRecipe.recipe.cocktail))
+ .where(QRecipe.recipe.isNull()).fetchOne();
+ verifyResult(ginTonic, cocktail);
+ }
+
+ @Test
+ public void whenQueringForCocktailThatHasRecipes_thenTheExpectedCocktailReturned() {
+ // JPQL
+ Cocktail cocktail = entityManager
+ .createQuery("select c from Cocktail c join c.recipeList", Cocktail.class)
+ .getSingleResult();
+ verifyResult(mojito, cocktail);
+
+ cocktail = entityManager
+ .createQuery("select c from Cocktail c join MultipleRecipe mr on mr.cocktail = c.name", Cocktail.class)
+ .getSingleResult();
+ verifyResult(mojito, cocktail);
+
+ // QueryDSL
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail).join(QCocktail.cocktail.recipeList).fetchOne();
+ verifyResult(mojito, cocktail);
+
+ cocktail = new JPAQuery(entityManager).from(QCocktail.cocktail)
+ .join(QMultipleRecipe.multipleRecipe)
+ .on(QCocktail.cocktail.name.eq(QMultipleRecipe.multipleRecipe.cocktail))
+ .fetchOne();
+ verifyResult(mojito, cocktail);
+ }
+
+ @Test
+ public void whenQueryingForCocktailThatHasNotRecipes_thenTheExpectedCocktailReturned() {
+ // JPQL
+ Cocktail cocktail = entityManager
+ .createQuery("select c from Cocktail c left join c.recipeList r where r is null", Cocktail.class)
+ .getSingleResult();
+ verifyResult(ginTonic, cocktail);
+
+ cocktail = entityManager.createQuery("select c from Cocktail c left join MultipleRecipe r " +
+ "on c.name = r.cocktail where r is null", Cocktail.class)
+ .getSingleResult();
+ verifyResult(ginTonic, cocktail);
+
+ // QueryDSL
+ QMultipleRecipe multipleRecipe = new QMultipleRecipe("alias");
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail)
+ .leftJoin(QCocktail.cocktail.recipeList, multipleRecipe)
+ .where(multipleRecipe.isNull())
+ .fetchOne();
+ verifyResult(ginTonic, cocktail);
+
+ cocktail = new JPAQuery(entityManager)
+ .from(QCocktail.cocktail).leftJoin(QMultipleRecipe.multipleRecipe)
+ .on(QCocktail.cocktail.name.eq(QMultipleRecipe.multipleRecipe.cocktail))
+ .where(QMultipleRecipe.multipleRecipe.isNull()).fetchOne();
+ verifyResult(ginTonic, cocktail);
+ }
+
+ @Test
+ public void whenQueryingForRumCocktailsInMenuRecipes_thenTheExpectedRecipesReturned() {
+ Consumer> verifyResult = recipes -> {
+ assertEquals(2, recipes.size());
+ recipes.forEach(r -> assertEquals(mojito.getName(), r.getCocktail()));
+ };
+
+ // JPQL
+ List recipes = entityManager
+ .createQuery("select distinct r from MultipleRecipe r join Cocktail c " +
+ "on r.cocktail = c.name " +
+ "and " +
+ "r.baseIngredient = :category",
+ MultipleRecipe.class)
+ .setParameter("category", mojito.getCategory())
+ .getResultList();
+ verifyResult.accept(recipes);
+
+ // QueryDSL
+ QCocktail cocktail = QCocktail.cocktail;
+ QMultipleRecipe multipleRecipe = QMultipleRecipe.multipleRecipe;
+ recipes = new JPAQuery(entityManager)
+ .from(multipleRecipe)
+ .join(cocktail)
+ .on(multipleRecipe.cocktail.eq(cocktail.name)
+ .and(multipleRecipe.baseIngredient.eq(mojito.getCategory())))
+ .fetch();
+ verifyResult.accept(recipes);
+ }
+
+ private void verifyResult(Cocktail expectedCocktail, Cocktail queryResult) {
+ assertNotNull(queryResult);
+ assertEquals(expectedCocktail, queryResult);
+ }
+}