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.