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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user