Hibernate Envers Audit Join Table defenition - spring-boot

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>

Related

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" />

spring-boot with liquibase #OneToMany mapping

I have two entity Person and Address. And Person can have multiple Address.
<createTable tableName="ADDRESS">
<column name="id" type="bigint(20)" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
... //columns
</column>
</createTable>
<createTable tableName="PERSON">
<column name="id" type="bigint(20)" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
... //columns
</column>
</createTable>
<addForeignKeyConstraint
constraintName="fk_constraint_worker_phone_number"
referencedTableName="CONTACT_NUMBER" baseColumnNames="ContactNumbers"
baseTableName="WORKER" referencedColumnNames="id" />
I want 3rd table (like hibernate generate in #OneToMany mapping).
How to do this with liquibase-springboot?
If the relation is truly a OnToMany, you don't need a 3rd table. Simply, add PrimaryKeyJoinColumn.
If the address can be reused for many persons, it's a ManyToMany relation.
You can use #ManytoMany and add information about you joined table un #jointable
Well, in case of liquibase we have to create the 3rd table manually and have to apply the necessary constraints.
Create the table which manages the mapping :
<createTable tableName="PERSON_ADDRESS">
<column name="PERSON_ID" type="BIGINT">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="ADDRESS_ID" type="BIGINT">
<constraints primaryKey="true" nullable="false" />
</column>
</createTable>
Apply the constraints:
1) Ensure that Persons id is unique in the mapping table
2) A foreign key relationship between ADDRESS's id and PERSON_ADDRESS's PERSON_ID
3) A foreign key relationship between PERSON's id and PERSON_ADDRESS's ADDRESS_ID
<addUniqueConstraint
columnNames="PERSON_ID" tableName="PERSON_ADDRESS"
constraintName="UK_PHONE_NUMBERS_ID" />
<addForeignKeyConstraint
constraintName="FK_ADDRESS_PERSON_ADDRESS"
referencedTableName="ADDRESS"
baseColumnNames="ADDRESS_ID"
baseTableName="PERSON_ADDRESS" referencedColumnNames="id" />
<addForeignKeyConstraint
constraintName="FK_PERSON_PERSON_ADDRESS"
referencedTableName="PERSON"
baseColumnNames="PERSON_ID"
baseTableName="PERSON_ADDRESS" referencedColumnNames="id" />

Hibernate Mapping - Converting XML to Annotation - Wrong Column name being generated

I have a two projects in hibernate one is for XML and one is for annotation.
My XML Mapping config works fine with composite keys while the annotation style generates a wrong column.
Annotation Style Generates this table
Hibernate: create table agent (agent_id number(10,0) not null, start_dttm timestamp not null, agent_cd varchar2(255 char), end_dttm timestamp, primary key (agent_id, start_dttm))
Hibernate: create table agent_license (agent_license_id number(10,0) not null, agent_agent_id number(10,0), agent_start_dttm timestamp, agent_id number(10,0), primary key (agent_license_id))
Hibernate: alter table agent_license add constraint FKncan0jjmt1siq0a6a19ll978m foreign key (agent_agent_id, agent_start_dttm) references agent
Hibernate: alter table agent_license add constraint FKp64d6078jnxi3hq0n0es0yhs foreign key (agent_id, agent_start_dttm) references agent
a redundant column for AgentLicense with agent_agent_id instead of agent_id only.
This is Working Correctly XML Config
XML generates
Agent Table with column agent_id,start_dttm,agent_cd
Agent_License with column agent_id,agent_start_dttm
Agent Model
<class name="com.poc.model.Agent" table="AGENT">
<composite-id name="id" class="com.poc.model.pk.AgentPK">
<key-property name="agentId" type="long">
<column name="AGENT_ID" precision="10" scale="0" />
</key-property>
<key-property name="startDateTime" type="timestamp">
<column name="START_DTTM" />
</key-property>
</composite-id>
<property name="agentCode" type="string">
<column name="AGENT_CD" length="20" />
</property>
<set name="agentLicenses" table="AGENT_LICENSE" inverse="true" lazy="true" fetch="select">
<key>
<column name="AGENT_ID" precision="10" scale="0" />
<column name="AGENT_START_DTTM" />
</key>
<one-to-many class="com.poc.model.AgentLicense" />
</set>
</class>
AgentLicense Model
<class name="com.poc.model.AgentLicense" table="AGENT_LICENSE">
<id name="agentLicenseId" type="long">
<column name="AGENT_LICENSE_ID" precision="10" scale="0" />
<generator class="assigned" />
</id>
<many-to-one name="agent" class="com.poc.model.Agent" fetch="select">
<column name="AGENT_ID" precision="10" scale="0" />
<column name="AGENT_START_DTTM" />
</many-to-one>
<property name="licenseNum" type="string">
<column name="LICENSE_NUM" length="40" />
</property>
</class>
Agent PK Model
<class name="com.poc.model.Agent" table="AGENT">
<composite-id name="id" class="com.poc.model.pk.AgentPK">
<key-property name="agentId" type="long">
<column name="AGENT_ID" precision="10" scale="0" />
</key-property>
<key-property name="startDateTime" type="timestamp">
<column name="START_DTTM" />
</key-property>
</composite-id>
<property name="agentCode" type="string">
<column name="AGENT_CD" length="20" />
</property>
<set name="agentLicenses" table="AGENT_LICENSE" inverse="true" lazy="true" fetch="select">
<key>
<column name="AGENT_ID" precision="10" scale="0" />
<column name="AGENT_START_DTTM" />
</key>
<one-to-many class="com.poc.model.AgentLicense" />
</set>
</class>
Correct Annotation is this
Result table generation
Agent Table with column agent_id,start_dttm,agent_cd
Agent_License with column agent_id,agent_start_dttm
public class Agent {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "agentId", column = #Column(name = "AGENT_ID", nullable = false)),
#AttributeOverride(name = "startDateTime", column = #Column(name = "START_DTTM", nullable = false)) })
private AgentPK agentPK;
#Column(name="AGENT_CODE")
private String agentCode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agent")
private Set<AgentLicense> agentLicenses = new HashSet<AgentLicense>();
}
public class AgentLicense {
#Id
#Column(name = "AGENT_LICENSE_ID", unique = true, nullable = false)
private long agentLicenseId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({ #JoinColumn(name = "AGENT_ID", referencedColumnName = "AGENT_ID"),
#JoinColumn(name = "AGENT_START_DTTM", referencedColumnName = "START_DTTM") })
private Agent agent;
}
#Embeddable
public class AgentPK implements Serializable{
#Column(name = "AGENT_ID", nullable = false, precision = 10, scale = 0)
private long agentId;
#Column(name = "START_DTTM", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date startDateTime;
//HashCode and equals here
}
Correct Full answer
Fix your inverse mapping for AgentLicense in Agent
public class Agent {
// ...
#OneToMany(mappedBy = "agent")
#JoinColumns( ... )
private Set<AgentLicense> agentLicenses;
}

Resources