I have two Classes mapping each one to an Entity in MySQL database. Whenever I try to map into to DB I got an MySQL Error
Class Owner:
#Entity
public class Owner {
#Id #GeneratedValue
private int idOwner;
public int getIdOwner() {
return idOwner;
}
public void setIdOwner(int idOwner) {
this.idOwner = idOwner;
}
}
Class Car with FK:
#Entity
public class Car {
#Id #GeneratedValue
private int idCar;
#ManyToOne
#JoinColumn( name = "idOwner")
private Owner owner;
public int getIdCar() {
return idCar;
}
public void setIdCar(int idCar) {
this.idCar = idCar;
}
public Owner getOwner() {
return owner;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
}
Code running:
EntityManagerFactory f = Persistence.createEntityManagerFactory("glorious");
EntityManager em = f.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
Car c = new Car();
Owner o = new Owner();
c.setOwner(o);
em.persist(c);
em.persist(o);
t.commit();
f.close();
em.close();
Error:
GRAVE: Unsuccessful: alter table .Car add index FK107B43F620606 (idOwner), add constraint FK107B43F620606 foreign key (idOwner) references .Owner (idOwner)
27/05/2014 20:35:01 org.hibernate.tool.hbm2ddl.SchemaExport create
GRAVE: Can't create table 'glorious.#sql-2aa_1f8' (errno: 150)
MySQL Version : 5.5.34
Engine : InnoDB
Hibernate Dialect : MySQL5InnoDBDialect
I took the script generated by Hibernate and tested it directly in phpMyAdmin, it didn't work.
SQL script :
alter table .Car add index FK107B43F620606 (idOwner), add constraint FK107B43F620606 foreign key (idOwner) references .Owner (idOwner)
If I fix the script by replacing the tables name with , e.g. Car or glorious.Car than it works. Anyone has an idea?
Solved it. My persistence.xml had the property default_schema set to empty. That's why no DB name was appearing before the table's name.
Related
I am new to Spring Boot. I am trying to use the save() functionality via the JPA library using Postman for the first time. My database is a legacy Mysql database. Generically speaking, this table contains data of baseball players who have been drafted into a fantasy baseball league. The primary key of my table is 'play_id', and I also track the player's 'mlb_id' (Major League Baseball's unique identifier) in the same table.
Here is my code:
Table setup in Mysql:
CREATE TABLE `mlb_rosters` (
`play_id` int(10) NOT NULL,
`mlb_id` int(10) NOT NULL,
`name_first` varbinary(255) NOT NULL,
`name_last` varbinary(255) NOT NULL,
`bats` varchar(1) NOT NULL,
`throws` varchar(1) NOT NULL,
`birthday` date NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `mlb_rosters`
ADD PRIMARY KEY (`play_id`),
ADD UNIQUE KEY `mlb_id` (`mlb_id`),
ADD UNIQUE KEY `mlb_id_2` (`mlb_id`);
ALTER TABLE `mlb_rosters`
MODIFY `play_id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6730;
I also ran insert statements for approximately ~1500 players, so this is not a blank table.
My object in Springboot:
package com.example.demo.entities;
import javax.persistence.*;
#Entity
#Table(name="mlb_rosters")
public class IbcMlbPlayer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="play_id", columnDefinition = "int(10)")
private Integer playId;
#Column(name="mlb_id")
private Integer mlbId;
#Column(name="name_first", columnDefinition = "varbinary(255)")
private String nameFirst;
#Column(name="name_last", columnDefinition = "varbinary(255)")
private String nameLast;
#Column(name="bats")
private String bats;
#Column(name="throws")
private String thrws;
#Column(name="birthday")
private String birthday;
public IbcMlbPlayer(){
}
public Integer getPlayId() {
return playId;
}
public void setPlayId(Integer playId) {
this.playId = playId;
}
public Integer getMlbId() {
return mlbId;
}
public void setMlbId(Integer mlbId) {
this.mlbId = mlbId;
}
public String getNameFirst() {
return nameFirst;
}
public void setNameFirst(String nameFirst) {
this.nameFirst = nameFirst;
}
public String getNameLast() {
return nameLast;
}
public void setNameLast(String nameLast) {
this.nameLast = nameLast;
}
public String getBats() {
return bats;
}
public void setBats(String bats) {
this.bats = bats;
}
public String getThrws() {
return thrws;
}
public void setThrws(String thrws) {
this.thrws = thrws;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
The relevant path of my controller:
#PostMapping(value = "/saveIbcMlbPlayer")
public IbcMlbPlayer saveIbcMlbPlayer(#RequestBody IbcMlbPlayer ibcMlbPlayer){
return ibcMlbPlayerDao.save(ibcMlbPlayer);
}
My Dao:
package com.example.demo.dao;
import com.example.demo.entities.IbcMlbPlayer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface IbcMlbPlayerDao extends JpaRepository<IbcMlbPlayer, Integer> {
}
When I attempt to do a Post request to the save path and pass in the JSON object of the player who I'm attempting to create, I get the following error:
Duplicate entry '25' for key 'PRIMARY'
In this case, I've tried this 25 times, so Postman/Spring Boot keep incrementing the 'play_id' field by 1 (this number goes up in the error message by one each time I test).
I understand the error, for whatever reason, Spring Boot isn't getting the max value of the 'play_id' field, incrementing it by one, and then attempting to do the insert. I would have expected 'play_id' to be 6730, which I believe is the table's max play_id plus one. Does anyone know how to fix this? Any help would be really appreciated!
AUTO shouldn't be used as GenerationType you must use IDENTITY
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="play_id", columnDefinition = "int(10)")
private Integer playId;
I guess hibernate is trying to assign a String value fetched from the database to long.Have done many-to-one unidirectional mapping.I'm trying to display the values from the region table in a drop down in CorporateGroupForm.jsp
CorporateGroup.java
#Entity
#Table(name="corporate_group")
public class CorporateGroup extends BaseObject implements Serializable {
private Region region;
private Long id;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="id")
public Region getRegion() {
return region;
}
public void setRegion(Region region) {
this.region = region;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
} }
corporateGroupForm.jsp
<li>
<appfuse:label styleClass="desc" key="corporateGroupDetail.region"/>
<select name="regionDesc">
<option value=""><fmt:message key="select.pleaseSelect"/></option>
<c:forEach var="region" items="${regionsList}">
<c:set var="selected" value="${corporateGroup.region ne null and corporateGroup.region.regionDesc eq region.regionDesc}"/>
<option ${selected ? 'selected' : ''} value="${region.regionDesc }">${region.regionDesc } </option>
</c:forEach>
</select>
</li>
DB:
CREATE TABLE `corporate_group` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`comment` text,`name` varchar(255) NOT NULL,`parent_id`bigint(20) DEFAULT NULL,`primary_contact_id` bigint(20) DEFAULT NULL,`account_manager_email` varchar(255) DEFAULT NULL,`dateCreated` datetime DEFAULT CURRENT_TIMESTAMP,`region_description` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`),KEY `FK61BCC225C8E0340A` (`parent_id`),KEY `FK61BC225F0655E4F` (`primary_contact_id`),KEY `FK_REGION_idx` (`region_description`),CONSTRAINT `fk_region` FOREIGN KEY (`region_description`) REFERENCES `region` (`region_description`) ON DELETE NO ACTION ON UPDATE NO ACTION,CONSTRAINT `FK61BC225F0655E4F` FOREIGN KEY (`primary_contact_id`) REFERENCES `app_user` (`id`),CONSTRAINT `FK61BCC225C8E0340A` FOREIGN KEY (`parent_id`) REFERENCES `corporate_group` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=843 DEFAULT CHARSET=latin1;
CREATE TABLE `region` (`id` bigint(20) NOT NULL,`country_code` varchar(50) NOT NULL,country_name` varchar(100) NOT NULL,`time_zone` varchar(100) NOT NULL,`region_description` varchar(255) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `description_UNIQUE` (`region_description`),KEY `id` (`id`),KEY `region_description` (`region_description`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Exception Stack Trace :
Hibernate: select corporateg0_.id as id2_,
corporateg0_.account_manager_email as account2_2_,
corporateg0_.comment as comment2_, corporateg0_.name as name2_,
corporateg0_.parent_id as parent6_2_, corporateg0_.primary_contact_id
as primary5_2_, corporateg0_.region_description as region7_2_ from
corporate_group corporateg0_ order by corporateg0_.name WARN
[http-bio-9080-exec-1] JDBCExceptionReporter.logExceptions(77) | SQL
Error: 0, SQLState: S1009 WARN [http-bio-9080-exec-1]
JDBCExceptionReporter.logExceptions(77) | SQL Error: 0, SQLState:
S1009 ERROR [http-bio-9080-exec-1]
JDBCExceptionReporter.logExceptions(78) | Invalid value for getLong()
- 'UK -UTC +0:00' ERROR [http-bio-9080-exec-1] JDBCExceptionReporter.logExceptions(78) | Invalid value for getLong()
- 'UK -UTC +0:00'
The error on the web page :
Data Access Failure
Hibernate operation: could not execute query; uncategorized SQLException for SQL [select corporateg0_.id as id2_, corporateg0_.account_manager_email as account2_2_, corporateg0_.comment as comment2_, corporateg0_.name as name2_, corporateg0_.parent_id as parent7_2_, corporateg0_.primary_contact_id as primary5_2_, corporateg0_.region_description as region6_2_ from corporate_group corporateg0_ order by corporateg0_.name]; SQL state [S1009]; error code [0]; Invalid value for getLong() - 'UK -UTC +0:00'; nested exception is java.sql.SQLException: Invalid value for getLong() - 'UK -UTC +0:00'
Region.java
#Entity
#Table(name = "region")
public class Region extends BaseObject implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private String countryCode;
private String countryName;
private String timeZone;
private String regionDesc;
#Column(name="country_code",nullable=false)
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
#Column(name="country_name",nullable=false)
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
#Column(name="time_zone",nullable=false)
public String getTimeZone() {
return timeZone;
}
public void setTimeZone(String timeZone) {
this.timeZone = timeZone;
}
#Column(name="region_description",nullable=false)
public String getRegionDesc() {
return regionDesc;
}
public void setRegionDesc(String regionDesc) {
this.regionDesc = regionDesc;
}
#Override
public String toString() {
StringBuffer strBuff = new StringBuffer();
if (getId() != null) {
strBuff = strBuff.append("ID:" + getId() + ",");
strBuff = strBuff.append("Country Name:" + getCountryName() + ",");
strBuff = strBuff.append("Country Code:" + getCountryCode() + ",");
strBuff = strBuff.append("Timezone:" + getTimeZone() + ",");
strBuff = strBuff.append("Region Description:" + getRegionDesc() + ",");
}
return strBuff.toString();
}
#Override
public boolean equals(Object o) {
// TODO Auto-generated method stub
if (!(o instanceof Region)) {
return false;
}
Region reg = (Region) o;
return !(regionDesc != null ? !regionDesc.equals(reg.getRegionDesc()) : reg.getRegionDesc() != null);
}
#Override
public int hashCode() {
// TODO Auto-generated method stub
int hashcode = 0;
if (this.regionDesc != null) {
hashcode = hashcode + this.regionDesc.hashCode();
}
return hashcode;
}
}
Now a different error :
ERROR [localhost-startStop-1 ContextLoader.initWebApplicationContext(215) | Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name '_filterChainProxyPostProcessor': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.config.internalTransactionAdvisor': Cannot create inner bean '(inner bean)' of type [org.springframework.transaction.interceptor.TransactionInterceptor] while setting bean property 'transactionInterceptor'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#1': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [applicationContext-dao.xml]: Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException:Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext-dao.xml]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Repeated column in mapping for entity: com.canvas8.model.CorporateGroup column: id (should be mapped with insert="false" update="false")
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:405)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:220)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:881)
at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:606)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:366)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:45)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4994)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5492)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1081)
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1877)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
In your CorporateGroup entity class, you mapped region with region_description of Region entity which has a primary key of Long
#ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name="region_description")
public Region getRegion() {
return region;
}
What you can do is you can map region member variable of CorporateGroup to primary key of Region entity class not the region_description.
Regarding this error :
Repeated column in mapping for entity:
com.canvas8.model.CorporateGroup column: id (should be mapped with
insert="false" update="false")
The error message is obvious, you have mapped same column twice. Refer this and fix the issue.
Hope this helps!
I was able to specify the referenced column name to resolve the issue for me. I didn't want to eager fetch the entities since it would only be used sometimes and didn't have permissions to modify the database schema.
Try this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "region_description", referencedColumnName = "region_description")
private Region region;
I have a running Spring Boot application with Mysql and Hibernate.
While launching it, i'm getting this error
Unsuccessful: alter table SMARTPARK.illuminazione add constraint FK_4kmtr3q9e2hnaoutsxgahhm63 foreign key (id_interruttore) references SMARTPARK.interruttori (id_interruttore)
2016-05-05 08:46:35 ERROR SchemaUpdate:262 - Cannot add foreign key constraint
I have two table/entities
Illuminazione.java is (just the interesting parts...)
#Entity
#Table(name = "illuminazione", catalog = "SMARTPARK")
public class Illuminazione {
private int idilluminazione;
private Interruttore interruttore;
private Date dateTime;
private Date lastDateTime;
private boolean isLit;
#ManyToOne
#JoinColumn(name = "id_interruttore")
public Interruttore getInterruttore() {
return this.interruttore;
}
public void setInterruttore(Interruttore interruttore) {
this.interruttore = interruttore;
}
In Interruttore.java I have the #OneToMany relation with Illuminazione
#Entity
#Table(name = "interruttori", catalog = "SMARTPARK", uniqueConstraints = #UniqueConstraint(columnNames = "id_interruttore"))
public class Interruttore implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int idInterruttore;
private int numeroInterruttore;
private String nomeInterruttore;
private String descrizione;
private List<Luce> luci;
private GpioController gpio;
private GpioPinDigitalOutput relePin;
private Pin pin;
private boolean remoto;
private boolean stato;
private Date dateTime;
private Set<Illuminazione> illuminazione;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "interruttore")
public Set<Illuminazione> getIlluminazione() {
return illuminazione;
}
public void setIlluminazione(Set<Illuminazione> illuminazione) {
this.illuminazione = illuminazione;
}
Every time I start the application, during the boot i'm getting this error (even if the application seems working good...)
I had the same error message, which I found out was caused by incorrect annotations. Hibernate was trying to run
alter table cidades
add constraint FKdt0b3ronwpi1upsrhaeq6r69n
foreign key (estado_id)
references estados (id)
And when I looked at my Cidade.java, I found this mapping
#ManyToOne
#JoinColumn(name = "cidade_id")
private Estado estado;
The error was in "cidade_id", which should have been "estado_id". It would be great if #besmart could provide the DB table info, since the error could be caused by a typo (e.g. id_interruttore could actually be id_interruttori).
I hope this helps someone in the future.
I hit similar problem .
To me apparently hibernate/Spring was NOT using mysql Engine -INNODB , you need engine INNODB for mysql to generate foreign key constraint.
Using the following properties in application.properties, makes spring boot/hibernate to use mysql engine INNODB. So foreign key constraint works and hence also delete cascade
spring.jpa.hibernate.use-new-id-generator-mappings=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
I have issues persisting a simple 2 classes on DataNucleus 3.1.3 on MySQL, where DataNucleus seems to create invalid foreign-keys, ending up in a "foreign key constraint fails" -exception from database.
Here my classes:
// datastore since i dont care about identity here
#PersistenceCapable(identityType = IdentityType.DATASTORE)
class A {
#Persistent
int x;
#Persistent
int y;
}
// identity type:application here to enable id lookups
#PersistenceCapable
class B {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.NATIVE)
long id;
#Persistent
double longitude;
#Persistent
double latitude;
// simple 1:1 unidirectional
#Persistent
A a;
}
The schemaTool created the tables (InnoDB) which looks good, but an insert fails, here the logs:
12:54:11,369 DEBUG [DataNucleus.Datastore.Native] - INSERT INTO `A` (`X`,`Y`) VALUES (<1>,<1>)
12:54:11,387 DEBUG [DataNucleus.Datastore.Persist] - Execution Time = 18 ms (number of rows = 1)
12:54:11,398 DEBUG [DataNucleus.Datastore.Persist] - Object "foo.A#624af1e" was inserted in the datastore and was given strategy value of "3"
12:54:11,403 DEBUG [DataNucleus.Datastore] - Closing PreparedStatement "org.datanucleus.store.rdbms.ParamLoggingPreparedStatement#6f5ba238"
12:54:11,404 DEBUG [DataNucleus.Datastore.Native] - INSERT INTO `B` (`LONGITUDE`,`LATITUDE`,`A_A_ID_OID`) VALUES (<0.5099776394799052>,<0.6191090630996077>,<51>)
12:54:11,419 WARN [DataNucleus.Datastore.Persist] ... Cannot add or update a child row: a foreign key constraint fails (`xperimental`.`B`, CONSTRAINT `B_FK1` FOREIGN KEY (`A_A_ID_OID`) REFERENCES `A` (`A_ID`))
Looking at the logs on lines (3) and (5) its very suspicious that an insert into table A returned a PK of "3" but DataNucleus instead uses a value of "51" as FK on A when inserting data into table B which causes the violation.
Where is the issue? Thanks
UPDATE: the resources
Class A
package jdotest.a;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
#PersistenceCapable(identityType = IdentityType.DATASTORE)
public class A {
#Persistent
private int x;
#Persistent
private int y;
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Class B
package jdotest.b;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import jdotest.a.A;
#PersistenceCapable
public class B {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.NATIVE)
long id;
#Persistent
double longitude;
#Persistent
double latitude;
// simple 1:1 unidirectional
#Persistent
A a;
public long getId() {
return id;
}
public double getLongitude() {
return longitude;
}
public double getLatitude() {
return latitude;
}
public void setA(A a) {
this.a = a;
}
public A getA() {
return a;
}
}
Dao
package dao;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import jdotest.b.B;
public class BDao {
public void write(B b) {
PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("cloud-sql");
PersistenceManager pm = pmf.getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(b);
tx.commit();
} finally {
if (tx.isActive())
tx.rollback();
pm.close();
}
}
}
execution
package exec;
import jdotest.a.A;
import jdotest.b.B;
import dao.BDao;
public class Ex{
public void persist(){
A a = new A();
B b = new B();
b.setA(a);
new BDao().write(b); //<-- exception
}
}
the exception *
java.sql.SQLException: Cannot add or update a child row: a foreign key constraint fails (xperimental.b, CONSTRAINT B_FK1 FOREIGN KEY (A_A_ID_OID) REFERENCES a (A_ID))
We have a fairly complex data model and are using Hibernate and Spring Data JPA on top of MySQL. We have a base class that all domain objects extend to minimize boiler plate code. I would like to be able to add soft delete functionality across all of our domain objects using only this class. However, #SQLDelete requires the table name in the clause:
#SQLDelete(sql="UPDATE (table_name) SET deleted = '1' WHERE id = ?")
#Where(clause="deleted <> '1'")
Does anybody know of a way to generalize the SQLDelete statement and allow the extending domain objects to populate their own table names?
If you use hibernate and #SQLDelete, there's no easy solution to your question. But you can consider another approach to soft delete with Spring Data's expression language:
#Override
#Query("select e from #{#entityName} e where e.deleteFlag=false")
public List<T> findAll();
//recycle bin
#Query("select e from #{#entityName} e where e.deleteFlag=true")
public List<T> recycleBin();
#Query("update #{#entityName} e set e.deleteFlag=true where e.id=?1")
#Modifying
public void softDelete(String id);
//#{#entityName} will be substituted by concrete entity name automatically.
Rewrite base repository like this. All sub repository interfaces will have soft delete ability.
Another approach, which could be more flexible.
On Entity level create
#MappedSuperclass
public class SoftDeletableEntity {
public static final String SOFT_DELETED_CLAUSE = "IS_DELETED IS FALSE";
#Column(name = "is_deleted")
private boolean isDeleted;
...
}
Update your Entity which should be soft deletable
#Entity
#Where(clause = SoftDeletableEntity.SOFT_DELETED_CLAUSE)
#Table(name = "table_name")
public class YourEntity extends SoftDeletableEntity {...}
Create a custom Interface Repository which extends the Spring's Repository. Add default methods for soft delete. It should be as a base repo for your Repositories. e.g.
#NoRepositoryBean
public interface YourBaseRepository<T, ID> extends JpaRepository<T, ID> {
default void softDelete(T entity) {
Assert.notNull(entity, "The entity must not be null!");
Assert.isInstanceOf(SoftDeletableEntity.class, entity, "The entity must be soft deletable!");
((SoftDeletableEntity)entity).setIsDeleted(true);
save(entity);
}
default void softDeleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.softDelete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", "", id), 1)));
}
}
NOTE: If your application doesn't have the hard delete then you could add
String HARD_DELETE_NOT_SUPPORTED = "Hard delete is not supported.";
#Override
default void deleteById(ID id) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void delete(T entity) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll(Iterable<? extends T> entities) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll() {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
Hope it could be useful.