deleteById not actually deleting, while custom @Modifying @Query does

Heya, fun issue I’m trying to work out. I have an entity called TermConcept, which has a whack of child entities, and a parent entity. I have code which is attempting to delete these, along with all their related entities. If you want the gist of the issue head to the bottom, otherwise, here are my entities to start with:

TermConcept

@Entity
@Table(name = "TRM_CONCEPT"}, indexes = {})
public class TermConcept implements Serializable {
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
	private List<TermConceptParentChildLink> myChildren;

	@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild")
	private List<TermConceptParentChildLink> myParents;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
	private TermCodeSystemVersion myCodeSystem;

	@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
	private Collection<TermConceptProperty> myProperties;
 
	@OneToMany(mappedBy = "myConcept", orphanRemoval = false, fetch = FetchType.LAZY)
	private Collection<TermConceptDesignation> myDesignations;

    @Column(name = "CODEVAL", nullable = false)
    private String myCode;

	public List<TermConceptParentChildLink> getChildren() {
		if (myChildren == null) {
			myChildren = new ArrayList<>();
		}
		return myChildren;
	}

	public Collection<TermConceptDesignation> getDesignations() {
		if (myDesignations == null) {
			myDesignations = new ArrayList<>();
		}
		return myDesignations;
	}

	public Collection<TermConceptProperty> getProperties() {
		if (myProperties == null) {
			myProperties = new ArrayList<>();
		}
		return myProperties;
	}
}

TermConceptParentChildLink


@Entity
@Table(name = "TRM_CONCEPT_PC_LINK", indexes = {})
public class TermConceptParentChildLink implements Serializable {
	@Id()
	@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")
	@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID")
	@Column(name = "PID")
	private Long myId;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CHILD_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CHILD"))
	private TermConcept myChild;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CODESYSTEM_PID", nullable = false, foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_CS"))
	private TermCodeSystemVersion myCodeSystem;

	@ManyToOne(fetch = FetchType.LAZY, cascade = {})
	@JoinColumn(name = "PARENT_PID", nullable = false, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_TERM_CONCEPTPC_PARENT"))
	private TermConcept myParent;
}

TermConceptDesignation

@Entity
@Table(name = "TRM_CONCEPT_DESIG", indexes={})
public class TermConceptDesignation implements Serializable {
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTDESIG_CONCEPT"))
	private TermConcept myConcept;
}

TermConceptProperty


@Entity
@Table(name = "TRM_CONCEPT_PROPERTY", uniqueConstraints = {})
public class TermConceptProperty implements Serializable {
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
	private TermConcept myConcept;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CS_VER_PID", nullable = true, referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CSV"))
	private TermCodeSystemVersion myCodeSystemVersion;
	
}

And now I have some code in which I delete a subset of these TermConcept, along with any of their children.


@Transactional
public void deleteTermConceptsWithCode(List<String> codes) {

for (String code: codes) {
    Optional<TermConcept> opt = myTermConceptDao.findByCode(code);
    if (opt.isPresent()) {
        TermConcept concept = opt.get()
        deleteConceptAndChildren(concept);        
    }
}

private void deleteConceptAndChildren(TermConcept concept) {
    for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) {
        deleteConceptAndChildren(nextChildLink.getChild(), );
    }
   
   myConceptParentChildLinkDao.deleteByConceptPid(theConcept.getId());
   myConceptDesignationDao.deleteAll(theConcept.getDesignations()
   myConceptPropertyDao.deleteAll(theConcept.getProperties());

   //myConceptDao.deleteByPid(theConcept.getId());  <-- This actually deletes concepts!
   myConceptDao.deleteById(theConcept.getId());    // <-- this does not!
}

The behaviour i’m experiencing is that when i use myConceptDao.deleteById(), which is a standard method of spring-data-jpa, the deletes just…don’t seem to happen. If however, I use my custom deleteByPid() operation, those deletes happen no problem. Here is the definition of that dao

	@Modifying
	@Query("DELETE FROM TermConcept t WHERE t.myId = :pid")
	void deleteByPid(@Param("pid") Long theId);

Am I missing something fundamental here? Should deleteById not perform exactly the same as my custom deleteByPid method?

Apart from this being a Spring-Data related question, you have to understand that Spring-Data uses entityManager.remove(entityManager.find(EntityClass.class, id)) to implement deleteById because you could have entity listeners that wouldn’t be called if it were using a DML delete query.

How do you know that it “doesn’t happen”? Do you maybe persist the entity with the same id again in the same transaction?

Morning! I know that it doesn’t delete because I’m logging all executed SQL, and there are no matching delete statements. Also subsequent asserts in a test also show that the entity still exists. I am using Hibernate as the JPA provider, and also using spring-data. I’ll run through all my code to see if there’s anything causing a persist to happen.

Thanks!

Are you running tests? Spring-Data JPA tests don’t commit, but rollback at the end of the test method. If you don’t flush manually, you won’t see the delete statements.

oh man…that might be what it is, I will investigate. Thanks again.

Upon checking, the test code actively runs the removal inside of a transaction, manually. e.g new TransactionTemplate.execute(() -> ). Dang