diff --git a/persistence-modules/java-jpa-2/pom.xml b/persistence-modules/java-jpa-2/pom.xml
index 76b1033dac..ab5bb39dfc 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,16 +112,33 @@
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
+
- 5.4.0.Final
+ 5.4.14.Final
2.7.4
42.2.5
2.2
@@ -118,6 +146,7 @@
3.5.1
3.3.3
3.0.0
+ 4.3.1
-
\ 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..96310c1cc5
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Cocktail.java
@@ -0,0 +1,90 @@
+package com.baeldung.jpa.unrelated.entities;
+
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
+import org.hibernate.annotations.NotFound;
+import org.hibernate.annotations.NotFoundAction;
+
+import javax.persistence.*;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "menu")
+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(fetch = FetchType.LAZY)
+ @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..8664d6fd7f
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/MultipleRecipe.java
@@ -0,0 +1,71 @@
+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.id = id;
+ this.cocktail = cocktail;
+ this.instructions = instructions;
+ this.baseIngredient = baseIngredient;
+ }
+
+ 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..4b3d200b60
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/main/java/com/baeldung/jpa/unrelated/entities/Recipe.java
@@ -0,0 +1,50 @@
+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..044e59b16e
--- /dev/null
+++ b/persistence-modules/java-jpa-2/src/test/java/com/baeldung/jpa/unrelated/entities/UnrelatedEntitiesUnitTest.java
@@ -0,0 +1,189 @@
+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 givenCocktailsWithRecipe_whenQuerying_thenTheExpectedCocktailsReturned() {
+ // 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 givenCocktailsWithoutRecipe_whenQuerying_thenTheExpectedCocktailsReturned() {
+ 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 givenCocktailsWithMultipleRecipes_whenQuerying_thenTheExpectedCocktailsReturned() {
+ // 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 givenCocktailsWithoutMultipleRecipes_whenQuerying_thenTheExpectedCocktailsReturned() {
+ // 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 givenMultipleRecipesWithCocktails_whenQuerying_thenTheExpectedMultipleRecipesReturned() {
+ 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.baseIngredient = c.category",
+ MultipleRecipe.class).getResultList();
+
+ verifyResult.accept(recipes);
+
+ // QueryDSL
+ QCocktail cocktail = QCocktail.cocktail;
+ QMultipleRecipe multipleRecipe = QMultipleRecipe.multipleRecipe;
+ recipes = new JPAQuery(entityManager).from(multipleRecipe)
+ .join(cocktail)
+ .on(multipleRecipe.baseIngredient.eq(cocktail.category))
+ .fetch();
+
+ verifyResult.accept(recipes);
+ }
+
+ private void verifyResult(Cocktail expectedCocktail, Cocktail queryResult) {
+ assertNotNull(queryResult);
+ assertEquals(expectedCocktail, queryResult);
+ }
+}