| 
 | 1 | +/*  | 
 | 2 | + * Hibernate, Relational Persistence for Idiomatic Java  | 
 | 3 | + *  | 
 | 4 | + * License: GNU Lesser General Public License (LGPL), version 2.1 or later  | 
 | 5 | + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html  | 
 | 6 | + */  | 
 | 7 | +package org.hibernate.orm.test.mapping.generated;  | 
 | 8 | + | 
 | 9 | +import java.time.ZonedDateTime;  | 
 | 10 | +import java.util.ArrayList;  | 
 | 11 | +import java.util.List;  | 
 | 12 | +import java.util.Locale;  | 
 | 13 | + | 
 | 14 | +import org.hibernate.annotations.CurrentTimestamp;  | 
 | 15 | + | 
 | 16 | +import org.hibernate.testing.jdbc.SQLStatementInspector;  | 
 | 17 | +import org.hibernate.testing.orm.junit.DomainModel;  | 
 | 18 | +import org.hibernate.testing.orm.junit.Jira;  | 
 | 19 | +import org.hibernate.testing.orm.junit.SessionFactory;  | 
 | 20 | +import org.hibernate.testing.orm.junit.SessionFactoryScope;  | 
 | 21 | +import org.junit.jupiter.api.AfterAll;  | 
 | 22 | +import org.junit.jupiter.api.BeforeAll;  | 
 | 23 | +import org.junit.jupiter.api.Test;  | 
 | 24 | + | 
 | 25 | +import jakarta.persistence.CascadeType;  | 
 | 26 | +import jakarta.persistence.Entity;  | 
 | 27 | +import jakarta.persistence.GeneratedValue;  | 
 | 28 | +import jakarta.persistence.Id;  | 
 | 29 | +import jakarta.persistence.ManyToOne;  | 
 | 30 | +import jakarta.persistence.OneToMany;  | 
 | 31 | + | 
 | 32 | +import static org.assertj.core.api.Assertions.assertThat;  | 
 | 33 | + | 
 | 34 | +/**  | 
 | 35 | + * @author Marco Belladelli  | 
 | 36 | + */  | 
 | 37 | +@DomainModel( annotatedClasses = {  | 
 | 38 | +		GeneratedNoOpUpdateTest.Pizza.class,  | 
 | 39 | +		GeneratedNoOpUpdateTest.Topping.class,  | 
 | 40 | +} )  | 
 | 41 | +@SessionFactory(useCollectingStatementInspector = true)  | 
 | 42 | +@Jira( "https://hibernate.atlassian.net/browse/HHH-18484" )  | 
 | 43 | +public class GeneratedNoOpUpdateTest {  | 
 | 44 | +	@Test  | 
 | 45 | +	public void testUpdate(SessionFactoryScope scope) {  | 
 | 46 | +		final SQLStatementInspector inspector = scope.getCollectingStatementInspector();  | 
 | 47 | +		inspector.clear();  | 
 | 48 | + | 
 | 49 | +		final ZonedDateTime updatedTime = scope.fromTransaction( session -> {  | 
 | 50 | +			final Pizza pizza = session.find( Pizza.class, 1L );  | 
 | 51 | +			final ZonedDateTime initialTime = pizza.getLastUpdated();  | 
 | 52 | +			// Create a new topping  | 
 | 53 | +			final Topping newTopping1 = new Topping();  | 
 | 54 | +			newTopping1.setName( "Cheese" );  | 
 | 55 | +			newTopping1.setPizza( pizza );  | 
 | 56 | +			// Let's mutate the existing list  | 
 | 57 | +			pizza.getToppings().add( newTopping1 );  | 
 | 58 | +			session.flush();  | 
 | 59 | +			// pizza was not dirty so no update is executed  | 
 | 60 | +			inspector.assertNoUpdate();  | 
 | 61 | +			assertThat( pizza.getLastUpdated() ).isEqualTo( initialTime );  | 
 | 62 | +			return pizza.getLastUpdated();  | 
 | 63 | +		} );  | 
 | 64 | + | 
 | 65 | +		inspector.clear();  | 
 | 66 | +		scope.inTransaction( session -> {  | 
 | 67 | +			// Now let's try adding a new topping via a new list  | 
 | 68 | +			final Pizza pizza = session.find( Pizza.class, 1L );  | 
 | 69 | +			// Create a new topping  | 
 | 70 | +			final Topping newTopping2 = new Topping();  | 
 | 71 | +			newTopping2.setName( "Mushroom" );  | 
 | 72 | +			newTopping2.setPizza( pizza );  | 
 | 73 | +			// This time, instead of mutating the existing list, we're creating a new list  | 
 | 74 | +			pizza.setToppings( List.of( pizza.getToppings().get( 0 ), newTopping2 ) );  | 
 | 75 | +			session.flush();  | 
 | 76 | +			// pizza this time was dirty, but still no update is executed because  | 
 | 77 | +			// only the unowned one-to-many association has changed  | 
 | 78 | +			inspector.assertNoUpdate();  | 
 | 79 | +			assertThat( pizza.getLastUpdated() ).isEqualTo( updatedTime );  | 
 | 80 | +		} );  | 
 | 81 | + | 
 | 82 | +		scope.inTransaction( session -> {  | 
 | 83 | +			final Pizza pizza = session.find( Pizza.class, 1L );  | 
 | 84 | +			assertThat( pizza.getToppings() ).hasSize( 3 )  | 
 | 85 | +					.extracting( Topping::getName )  | 
 | 86 | +					.containsExactlyInAnyOrder( "Pepperoni", "Cheese", "Mushroom" );  | 
 | 87 | +			// This time we mutate the pizza to trigger a real update  | 
 | 88 | +			pizza.setName( "Salamino e funghi" );  | 
 | 89 | +			session.flush();  | 
 | 90 | +			assertThat( inspector.getSqlQueries() ).anyMatch( sql -> sql.toLowerCase( Locale.ROOT ).contains( "update" ) );  | 
 | 91 | +			assertThat( pizza.getLastUpdated() ).isAfter( updatedTime );  | 
 | 92 | +		} );  | 
 | 93 | +	}  | 
 | 94 | + | 
 | 95 | +	@BeforeAll  | 
 | 96 | +	public void setUp(SessionFactoryScope scope) {  | 
 | 97 | +		scope.inTransaction( session -> {  | 
 | 98 | +			final Pizza pizza = new Pizza( 1L, "Salamino" );  | 
 | 99 | +			session.persist( pizza );  | 
 | 100 | +			final Topping topping = new Topping();  | 
 | 101 | +			topping.setName( "Pepperoni" );  | 
 | 102 | +			topping.setPizza( pizza );  | 
 | 103 | +			pizza.getToppings().add( topping );  | 
 | 104 | +		} );  | 
 | 105 | +	}  | 
 | 106 | + | 
 | 107 | +	@AfterAll  | 
 | 108 | +	public void tearDown(SessionFactoryScope scope) {  | 
 | 109 | +		scope.getSessionFactory().getSchemaManager().truncateMappedObjects();  | 
 | 110 | +	}  | 
 | 111 | + | 
 | 112 | +	@Entity( name = "Pizza" )  | 
 | 113 | +	static class Pizza {  | 
 | 114 | +		@Id  | 
 | 115 | +		private Long id;  | 
 | 116 | + | 
 | 117 | +		@OneToMany( mappedBy = "pizza", cascade = CascadeType.ALL )  | 
 | 118 | +		private List<Topping> toppings = new ArrayList<>();  | 
 | 119 | + | 
 | 120 | +		@CurrentTimestamp  | 
 | 121 | +		private ZonedDateTime lastUpdated;  | 
 | 122 | + | 
 | 123 | +		private String name;  | 
 | 124 | + | 
 | 125 | +		public Pizza() {  | 
 | 126 | +		}  | 
 | 127 | + | 
 | 128 | +		public Pizza(Long id, String name) {  | 
 | 129 | +			this.id = id;  | 
 | 130 | +			this.name = name;  | 
 | 131 | +		}  | 
 | 132 | + | 
 | 133 | +		public Long getId() {  | 
 | 134 | +			return id;  | 
 | 135 | +		}  | 
 | 136 | + | 
 | 137 | +		public List<Topping> getToppings() {  | 
 | 138 | +			return toppings;  | 
 | 139 | +		}  | 
 | 140 | + | 
 | 141 | +		public void setToppings(final List<Topping> toppings) {  | 
 | 142 | +			this.toppings = toppings;  | 
 | 143 | +		}  | 
 | 144 | + | 
 | 145 | +		public ZonedDateTime getLastUpdated() {  | 
 | 146 | +			return lastUpdated;  | 
 | 147 | +		}  | 
 | 148 | + | 
 | 149 | +		public String getName() {  | 
 | 150 | +			return name;  | 
 | 151 | +		}  | 
 | 152 | + | 
 | 153 | +		public void setName(String name) {  | 
 | 154 | +			this.name = name;  | 
 | 155 | +		}  | 
 | 156 | +	}  | 
 | 157 | + | 
 | 158 | +	@Entity( name = "Topping" )  | 
 | 159 | +	static class Topping {  | 
 | 160 | +		@Id  | 
 | 161 | +		@GeneratedValue  | 
 | 162 | +		private Long id;  | 
 | 163 | + | 
 | 164 | +		@ManyToOne  | 
 | 165 | +		private Pizza pizza;  | 
 | 166 | + | 
 | 167 | +		private String name;  | 
 | 168 | + | 
 | 169 | +		public void setName(final String name) {  | 
 | 170 | +			this.name = name;  | 
 | 171 | +		}  | 
 | 172 | + | 
 | 173 | +		public String getName() {  | 
 | 174 | +			return name;  | 
 | 175 | +		}  | 
 | 176 | + | 
 | 177 | +		public void setPizza(final Pizza pizza) {  | 
 | 178 | +			this.pizza = pizza;  | 
 | 179 | +		}  | 
 | 180 | + | 
 | 181 | +	}  | 
 | 182 | +}  | 
0 commit comments