Hibernate (on postgres) dropping Schema from #JoinTable - java

I'm using Hibernate 4.3.10.Final (with SpringData JPA) running on a Postgres 4 database and have run into a very strange bug. Our app utilizes a database outside of the default "public" schema, and when we try to insert data Hibernate drops the correct schema.
Our model consists of an abstract "Log" class that uses single class inheritance to allow many different object types to insert a associated log message. See code below.
The schema already exists (hibernate doesn't create it) and booting validation runs fine, but when try to insert a new record we get the error relation "booking_log" does not exist -- which is missing the schema modifier (say customapp for our purposes). See the first line from the logs below to get an idea of what other insert statements look like.
I've dug through the mapping phase and verified Hibernate is indeed picking up the schema from the #JoinTable annotation, but not sure how we're losing it.
Any help debugging or possible solutions would be appreciated.
Thanks!
Log - Abstract super class
#MappedSuperclass
#Table(name="log", schema=Constants.DB_SCHEMA)
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="log_type_id", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Log {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="log_seq_gen")
#SequenceGenerator(allocationSize = 1, name="log_seq_gen", sequenceName=Constants.DB_SCHEMA + ".log_id_seq")
private Long id;
// ...
}
BookingLog
#Entity
#DiscriminatorValue("2")
public class BookingLog extends Log implements TenantResource<Company,Long> {
#JoinTable(name="booking_log",
schema = Constants.DB_SCHEMA,
joinColumns = { #JoinColumn(name="log_id", referencedColumnName="id", nullable=false, updatable=false)},
inverseJoinColumns = { #JoinColumn(name="booking_id", referencedColumnName="id", nullable=false, updatable=false)})
#ManyToOne(cascade=CascadeType.ALL)
private Booking booking;
///...
}
** Logs **
2015-07-20_18:14:09.055 DEBUG org.hibernate.SQL - insert into customapp.booking_product (created_dt, created_by, modified_dt, modified_by, include_in_payroll, include_in_revenue, booking_id, description, payroll_percent, price, product_id, qty, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2015-07-20_18:14:09.072 DEBUG org.hibernate.SQL - insert into booking_log (log_date, details, log_time, user_id, booking_id, id) values (?, ?, ?, ?, ?, ?)
2015-07-20_18:14:09.176 DEBUG o.h.e.jdbc.spi.SqlExceptionHelper - could not execute statement [n/a]
org.postgresql.util.PSQLException: ERROR: relation "booking_log" does not exist

Based on your #JoinTable configuration and the insert statement that hibernate generates looks like the problem is the way you are triyng to add extra fields/data to the booking_log table.
I would need more details about your model to be sure but I think you are using a join-table and something else instead of create a class that models the join-table.
I mean, you have this
BookingLog (*) --------------------------------------> (1) Booking
but I think you really need this
BookingLog (1) ---> (1) BookingLogAssociation (*) ---> (1) Booking
Then the mapping will be like this,
#Entity
#DiscriminatorValue("2")
public class BookingLog extends Log implements TenantResource<Company,Long> {
#OneToOne(mappedBy="bookingLog")
private BookingLogAssociation booking;
}
Note the attributes of BookingLogAssociation, they are the extra field/data you want to add in booking_log table.
#Entity
#Table(name="booking_log")
#IdClass(BookingLogAssociationId.class)
public class BookingLogAssociation {
#Id
private long log_id;
#Id
private long booking_id;
#OneToOne
#PrimaryKeyJoinColumn(name="log_id", referencedColumnName="id")
private BookingLog bookingLog;
#ManyToOne
#PrimaryKeyJoinColumn(name="booking_id", referencedColumnName="id")
private Booking booking;
#Column(name="log_date")
#Temporal(TemporalType.DATE)
private Calendar logDate;
#Column(name="log_time")
#Temporal(TemporalType.TIME)
private Calendar logTime;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
// Could be just an attribute too
//#Column(name="user_id")
//private long userId;
...
}
The BookingLogAssociationId class that represents the BookingLogAssociation's composite key.
public class BookingLogAssociationId implements Serializable {
private long log_id;
private long booking_id;
public int hashCode() {
return (int)(log_id + booking_id);
}
public boolean equals(Object object) {
if (object instanceof BookingLogAssociationId) {
BookingLogAssociationId otherId = (BookingLogAssociationId) object;
return (otherId.log_id == this.log_id) && (otherId.booking_id == this.booking_id);
}
return false;
}
You can read more about this option here

Related

Hibernate uploads data with #OneToMany and #ManyToOne but the foreign key remains Null

Code for Module:
#Data
#NoArgsConstructor
#ToString
#Entity(name = "modules")
#Table(name = "modules")
public class Module {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "module_id")
private int moduleId;
#Column(name = "module_name")
private String moduleName;
#Column(name = "module_code")
private String moduleCode;
#Column(name = "moderator_lecturer")
private String moderatorLecturerId;
#Column(name = "secondary_lecturer")
private String secondaryLecturerId;
#OneToMany(mappedBy = "foreignModuleId", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Assessment> assessments;
public void addAssessment(Assessment assessment) {
assessments.add(assessment);
}
}
Code for Assignment
#Data
#NoArgsConstructor
#ToString
#Entity(name = "assessments")
#Table(name = "assessments")
public class Assessment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "assessment_id")
private int assessmentId;
#Enumerated(EnumType.STRING)
#Column(name = "assessment_type")
private AssessmentType assessmentType;
#Column(name = "assessment_weight")
private int assessmentWeight;
#Column(name = "assessment_weeks")
private String weeks;
#Column(name = "assessment_upload_date")
private LocalDate uploadDate;
#Column(name = "assessment_deadline_date")
private LocalDate deadlineDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "assessment_belongsTo_module", referencedColumnName = "module_id")
private Module foreignModuleId;
}
A Module can have many Assessments hence why I chose these annotations.
I firstly extract those data from an excel file and combine them in a list (that is passed later on as an argument called "modules"). The list is of the form:
Module(moduleId=0, moduleName=Programming Principles and Algorithms , moduleCode= CCS1110, moderatorLecturerId=Dr Stamatopoulou, secondaryLecturerId= Dr Efremidis, assessments=[Assessment(assessmentId=0, assessmentType=ASSESSED_LAB, assessmentWeight=35, weeks=00001000000000000, uploadDate=null, deadlineDate=null, foreignModuleId=null), Assessment(assessmentId=0, assessmentType=ASSESSED_LAB, assessmentWeight=65, weeks=00000000000000001, uploadDate=null, deadlineDate=null, foreignModuleId=null)])
Module(moduleId=0, moduleName=Programming Methodology and Design, moduleCode= CCS1115, moderatorLecturerId=Dr Stamatopoulou, secondaryLecturerId= Dr Efremidis, assessments=[Assessment(assessmentId=0, assessmentType=PROJECT, assessmentWeight=35, weeks=00000000000000100, uploadDate=null, deadlineDate=null, foreignModuleId=null), Assessment(assessmentId=0, assessmentType=ASSESSED_LAB, assessmentWeight=65, weeks=00000000000000001, uploadDate=null, deadlineDate=null, foreignModuleId=null)])
Then I upload the list on the database:
#NoArgsConstructor
#Data
public class AppDAOImpl implements AppDAO{
private SessionFactory factory;
public void upload(List<com.project.model.Module> modules) {
Session currentSession = factory.getCurrentSession();
try {
currentSession.beginTransaction();
for(Module module : modules) {
currentSession.save(module);
}
currentSession.getTransaction().commit();
}
finally {
currentSession.close();
factory.close();
}
}
}
When I execute Hibernate create queries of form:
Hibernate: insert into modules (moderator_lecturer, module_code, module_name, secondary_lecturer) values (?, ?, ?, ?)
Hibernate: insert into assessments (assessment_type, assessment_weight, assessment_deadline_date, assessment_belongsTo_module, assessment_upload_date, assessment_weeks) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into assessments (assessment_type, assessment_weight, assessment_deadline_date, assessment_belongsTo_module, assessment_upload_date, assessment_weeks) values (?, ?, ?, ?, ?, ?)
But in the database on the table for Assessments, the field assessment_belongsTo_module is null.
My database has this form:
I have tried a lot of things and cannot fix the problem. I have also read similar threads and still nothing. Maybe there is a problem in the way I have created the fields on each table in the db (e.g. the foreign key)?
Do not use the name Module for a class, say MModule. It is confused with the java.lang.Module.
Create JpaRepository MModuleRepository. Then in a controller, write simply something like
for(MModule module :modules){
moduleRepository.save(module);
}
The problem that was causing this was related to the fact that I did not initialize the attribute "foreignModuleId" in the Assessment class. So when I extract the data from my excel at some point I have the lines:
assessment.setForeignModuleId(module); // I add the module in which the assessment belongs.
module.addAssessment(assessment); // I then add that assessment to the #OneToMany assessments List
I also fixed a stackOverFlow exception that was caused whenever I did operations like retrieving those data from the db, by including this in the Assessment class:
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "assessment_belongsTo_module", referencedColumnName = "module_id")
private Module foreignModuleId = new Module();
It is very important because Lombok's toString causes a recursive call which then results in the previously mentioned exception.

Moving #ElementCollection to #Embeddable class not working

I am trying to move an #ElementCollection annotated field from my #Entity class into a different class that I annotated with #Embeddable. When the field is in the entity, Hibernate will issue the proper INSERT statements. When the field is moved to the #Embeddable class, no INSERT statements are generated.
So this works:
#Entity
public class MyEntity extends AbstractEntity<MyEntityId> {
#ElementCollection
#OrderColumn
private List<Double> doubles;
}
But this does not:
#Entity
public class MyEntity extends AbstractEntity<MyEntityId> {
#Embedded
private Doubles doubles;
}
#Embeddable
public class Doubles {
#ElementCollection
#OrderColumn
private List<Double> doubles;
}
There is no exception when saving a MyEntity instance (and flushing the EntityManager). If I disable creating the appropriate table in Flyway, the Hibernate validation does trigger a "missing table [my_entity_doubles]" error, so Hibernate does seem to correct interpret the setup.
Using Spring Boot 2.3.4 that uses Hibernate 5.4.21.
UPDATE: There is a reproducible testcase at https://github.com/wimdeblauwe/so-64336648
I don't know why. But assigning with a collection works.
#Embeddable
public class Doubles {
#ElementCollection
private List<Double> doubles = new ArrayList<>();
}
As it stated in the JPA specification (see 2.5 Embeddable Classes section):
An embeddable class (including an embeddable class within another embeddable class) may contain a collection of a basic type or other embeddable class. Direct or indirect circular containment dependencies among embeddable classes are not permitted.
And simple example (based on Hibernate 5.4.21).
Assuming that we have the following schema:
create table T1_MY_ENTITY
(
tm_id int not null,
primary key (tm_id)
);
insert into T1_MY_ENTITY values (1);
create table T1_MY_COLL
(
tc_tm_id int not null,
tc_ord_id int not null,
tc_code varchar(100),
foreign key (tc_tm_id) references T1_MY_ENTITY(tm_id)
);
insert into T1_MY_COLL values (1, 1, 'CL1'), (1, 0, 'CL2');
and the following mapping:
#Entity
#Table(name = "T1_MY_ENTITY")
public class MyEntity
{
#Id
#Column(name = "tm_id")
private Long id;
#Embedded
private Doubles dou;
// constructor, getters, setters
}
#Embeddable
public class Doubles
{
#OrderColumn(name = "tc_ord_id")
#ElementCollection
#CollectionTable(name = "T1_MY_COLL", joinColumns = #JoinColumn(name = "tc_tm_id"))
#Column(name = "tc_code")
private List<String> colls;
// constructor, getters, setters
}
the following code:
MyEntity myEntity = entityManager.find(MyEntity.class, 1L);
myEntity.getDou().getColls().add("CL5");
entityManager.merge(myEntity);
will generate insert:
19:51:29,612 DEBUG SQL:128 -
/* insert collection
row com.my.hibernate.entities.MyEntity.dou.colls */ insert
into
TEST_SCHEMA.T1_MY_COLL
(tc_tm_id, tc_ord_id, tc_code)
values
(?, ?, ?)
Hibernate:
/* insert collection
row com.my.hibernate.entities.MyEntity.dou.colls */ insert
into
TEST_SCHEMA.T1_MY_COLL
(tc_tm_id, tc_ord_id, tc_code)
values
(?, ?, ?)
19:51:29,612 TRACE ResourceRegistryStandardImpl:83 - Registering statement [/* insert collection row com.my.hibernate.entities.MyEntity.dou.colls */ insert into TEST_SCHEMA.T1_MY_COLL (tc_tm_id, tc_ord_id, tc_code) values (?, ?, ?)]
19:51:29,612 TRACE BasicBinder:64 - binding parameter [1] as [BIGINT] - [1]
19:51:29,613 TRACE BasicBinder:64 - binding parameter [2] as [INTEGER] - [2]
19:51:29,613 TRACE BasicBinder:64 - binding parameter [3] as [VARCHAR] - [CL5]
So, It looks like your problem lies in the other plane.
UPDATE: As for your test case, please try to correct it in the following way:
#Test
void test() {
MyEntity2 entity = new MyEntity2();
Doubles doubles = new Doubles();
doubles.setDoubles(List.of(1.0, 1.5));
entity.setDoubles(doubles); // It was absent !!!
repository.save(entity);
entityManager.flush();
assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM my_entity2_doubles", Long.class)).isEqualTo(2);
}

One To Many referencing an entity mapped with Inheritance mapping leads to insert, then update query

I am using Hibernate / JPA 2.1 for persistence in my project. I try to save a FinancialInstrument, which has an embedded field interestRate, which has several PlanSteps The following mapping is set up:
#Entity
#Table(name = "tbl_financial_instrument")
public class FinancialInstrument {
#Embedded
private InterestRate interestRate;
// ...
}
#Embeddable
public class InterestRate {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "ir_id")
private Set<InterestPaymentPlanStep> interestPaymentPlanSteps = new LinkedHashSet<>();
// ...
}
The plan steps can be reused by different classes, I use inheritance here (single table type).
#Entity
#DiscriminatorValue("INTEREST_PAYMENT")
public class InterestPaymentPlanStep extends PlanStep {
// ...
}
#Entity
#Table(name = "tbl_plan_step")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "PLAN_STEP_TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class PlanStep extends AbstractBaseObject {
// ...
}
I have filled a Financial Instrument with an InterestRate, containing plan steps. Now I'm trying to persist everything to the database I use this code:
private void persistFinancialInstrument(FinancialInstrument financialInstrument) {
financialInstrument = financialInstrumentRepository.save(financialInstrument);
if (financialInstrument.getInterestRate() != null) {
Set<InterestPaymentPlanStep> interestRatePaymentPlanSteps = financialInstrument.getInterestRate().getInterestPaymentPlanSteps();
for (PlanStep planStep : interestRatePaymentPlanSteps) {
planStepRepository.save(planStep);
}
}
}
The strange thing now is that when I turn on the logging of my queries I see that it executed 2 queries for persisting the plan steps:
This is the first one, note that it does not contain the ID of the financial instrument while I already would expect it here:
insert into tbl_plan_step (creation_datetime, modification_datetime, amount, anchor_date, cap, cycle, floor, rate_add, rate_value, plan_step_type, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, 'INTEREST_PAYMENT', ?)
And the second one:
update tbl_plan_step set ir_id=? where id=?
I have no clue why the saving is being executed in 2 queries. Can anyone find an explanation for this?
This likely is due to PlanStep not having the relation to InterestRate. Make the link bidirectional.
#ManyToOne
private InterestRate interestRate
in PlanStep to perform a single insert.
see hibernate documentation for extensive explanation

jpa unidirectional One-to-Many with a Collection of Abstract type

I'm having an issue with JPA. Basically, what I have is an entity with a list of an abstract type and I need each element of the list to be persisted in its corresponding table with a foreign key (to relate to the entity). Here is the code:
#Entity(name = "entity")
public class Entity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private BigInteger id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="entity_id", referencedColumnName="id")
private List<AbstractType> types;
}
Abstract type:
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractType{
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private BigInteger id;
private String someProp;
#Column(name="entity_id")
private BigInteger entityId;
}
Type A:
#Entity(name = "type_a")
#Transactional
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class TypeA extends AbstractType{
private String prop1;
private String prop2;
}
Type B:
#Entity(name = "type_b")
#Transactional
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class TypeB extends AbstractType{
private String prop3;
private String prop4;
}
I'm having a SQL error. The generated query tries to update the table of the abstract type (which shouldnt exist). This is part of the query:
update hibernate_sequences set sequence_next_hi_value = ? where
sequence_next_hi_value = ? and sequence_name = 'abstract_type'
insert into type_a (some_prop, entity_id, prop1, prop2) values (?, ?, ?, ?)
insert into type_b (some_prop, entity_id, prop3, prop4) values (?, ?, ?, ?)
update abstract_type set entity_id=? where id=?
As you can see, it's trying to update a table which doesn't (and shouldnt) exist. 'abstract_type' table.
Thanks in advance.
Of course you can't name a class Entity without causing problems.
You can make this much simpler by minimizing the annotations and let JPA do it's work. A Container class holds a collection of AbstractTypes:
#Entity
public class Container {
#Id #GeneratedValue
private Long id;
#OneToMany(cascade=CascadeType.ALL)
private List<AbstractType> types;
public List<AbstractType> getTypes() { return types; }
public void setTypes(List<AbstractType> types) { this.types = types; }
}
The AbstractType is just that:
#Entity
public abstract class AbstractType {
#Id #GeneratedValue
private Long id;
}
And a couple concrete types to extend and inherit from the abstract superclass:
EDIT: A FK back to the Container class can be added with a ManyToOne association.
#Entity
public class TypeA extends AbstractType {
#ManyToOne
private Container container;
}
And:
#Entity
public class TypeB extends AbstractType {
#ManyToOne
private Container container;
}
When I run this Minimal, Complete, Verifiable Example I get the following output:
Hibernate: create table AbstractType (DTYPE varchar(31) not null, id bigint not null, container_id bigint, primary key (id))
Hibernate: create table Container (id bigint not null, primary key (id))
Hibernate: create table Container_AbstractType (Container_id bigint not null, types_id bigint not null)
Hibernate: insert into Container (id) values (?)
Hibernate: insert into AbstractType (container_id, DTYPE, id) values (?, 'TypeA', ?)
Hibernate: insert into AbstractType (container_id, DTYPE, id) values (?, 'TypeB', ?)
Hibernate: insert into Container_AbstractType (Container_id, types_id) values (?, ?)
Hibernate: insert into Container_AbstractType (Container_id, types_id) values (?, ?)
Hibernate: select container0_.id as id1_1_ from Container container0_
model.Container#40591559

ID from parent to child in hibernate

Here is my code. I would like to generate an automatic ID based on parent class. I'm using a method to create Airport, so my ID it's coming with is a null value. ID in AirportModel will be generated, but I don't know how to make it in child class.
#Entity(name = "Airport")
#Table(name = "ai_airport")
public class AirportModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "airport_id")
private List<AirportTranslatedModel> translations;
Second class(child):
#Entity(name = "AirportTranslated")
#IdClass(AirportTranslatedModelKey.class)
#Table(name = "ai_translated_airport")
public class AirportTranslatedModel
#Id
#Column(name="airport_id")
private Long airportId;
#Id
#Column(name="language_code", length=2)
private String languageCode;
Third one(keys):
#Embeddable
public class AirportTranslatedModelKey implements Serializable {
#Column(name="airport_id")
private Long airportId;
#Column(name="language_code", length=2)
private String languageCode;
I still got the same errors; log:
Hibernate: insert into ai_airport (active, airport_code, city_code, country_code, externa
l_id, is_default, latitude, longitude, market_code, min_connection_time_DD, min_connection_time_DI, min_connection_time_id, min_connection_time_II, time_diff, VERSION) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into ai_translated_airport (airport_long_name, airport_short_name, airp
ort_id, language_code) values (?, ?, ?, ?)
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Column 'airport_id' cannot be null
Your current setup has the AirportTranslatedModel airport_id field mapped through a Long- you will need to set the airportId manually to have it set the id in the database. This will likely require that you persist AirportModel and possibly flush to have its PK assigned and available prior to making the AirportModel->AirportTranslatedModel association, so that you can then set the AirportTranslatedModel.airportId.
JPA 2 though allows derived Ids. If you want AirportTranslatedModel to have its ID assigned from AirportModel, it needs to have a relationship to it. There is a simple example at http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/DerivedIdentifiers
If you were to model your classes in a similar fashion, it might look like:
public class AirportModel {
..
#OneToMany(mappedby="airportModel", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AirportTranslatedModel> translations;
..
}
public class AirportTranslatedModel {
#Id
#JoinColumn(name="airport_id")
private AirportModel airportModel;
#Id
#Column(name="language_code", length=2)
private String languageCode;
..
}
public class AirportTranslatedModelKey implements Serializable {
private Long airportModel;
private String languageCode;
}
Notice that there is no need to make the AirportTranslatedModelKey and embeddable if you are just using it as a pk class. Also note that the AirportTranslatedModelKey contains a Long airportModel - this must match the type of the pk in the AirportModel, and the name of the relationship property in AirportTranslatedModel.
This will allow AirportTranslatedModel to pull the airport_id value from AirportModel and use it as its PK even though it might not have been generated yet when both entities are still new.

Categories