Merge pull request #9034 from kkaravitis/master
[BAEL-3936] Constructing a JPA query between unrelated entities
This commit is contained in:
commit
09e9f0ce0b
|
@ -48,6 +48,17 @@
|
|||
<version>${postgres.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</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>
|
||||
<groupId>org.assertj</groupId>
|
||||
|
@ -101,16 +112,33 @@
|
|||
<configuration>
|
||||
<sources>
|
||||
<source>target/metamodel</source>
|
||||
<source>${project.build.directory}/generated-sources/java/</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<hibernate.version>5.4.0.Final</hibernate.version>
|
||||
<hibernate.version>5.4.14.Final</hibernate.version>
|
||||
<eclipselink.version>2.7.4</eclipselink.version>
|
||||
<postgres.version>42.2.5</postgres.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-processor-plugin.version>3.3.3</maven-processor-plugin.version>
|
||||
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
|
||||
<querydsl.version>4.3.1</querydsl.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -163,4 +163,25 @@
|
|||
</properties>
|
||||
</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>
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue