In a spring mvc application using hibernate and MySQL, I am getting the following constraint violation exception:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row: a foreign key constraint fails
(`mybd`.`hl7_documententity`, CONSTRAINT `hl7_documententity_ibfk_1`
FOREIGN KEY (`ptcode`, `ptcodesystem`)
REFERENCES `hl7_generalcode` (`code`, `codesystem`))
The problem occurs when I try to save a DocumentEntity containing a property of type GeneralCode, both of which are defined below.
I have read many postings and blogs on this error, but none seem to resolve my problem. How can I resolve this error?
Here is the DocumentEntity class:
#Entity
#Table(name = "hl7_documententity")
public class HL7DocumentEntity extends BaseEntity{
//other properties
#ManyToOne
#JoinColumns({ #JoinColumn(name = "ptcode", referencedColumnName = "code"),
#JoinColumn(name = "ptcodesystem", referencedColumnName = "codesystem")
})
private HL7GeneralCode providertype;
//getters and setters
}
Here is the GeneralCode class:
#Entity
#Table(name = "hl7_generalcodes")
public class HL7GeneralCode implements Serializable{
private static final long serialVersionUID = -8620565054475096516L;
#EmbeddedId
private HL7EmbedCodePK codePk;
#OneToMany(mappedBy = "providertype")
private Set<HL7DocumentEntity> documententities;
////////////getters and setters
}
Here is the code from the controller:
HL7GeneralCode authcode = processGeneralCode(grandkid);
HL7GeneralCode testcode = this.clinicService.findGeneralCodeByPK(authcode.getCodePk().getCode(), authcode.getCodePk().getCodesystem());
if(testcode==null){
authcode.addDocumententity(mydent);
this.clinicService.savehl7GeneralCode(authcode);
mydent.setProvidertype(authcode);
//this next line throws the error
this.clinicService.savehl7DocumentEntity(mydent);
}else{
//other stuff
}
Here is the dao method:
#Repository
public class JpaSomethingRepositoryImpl implements SomethingRepository {
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public void savehl7DocumentEntity(HL7DocumentEntity de) {
HL7GeneralCode code = de.getProvidertype();
if(code !=null && code.getCodePk()==null){//HL7GeneralCode is not persistent. We don't support that
throw new IllegalStateException("Cannot persist an adress using a non persistent HL7GeneralCode");
}
System.out.println("=========================== inside jpaCdaRespository.saveDocEntity(de)");
de.setProvidertype(null);
if(code.getDocumententities()!=null){
ArrayList<HL7DocumentEntity> addrList = new ArrayList<HL7DocumentEntity>();
addrList.addAll(code.getDocumententities());
addrList.remove(de);
Set<HL7DocumentEntity> myaddrs = new HashSet<HL7DocumentEntity>(addrList);
code.setDocumententities(myaddrs);
}
code = em.merge(code);
de.setProvidertype(code);
code.addDocumententity(de);
if (de.getId() == null) {
System.out.println("[[[[[[[[[[[[ about to persist de ]]]]]]]]]]]]]]]]]]]]");
em.persist(de);
} else {
System.out.println("]]]]]]]]]]]]]]]]]] about to merge de [[[[[[[[[[[[[[[[[[[[[");
de = em.merge(de);
}
}
}
The executed SQL statement and the actual values that hibernate is trying to insert via the sql are:
[[[[[[[[[[[[ about to persist de ]]]]]]]]]]]]]]]]]]]]
DEBUG SQL - insert into hl7_documententity (author_id, authpar_id, entitytype, id_extension, id_root, ptcode, ptcodesystem, id) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into hl7_documententity (author_id, authpar_id, entitytype, id_extension, id_root, ptcode, ptcodesystem, id) values (?, ?, ?, ?, ?, ?, ?, ?)
TRACE BasicBinder - binding parameter [1] as [INTEGER] - <null>
TRACE BasicBinder - binding parameter [2] as [INTEGER] - <null>
TRACE BasicBinder - binding parameter [3] as [VARCHAR] - <null>
TRACE BasicBinder - binding parameter [4] as [VARCHAR] - NI
TRACE BasicBinder - binding parameter [5] as [VARCHAR] - nullFlavor
TRACE BasicBinder - binding parameter [6] as [VARCHAR] - UNK
TRACE BasicBinder - binding parameter [7] as [VARCHAR] - HL7NullFlavor
TRACE BasicBinder - binding parameter [8] as [INTEGER] - 32787
WARN SqlExceptionHelper - SQL Error: 1452, SQLState: 23000
ERROR SqlExceptionHelper - Cannot add or update a child row: a foreign key constraint fails (`docbd`.`hl7_documententity`, CONSTRAINT `hl7_documententity_ibfk_1` FOREIGN KEY (`ptcode`, `ptcodesystem`) REFERENCES `hl7_generalcode` (`code`, `codesystem`))
INFO AbstractBatchImpl - HHH000010: On release of batch it still contained JDBC statements
WARN warn - Handler execution resulted in exception
You can read the EmbedCodePK class code by clicking on this link.
You can read the entire stack trace by clicking on this link.
Here is a link to the code for the BaseEntity class.
Change this:
#OneToMany(mappedBy = "providertype")
private Set<HL7DocumentEntity> documententities;
To this:
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "Link_Documents", joinColumns = {#JoinColumn(name = "codePk", unique = true)}, inverseJoinColumns = {#JoinColumn(name = "change_this_with_primary_key_variable_name_from_HL7DocumentEntity")})
private Set<HL7DocumentEntity> documententities;
And in HL7DocumentEntity change as follows:
This
#ManyToOne
#JoinColumns({ #JoinColumn(name = "ptcode", referencedColumnName = "code"),
#JoinColumn(name = "ptcodesystem", referencedColumnName = "codesystem")
})
private HL7GeneralCode providertype;
Change to this:
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "Link_Documents", joinColumns = {#JoinColumn(name = "change_this_with_primary_key_variable_name_from_HL7DocumentEntity")}, inverseJoinColumns = {#JoinColumn(name = "codePk")})
private HL7GeneralCode providertype;
I think you have to change "change_this_with_primary_key_variable_name_from_HL7DocumentEntity" with "id" like it is in BaseEntity but take a look at your sql table, you willsee there the correct name.
I hope you notice How I told JPA to use the same "Link_Documents" table for linking the 2 tables. I think this is were your mistake is. Just make sure to change where I told you with the correct variable name and I think it should work
you know i would start by cleaning up this code.
Though the hibernate documentation might tell you placing those annotations on the field variables is ok,,, its a bad idea as a standard. field level requires hibernate to create a proxy before the field can be access in the case of lazy fetches. so just get use to doing it correctly and you wont have the misfortune of finding issues. move all those annotations to the getters as property level access doesnt require the proxy.
field level variables should be private.
you are casting an Integer to an Integer in there... some of that stuff makes me not want to look at it and truthfully will probably be a silly bit of laziness that ends up being your problem.
while you are at it go ahead an initialize the set with new HashSet(0); Things like that. Still broken update us.
Related
I'm sitting in this peculiar situation where I have the following database design
What this image illustrates is the following:
Table A has a_c_id which is "C id" but without a foreign key reference
Table B has c_id which is "C id" but without a foreign key reference.
Table C has id as primary key.
I'm trying in my java code to create following entities using hibernate and hibernate annotations
#Entity
#Table(name = "A")
Class A {
private Long id;
private List<B> bList; // This is the relation i'm struggling with
}
#Entity
#Table(name = "B")
Class B {
private Long Id;
private Long c_id;
}
I've tried alot of difference combinations using:
#JoinColumn(name = "a_c_id", referencedColumnName = "c_id")
private List<B> bList;
And:
#JoinTable(
name = "C",
joinColumns = {
#JoinColumn(
name = "id",
referencedColumnName = "a_c_id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "id",
referencedColumnName = "c_id"
),
}
)
private List<B> bList;
This last example with #JoinTable gives me an exception:
Repeated column in mapping for collection: id
which makes me wonder if mapping to the same PK from 2 different tables is not possible.
But i'm starting to run out of ideas on how to do this. I didn't make this database and is wondering if it's even possible to achieve making a relation from A to B without a foreign key to reference.
I thought I could perhaps use #JoinTable, however they refer to the same ID rather than 2 different ones as you would in a JoinTable.
If anyone has some input on what to read up on, or perhaps could come with an idea on how to achieve this without changing the database design, I'd apppreciate it!
EDIT: I've added images of table records:
EDIT 2: I've tried to trace binding values based on me and Nikos conversation:
Hibernate: select a0_.id as id1_0_0_, a0_.a_c_id as a_c_id2_0_0_ from a a0_ where a0_.id=?
2022-07-27 10:25:06.101 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2022-07-27 10:25:06.107 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicExtractor : extracted value ([a_c_id2_0_0_] : [BIGINT]) - [41]
2022-07-27 10:25:06.113 TRACE 16404 --- [nio-8080-exec-8] org.hibernate.type.CollectionType : Created collection wrapper: [eu.sos.auditing.models.HibernateTestModels.A.bList#1]
Hibernate: select blist0_.c_id as c_id2_3_0_, blist0_.id as id1_3_0_, blist0_.id as id1_3_1_, blist0_.c_id as c_id2_3_1_ from b blist0_ where blist0_.c_id=?
2022-07-27 10:25:06.149 TRACE 16404 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
EDIT 3: What solved my issue.
Due to Nikos help I was able to solve my issue by changing my class A to the following:
#Entity
#Table(name = "A")
#Data
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "a_c_id")
private Long aCId;
#OneToMany
#JoinColumn(name="c_id", referencedColumnName = "a_c_id")
private List<B> bList;
}
Kind regards
The solution can be as straightforward as pretending that B.c_id points to A.a_c_id:
Class A {
#OneToMany
// remember, the JoinColumn is in the other table (here B)
// while the referencedColumnName is in this table
#JoinColumn(name="c_id ", referencedColumnName = "a_c_id")
private List<B> bList;
}
EDIT: It is important to include the referencedColumnName, which I has forgotten before the edit. Also see full solution below for another caveat with Serializable.
If the DB constraints are different, you will have to make sure a C record exists before inserting into A and B.
The other solution is NOT to map the relation in the entity classes. Or, rather, map the existing relations, i.e. any of: (1) "A relates to 1 C", (2) "B relates to 1 C", (3) "C relates to many As", (4) "C relates to many Bs". And fetch the Bs that correspond to an A with an independent query.
FULL WORKING SOLUTION (with slightly different names - omitting getters/setters for brevity)
The entities:
#Entity
#Table(name = "AA")
public class Alpha implements Serializable {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_c_id")
private Charlie charlie;
#OneToMany
#JoinColumn(name="c_id", referencedColumnName = "a_c_id")
private List<Bravo> bList;
}
CAVEAT: I had to make Alpha implements Serializable because of HHH-7668. It applies only to the Hibernate implementation of JPA.
#Entity
#Table(name = "BB")
public class Bravo {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "c_id")
private Charlie charlie;
}
#Entity
#Table(name = "CC")
public class Charlie {
#Id
private Long id;
}
Sample code to read, assuming the contents of the tables are as in the question:
EntityManager em = ...;
Alpha a = em.find(Alpha.class, 2L);
a.getbList().forEach(b -> System.out.println(b.getId()));
// prints 3, 4
One of my entities is throwing a StaleStateException when trying to delete it. Code in question:
#RestController
#RequestMapping("/cycles")
public class DeleteCycleController {
#DeleteMapping("{cycleId}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCycleById(#PathVariable("cycleId") UUID cycleId, Principal principal) {
// cycleRepository extends CrudRepository
cycleRepository.deleteById(new CycleId(cycleId));
// deleter.deleteCycle(new CycleId(cycleId));
}
}
Throws the following:
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid
Here are the hibernate logs just before the failure:
2021-03-11 20:34:11.532 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
Hibernate: select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
2021-03-11 20:34:11.533 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
2021-03-11 20:34:11.542 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : delete from cycle where id=?
Hibernate: delete from cycle where id=?
2021-03-11 20:34:11.542 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
And the Cycle entity:
#javax.persistence.Entity
#Accessors(fluent = true)
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
#TypeDefs({#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})
public class Cycle {
#Id private UUID id;
private CycleName name;
private CycleDescription description;
private UUID cycleTypeId;
private UUID organizationId;
private Instant createdAt;
private Instant lastModifiedAt;
#Type(type = "jsonb")
private Map<String, Object> attributes;
private CycleDuration duration;
#Column(columnDefinition = "Geometry", nullable = true)
private Geometry startLocation;
private Geometry endLocation;
#OneToMany(mappedBy = "cycleId", cascade = CascadeType.ALL, orphanRemoval = false)
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PUBLIC)
private List<CycleEntityMapping> entities = new ArrayList<>();
...
}
Any ideas as to what could be going wrong? Another (simpler) entity deletes fine.
Not sure if its the OneToMany causing issues, but I've tried changing the cascade type, orphan removal and fetch type with no luck. Otherwise, could it be caused by the value objects? Note that updating the entity works fine - it's only failing on delete.
In this case, it was caused by a bug in a before delete trigger. There was a bug in the trigger causing the row to not be deleted:
CREATE OR REPLACE FUNCTION trigger_delete_row_backup()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO deleted_cycle values (OLD.*);
RETURN NEW;
-- should actually be RETURN OLD
END;
$BODY$
language PLPGSQL;
Once the trigger was fixed, the issue no was resolved.
I feel like this is answered 100 times. but for some reason, I've tried every single answer and none of them fix my problem.
I have a simple Parent with a simple primary key.
It has a list of children. The foreign key is part of the child's composite primary key.
The Db is postgresql.
Parent class
#Entity
#Table(name = "person")
class Person {
#Id
#GeneratedValue(generator = "person_id_seq")
#Column(name = "person_id")
private Long personId;
#OneToMany(mappedBy="person", cascade = CascadeType.ALL)
private List<PersonIdentifier> personIdentifiers = new ArrayList<>();
public void addPersonIdentifier(PersonIdentifier pi) {
this.personIdentifiers.add(pi);
pi.setPerson(this);
}
// other columns and getters / setters left out for brevity
}
Child class w/pk class
#Entity
#Table(name = "person_identifier")
#IdClass(PersonIdentifierPK.class)
public class PersonIdentifier {
#Id
#Column(name="person_id")
private Long personId;
#Id
#Column(name="person_identifier")
private String personIdentifier;
#Id
#Column(name="person_identifier_type")
private String personIdentifierType;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="person_id", insertable=false, updatable=false)
private Person person;
// other columns and getters / setters left out for brevity
}
public class PersonIdentifierPK {
#Column(name = "person_id")
private Long personId;
#Column(name = "person_identifier")
private String personIdentifier;
#Column(name = "person_identifier_type")
private String personIdentifierType;
// getters/setters left out
#Override
public int hashCode() {
// implementation
}
#Override
public boolean equals(Object obj) {
// implementation
}
}
USAGE:
Patient p = new Patient();
PersonIdentifier pi = new PersonIdentifier();
pi.setPersonIdentifierType("NAME");
pi.setPersonIdentifier("John Doe");
p.addPersonIdentifier(pi);
personRepository.save(p);
TransactionManager
public static <T> T getRepository(Class<T> repositoryInterface, Project project) {
if (entityManager == null) {
DataSource ds = ConnectionPools.getStagingDB();
emFactory = entityManagerFactory(ds, hibernateProperties(project.getStagingDbJdbcUrl(), project.getStagingDbUserName(), project.getStagingDbPassword()));
EntityManagerCreator creator = new EntityManagerCreator();
entityManager = creator.create(emFactory);
}
final JpaTransactionManager xactManager = new JpaTransactionManager(emFactory);
JpaRepositoryFactory factory = new JpaRepositoryFactory(entityManager);
factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
#Override
public void postProcess(ProxyFactory factory, RepositoryInformation info) {
factory.addAdvice(new TransactionInterceptor(xactManager, new MatchAlwaysTransactionAttributeSource()));
}
});
return factory.getRepository(repositoryInterface);
}
if i remove the cascade=CascadeType.ALL then i end up with a new person in my person database with a properly generated Id, but the person_identifier table is still empty obviously.
however, with the cascading, it fails to commit anything due to the personIdentifier's "person_id" column trying to be set to null.
my understanding is with the mapping, and the join column, and setting the parent on the child, that this column will get set automatically to whatever it is on the parent?
the spring sql output looks like this (there are more columns in parent table i just removed them for privacy reasons):
select nextval ('person_id_seq')
insert into person (person_id) values (?)
binding parameter [1] as [BIGINT] - [70]
insert into person_identifier (active, create_date, modified_date, person_id, person_identifier, person_identifier_type) values (?, ?, ?, ?, ?, ?)
binding parameter [1] as [BOOLEAN] - [true]
binding parameter [2] as [TIMESTAMP] - [Thu Sep 19 12:34:52 EDT 2019]
binding parameter [3] as [TIMESTAMP] - [null]
binding parameter [4] as [BIGINT] - [null]
binding parameter [5] as [VARCHAR] - [John Doe]
binding parameter [6] as [VARCHAR] - [NAME]
you can see where it passed null into person_id of person_identifier insert, even though it passed 70 into the person tables insert statement.
this obviously leads to
ERROR: null value in column "person_id" violates not-null constraint
I've tried refactoring the code a multitude of ways. Using embeddedId, removing mappedBy and using joincolumn on the parent instead. etc etc.
Disclaimer: This is my first Java project; learning as I go.
Background: I've inherited a legacy database on which to build a new RESTful API. We're using Elide with Spring Boot to provide a JSON API compliant service.
Reference: Example source code
Problem: We have entities with a many-to-many relationship to each other and themselves by way of a join table. Consider the followig schema:
CREATE TABLE ALPHA (
ID VARCHAR(255),
NAME VARCHAR(255),
CONSTRAINT PK_ALPHA PRIMARY KEY (ID)
);
CREATE TABLE BRAVO (
ID VARCHAR(255),
NAME VARCHAR(255),
CONSTRAINT PK_BRAVO PRIMARY KEY (ID)
);
CREATE TABLE RELATIONSHIP (
ID INT AUTO_INCREMENT,
FROM_ID VARCHAR(255),
TO_ID VARCHAR(255)
);
Where the resource entities are modeled as follows:
public class Alpha implements Serializable {
private String id;
private String name;
private Set<Alpha> alphas = new HashSet<>();
private Set<Bravo> bravos = new HashSet<>();
#Id
#Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
public String getId() {
return id;
}
public String getName() {
return name;
}
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "RELATIONSHIP",
joinColumns = #JoinColumn(name = "FROM_ID"),
inverseJoinColumns = #JoinColumn(name = "TO_ID")
)
public Set<Alpha> getAlphas() {
return alphas;
}
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "RELATIONSHIP",
joinColumns = #JoinColumn(name = "FROM_ID"),
inverseJoinColumns = #JoinColumn(name = "TO_ID")
)
public Set<Bravo> getBravos() {
return bravos;
}
}
And the relationship table:
public class Relationship implements Serializable {
private Integer id;
private String fromId;
private String toId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
#Column(name = "FROM_ID")
public String getFromId() {
return fromId;
}
#Column(name = "TO_ID")
public String getToId() {
return toId;
}
}
Now let's say we have an Alpha record A1 with relationships to A2, A3, B1, and B2. First we delete the relationship to A2.
From our API this would be a DELETE request to http://localhost:9000/api/alphas/a1/relationships/alphas with BODY
{
"data": [
{
"type": "alphas",
"id": "a2"
}
]
}
Behind the scenes Hibernates does what I'm expecting and generates the following SQL queries:
2018-07-13 09:48:23.687 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alpha0_.id as id1_0_,
alpha0_.name as name2_0_
from
alpha alpha0_
where
alpha0_.id in (
?
)
2018-07-13 09:48:23.688 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.690 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alphas0_.from_id as from_id2_2_0_,
alphas0_.to_id as to_id3_2_0_,
alpha1_.id as id1_0_1_,
alpha1_.name as name2_0_1_
from
relationship alphas0_
inner join
alpha alpha1_
on alphas0_.to_id=alpha1_.id
where
alphas0_.from_id=?
2018-07-13 09:48:23.690 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.699 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alpha0_.id as id1_0_,
alpha0_.name as name2_0_
from
alpha alpha0_
where
alpha0_.id in (
?
)
2018-07-13 09:48:23.699 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a2]
2018-07-13 09:48:23.721 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
delete
from
relationship
where
from_id=?
and to_id=?
2018-07-13 09:48:23.722 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.724 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [a2]
The key piece being delete from relationship where from_id=? and to_id=?
Now the problem arises when trying to delete the second Alpha relationship A3, in which Hibernate does almost the exact same sequence, except for the DELETE query which omits the and to_id=? from the query, i.e.
Hibernate:
delete
from
relationship
where
from_id=?
Which has the unintended consequence of deleting all other A1 relationships in the table, i.e. B1 and B2.
So that is the crux of my problem. It seems like Hibernate is only seeing one other related Alpha record and therefore deciding to simplify the query by omitting the and to_id statement.
I'm probably missing something terribly obvious!
I should also point out that I attempted to use a composite key on the relationship table but to no avail.
This is an unusual design, which I suspect is confusing Hibernate. Sharing a single join table between multiple Many-to-many relationships, isn't good database design, for one it can't have any foreign keys/referential integrity.
Secondly, Hibernate manages relationships, and therefore has control over the #JoinTable, I don't know how it would handle multiple entity relationships mapped with the same table. Evidently, not very well though!
The simplest solution (if you're able to), would be to have 2 mapping tables. One for the relationship between Alpha-Alpha and another between Alpha-Bravo.
I'm using SpringBoot 2.0.2 with hibernate 5.2.17 and MariaDB 10.1
Customer:
#Entity
public class Customer extends Company {
#ManyToOne(fetch = FetchType.LAZY, cascade = ALL)
#JoinTable(name = "company_services",
joinColumns = #JoinColumn(name = "companyId", insertable = true, updatable = true),
inverseJoinColumns = #JoinColumn(name = "serviceId", insertable = true, updatable = true))
private Service service;
Service:
#Entity
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
With Hibernate log enabled I can see this:
Hibernate:
/* insert com.example.company.Customer
*/ insert
into
`
companies` (
...
)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
select
last_insert_id()
Hibernate:
/* insert com.example.company.Customer
*/ insert
into
`
company_service` (
`serviceId`, `companyId`
)
values
(?, ?)
20:43:53.524 TRACE [cid: none] [session: none] org.hibernate.type.descriptor.sql.BasicBinder -- binding parameter [1] as [BIGINT] - [3]
20:43:53.524 TRACE [cid: none] [session: none] org.hibernate.type.descriptor.sql.BasicBinder -- binding parameter [2] as [BIGINT] - [3]
But when I confirm the result in the db the companies table is OK but the company_services record does not get saved.
Is that a known bug? I'm missing something?
Hibernate does not use join table for many to one relationship. It uses a join column for many to one relationship.