OneToMany relation cannot be saved in spring with liquibase - spring

I am trying to build a data model in spring that cascades up to 3 levels down with one-to-many relationships, but I cannot get it to work with liquibase scripts.
I am using spring boot with Kotlin and liquibase with a PostgreSQL database.
What I did so far:
cutting down the code to only contain the part that does not work (see below)
I tried both #OneToMany with #JoinTable as well as with #JoinColumn, and I also tried the same with #ManyToMany to rule out an issue with #OneToMany
I ran the same code (below) without liquibase to have Hibernate/JPA create the tables from the models
this actually worked, so I generated liquibase scripts out of these tables, but they look exactly the same as my own (except key names)
retrieving data with these models work (if I insert data via SQL directly)
To be honest I am not sure, if the issue is in the model, in the configuration, or in the liquibase scripts, so I'll just post all of those. Am I missing a configuration? Did I configure the cascading correct? Are my model definitions/liquibase scripts wrong?
The exception I get on saving a parent is:
Hibernate: insert into parent (name) values (?)
2021-12-15 23:29:16.797 WARN 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2021-12-15 23:29:16.798 ERROR 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 1).
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [id" of relation "parent]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 2).
The code I am trying to run:
val parent = Parent(
id = 0,
name = "Test 2"
).apply {
children = mutableSetOf(
Child(
id = 0,
name = "Test 21",
parent = this
).apply {
grandchildren =
mutableSetOf(
Grandchild(
id = 0,
name = "Test 211",
child = this
)
)
},
Child(
id = 0,
name = "Test 22",
parent = this
)
)
}
val saveParent: Parent = parentRepository.save(parent)
models:
#Entity
class Parent(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
#OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
var children: MutableSet<Child> = mutableSetOf()
)
#Entity
class Child(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
#ManyToOne #JoinColumn(name = "child_id")
var parent: Parent,
#OneToMany(mappedBy = "child", cascade = [CascadeType.ALL])
var grandchildren: MutableSet<Grandchild> = mutableSetOf()
)
#Entity
class Grandchild(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
#ManyToOne #JoinColumn(name = "child_id")
var child: Child
)
application.yml
spring:
datasource:
platform: postgres
url: jdbc:postgresql://localhost:5432/onetomany?ssl=false
driver-class-name: org.postgresql.Driver
initialization-mode: always
jpa:
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
generate-ddl: false
hibernate:
ddl-auto: none
liquibase:
enabled: true
change-log: classpath:db/master.xml
liquibase script:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="bruce (generated)" id="data-1">
<createTable tableName="parent">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_PARENT"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-2">
<createTable tableName="child">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_CHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="parent_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-3">
<createTable tableName="grandchild">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_GRANDCHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="child_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-6">
<addForeignKeyConstraint baseColumnNames="parent_id" baseTableName="child" constraintName="FK_CHILD_PARENT"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="parent" validate="true"/>
</changeSet>
<changeSet author="bruce (generated)" id="data-8">
<addForeignKeyConstraint baseColumnNames="child_id" baseTableName="grandchild" constraintName="FK_CHILD_GRANDCHILD"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="child" validate="true"/>
</changeSet>
</databaseChangeLog>

#GeneratedValue(strategy = GenerationType.IDENTITY) would normally work by specifying an auto-incrementing default value at the database, such as nextval('my_entity_sequence'::regclass). Upon insert, the DB will generate the identifier.
In Postgres, there are serial/bigserial pseudo-types to specify an auto-increment column (which will internally create the sequence as well as the column default value), so the DDL could e.g. look like this:
create table my_entity ( id bigserial not null, primary key (id) )
https://www.postgresql.org/docs/current/datatype-numeric.html
In your case, liquibase missed the type/defaults for all of the ID columns (right now just a "parent"-insert fails, but inserts for the other entities will fail as well).
This is a known liquibase issue: https://github.com/liquibase/liquibase/issues/1009 - suggestions to work around it include manually specifying autoIncrement="true" in the changeset.

Related

Hibernate Envers Audit Join Table defenition

I want to add history to my entities and I am playing arround with hibernate Envers.
I have defined Handbook and Chapter classes with the coresponding 5 tables:
HANDBOOK, HANDBOOK_AUD, CHAPTER, CHAPTER_AUD and REVINFO.
If there is no connection between the 2 entities, everithing works fine, but when I add oneToMany relationship for HANDBOOK and CHPATER the application fails to start because of missing HANDBOOK_CHAPTER_AUD table.
Thinking about it is absolutely fine to have that JoinTable, but the problem is how should define it.
Handbook entity:
#Entity
#Audited
#Getter
#Setter
#NoArgsConstructor
public class Handbook {
#Id
#SequenceGenerator(name = "HANDBOOK_ID_SEQUENCE", sequenceName = "HANDBOOK_ID_SEQUENCE", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HANDBOOK_ID_SEQUENCE")
private Long id;
private String title;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "HANDBOOK_ID")
#AuditJoinTable(name = "REV_HANDBOOK_CHAPTER")
private Set<Chapter> chapters;
}
Chapter entity:
#Entity
#Audited
#Getter
#Setter
#NoArgsConstructor
public class Chapter {
#Id
#SequenceGenerator(name = "CHAPTER_ID_SEQUENCE", sequenceName = "CHAPTER_ID_SEQUENCE", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHAPTER_ID_SEQUENCE")
private Long id;
private String name;
#Column(name = "HANDBOOK_ID")
private Long handbookId;
}
Tables deffinition:
<createTable tableName="REVINFO">
<column name="rev" type="integer">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="revtstmp" type="bigint"/>
</createTable>
<!-- Hibernate Envers need this seq exact to increase the revision number for versioned entities-->
<createSequence sequenceName="HIBERNATE_SEQUENCE"
startValue="1"
incrementBy="1"/>
<createTable tableName="HANDBOOK">
<column name="ID" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="TITLE" type="varchar2(128 char)"/>
</createTable>
<createTable tableName="REV_HANDBOOK">
<column name="ID" type="bigint"/>
<column name="TITLE" type="varchar2(128 char)"/>
<column name="REV_ID" type="integer">
<constraints foreignKeyName="FK_HANDBOOK_REV"
references="REVINFO(REV)"/>
</column>
<column name="REV_TYPE" type="smallint"/>
</createTable>
<!-- CHAPTER TABLES -->
<createTable tableName="CHAPTER">
<column name="ID" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="NAME" type="varchar2(128 char)"/>
<column name="HANDBOOK_ID" type="integer">
<constraints foreignKeyName="FK_CHAPTER_HANDBOOK_ID"
references="HANDBOOK(ID)"/>
</column>
</createTable>
<createTable tableName="REV_CHAPTER">
<column name="ID" type="bigint"/>
<column name="NAME" type="varchar2(128 char)"/>
<column name="HANDBOOK_ID" type="integer">
<constraints foreignKeyName="FK_CHAPTER_AUD_HANDBOOK_ID"
references="HANDBOOK(ID)"/>
</column>
<column name="REV_ID" type="integer">
<constraints foreignKeyName="FK_CHAPTER_REV"
references="REVINFO(REV)"/>
</column>
<column name="REV_TYPE" type="smallint"/>
</createTable>
NOTE:
I have edited some namings according to the hibernate envers documentation:
org:
hibernate:
envers:
audit_table_prefix: REV_
audit_table_suffix: ~ # No suffix
revision_field_name: REV_ID
revision_type_field_name: REV_TYPE
I have found out a sollution based on the series of errors hibernate gave me after running the code. Here is how I defiend the revision join table:
<createTable tableName="REV_HANDBOOK_CHAPTER">
<column name="ID" type="bigint"/>
<column name="REV_ID" type="integer">
<constraints foreignKeyName="FK_REV_HANDBOOK_CHAPTER_REV"
references="REVINFO(REV)"/>
</column>
<column name="HANDBOOK_ID" type="bigint">
<constraints foreignKeyName="FK_REV_HANDBOOK_CHAPTER_HANDBOOK"
references="HANDBOOK(ID)"/>
</column>
<column name="REV_TYPE" type="smallint"/>
</createTable>

Unkown column in 'field list' after creating tables by liquibase

I prepare liquibase scripts to generate my tables and relationships.
ChangeLog which create relationships:
<changeSet id="1" author="xyz">
<createTable tableName="drivers_and_tickets">
<column name="driver_id" type="varchar(36)">
<constraints nullable="false" />
</column>
<column name="ticket_id" type="varchar(36)">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint constraintName="fk_drivers"
baseTableName="drivers_and_tickets" baseColumnNames="driver_id"
referencedTableName="drivers" referencedColumnNames="id" />
<addForeignKeyConstraint constraintName="fk_tickets"
baseTableName="drivers_and_tickets" baseColumnNames="ticket_id"
referencedTableName="tickets" referencedColumnNames="id" />
</changeSet>
<changeSet id="2" author="xyz">
<createTable tableName="tickets_and_offences">
<column name="ticket_id" type="varchar(36)">
<constraints nullable="false" />
</column>
<column name="offence_id" type="varchar(36)">
<constraints nullable="false" />
</column>
</createTable>
<addForeignKeyConstraint constraintName="fk_tickets_from_offences"
baseTableName="tickets_and_offences" baseColumnNames="ticket_id"
referencedTableName="tickets" referencedColumnNames="id" />
<addForeignKeyConstraint constraintName="fk_offences"
baseTableName="tickets_and_offences" baseColumnNames="offence_id"
referencedTableName="offences" referencedColumnNames="id" />
</changeSet>
And it ok, app is starting without any errors.
I use hibernate too, so I prepared entites:
public class DriverEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
private String name;
private String surname;
private String email;
#OneToMany(mappedBy="driver")
private List<TicketEntity> tickets;
public class TicketEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
private LocalDate date;
#OneToMany(mappedBy="ticket")
private Set<OffenceEntity> offences;
#ManyToOne
#JoinColumn(name="driver_id")
private DriverEntity driver;
and next when I call repository findAllDrivers I am getting error:
"Unknown column 'tickets0_.driver_id' in 'field list'" .
When I was using hibernate.ddl-auto mapping like that in entities was enough to create tables and getting records from database.
Edit:
I have checked how it looks in my DB and do not know what is wrong

How to get db information using Spring JdbcTemplate and plain SQL (Micronaut)

I am trying to create a web application using Micronaut and need to get information from travelLog table using Spring JdbcTemplate and plain
SQL. I was using this tutorial https://www.greggbolinger.com/posts/using-springs-jdbctemplate-with-micronaut/ to solve this, but I faced the following problem:
30.869 [default-nioEventLoopGroup-1-2] ERROR i.m.h.s.netty.RoutingInBoundHandler - Unexpected error occurred: StatementCallback; bad SQL grammar [SELECT * FROM travelLog]; nested exception is org.postgresql.util.PSQLException: ERROR: relation "travellog" does not exist
Position: 15
org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [SELECT * FROM travelLog]; nested exception is org.postgresql.util.PSQLException: ERROR: relation "travellog" does not exist
Position: 15
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:237)
at
Here is travelLog table Schema
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="01" author="julia">
<createTable tableName="travelLog"
remarks="A table to contain all travel logs">
<column name="id" type="int">
<constraints nullable="false" unique="true" primaryKey="true"/>
</column>
<column name="date" type="timestamp">
<constraints nullable="false"/>
</column>
<column name="regNumber" type="varchar">
<constraints nullable="false"/>
</column>
<column name="ownersName" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="odometerValueBeg" type="int">
<constraints nullable="false"/>
</column>
<column name="odometerValueEnd" type="int">
<constraints nullable="false"/>
</column>
<column name="departurePlace" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="destinationPlace" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="description" type="varchar">
</column>
</createTable>
</changeSet>
</databaseChangeLog>
Here is JdbcTemplateFactory.java
#Factory
public class JdbcTemplateFactory {
#Inject
DataSource dataSource;
#Bean
#Singleton
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
Here is TravelLogService.java
#Singleton
#Requires(beans = JdbcTemplate.class)
public class TravelLogService {
private final JdbcTemplate jdbcTemplate;
public TravelLogService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#Transactional
public void printUsernames() {
jdbcTemplate.query("SELECT * FROM travelLog", (rs) -> {
System.out.println(rs.getString("ownersName"));
});
}
To create and query case sensitive table, columns etc, names must be quoted like this:
SELECT * FROM "travelLog"
For liquibase settings check this answer https://stackoverflow.com/a/60654633/1854103
Also please consider change your naming conventions

Liquibase , how to add a group of fields like primary key?

I have an entity with a group of fields in primary key.
Like this :
#Entity
#Table(name = "pv_object")
#NamedQuery(name = "PreviousObject.findAll", query = "SELECT p FROM PreviousObject p")
public class PreviousObject implements Serializable {
#EmbeddedId
private FieldsDTO fieldsdto;
//
}
FieldsDTO class contains 2 String and 2 Integer.
I have and I use Liquidbase on my project in a XML file, but, I don't know how to represent this ID of 4 fields in liquidbase.
Thanks for your help :)
In <addPrimaryKey you can configure columnNames by all your columns that compose your primary key
<changeSet author="liquibase-docs" id="addPrimaryKey-example">
<addPrimaryKey
columnNames="id, name"
constraintName="pk_person"
schemaName="public"
tableName="person"
tablespace="A String"/>
</changeSet>
Assign the same primaryKeyName to them.
<createTable tableName="pv_object">
<column name="x" type="bigint">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_pv_object"/>
</column>
<column name="y" type="bigint">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_pv_object"/>
</column>
</createTable>
or add separately
<addPrimaryKey tableName="REPRESENTATIVE" columnNames="REPRESENTED_USER_ID,REPRESENTATIVE_ID"
constraintName="REPRESENTED_REPRESENTATIVE_PK" />

Liquibase and spring jpa

I'm having problem with deploy postgres on a linux environment though I don't sure it's related.
Linux version: 9.3.11
Windows version: 9.5
The error that I get:
2016-03-15_19:19:40.478 [http-nio-9090-exec-3] WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
2016-03-15_19:19:40.479 [http-nio-9090-exec-3] ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: relation "rbac_roles" does not exist
Position: 125
2016-03-15_19:19:40.520 [http-nio-9090-exec-3] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
org.postgresql.util.PSQLException: ERROR: relation "rbac_roles" does not exist
Position: 125
On my windows environment table definition created by Liquibase looks as following:
-- Table: public.rbac_roles
-- DROP TABLE public.rbac_roles;
CREATE TABLE public.rbac_roles
(
tenantid character varying(255) NOT NULL,
id integer NOT NULL DEFAULT nextval('rbac_roles_id_seq'::regclass),
name character varying(255) NOT NULL,
urlprefix character varying(255),
CONSTRAINT pk_rbac_roles PRIMARY KEY (id),
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.rbac_roles
OWNER TO postgres;
On my linux (the prblematic) environment table definition created by Liquibase looks as following:
-- Table: public.rbac_roles
-- DROP TABLE public.rbac_roles;
CREATE TABLE public.rbac_roles
(
tenantid character varying(255) NOT NULL,
id serial NOT NULL,
name character varying(255) NOT NULL,
urlprefix character varying(255),
CONSTRAINT pk_rbac_roles PRIMARY KEY (id),
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.rbac_roles
OWNER TO postgres;
The Spring Jpa object looks like this:
#Entity(name = "rbac_roles")
public class Role implements HasTenantId {
#Id
#SequenceGenerator(name="roles_seq", sequenceName = "rbac_roles_id_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "roles_seq")
private int id;
...
The Liquibase config looks like this:
<createTable tableName="rbac_roles">
<column name="tenantid" type="varchar(255)">
<constraints primaryKeyName="pk_roles" nullable="false" />
</column>
<column name="id" autoIncrement="true" type="integer">
<constraints primaryKey="true" nullable="false" />
</column>
...
</createTable>
The table rbac_roles created successfully but ID looks diffrent
What I'm doing wrong, why the behavior is different?
In your JPA Annotations you are using a sequence generator, but in your changelog there is an autoincrement type for ID attribute
<createTable tableName="rbac_roles">
<column name="tenantid" type="varchar(255)">
<constraints primaryKeyName="pk_roles" nullable="false" />
</column>
<!-- HERE you specify autoincrement -->
<column name="id" autoIncrement="true" type="integer">
<constraints primaryKey="true" nullable="false" />
</column>
...
</createTable>
It is confusing, and I don't know why this code is generating different tables on windows and linux, maybe your postgresql is not the same version ...
Please, fix your changelog and put the following
<createTable tableName="rbac_roles">
<column name="tenantid" type="varchar(255)">
<constraints primaryKeyName="pk_roles" nullable="false" />
</column>
<!-- Removed autoincrement -->
<column name="id" type="integer">
<constraints primaryKey="true" nullable="false" />
</column>
...
</createTable>
<!-- HERE I specify sequence -->
<createSequence sequenceName="rbac_roles_id_seq" incrementBy="1"/>
This way, it should generate same table on both systems.

Resources