I have created CmsNav entities, that are translateable, and also sit in some sort of hierarchical structure. Upon deleting a CmsNav by id, i get the following error, and i cannot seem to figure out whats going on. Perhaps someone can give some insight. thanks!
Future{cause=jakarta.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bearden.cms.model.ui.CmsNavLang#10]}
CmsNav
@Entity
@Table(name = "cms_nav")
public class CmsNav extends Hierarchical {
/* FIELDS */
private Long id;
private String link;
private String target; // _blank _self ...
private String fontawesomeIcon; // icon
private boolean prioritizeSubmenu; // in case of children, whether it will open the submenu, or function as a direct link itself.
private CmsNavType cmsNavType;
/* CONSTRUCTOR */
public CmsNav() {
}
/* METHODS */
/**
* Sets the language for this translated class
*/
@Transient
public void setLang(Lang lang) {
try {
super.setLang(lang, CmsNavLang.class, this);
} catch (TranslationNotFoundException e) {
e.printStackTrace();
}
}
public static CmsNav init(String link, String target, String fontawesomeIcon, boolean prioritizeSubmenu, CmsNavType cmsNavType, String label, Lang lang) {
CmsNav cmsNav = new CmsNav();
cmsNav.setLink(link);
cmsNav.setTarget(target);
cmsNav.setFontawesomeIcon(fontawesomeIcon);
cmsNav.setPrioritizeSubmenu(prioritizeSubmenu);
cmsNav.setCmsNavType(cmsNavType);
CmsNavLang cmsNavLang = new CmsNavLang();
cmsNavLang.setLabel(label);
cmsNavLang.setTranslated(cmsNav);
cmsNavLang.setLang(lang);
cmsNav.addTranslation(cmsNavLang);
return cmsNav;
}
@Override
public Translation getUntranslated(Lang lang) {
CmsNavLang cmsNavLang = new CmsNavLang();
cmsNavLang.setId(-1L); // signals non-db entity
cmsNavLang.setLabel(ConfigHolder.project_config.getJsonObject("framework").getString("missing_translation_text"));
cmsNavLang.setLang(lang);
return cmsNavLang;
}
/* GETTERS AND SETTERS */
@OneToMany(mappedBy = "translated", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = CmsNavLang.class)
public List<Translation> getTranslations() {
return super.getTranslations();
}
public void setTranslations(List<Translation> translations) {
super.setTranslations(translations);
}
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cms_nav_seq")
@SequenceGenerator(name = "cms_nav_seq", sequenceName = "cms_nav_seq", allocationSize = 1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(length = 255)
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
@Column(length = 7)
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
@Column(name = "prioritize_submenu")
public boolean isPrioritizeSubmenu() {
return prioritizeSubmenu;
}
public void setPrioritizeSubmenu(boolean prioritizeSubmenu) {
this.prioritizeSubmenu = prioritizeSubmenu;
}
@Column(name = "fontawesome_icon", length = 30)
public String getFontawesomeIcon() {
return fontawesomeIcon;
}
public void setFontawesomeIcon(String fontawesomeIcon) {
this.fontawesomeIcon = fontawesomeIcon;
}
@Enumerated(EnumType.STRING)
@Column(length = 10, name = "cms_nav_type")
public CmsNavType getCmsNavType() {
return cmsNavType;
}
public void setCmsNavType(CmsNavType cmsNavType) {
this.cmsNavType = cmsNavType;
}
@Transient
public String getLabel() {
return ((CmsNavLang) super.getActiveTranslation()).getLabel();
}
public void setLabel(String label) {
((CmsNavLang)super.getActiveTranslation()).setLabel(label);
}
}
Hierarchical
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Hierarchical extends Translated {
/* FIELDS */
private Long id;
private List<Hierarchical> children = new ArrayList<>(); // hierarchical children
private Hierarchical parent; // (unless root node), this is the parent of this hierarchical
/* CONSTRUCTOR */
public Hierarchical(){
}
@Override
public abstract Translation getUntranslated(Lang lang);
/* METHODS */
@Transient
public void addChild(Hierarchical hierarchical){
this.children.add(hierarchical);
}
@Transient
public boolean hasChildren(){
return !this.children.isEmpty();
}
/* GETTERS AND SETTERS */
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
public List<Hierarchical> getChildren() {
return children;
}
public void setChildren(List<Hierarchical> children) {
this.children = children;
}
@ManyToOne()
@JoinColumn(name = "parent_id", nullable = true)
public Hierarchical getParent() {
return parent;
}
public void setParent(Hierarchical parent) {
this.parent = parent;
}
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hierarchical_seq")
@SequenceGenerator(schema = "public", name = "hierarchical_seq", sequenceName = "hierarchical_seq", allocationSize = 1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Translated
@MappedSuperclass
public abstract class Translated {
/* FIELDS */
private List<Translation> translations = new ArrayList<>();
private Translation activeTranslation;
/* CONSTRUCTOR */
public Translated() {
}
/* METHODS */
/**
* This abstract method enforces translated classes to provide a default translation object which is going to be
* fallen back to in case the translated does not have a translation record present in the db for a certain language.
*/
public abstract Translation getUntranslated(Lang lang);
/**
* Sets the active lang based on the provided lang object
*/
@Transient
public <T extends Translation> void setLang(Lang lang, Class<T> clazz, Translated self) throws TranslationNotFoundException {
Optional<T> activeTranslation = this.translations.stream()
.filter(translation -> clazz.isInstance(translation) && translation.getLang().equals(lang))
.map(clazz::cast)
.findFirst();
if(activeTranslation.isPresent()){
this.activeTranslation = activeTranslation.get();
}
else{
Translation untranslated_translation = self.getUntranslated(lang);
if(untranslated_translation != null){
this.activeTranslation = untranslated_translation;
this.translations.add(untranslated_translation);
untranslated_translation.setTranslated(self);
}else{
throw new TranslationNotFoundException("No translation could be found for entity: ".concat(clazz.getName()));
}
}
}
/**
* Adds a translation to this translated entity
*/
@Transient
public void addTranslation(Translation translation) {
if(translation.getLang() != null){ // no lang indicates new lang record
Optional<Translation> existingTranslation = this.translations.stream().filter(it -> it.getLang().getLangCode().equals(translation.getLang().getLangCode())).findFirst();
if(existingTranslation.isPresent()){
// Results in an overwrite
this.translations.remove(existingTranslation.get());
}
}
this.translations.add(translation);
}
/**
* Retrieves the current translation
*/
@Transient
public Translation getActiveTranslation() {
return this.activeTranslation;
}
/* GETTERS AND SETTERS */
@Transient
public List<Translation> getTranslations() {
return translations;
}
public void setTranslations(List<Translation> translations) {
this.translations = translations;
}
}
CmsNavLang
@Entity
@Table(name = "cms_nav_lang")
public class CmsNavLang extends Translation {
/* FIELDS */
private Long id;
private String label; // nav label
/* CONSTRUCTOR */
public CmsNavLang(){
}
/* METHODS */
/* GETTERS AND SETTERS */
@ManyToOne(fetch = FetchType.LAZY, targetEntity = CmsNav.class)
@JoinColumn(name="translated_id", nullable = false)
public Translated getTranslated() {
return super.getTranslated();
}
@Override
public void setTranslated(Translated translated) {
super.setTranslated(translated);
}
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cms_nav_lang_seq")
@SequenceGenerator(name = "cms_nav_lang_seq", sequenceName = "cms_nav_lang_seq", allocationSize = 1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(length = 255)
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
Translation
@MappedSuperclass
public abstract class Translation {
/* FIELDS */
private Lang lang;
private Translated translated; // the non-language dependant base class.
/* CONSTRUCTOR */
public Translation(){
}
public Translation(String langCode){
this.lang = Lang.createLang(langCode);
}
/* METHODS */
/* GETTERS AND SETTERS */
@Convert(converter = LangConverter.class)
@Column(length = 2)
public Lang getLang() {
return lang;
}
public void setLang(Lang lang) {
this.lang = lang;
}
@Transient
public Translated getTranslated() {
return translated;
}
public void setTranslated(Translated translated) {
this.translated = translated;
}
}
Delete controller/repository
public void deleteNav(RoutingContext routingContext) {
PostBody postBody = PostBody.postBody(routingContext, true, false)
.withLongPath("navId", true)
.build();
if(!postBody.hasErrors()){
navRepository.deleteCmsNavById((postBody.getLong("navId"))).onComplete(deleteResult -> {
if (deleteResult.succeeded() && deleteResult.result()) {
ResponseUtil.ok(routingContext, "");
}else if(deleteResult.failed()){
ResponseUtil.internal_server_error(routingContext, deleteResult.cause().getMessage());
}
});
}
}
public Future<Boolean> deleteCmsNavById(Long id) {
CompletionStage<Boolean> deletionStage = super.getSessionFactory().withTransaction((session, transaction) -> {
return session.find(CmsNav.class, id).thenCompose(cmsNav -> {
if (cmsNav != null) {
return session.remove(cmsNav).thenApply(v -> true);
} else {
return CompletableFuture.completedFuture(false);
}
});
});
return Future.fromCompletionStage(deletionStage);
}