Bi-directional OneToMany to child with SINGLE_TABLE interitance - the parent modification cause SQL insert instead of update

Hello

Looks like there is a regression in Hibernate 6 compared to Hibernate 5 where I don’t see such issue.

In bi-directional OneToMany association to child entity with SINGLE_TABLE interitance the parent modification trigger SQL insert insetad of SQL update. So one more entity

Let’s say we have the parent entity called Whiteboard

@Entity
public class Whiteboard {

	@Id
	@Getter
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	@Getter
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "whiteboard", cascade = CascadeType.ALL, orphanRemoval = true)
	private final List<Circle> circles = new ArrayList<>();

	@Getter
	@Setter
	@Column
	private String name;


	public void addCircle(Circle circle) {
		circle.setWhiteboard(this);
		circles.add(circle);
	}
}

and child entity

@Entity
@DiscriminatorValue(ShapeType.ORDINAL_CIRCLE)
public class Circle extends Shape {

	@Getter
	@Setter
	@ManyToOne(fetch = FetchType.LAZY)
	private Whiteboard whiteboard;
}

inherited from

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "shapeType", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Shape {

	@Id
	@Getter
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	@Column(insertable = false, updatable = false)
	@Enumerated(EnumType.ORDINAL)
	private ShapeType shapeType;
}

And repository

@Repository
public interface WhiteboardRepo extends JpaRepository<Whiteboard, Long> {

	List<Whiteboard> findAllByName(String name);
}

And finally ShapeType enum

public enum ShapeType {
	CIRCLE,
	SQUARE;

	public static final String ORDINAL_CIRCLE = "0";
	public static final String ORDINAL_SQUARE = "1";
}

The next test will fail. Both assertThat() will fail, you can reorder them to see it.

@SpringBootTest
class DemoApplicationTests {

	@Autowired private WhiteboardRepo whiteboardRepo;

	@BeforeEach
	void init() {

		Whiteboard wb = new Whiteboard();
		wb.setName("my-board");
		whiteboardRepo.save(wb);
	}

	@Test
	void testNoInsertWhenUpdateIsExpected() {

		Whiteboard wb = whiteboardRepo.findAllByName("my-board").stream().findAny().orElseThrow();
		long whiteboardId = wb.getId();

		wb.setName("new-board");
		whiteboardRepo.save(wb);

		assertThat(whiteboardRepo.findAllByName("my-board")).isEmpty();
		assertThat(whiteboardRepo.findById(whiteboardId).orElseThrow().getName()).isEqualTo("new-board");
	}

}

The problem is that Hibernate will insert new Whiteboard entity to database instead of updating existing one. The reason is because on repository save() method Hibernate will check if such entry already exists in database. For that Hibernate generates the next query:

    select
        wb.id,
        wb.name, 
        shp.id,
        shp.shape_type,
        shp.whiteboard_id
    from
        whiteboard wb 
    left join
        shape shp on wb.id=shp.whiteboard_id 
    where
        shp.shape_type=0 
        and wb.id=?

The query uses LEFT JOIN, but because of WHERE shp.shape_type=0 the query in becomes effectively an INNER JOIN and returns no rows when Whiteboard exists, but has no related circles (the shape table is empty).

I use org.hibernate.orm:hibernate-core:jar:6.1.5.Final in my Spring Boot 3 project (parent POM is org.springframework.boot:spring-boot-starter:jar:3.0.0). If needed I can provide corresponding demo project, but cannot upload ZIP here.

Thanks for help and if this really a bug, would be nice to fix in the future releases of Hibernate.

I think this is the same issue as [HHH-15902] - Hibernate JIRA
Please watch that issue for updates as we will fix it as part of that.