Liquibase not including new entities in change log using diff - spring-boot

I have an Entity:
package com.mycompany.myapp.domain;
#Entity
#Table(name = "bob")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Bob extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
// ..
And running ./gradlew liquibaseDiff -PrunList=main, to generate the projects first changelog.
My expectation is that liquibase:
finds my entity
compares against an offline postgres db
generates a changelog off all entities
(Assuming the diff will consider all entities found to be new)
Basically for retrospectively adding liquibase to an existing project. I don't want to have to write an initial schema change log.
Unfortunately, the result is that no changelog is created.
Below is a snippet of command output:
Liquibase command 'diff' was executed successfully.
Reference Database: null # hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy (Default Schema: HIBERNATE)
Comparison Database: hip2 # offline:postgresql
Compared Schemas: HIBERNATE ->
Product Name:
Reference: 'Hibernate'
Target: 'Offline postgresql'
Product Version:
Reference: '5.6.7.Final'
Target: NULL
Missing Catalog(s):
HIBERNATE
Unexpected Catalog(s): NONE
Changed Catalog(s): NONE
Missing Column(s): NONE
Unexpected Column(s): NONE
Changed Column(s): NONE
Missing Foreign Key(s): NONE
Unexpected Foreign Key(s): NONE
Changed Foreign Key(s): NONE
Missing Index(s): NONE
Unexpected Index(s): NONE
Changed Index(s): NONE
Missing Primary Key(s): NONE
Unexpected Primary Key(s): NONE
Changed Primary Key(s): NONE
Missing Schema(s):
HIBERNATE
Unexpected Schema(s): NONE
Changed Schema(s): NONE
Missing Sequence(s): NONE
Unexpected Sequence(s): NONE
Changed Sequence(s): NONE
Missing Table(s): NONE
Unexpected Table(s): NONE
Changed Table(s): NONE
Missing Unique Constraint(s): NONE
Unexpected Unique Constraint(s): NONE
Changed Unique Constraint(s): NONE
Missing View(s): NONE
Unexpected View(s): NONE
Changed View(s): NONE
build.gradle
plugins {
id "java"
id "org.springframework.boot"
id "org.liquibase.gradle"
..
}
liquibase {
activities {
main {
driver "org.postgresql.Driver"
url "offline:postgresql"
username "hip2"
password ""
changeLogFile "src/main/resources/config/liquibase/master.xml"
defaultSchemaName "public"
logLevel "debug"
referenceUrl "hibernate:spring:com.mycompany.myapp.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"
}
}
runList = project.ext.runList
}
dependencies {
implementation "org.hibernate:hibernate-core"
implementation "org.hibernate:hibernate-entitymanager"
implementation "org.hibernate.validator:hibernate-validator"
implementation "org.liquibase:liquibase-core"
liquibaseRuntime "org.liquibase:liquibase-core"
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate5:4.6.1"
liquibaseRuntime sourceSets.main.compileClasspath
liquibaseRuntime "org.postgresql:postgresql"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.springframework:spring-beans"
..
}

Related

Import data at startup Spring boot

I'm trying to launch a SQL file at my database initialization.
Here is my configuration:
spring:
profiles: local
jpa:
properties:
hibernate.temp.use_jdbc_metadata_defaults: false
generate-ddl: true
hibernate:
ddl-auto: update
database: h2
show-sql: true
autoCommit: false
datasource:
platform: h2
url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;
driver-class-name: org.h2.Driver
initialization-mode: always
data: classpath:/sql/CreateGeographicZones.sql
My script is just this line (atm):
INSERT INTO GEOGRAPHIC_ZONE (name) VALUES ('EUROPE');
And the related entity:
#NoArgsConstructor
#Data
#Entity
#Table(name = "GEOGRAPHIC_ZONE")
public class GeographicZone {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "geo_zone_sequence")
#SequenceGenerator(name = "geo_zone_sequence", sequenceName = "geo_zone_id_seq", allocationSize = 1)
#Column(nullable = false)
private Long id;
...
}
The table is created as I can see in the logs:
Hibernate: create table geographic_zone (id bigint not null, name varchar(100) not null, primary key (id))
But I have an SQL error when the script is executed:
Table "GEOGRAPHIC_ZONE" not found; SQL statement:
INSERT INTO GEOGRAPHIC_ZONE (name) VALUES ('EUROPE')
In the logs I can see that my table is created before the script execution, so why it's not working ?
According with your entity's metadata Hibernate is querying geo_zone_id_seq sequence's next value and using it for the ID on each insert.
If you would like to use the same approach when inserting directly in your database then you will need to implement a H2 Trigger
Also you may use either the EntityManager bean or your Spring JPA Repository to insert your data after application startup via CommandLineRunner interface.
Using EntityManager:
#Bean
CommandLineRunner registerZonesDataRunner(EntityManager entityManager, TransactionTemplate transactionTemplate) {
return args -> transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// presuming that GeographicZone has a constructor expecting NAME
Stream.of("AFRICA", "EUROPE")
.map(GeographicZone::new)
.forEach(entityManager::persist);
}
});
Using Spring JPA Repository:
#Bean
CommandLineRunner registerZonesDataRunner(GeographicZoneRepository repository) {
// presuming that GeographicZone has a constructor expecting NAME
return args -> repository.saveAll(Stream.of("AFRICA", "EUROPE")
.map(GeographicZone::new)
.collector(Collectors.toList()));
}
minimal, reproducible example
You don't show how you've defined the id column but the schema indicates there is no auto-generation scheme. So, try:
INSERT INTO GEOGRAPHIC_ZONE (id, name) VALUES (1, 'EUROPE');
in your data file. If that works, you'll need to either manually set the id in your inserts or add something like #GeneratedValue(strategy = AUTO) to your #Id property.

Hibernate does not generate Sequence when it's already available on a different schema

I have a "simple" Spring Boot application with a single datasource.
The following configuration is present in my configuration:
spring:
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
default_schema: CORE
flyway:
schemas:
- CORE
and the following ID Generator
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "seq-pooled-lo")
#GenericGenerator(
name = "seq-pooled-lo",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#org.hibernate.annotations.Parameter(
name = SequenceStyleGenerator.INCREMENT_PARAM,
value = "50"
),
#org.hibernate.annotations.Parameter(
name = SequenceStyleGenerator.OPT_PARAM,
value = "pooled"
),
#org.hibernate.annotations.Parameter(
name = SequenceStyleGenerator.SEQUENCE_PARAM,
value = "seq_pooled_lo_sequence"
)
})
Now my issue is the following:
When I launch this app, it will create a sequence on the "CORE" schema. Everything works fine. Storing, retrieving data is no problem. When I then launch a second instance of the app but override the YAML file to define a different default_schema: SECOND it will not generate a new sequence on the "SECOND" schema. If I would first start the app with the "SECOND" schema defined and then start the one with "CORE" it would create the sequence on the "SECOND" schema and not on "CORE".
I would expect it to create different sequences on both schemas. Why does it not do that?
I also tried to add the sequence manually to the schema where it's missing but sadly that did not seem to help.
If you add the sequence manually to the schema.
You can create CustomIdGenerator Class as the example for return sequence id by defaultSchema
public class CustomIdGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor sessionImplementor, Object o) throws HibernateException {
SequenceRepository sequenceRepository = ApplicationContextProvider.getApplicationContext().getBean(SequenceRepository.class);
Environment env = ApplicationContextProvider.getApplicationContext().getBean(Environment.class);
String defaultSchema = env.getProperty("yourproperty.default_schema");
return sequenceRepository.getSequence(defaultSchema);
}
}
Let's create SequenceRepository class for generate sequence. I assume that my database is Oracle.
NEXTVAL: Increments the sequence and returns the next value
public interface SequenceRepository extends JpaRepository<Object,Long> {
#Query(value = "SELECT ?1.NEXTVAL FROM DUAL", nativeQuery = true)
Long getSequence(String sequenceName);
}
and your Entity just edit strategy to using CustomGenerator Class
#Id
#GenericGenerator(name = "sequence_generator", strategy = "yourpackage.CustomIdGenerator")
#GeneratedValue(generator = "sequence_generator")
#Column(name = "id")
private Long id;

Sequence "HIBERNATE_SEQUENCE" not found; SQL statement

In my spring mvc app, i have the following object. I am trying to make a visual of data using devtool in my app.
#Entity
#Data
public class ConsultationRequest {
#Id
#GeneratedValue
private Long id;
private String name;
private String email;
private String purpose;
private String programme;
private int year;
private String language;
private String comments;
#Enumerated(EnumType.STRING)
private ConsultationStatus status;
}
Then i used the jpa to make the entity:
#Repository
public interface ConsultationRequestRepository extends JpaRepository<ConsultationRequest, Long> {
}
The problem is when i load my application, i face with 2 errors:
Unsuccessful: drop sequence hibernate_sequence
[36morg.hibernate.tool.hbm2ddl.SchemaExport Sequence "HIBERNATE_SEQUENCE" not found; SQL statement:
Then when i open the
http://localhost:8080/h2-console/
I cannot see the table.
It seems that the in the boot process, table is not made.
Update your code as below:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
As you have not specified a sequence table name, hibernate will look for a sequence table named as hibernate_sequence and use it as default.
For Oracle/Postgres, increment fields used are sequence tables.
In MySql, there are increment fields that automatically increment.
If someone is getting this error with Spring Boot testing(with H2) , we have to use following in the application.properties(or whatever profile we are using) :
spring.jpa.hibernate.ddl-auto=create-drop
Setting the following property in the properties file helped me solve the hibernate_sequence problem for hibernate 5.4v
spring:
jpa:
hibernate:
use-new-id-generator-mappings: false
Check persistence.xml
property name="hibernate.hbm2ddl.auto" value="create"
not hdm2ddl
This worked in my case.
SQL operation ERROR when start app spring-boot.
I added the setting in spring properties and solved: in the spring:jpa.hibernate.ddl-auto= create-drop to hibernate be able create or drop table automatically.
If you use a 2nd cache with liquidbase, you have to add the sequence in the changelog like this:
<changeSet author="liquibase-docs"
id="createSequence-example">
<createSequence catalogName="cat" cycle="false"
incrementBy="1" ordered="true" schemaName="public"
sequenceName="hibernate_sequence" startValue="0" />
</changeSet>
For spring-boot 2.7.x and h2 2.x you need to add MODE=LEGACY; in the database connection:
example application.yml:
spring:
datasource:
url: jdbc:h2:mem:test;MODE=LEGACY;
exemple application.properties:
spring.datasource.url=jdbc:h2:mem:test;MODE=LEGACY;
For Mysql:
the auto-increment is not added, modify ur table:
ALTER TABLE table_name MODIFY COLUMN id BIGINT AUTO_INCREMENT=1

Grails 3.x: Re-using JPA/Hibernate Domain classes: Domain class not found

I have a Spring 4.1.0 back-end application with domain classes annotated in JPA (w/Hibernate 4.3.5 as the persistence provider) using Maven as the build tool. I now want to add a web front-end component to this app and have decided to use Grails 3.x as my web framework. I want to re-use my existing JPA annotated domain classes with Grails and then use generate-all to create controllers and views for each domain model. My first milestone goal is to get basic CRUD functionality on the old domain models from this web app.
Following the information I found in the Grails documentation and in some older blog posts as well as some slightly related SO posts, I created a new Grails project and packaged up my existing project as a jar and installed it (I ran mvn install -DskipTests to be exact) and then added it to build.gradle (actually I just want to have one project in the end, but I thought I'd try it this way first because I don't want to wrestle with having Maven and Gradle in the same project yet):
repositories {
...
mavenLocal()
...
}
dependencies {
...
compile "com.my-company:my-spring-app:1.0.0.CI-SNAPSHOT"
...
}
No warnings or errors from IntelliJ IDEA 14 so far. Then I created the grails-app/conf/hibernate/hibernate.cfg.xml file and tried putting just one of my old JPA annotated entities in it for now:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping package="com.my-company.my-spring-app.domain" />
<mapping class="com.my-company.my-spring-app.domain.City" />
</session-factory>
</hibernate-configuration>
No complaints from the IDE here either.
The City.java entity class looks something like:
package com.my-company.my-spring-app.domain;
import javax.persistence.*;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* City generated by hbm2java
*/
#Entity
#Table(name = "city",
schema = "public",
uniqueConstraints = #UniqueConstraint(columnNames = "name"))
public class City implements java.io.Serializable {
private static final long serialVersionUID = 4674557242772722625L;
#Id
#SequenceGenerator(name = "city_gen",
schema = "public",
sequenceName = "city_id_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "city_gen")
#Column(name = "id",
unique = true,
nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "countryid",
nullable = false)
// #JoinColumn(name = "country_id", nullable = false)
private Country country;
#Column(name = "name",
unique = true,
length = 200)
private String name;
...
}
Then I jumped into the Grails console and tried to generate the controller and views for the City domain class:
grails generate-all com.my-company.my-spring-app.domain.City
But I just get a Domain class not found error:
| Error domain class not found for name com.my-company.my-spring-app.domain.City
I quickly created a Grails 2.5.0 app, put the my-spring-app.jar in lib/ and tried this again to see if it was an issue with the 'bleeding edge' Grails 3.0.1 but got the same result.
Does anyone know what's going on here? How can I re-use my old JPA domain classes with Grails 3.x so that I can stay DRY with only one set of domain entities?
I resolved a similar issue by putting hibernate.cfg.xml in grails-app/conf (not inside a hibernate subdirectory) as described in mapping-with-hibernate-annotations-in-grails-3-0-1.

Entity Class name is transformed into SQL table name with underscores

I have the following entity defined:
#Entity
#Table(name = "EmailTemplate")
public class EmailTemplate {
Despite the table annotation, I receive java.sql.SQLException: Invalid object name 'email_template'. How can I prevent an entity class such as EmailTemplate being transformed into email_template table name?
Edit:
I'm using Spring Boot: start JPA. From my build.gradle file,
compile("org.springframework.boot:spring-boot-starter-data-jpa")
Spring by default uses org.springframework.boot.orm.jpa.SpringNamingStrategy which splits camel case names with underscore. Try setting spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.EJB3NamingStrategy in application.properties. Check out this and this for more info.
For hibernate v5:
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
For Spring Boot 2 (checked with 2.2.6.RELEASE) it should be configuration yml file:
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
So you could have model like:
#Table(name = "tblDepartments")
public class Department {
#Id
#Column(name = "dpID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#Size(min = 1, max = 25)
#Column(name = "dpName", length = 25)
private String name;
and populate tables at startup with data.sql:
INSERT INTO tblDepartments (dpName) VALUES ('Gryffindor');
INSERT INTO tblDepartments (dpName) VALUES ('Hufflepuff');
Use this in your appplication.properties.
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
Use
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
In application.properties set
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
There are two most common org.hibernate.boot.model.naming.PhysicalNamingStrategys:
org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
# also deprecated in 2.6 in favor of CamelCaseToUnderscoresNamingStrategy
# for removal in 2.8
org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties holds:
private void applyNamingStrategies(Map<String, Object> properties) {
applyNamingStrategy(properties, AvailableSettings.IMPLICIT_NAMING_STRATEGY, this.implicitStrategy,
() -> SpringImplicitNamingStrategy.class.getName());
applyNamingStrategy(properties, AvailableSettings.PHYSICAL_NAMING_STRATEGY, this.physicalStrategy,
() -> CamelCaseToUnderscoresNamingStrategy.class.getName());
}
so by default CamelCaseToUnderscoresNamingStrategy is in use and you have underscores...
org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a];
nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement
Both are required :
implicit-strategy
physical-strategy
Solved.
Invalid Object Name: Springboot with JPA(SQL server)
In application.yaml/properties specify the
spring.jpa.hibernate.naming.implicit-strategy
spring.jpa.hibernate.naming.physical-strategy
jpa:
show-sql: false
hibernate:
ddl-auto: none # Defaults to "none" when NOT in embedded mode
naming:
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

Resources