Merge pull request #9034 from kkaravitis/master

[BAEL-3936] Constructing a JPA query between unrelated entities
This commit is contained in:
Greg 2020-04-28 14:25:46 -04:00 committed by GitHub
commit 09e9f0ce0b
6 changed files with 454 additions and 4 deletions

View File

@ -48,6 +48,17 @@
<version>${postgres.version}</version> <version>${postgres.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.assertj</groupId> <groupId>org.assertj</groupId>
@ -101,16 +112,33 @@
<configuration> <configuration>
<sources> <sources>
<source>target/metamodel</source> <source>target/metamodel</source>
<source>${project.build.directory}/generated-sources/java/</source>
</sources> </sources>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<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> </plugins>
</build> </build>
<properties> <properties>
<hibernate.version>5.4.0.Final</hibernate.version> <hibernate.version>5.4.14.Final</hibernate.version>
<eclipselink.version>2.7.4</eclipselink.version> <eclipselink.version>2.7.4</eclipselink.version>
<postgres.version>42.2.5</postgres.version> <postgres.version>42.2.5</postgres.version>
<javax.persistence-api.version>2.2</javax.persistence-api.version> <javax.persistence-api.version>2.2</javax.persistence-api.version>
@ -118,6 +146,7 @@
<maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version> <maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
<maven-processor-plugin.version>3.3.3</maven-processor-plugin.version> <maven-processor-plugin.version>3.3.3</maven-processor-plugin.version>
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version> <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
<querydsl.version>4.3.1</querydsl.version>
</properties> </properties>
</project> </project>

View File

@ -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<MultipleRecipe> 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<MultipleRecipe> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -163,4 +163,25 @@
</properties> </properties>
</persistence-unit> </persistence-unit>
<persistence-unit name="jpa-h2-unrelated-entities">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.baeldung.jpa.unrelated.entities.Cocktail</class>
<class>com.baeldung.jpa.unrelated.entities.Recipe</class>
<class>com.baeldung.jpa.unrelated.entities.MultipleRecipe</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver"
value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:h2:mem:test" />
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="show_sql" value="true" />
<property name="hibernate.temp.use_jdbc_metadata_defaults"
value="false" />
</properties>
</persistence-unit>
</persistence> </persistence>

View File

@ -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<Cocktail>(entityManager).from(QCocktail.cocktail)
.join(QCocktail.cocktail.recipe)
.fetchOne();
verifyResult(mojito, cocktail);
cocktail = new JPAQuery<Cocktail>(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<Cocktail>(entityManager).from(QCocktail.cocktail)
.leftJoin(QCocktail.cocktail.recipe, recipe)
.where(recipe.isNull())
.fetchOne();
verifyResult(ginTonic, cocktail);
cocktail = new JPAQuery<Cocktail>(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<Cocktail>(entityManager).from(QCocktail.cocktail)
.join(QCocktail.cocktail.recipeList)
.fetchOne();
verifyResult(mojito, cocktail);
cocktail = new JPAQuery<Cocktail>(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<Cocktail>(entityManager).from(QCocktail.cocktail)
.leftJoin(QCocktail.cocktail.recipeList, multipleRecipe)
.where(multipleRecipe.isNull())
.fetchOne();
verifyResult(ginTonic, cocktail);
cocktail = new JPAQuery<Cocktail>(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<List<MultipleRecipe>> verifyResult = recipes -> {
assertEquals(2, recipes.size());
recipes.forEach(r -> assertEquals(mojito.getName(), r.getCocktail()));
};
// JPQL
List<MultipleRecipe> 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<MultipleRecipe>(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);
}
}