jpa generated schema doesn't include property of extended class - spring

I've a very complex database which i will try to resume in here
#Embeddable
open class ChargeableDTO(
#NotBlank var name: String,
#NotBlank var ref: String,
#Min(1) var priceCents: Int,
#NotNull #Min(1) #Max(12) var maxInstallments: Int = 1,
#NotNull var gateway: PaymentGateway) {
#Embeddable
class CreditPackageDTO(name: String,
ref: String,
priceCents: Int,
maxInstallments: Int = 1,
gateway: PaymentGateway,
#Min(1) var creditAmount : Int) : ChargeableDTO(name, ref, priceCents, maxInstallments, gateway) {
#Entity
#Table(name = "credit_packages", uniqueConstraints = [UniqueConstraint(columnNames = ["gateway", "ref"])])
class CreditPackage(dto: CreditPackageDTO) : ChargeableEntity(dto)
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class ChargeableEntity(#field:Embedded open var dto: ChargeableDTO) : HydraEntity()
many other classes that are not relevant to this problem....
but when running the schema generation script hibernate generates a code like
create table credit_packages (
id bigint not null,
created_date datetime(6),
last_modified_date datetime(6),
public_id varchar(255),
gateway integer,
max_installments integer not null,
name varchar(255),
price_cents integer not null,
ref varchar(255),
primary key (id)
) engine=InnoDB
the first 4 fields come from a parent class which all my entities inherit from.
but this schema complete ignores the property creditAmount which is defined in the extended dto
also this code doesn't metion the limit 1 to 12 for maxinstallments
am i doing anything wrong, how can i fix it?

Related

Error while testing with H2 database with Reactive Spring boot

I am using Reactive Spring boot to create a REST API and while the API works perfectly (I have tested it with postman), I also need to write some Unit tests and for the Unit tests I need to use a seperate in-memory database for it. I am using H2 database for testing and a hosted Postgres DB for the actual application and while my application entities are being loaded successfully in the postgres DB, None of my tests are passing as I receieve the following error:
Failed to execute SQL script statement #1 of class path resource [data.sql]: CREATE TABLE IF NOT EXISTS full_connection ( id BIGINT PRIMARY KEY , aa TEXT NOT NULL, fip TEXT NOT NULL, status TEXT, created_on TIMESTAMP, created_by TEXT, last_updated_on TIMESTAMP, last_updated_by TEXT ); nested exception is java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer (java.lang.Long and java.lang.Integer are in module java.base of loader 'bootstrap')
I am assuming the error is being thrown because of the id paramater which has the datatype BIGINT which the official documentation of H2 says is mapped to java.long.Long.
Here's my entity:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#Table
public class FullConnection {
#Id
#Column(name = "id")
private Long id;
#Column(name = "AA", nullable = false)
private String aa;
#Column(name = "FIP", nullable = false)
private String fip;
private String status;
private LocalDateTime createdOn;
private String createdBy;
private LocalDateTime lastUpdatedOn;
private String lastUpdatedBy;
}
Here's how I am initializing the data:
#Bean
ConnectionFactoryInitializer initializer(#Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
ResourceDatabasePopulator resource =
new ResourceDatabasePopulator(new ClassPathResource(dataInitializerName));
initializer.setDatabasePopulator(resource);
return initializer;
}
where it's reading the dataInitializerName string from application-test.yaml and is being read correctly, the script (for testing)(the script for prod and testing are different) is:
CREATE TABLE IF NOT EXISTS full_connection (
id BIGINT PRIMARY KEY ,
aa TEXT NOT NULL,
fip TEXT NOT NULL,
status TEXT,
created_on TIMESTAMP,
created_by TEXT,
last_updated_on TIMESTAMP,
last_updated_by TEXT
);
Here: https://www.h2database.com/html/datatypes.html#bigint_type it clearly states that BIGINT is mapped to java.long.Long, then why is it trying to cast it to int? Or is it regarding some other field?
I had a similar problem. Explicitly defining the version for the dependency r2dbc-h2 in pom.xml is causing the problem. I removed the version so that springboot will automatically use the compatible version of r2dbc-h2, and then the problem resolved.

Get list of database errors on saveAll()

I have a CountryObject:
#Entity
#Table(name = "country")
class CountryObject(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
val id: Long? = null,
#Column(name = "name")
val name: String,
#Column(name = "ISO2", length = 2)
val ISO2: String,
#Column(name = "ISO3", length = 3)
val ISO3: String
) : Serializable
with the corresponding PostgreSQL create script that has some unique constraints on ISO2 and ISO3:
CREATE TABLE country
(
id BIGINT NOT NULL,
name VARCHAR(255) NOT NULL,
iso2 VARCHAR(2) NOT NULL,
iso3 VARCHAR(3) NOT NULL,
CONSTRAINT pk_country PRIMARY KEY (id)
);
ALTER TABLE country
ADD CONSTRAINT UNIQUE_CONSTRAINT_ISO2 UNIQUE (iso2);
ALTER TABLE country
ADD CONSTRAINT UNIQUE_CONSTRAINT_ISO3 UNIQUE (iso3);
Along with this I have a corresponding repository:
#Repository
interface CountryRepository : JpaRepository<CountryObject, Long>
And out of the box implementation of the CountryRepository has a saveAll() method that tries to insert a list of objects into the country table. So far, everything behaves as expected.
Once I try to insert a list that have multiple objects violating the unique constraints, the exception is thrown on the first and stopped. What I want to have is to get some list with the errors that I can use as a report, which mentions which entries failed saving. Of course nothing should be saved in that case and the transaction is rolled back.
Any hints how something like this can me achieved? Of course one option would be to try to save each object separately and then collect the errors, but that might be not that performant.

Strange validation conflict in Spring JPA TableGenerator

I have a legacy database with composite primary key in table project. (BaseEntity contains common properties for lastModifiedDate and lastModifiedBy)
#Entity
#IdClass(ProjectPk.class)
public class Project extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.TABLE, generator="nextProjectId")
#TableGenerator(
name="nextProjectId",
table="projectId",
pkColumnName = "proj_Id",
pkColumnValue="proj_id"
)
private Long projId;
#Id
private int version;
//other properties, getters and setters omitted for clarity
}
PK class
public class ProjectPk implements java.io.Serializable {
private int projId;
private int version;
//both constructoirs, equals, hashcode, getters and setters omitted for clarity
}
I have flyway migration files to simulate production database.
drop table if exists project;
CREATE TABLE project
(
proj_id bigint,
version int,
-- other columns omitted for clarity
PRIMARY KEY (`proj_id`, `version`)
) ENGINE=InnoDB;
drop table if exists project_id;
CREATE TABLE project_id
(
proj_id bigint
) ENGINE=InnoDB;
flyway creates tables as ordered in migration file
Table: project_id
Columns:
proj_id bigint
...
Table: project
Columns:
proj_id bigint PK
version int PK
...
during maven build I'm getting validation error
Schema-validation: wrong column type encountered in column [proj_id] in table [project_id]; found [bigint (Types#BIGINT)], but expecting [varchar(255) (Types#VARCHAR)]
What I did wrong to make hibernate expect [varchar(255) (Types#VARCHAR)]?
This is SpringBoot project 2.6.6 with MySql database
I see the following problems with your code:
Type mismatch between Project.projId (Long type) and ProjectPk.projId (int type).
You use wrong table structure for the project_id table.
You can see a working example below.
Assuming that you have the following tables:
CREATE TABLE test_project
(
proj_id bigint,
version int,
title VARCHAR(50),
PRIMARY KEY (proj_id, version)
);
create table table_identifier (
table_name varchar(255) not null,
product_id bigint,
primary key (table_name)
);
insert into table_identifier values ('test_project', 20);
and the following mapping:
#Entity
#Table(name = "test_project")
#IdClass(ProjectPk.class)
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "nextProjectId")
#TableGenerator(
name="nextProjectId",
table="table_identifier",
pkColumnName = "table_name",
valueColumnName="product_id",
allocationSize = 5
)
#Column(name = "proj_id")
private Long projId;
#Id
private int version;
// other fields, getters, setters ...
}
you will be able to persist the entity like below:
Project project = new Project();
project.setVersion(1);
// ...
entityManager.persist(project);

Table not created when #EmbeddedId has an extra String property

I'm trying to automatically generate the table for following entity:
#Entity
#Table(name = "gas_price")
class GasPrice(
#EmbeddedId
var id: GasPriceId,
var price: Double,
)
#Embeddable
class GasPriceId(
#ManyToOne(optional = false)
var gasStation: GasStation,
var isSelf: Boolean,
var readDate: Instant,
var description: String
) : Serializable
As it is, the table is not generated on application launch, but if I remove
var description: String
the table does generate with correct values.
I am out of ideas on why it happens, anyone faced a similar issue?
Apparently inside #Embeddable for the String property I had to define the column length. So:
#Column(length = 200)
var description: String
and the table is generated. I'd have to look into why, but this solved my problem.

Is #ManyToOne's "optional" param automatically set using Kotlin's nullability

I read that specifying optional = false in the #ManyToOne association annotation could help Spring improve the performance of the queries.
In a Kotlin data class entity, do I actually need to specify the parameter in the annotation, or can Spring figure this out by itself using the nullability of the item field?
For instance, if I have the following declaration:
#Entity
#Table(name = ACCESS_LOGS_ARCHIVES_TABLE, indexes = [
Index(name = "access_logs_archives_item_idx", columnList = "access_item_id")
])
data class AccessLogArchive(
val date: LocalDate,
#ManyToOne(optional = false)
#JoinColumn(name = "access_item_id", nullable = false)
val item: AccessLogItem,
val occurrences: Int
) {
#Id
#GeneratedValue
var id: Long? = null
}
#Entity
#Table(name = ACCESS_ITEMS_TABLE)
data class AccessLogItem(
#Column(length = 3) val code: String,
#Column(columnDefinition = "text") val path: String,
#Column(length = 10) val verb: String
) {
#Id
#GeneratedValue
var id: Long? = null
}
In this case, I would for instance expect Spring to know that the item field is not nullable, and thus the relationship should be understood as optional=false even without specifying it as I did. Is this the case?
Same question goes for the #JoinColumn's nullable = false, by the way.
Consider a simple entity like a Room which has a #ManyToOne relationship to House.
#Entity
class Room(
#ManyToOne(optional = true)
val house: House
) {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = 0
}
JPA will create a room table with a column
`house_id` bigint(20) DEFAULT NULL
If you specify #ManyToOne(optional = false)
the column will look like this:
`house_id` bigint(20) NOT NULL
By specifiying optional you tell JPA how the schema should be generated, whether the column can be NULL or not.
At runtime trying to load a Room without a House will cause an Exception if the house property is not nullable (House instead of House?) even when value of optional is true.
The same applies to #JoinColumn.
Is #ManyToOne's “optional” param automatically set using Kotlin's
nullability?
No it is not. It is independent from that and by default set to true.
Conclusion: In order for you schema to reflect your entities it is a good idea to use optional = true if the house property would be nullable and optional = false if the house property would be non-nullable.

Resources