Not null constraint violation when flushing unidirectional entities - java

I am playing a little bit with unidirectional and bidirectional mappings using #OneToMany and #ManyToOne annotations, but I cannot break the wall through for unidirectional one when persisting entities and flushing them into the database.
So, two tables delivery_company might have many delivery:
SQL (Oracle):
CREATE TABLE delivery (
delivery_id NUMBER(6) NOT NULL,
price NUMBER(5, 2) NOT NULL,
delivery_time DATE NOT NULL,
delivery_company_id NUMBER(2) NOT NULL
);
ALTER TABLE delivery ADD CONSTRAINT delivery_pk PRIMARY KEY ( delivery_id );
CREATE TABLE delivery_company (
delivery_company_id NUMBER(2) NOT NULL,
delivery_company_name VARCHAR2(15 CHAR) NOT NULL
);
ALTER TABLE delivery_company ADD CONSTRAINT delivery_company_pk PRIMARY KEY ( delivery_company_id );
ALTER TABLE delivery
ADD CONSTRAINT delivery_delivery_company_fk FOREIGN KEY ( delivery_company_id )
REFERENCES delivery_company ( delivery_company_id );
Unidirectional mapping:
#Entity
#Table(name = "Delivery")
class DeliveryUniDirectional
{
#Id
#SequenceGenerator(name = "delivery_id_sequence", sequenceName = "delivery_id_sequence", allocationSize = 1)
#GeneratedValue(generator = "delivery_id_sequence", strategy = GenerationType.SEQUENCE)
#Column(name = "delivery_id")
public Long deliveryId;
public BigDecimal price;
#Temporal(TemporalType.DATE)
public Date deliveryTime;
// setters, getters
}
#Entity
#Table(name = "delivery_company")
class DeliveryCompanyUniDirectional {
#Id
#Column(name = "delivery_company_id")
#SequenceGenerator(name = "delivery_company_id_sequence", sequenceName = "delivery_company_id_sequence", allocationSize = 1)
#GeneratedValue(generator = "delivery_company_id_sequence", strategy = GenerationType.SEQUENCE)
private Long deliveryCompanyId;
#Column(unique = true)
private String deliveryCompanyName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "delivery_id", nullable = false, insertable = false, updatable = false)
private List<DeliveryUniDirectional> deliveries = new LinkedList<>();
// setters getters
}
When I run #DataJpaTest test:
#Test
public void insertDeliveryUniDirectional()
{
DeliveryCompanyUniDirectional deliveryCompany = new DeliveryCompanyUniDirectional();
deliveryCompany.setDeliveryCompanyName("aa");
DeliveryUniDirectional delivery = new DeliveryUniDirectional();
delivery.setPrice(BigDecimal.ONE);
delivery.setDeliveryTime(new Date());
deliveryCompany.getDeliveries().add(delivery);
entityManager.persist(deliveryCompany);
entityManager.flush();
}
I receive
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute batch ...
//
Caused by: java.sql.BatchUpdateException: ORA-01400: cannot insert NULL into ("TESTUSER"."DELIVERY"."DELIVERY_COMPANY_ID")
when entityManager.flush();.
I tried in DeliveryCompanyUniDirectional to use #JoinColumn without insertable and updatable, but in that case hibernate complains:
Error creating bean with name 'entityManagerFactory' defined in class path resource ...
// ...
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: wieczorek.jakub.shop.business.spring.model.domain.DeliveryUniDirectional column: delivery_id (should be mapped with insert="false" update="false")
Definitely there is a problem with NOT NULL constraint for the foreign key in delivery table. When I try it with bidirectional mapping, persisting and flushing work very good, but I would like to achieve the same using unidirectional.
Thanks for reading

Your #JoinColumn should be delivery_company_id since it's your foreign key
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="delivery_company_id", referencedColumnName="delivery_company_id", nullable = false)
private List<DeliveryUniDirectional> deliveries = new LinkedList<>();

Related

SqlExceptionHelper thrown when inserting linked entities in a Postgresql db with Hibernate and JPA

So, I have 2 entities defined in my code, like this:
public class EntityA {
#ManyToOne(cascade = ALL, fetch = LAZY, optional = false)
#JoinColumn(name = "entity_b_id", nullable = false)
private EntityA entityA;
}
public class EntityB {
#Id
#SequenceGenerator(name = "seq_entity_b", sequenceName = "seq_entity_b", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_entity_b")
#Column(name = "id", unique = true, nullable = false)
private Long id;
[...other stuff...]
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_entity_b_external_id")
#SequenceGenerator(name = "seq_entity_b_external_id", sequenceName = "seq_entity_b_external_id", allocationSize = 1)
#Column(name = "external_id", nullable = false, unique = true)
private Long externalId;
}
Then I have a dao layer defined like this:
#Repository
public class EntityADao {
#PersistenceContext
private EntityManager entityManager;
public void save(EntityA entityA) {
entityManager.persist(entityA);
entityManager.flush();
}
}
When this code is triggered, I get this exception thrown though:
2021-02-18 13:40:21.434 ERROR 17530 --- [tp1239821079-39] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "external_id" of relation "entity_b" violates not-null constraint
Detail: Failing row contains (111111, aaaaaaaa, Aaaaaa, 111111, AAAAAA, 1, VALUE, null).
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [external_id" of relation "entity_b]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
Notice how the last value is null, that one should be the external_id. When I turn on the sql logs for jpa, I do not see anywhere the seq_entity_b_external_id called (I expected something like select next_val('seq_entity_b_external_id').
Can anyone help me solve this issue? Thanks in advance.
Thanks to #Christian Beikov answer, I got an hint. The error I did was to use multiple sequences on an entity, which is something you cannot do. I solved the issue following the solution in this post

Hibernate OneToOne between PK's with lazy behaviour

I'm trying to achieve to have an entity called MyEntity along with another entity called MyEntityInfo using Hibernate 5.3.13.Final with annotations under Wildfly 18.
The idea is to have MyEntity store some commonly requested fields, and MyEntityInfo store some rarely requested fields. Both share the same primary key called SID (Long), and there is a FK from Info's SID to Entity's SID. There can be entities without info.
Normally you will not require the additional info. For example, I don't want the info entity to be fetched when I query my entity like this:
MyEntityImpl entity = em.find(MyEntityImpl.class, 1L);
However, when I run this code, I find that there's a second query, fetching the Info entity along the main one, as in an EAGER behaviour.
I'm mapping the relationship using #OneToOne. I've tried several combinations of FetchType, optional and #LazyToOne, but so far without success.
Here is the code for both MyEntity and MyEntityInfo classes (additional getters and setters removed):
MyEntity (ID generator is a custom sequence generator):
#Entity
#Table(name = MyEntityImpl.TABLE_NAME)
public class MyEntityImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY";
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_" +
TABLE_NAME)
#GenericGenerator(name = "GEN_" +
TABLE_NAME, strategy = CoreIdGenerator.ID_GENERATOR, parameters = {
#Parameter(name = "tableName", value = TABLE_NAME) })
#Column(name = "sid", nullable = false, unique = true)
private Long sid;
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#LazyToOne(LazyToOneOption.NO_PROXY)
private MyEntityInfoImpl info;
#Column
private String field;
MyEntityInfo:
#Entity
#Table(name = MyEntityInfoImpl.TABLE_NAME)
public class MyEntityInfoImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY_INFO";
#Id
#Column(name = "SID", nullable = false, unique = true)
private Long sid;
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;
#Column(name = "INFO_FIELD")
private String infoField;
I've tried this solution, but as I said, it didn't work for me:
Hibernate lazy loading for reverse one to one workaround - how does this work?
I've managed to do something somewhat similar using #OneToMany and managing data manually, but that's not what I'd like to do. However, another alternatives and information on whether this can be achieved or not using #OneToOne, or the right design pattern to do this are also welcome.
PS: Database tables creation for SQL Server, in case you want to try it:
create table TMP_MY_ENTITY (SID NUMERIC(19,0) NOT NULL, FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY ADD CONSTRAINT PK_TMP_MY_ENTITY PRIMARY KEY CLUSTERED (SID);
go
create table TMP_MY_ENTITY_INFO (SID NUMERIC(19,0) NOT NULL, INFO_FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY_INFO ADD CONSTRAINT PK_TMP_MY_ENTITY_INFO PRIMARY KEY CLUSTERED (SID);
go
CREATE SEQUENCE SEQ_TMP_MY_ENTITY START WITH 1 INCREMENT BY 1 MINVALUE 1 CACHE 20;
alter table TMP_MY_ENTITY_INFO add constraint FK_TMP_MY_ENT_INFO_MY_ENT FOREIGN KEY (SID) references TMP_MY_ENTITY(SID);
go
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 1');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 1');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 2');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 2');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 3 no info');
-- DELETE ALL
drop table TMP_MY_ENTITY_INFO;
drop table TMP_MY_ENTITY;
drop sequence SEQ_TMP_MY_ENTITY;
After following #SternK link, and upgrading to Wildfly 19 and Hibernate 5.4.14, it finally worked by using #MapsId.
The right mapping to use is this:
MyEntity:
public class MyEntityImpl {
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "SID")
private MyEntityInfoImpl info;
MyEntityInfo:
public class MyEntityInfoImpl {
#OneToOne(fetch = FetchType.EAGER, optional = false)
#MapsId
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;

hibernate one to one relationship mapping, save only inserts child

I have the following relationship with person and transaction (one to one in my case). I want to be able to save a Person with a Transaction attached resulting in two inserts. One in tbl_person and one in tbl_Transaction. But the following only generates one insert instead of two. The one insert is in tbl_Transaction:
`CREATE TABLE `tbl_person` (
`ID` char(36) NOT NULL,
`TransactionID` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `TransactionID` (`TransactionID`),
CONSTRAINT `tbl_person_ibfk_1` FOREIGN KEY (`TransactionID`)
REFERENCES `tbl_Transaction` (`TransactionID`)
);
CREATE TABLE `tbl_transaction` (
`TransactionID` int(11) NOT NULL,
PRIMARY KEY (`TransactionID`)
);
#Table(name="tbl_person")
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#ToString
#Data
public class Person {
#Id
#GeneratedValue(generator = "hibernate-uuid")
#GenericGenerator(name = "hibernate-uuid", strategy = "uuid2")
#Column(name="ID", nullable = false)
private String ID;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "transactionId")
private Transaction transaction;
}
#Table(name="tbl_transaction")
#Entity
#Data
public class Transaction {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer transactionId;
}
public class Service() {
public void saveTransaction(Transaction transaction) {
Person person = new Person();
person.setTransaction(transaction);
getSessionCurrent().save(person);
}
}
`
service.saveTransaction(transaction);
The service.saveTransaction returns with no exception but it only inserts the transaction but not the person.
Can any one tell me what I am doing wrong ??
you need to define a #OneToOne field in Transaction class
like specified in this question:
#OneToOne bidirectional mapping with #JoinColumn
and then add this line:
transcation.setPerson(person);

Find Hibernate objects without many to many relationship

I need to find all the products that do not contain a specific allergen using Hibernate.
Here is the SQL used to create the database tables:
CREATE TABLE ALLERGEN (id integer IDENTITY PRIMARY KEY, name varchar(20), UNIQUE (id), UNIQUE(name));
CREATE TABLE PRODUCT (id integer IDENTITY PRIMARY KEY, name varchar(20), UNIQUE (id), UNIQUE(name));
CREATE TABLE PRODUCT_ALLERGEN (product_id integer, allergen_id integer, UNIQUE (product_id, allergen_id), FOREIGN KEY (product_id) REFERENCES PRODUCT (id), FOREIGN KEY (allergen_id) REFERENCES ALLERGEN (id));
Here are the Hibernate annotated Java classes:
#Entity
#Table(name = "ALLERGEN")
class Allergen {
#Id
#Column(unique = true, nullable = false)
#GeneratedValue
private Integer id;
private String name;
// ...
}
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#Column(unique = true, nullable = false)
#GeneratedValue
private Integer id;
private String name;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = {#JoinColumn(name = "allergen_id")})
private final Set<Allergen> allergens = new HashSet<>();
// ...
}
This SQL appears to give me the result I want, but I don't see how to represent it using Hibernate criteria.
SELECT * FROM PRODUCT WHERE (SELECT COUNT(*) FROM PRODUCT_ALLERGEN WHERE product_id = PRODUCT.id AND allergen_id = 0) = 0;
With the Criteria API you should be able to get all Product without Allergens by creating a left join from Product to Allergen and checking if it is null:
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Product> c = builder.createQuery(Product.class);
final Root<Product> root = c.from(Product.class);
Join<Product, Allergen> allergenJoin = root.join("allergens", JoinType.LEFT);
c.where(builder.isNull(allergenJoin));
c.select(root);
List<Product> = entityManager.createQuery(c).getResultList();
Note: I didn't include where you get the EntityManager from. Usually I use injection for that, but there are other methods like using a factory.
This code uses JPQL to get the products without a specific allergen.
List<Product> results = manager.createQuery(
"SELECT p from Product AS p WHERE (SELECT COUNT(a) FROM p.allergens a WHERE a.name = :an) = 0",
Product.class)
.setParameter("an", "nuts")
.getResultList();

Java hibernate: Update foreign key in manytoone relationship automatically

I have a problem with the auto update of foreign keys which appears as following:
I have a two tables HamKeyword and HamKeywordAlias. One entry in the hamKeyword has 0…n entries in HamKeywordAlias. This relationship is reflected with a foreign key field in the HamKeywordAlias table. Both tables have their own primary keys. I defined the two tables using reverse engineering of hibernate eclipse tools as follows:
#Entity
#Table(name = "HAM_KEYWORDS")
public class HamKeywords implements java.io.Serializable {
private long keywordid;
private String keyword;
…
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "KEYWORDID", unique = true)
public long getKeywordid() {
return this.keywordid;
}
…
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "hamKeywords")
public Set<HamKeywordsAlias> getHamKeywordsAliases() {
return this.hamKeywordsAliases;
}
#Entity
#Table(name = "HAM_KEYWORDS_ALIAS", schema = "dbo", catalog = "ham")
public class HamKeywordsAlias implements java.io.Serializable {
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(generator="gen")
#Column(name = "ALIASID", unique = true, nullable = false)
public long getAliasid() {
return this.aliasid;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "KEYWORDID", nullable = false, updatable = false, insertable = true)
public HamKeywords getHamKeywords() {
return this.hamKeywords;
}
Now to my problem. I try to add a new entry to HamKeyword with 1 new related HamKeywordAlias:
HamKeywords hkw = new HamKeywords();
HamKeywordsAlias hka = new HamKeywordsAlias();
hka.setAlias("new alias");
hkw.setHamKeywordsAliases(new HashSet<HamKeywordsAlias>());
Set<HamKeywordsAlias> hkaS = hkw.getHamKeywordsAliases();
hkaS.add(hka);
hkw.setHamKeywordsAliases(hkaS);
session.flush();
session.save(hkw);
session.getTransaction().commit();
This code fails with the error message:
ERROR: The value NULL can not be inserted in table 'KEYWORDID'-Spalte, 'ham.dbo.HAM_KEYWORDS'. No NULL allowed for INSERT. Exception in thread "main" org.hibernate.exception.ConstraintViolationException: could not execute statement
(Please note that I translated the error message into english, it might be a bit different languagewise)
Obviously, the foreign key in field KEYWORDID of the HamKeywordAlias table is not be updated. I double checked this by removing the NOT NULL constraint. What happens is, that the enty into the ALIAS table is inserted but with a NULL in the field keywordid.
I tested furthermore adding manually rows into the HamKeywordAlias table. Retrieving an entry of the HamKeyword table and retrieving the related Aliases with following code works great:
HamKeywords hamCurrentKeyword = (HamKeywords) session.get(HamKeywords.class, (long)1);
hamCurrentKeyword.getHamKeywordsAliases();
Thus I assume that I defined the many to one relation correctly. However, the foreign key is not updated automatically.
Can you assist me why this is not be done?
Thanks
Felix
You have a bidirectional OneToMany association. The owner of the association is the Many side: HamKeywordsAlias.hamKeywords. That's the side that Hibernate cares about. But you didn't initialize it. You added an alias to the keywords' collection of aliases, but failed to set the keywords of the alias:
hka.setHamKeywords(hkw);

Categories

Resources