[HHH-17294] Test verifying issue where Entity is not marked as dirty if one of its JSON children is modified
This commit is contained in:
parent
d6e695055e
commit
2e0f0c5b58
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.embeddable;
|
||||
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Temporal;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||
import org.hibernate.testing.orm.domain.gambit.MutableValue;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.sql.Clob;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Access( AccessType.PROPERTY )
|
||||
public class Aggregate {
|
||||
|
||||
private Boolean theBoolean = false;
|
||||
private Boolean theNumericBoolean = false;
|
||||
private Boolean theStringBoolean = false;
|
||||
private String theString;
|
||||
private Integer theInteger;
|
||||
private int theInt;
|
||||
private double theDouble;
|
||||
private URL theUrl;
|
||||
private Clob theClob;
|
||||
private byte[] theBinary;
|
||||
private Date theDate;
|
||||
private Date theTime;
|
||||
private Date theTimestamp;
|
||||
private Instant theInstant;
|
||||
private UUID theUuid;
|
||||
private EntityOfBasics.Gender gender;
|
||||
private EntityOfBasics.Gender convertedGender;
|
||||
private EntityOfBasics.Gender ordinalGender;
|
||||
private Duration theDuration;
|
||||
|
||||
private LocalDateTime theLocalDateTime;
|
||||
private LocalDate theLocalDate;
|
||||
private LocalTime theLocalTime;
|
||||
private ZonedDateTime theZonedDateTime;
|
||||
private OffsetDateTime theOffsetDateTime;
|
||||
|
||||
private MutableValue mutableValue;
|
||||
|
||||
public Aggregate() {
|
||||
}
|
||||
|
||||
public String getTheString() {
|
||||
return theString;
|
||||
}
|
||||
|
||||
public void setTheString(String theString) {
|
||||
this.theString = theString;
|
||||
}
|
||||
|
||||
public Integer getTheInteger() {
|
||||
return theInteger;
|
||||
}
|
||||
|
||||
public void setTheInteger(Integer theInteger) {
|
||||
this.theInteger = theInteger;
|
||||
}
|
||||
|
||||
public int getTheInt() {
|
||||
return theInt;
|
||||
}
|
||||
|
||||
public void setTheInt(int theInt) {
|
||||
this.theInt = theInt;
|
||||
}
|
||||
|
||||
public double getTheDouble() {
|
||||
return theDouble;
|
||||
}
|
||||
|
||||
public void setTheDouble(double theDouble) {
|
||||
this.theDouble = theDouble;
|
||||
}
|
||||
|
||||
public URL getTheUrl() {
|
||||
return theUrl;
|
||||
}
|
||||
|
||||
public void setTheUrl(URL theUrl) {
|
||||
this.theUrl = theUrl;
|
||||
}
|
||||
|
||||
public Clob getTheClob() {
|
||||
return theClob;
|
||||
}
|
||||
|
||||
public void setTheClob(Clob theClob) {
|
||||
this.theClob = theClob;
|
||||
}
|
||||
|
||||
public byte[] getTheBinary() {
|
||||
return theBinary;
|
||||
}
|
||||
|
||||
public void setTheBinary(byte[] theBinary) {
|
||||
this.theBinary = theBinary;
|
||||
}
|
||||
|
||||
@Enumerated( EnumType.STRING )
|
||||
public EntityOfBasics.Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(EntityOfBasics.Gender gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
@Convert( converter = EntityOfBasics.GenderConverter.class )
|
||||
@Column(name = "converted_gender", length = 1)
|
||||
@JdbcTypeCode( Types.CHAR )
|
||||
public EntityOfBasics.Gender getConvertedGender() {
|
||||
return convertedGender;
|
||||
}
|
||||
|
||||
public void setConvertedGender(EntityOfBasics.Gender convertedGender) {
|
||||
this.convertedGender = convertedGender;
|
||||
}
|
||||
|
||||
@Column(name = "ordinal_gender")
|
||||
public EntityOfBasics.Gender getOrdinalGender() {
|
||||
return ordinalGender;
|
||||
}
|
||||
|
||||
public void setOrdinalGender(EntityOfBasics.Gender ordinalGender) {
|
||||
this.ordinalGender = ordinalGender;
|
||||
}
|
||||
|
||||
@Temporal( TemporalType.DATE )
|
||||
public Date getTheDate() {
|
||||
return theDate;
|
||||
}
|
||||
|
||||
public void setTheDate(Date theDate) {
|
||||
this.theDate = theDate;
|
||||
}
|
||||
|
||||
@Temporal( TemporalType.TIME )
|
||||
public Date getTheTime() {
|
||||
return theTime;
|
||||
}
|
||||
|
||||
public void setTheTime(Date theTime) {
|
||||
this.theTime = theTime;
|
||||
}
|
||||
|
||||
@Temporal( TemporalType.TIMESTAMP )
|
||||
public Date getTheTimestamp() {
|
||||
return theTimestamp;
|
||||
}
|
||||
|
||||
public void setTheTimestamp(Date theTimestamp) {
|
||||
this.theTimestamp = theTimestamp;
|
||||
}
|
||||
|
||||
@Temporal( TemporalType.TIMESTAMP )
|
||||
public Instant getTheInstant() {
|
||||
return theInstant;
|
||||
}
|
||||
|
||||
public void setTheInstant(Instant theInstant) {
|
||||
this.theInstant = theInstant;
|
||||
}
|
||||
|
||||
public UUID getTheUuid() {
|
||||
return theUuid;
|
||||
}
|
||||
|
||||
public void setTheUuid(UUID theUuid) {
|
||||
this.theUuid = theUuid;
|
||||
}
|
||||
|
||||
public LocalDateTime getTheLocalDateTime() {
|
||||
return theLocalDateTime;
|
||||
}
|
||||
|
||||
public void setTheLocalDateTime(LocalDateTime theLocalDateTime) {
|
||||
this.theLocalDateTime = theLocalDateTime;
|
||||
}
|
||||
|
||||
public LocalDate getTheLocalDate() {
|
||||
return theLocalDate;
|
||||
}
|
||||
|
||||
public void setTheLocalDate(LocalDate theLocalDate) {
|
||||
this.theLocalDate = theLocalDate;
|
||||
}
|
||||
|
||||
public LocalTime getTheLocalTime() {
|
||||
return theLocalTime;
|
||||
}
|
||||
|
||||
public void setTheLocalTime(LocalTime theLocalTime) {
|
||||
this.theLocalTime = theLocalTime;
|
||||
}
|
||||
|
||||
public OffsetDateTime getTheOffsetDateTime() {
|
||||
return theOffsetDateTime;
|
||||
}
|
||||
|
||||
public void setTheOffsetDateTime(OffsetDateTime theOffsetDateTime) {
|
||||
this.theOffsetDateTime = theOffsetDateTime;
|
||||
}
|
||||
|
||||
public ZonedDateTime getTheZonedDateTime() {
|
||||
return theZonedDateTime;
|
||||
}
|
||||
|
||||
public void setTheZonedDateTime(ZonedDateTime theZonedDateTime) {
|
||||
this.theZonedDateTime = theZonedDateTime;
|
||||
}
|
||||
|
||||
public Duration getTheDuration() {
|
||||
return theDuration;
|
||||
}
|
||||
|
||||
public void setTheDuration(Duration theDuration) {
|
||||
this.theDuration = theDuration;
|
||||
}
|
||||
|
||||
public Boolean isTheBoolean() {
|
||||
return theBoolean;
|
||||
}
|
||||
|
||||
public void setTheBoolean(Boolean theBoolean) {
|
||||
this.theBoolean = theBoolean;
|
||||
}
|
||||
|
||||
@JdbcTypeCode( Types.INTEGER )
|
||||
public Boolean isTheNumericBoolean() {
|
||||
return theNumericBoolean;
|
||||
}
|
||||
|
||||
public void setTheNumericBoolean(Boolean theNumericBoolean) {
|
||||
this.theNumericBoolean = theNumericBoolean;
|
||||
}
|
||||
|
||||
@JdbcTypeCode( Types.CHAR )
|
||||
public Boolean isTheStringBoolean() {
|
||||
return theStringBoolean;
|
||||
}
|
||||
|
||||
public void setTheStringBoolean(Boolean theStringBoolean) {
|
||||
this.theStringBoolean = theStringBoolean;
|
||||
}
|
||||
|
||||
@Convert( converter = EntityOfBasics.MutableValueConverter.class )
|
||||
public MutableValue getMutableValue() {
|
||||
return mutableValue;
|
||||
}
|
||||
|
||||
public void setMutableValue(MutableValue mutableValue) {
|
||||
this.mutableValue = mutableValue;
|
||||
}
|
||||
|
||||
static void assertEquals(Aggregate a1, Aggregate a2) {
|
||||
Assertions.assertEquals( a1.theInt, a2.theInt );
|
||||
Assertions.assertEquals( a1.theDouble, a2.theDouble );
|
||||
Assertions.assertEquals( a1.theBoolean, a2.theBoolean );
|
||||
Assertions.assertEquals( a1.theNumericBoolean, a2.theNumericBoolean );
|
||||
Assertions.assertEquals( a1.theStringBoolean, a2.theStringBoolean );
|
||||
Assertions.assertEquals( a1.theString, a2.theString );
|
||||
Assertions.assertEquals( a1.theInteger, a2.theInteger );
|
||||
Assertions.assertEquals( a1.theClob, a2.theClob );
|
||||
assertArrayEquals( a1.theBinary, a2.theBinary );
|
||||
Assertions.assertEquals( a1.theDate, a2.theDate );
|
||||
Assertions.assertEquals( a1.theTime, a2.theTime );
|
||||
Assertions.assertEquals( a1.theTimestamp, a2.theTimestamp );
|
||||
Assertions.assertEquals( a1.theInstant, a2.theInstant );
|
||||
Assertions.assertEquals( a1.theUuid, a2.theUuid );
|
||||
Assertions.assertEquals( a1.gender, a2.gender );
|
||||
Assertions.assertEquals( a1.convertedGender, a2.convertedGender );
|
||||
Assertions.assertEquals( a1.ordinalGender, a2.ordinalGender );
|
||||
Assertions.assertEquals( a1.theDuration, a2.theDuration );
|
||||
Assertions.assertEquals( a1.theLocalDateTime, a2.theLocalDateTime );
|
||||
Assertions.assertEquals( a1.theLocalDate, a2.theLocalDate );
|
||||
Assertions.assertEquals( a1.theLocalTime, a2.theLocalTime );
|
||||
if ( a1.theZonedDateTime == null ) {
|
||||
assertNull( a2.theZonedDateTime );
|
||||
}
|
||||
else {
|
||||
assertNotNull( a2.theZonedDateTime );
|
||||
Assertions.assertEquals( a1.theZonedDateTime.toInstant(), a2.theZonedDateTime.toInstant() );
|
||||
}
|
||||
if ( a1.theOffsetDateTime == null ) {
|
||||
assertNull( a2.theOffsetDateTime );
|
||||
}
|
||||
else {
|
||||
assertNotNull( a2.theOffsetDateTime );
|
||||
Assertions.assertEquals( a1.theOffsetDateTime.toInstant(), a2.theOffsetDateTime.toInstant() );
|
||||
}
|
||||
if ( a1.mutableValue == null ) {
|
||||
assertNull( a2.mutableValue );
|
||||
}
|
||||
else {
|
||||
assertNotNull( a2.mutableValue );
|
||||
Assertions.assertEquals( a1.mutableValue.getState(), a2.mutableValue.getState() );
|
||||
}
|
||||
}
|
||||
|
||||
public static Aggregate createAggregate1() {
|
||||
final Aggregate aggregate = new Aggregate();
|
||||
aggregate.theBoolean = true;
|
||||
aggregate.theNumericBoolean = true;
|
||||
aggregate.theStringBoolean = true;
|
||||
aggregate.theString = "String \"<abc>A&B</abc>\"";
|
||||
aggregate.theInteger = -1;
|
||||
aggregate.theInt = Integer.MAX_VALUE;
|
||||
aggregate.theDouble = 1.3e20;
|
||||
try {
|
||||
aggregate.theUrl = new URL( "https://hibernate.org" );
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
aggregate.theBinary = new byte[] { 1 };
|
||||
aggregate.theDate = new java.sql.Date( 2000 - 1900, 0, 1 );
|
||||
aggregate.theTime = new Time( 1, 0, 0 );
|
||||
aggregate.theTimestamp = new Timestamp( 2000 - 1900, 0, 1, 1, 0, 0, 1000 );
|
||||
aggregate.theInstant = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC );
|
||||
aggregate.theUuid = UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" );
|
||||
aggregate.gender = EntityOfBasics.Gender.FEMALE;
|
||||
aggregate.convertedGender = EntityOfBasics.Gender.MALE;
|
||||
aggregate.ordinalGender = EntityOfBasics.Gender.OTHER;
|
||||
aggregate.theDuration = Duration.ofHours( 1 );
|
||||
aggregate.theLocalDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 );
|
||||
aggregate.theLocalDate = LocalDate.of( 2000, 1, 1 );
|
||||
aggregate.theLocalTime = LocalTime.of( 1, 0, 0 );
|
||||
aggregate.theZonedDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).atZone( ZoneOffset.UTC );
|
||||
aggregate.theOffsetDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).atOffset( ZoneOffset.UTC );
|
||||
aggregate.mutableValue = new MutableValue( "some state" );
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public static Aggregate createAggregate2() {
|
||||
final Aggregate aggregate = new Aggregate();
|
||||
aggregate.theString = "String 'abc'";
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public static Aggregate createAggregate3() {
|
||||
final Aggregate aggregate = new Aggregate();
|
||||
aggregate.theString = "ABC";
|
||||
aggregate.theBinary = new byte[] { 1 };
|
||||
aggregate.theUuid = UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" );
|
||||
aggregate.theLocalDateTime = LocalDateTime.of( 2022, 12, 1, 1, 0, 0 );
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Aggregate that = (Aggregate) o;
|
||||
|
||||
if ( theInt != that.theInt ) {
|
||||
return false;
|
||||
}
|
||||
if ( Double.compare( that.theDouble, theDouble ) != 0 ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theBoolean, that.theBoolean ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theNumericBoolean, that.theNumericBoolean ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theStringBoolean, that.theStringBoolean ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theString, that.theString ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theInteger, that.theInteger ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theUrl, that.theUrl ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theClob, that.theClob ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Arrays.equals( theBinary, that.theBinary ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theDate, that.theDate ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theTime, that.theTime ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theTimestamp, that.theTimestamp ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theInstant, that.theInstant ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theUuid, that.theUuid ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( gender != that.gender ) {
|
||||
return false;
|
||||
}
|
||||
if ( convertedGender != that.convertedGender ) {
|
||||
return false;
|
||||
}
|
||||
if ( ordinalGender != that.ordinalGender ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theDuration, that.theDuration ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theLocalDateTime, that.theLocalDateTime ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theLocalDate, that.theLocalDate ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theLocalTime, that.theLocalTime ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theZonedDateTime, that.theZonedDateTime ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !Objects.equals( theOffsetDateTime, that.theOffsetDateTime ) ) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals( mutableValue, that.mutableValue );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.embeddable;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
//@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonAggregate.class)
|
||||
public class JsonAggregateTest extends BaseSessionFactoryFunctionalTest {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
JsonHolder.class
|
||||
};
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
inTransaction(
|
||||
session -> {
|
||||
session.persist( new JsonHolder( 1L, Aggregate.createAggregate2() ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
protected void cleanupTest() {
|
||||
inTransaction(
|
||||
session -> {
|
||||
session.createMutationQuery( "delete from JsonHolder h" ).executeUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey("HHH-17294")
|
||||
public void testDirtyChecking() {
|
||||
sessionFactoryScope().inTransaction(
|
||||
entityManager -> {
|
||||
JsonHolder jsonHolder = entityManager.find( JsonHolder.class, 1L );
|
||||
assertEquals("String 'abc'", jsonHolder.getAggregate().getTheString());
|
||||
jsonHolder.getAggregate().setTheString( "MyString" );
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
// Fails, when it should pass
|
||||
assertEquals( "String 'MyString'", entityManager.find( JsonHolder.class, 1L ).getAggregate().getTheString() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//tag::json-type-mapping-example[]
|
||||
@Entity(name = "JsonHolder")
|
||||
public static class JsonHolder {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
private Aggregate aggregate;
|
||||
|
||||
//end::json-type-mapping-example[]
|
||||
//Getters and setters are omitted for brevity
|
||||
|
||||
public JsonHolder() {
|
||||
}
|
||||
|
||||
public JsonHolder(Long id, Aggregate aggregate) {
|
||||
this.id = id;
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Aggregate getAggregate() {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
public void setAggregate(Aggregate aggregate) {
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
//tag::json-type-mapping-example[]
|
||||
}
|
||||
|
||||
//end::json-type-mapping-example[]
|
||||
}
|
||||
|
Loading…
Reference in New Issue