Spring boot not creating schema correctly? - spring-boot

I have very simple spring boot app which is using h2 database on local. I have schema-h2.sql which has below script -
CREATE TABLE USERS(
userid NUMBER(10,0) AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(256) UNIQUE NOT NULL,
fname VARCHAR(256),
lname VARCHAR(256)
);
And in application-default.properties -
spring.h2.console.enabled=true
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MSSQLServer;DB_CLOSE_DELAY=-1;DB_CLO SE_ON_EXIT=false
When I look at /h2-console, I can see table being created but it is missing unique constraint on email column.
I even tried adding alter statement in schema-h2.sql script to create constraint but that doesn't work either -
ALTER TABLE USERS ADD CONSTRAINT UQ_EMAIL UNIQUE(email);
Also, when run those script in h2-console it creates unique constraint correctly so it does seem like something to do with how spring executes them.
When debugging I can see that Alter statement gets executed without any exception in ScriptUtils class.
Not sure how to take this forward so any help will be greatly appreciated.

For anyone else coming across this, I was able to fix it by altering my JPA annotation. Adding the unique parameter in addition to creating the constraint in h2-schema.sql allowed the constraint to actually be created.
#Column(name = "email", unique = true, nullable = false)
private String email;

If schema is beign created by spring boot with schema.sql file, make sure to disable hibernate schema auto create:
spring.jpa.hibernate.ddl-auto=none
Otherwise, Hibernate will overwritte it everytime the app starts.

Related

Tests failing when upgrading to Spring Boot 2.7 - "CommandAcceptanceException: Error executing DDL"

After upgrading to Boot 2.7 the integration tests that were using an embedded H2 database started failing.
I see this WARN message in the logs, but it's not very clear the cause or the solution for this:
WARN 8053 ---[ main] o.h.t.s.i.ExceptionHandlerLoggedImpl :GenerationTarget encountered exception accepting command : Error executing DDL "create table user (id bigint generated by default as identity, email varchar(255) not null, name varchar(255), primary key (id))" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table user (id bigint generated by default as identity, email varchar(255) not null, name varchar(255), primary key (id))" via JDBC Statement
...
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "create table [*]user (id bigint generated by default as identity, email varchar(255) not null, name varchar(255), primary key (id))"; expected "identifier"; SQL statement:
create table user (id bigint generated by default as identity, email varchar(255) not null, name varchar(255), primary key (id)) [42001-212]
...
It seems my User table is not created after the upgrade, thus making my tests fail.
It seems Boot 2.7 upgraded to its H2 dependency to 2.x, which is not backward compatible and introduces several changes:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#h2-21
The issue was that User is now a Keyword / reserved word (The H2 "migration-to-v2" guide was not very helpful in my case; it mentioned new keywords were added, but it didn't provide a link to ):
https://www.h2database.com/html/advanced.html#keywords
So, what I had to do is use "quoted names" to define the Table name of my Entity (it seems I can use backticks in the table annotation too instead of escaping double quotes):
#Table(name="\"user\"")
#Entity
public class User {
...
I also had to use double quotes on my data.sql files for this table:
INSERT INTO "user"(id, email, name) VALUES(1, 'test#user.com', 'Test User');
Note: the migration guide also mentions the possibility of using the SET NON_KEYWORDS command as a workaround, but it also discourages it.
Add the following to your src/test/resources/application-test.properties file (assuming your tests run with the test profile):
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions = true
If any of your JPA entities have UUID fields, ensure those fields are annotated with #Column and the annotation's columnDefinition defines the column as type UDID. (In its simplest form: #Column(columnDefinition="UDID").) This works around a Hibernate bug.
It could be because of Identity columns in schema.sql.Identity columns should be normally declared with GENERATED BY DEFAULT AS IDENTITY.
Ex: JOB_EXECUTION_ID BIGINT IDENTITY -- should be changed as JOB_EXECUTION_ID BIGINT GENERATED BY DEFAULT AS IDENTITY.
Refer http://www.h2database.com/html/migration-to-v2.html for more such changes occurred due to upgrade in H2 version.

Spring Boot entity with Pk using Oracle DB Sequence in a trigger

I need a help on persisting an entity in a Oracle DB table that uses trigger and sequence for PK.
By now, I tried these from other stackoverflow questions:
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy="increment")
This approach works. It finds the max value of PK and increment the value in 1. But, this doesn't update the Db sequence causing "constraint violation" error at some point.
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_sequence")
#SequenceGenerator(name="id_sequence", sequenceName="MY_DB_SEQ")
This approach doesn't work for me either. Sadly, the Db sequence is not accessible and causing error when it is trying to run internally "select MY_DB_SEQ.nextval from dual". Why is not accesible? Go and ask DB admin :)
It looks like the only option I have is passing null in the Entity PK attribute so that the DB trigger, which uses the a DB sequence to get the nextval when the ID is null, assigns the PK value in DB record.
How can I pass a null value for #Id? Of course this is throwing error because is needed. Is there any other annotation I can use for this?
If this is not possible, what other ways I should try?
Thanks for your help.
UPDATE
I couldn't find another way for this, having a DB Seq that is not accessible for user, and when it requires to pass NULL to PK in order to have DB use the trigger which check NULL value in PK to run seq nexval.
After granting access to DB Seq, this approach of course works.
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="id_sequence")
#SequenceGenerator(name="id_sequence", sequenceName="MY_DB_SEQ")
Did you create the sequence in the database as below?
CREATE SEQUENCE id_seq INCREMENT BY 1 START WITH 0 MINVALUE 0 MAXVALUE 100 NOCYCLE NOCACHE;
if not create first.
or else add the below properties in the application but it's not good practice.
spring.jpa.hibernate.ddl-auto = update
As of now, I couldn't find any solution to pass NULL to PK in Entity.
In DB, the DB Sequences were granted access. Spring Boot can now read the sequence and gran the next value.

Hibernate doesn't add auto_increment

I'm running into an issue when running my SpringBootTests.
The tests are using an H2 database, so they recreate the schema every time they run. For one of my entities, Hibernate doesn't add auto_increment to the id column.
I can't find any relevant differences between the failing entity (Payment) and others that work correctly (e.g. Invoice). They all have the following annotations on the id field:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
I set spring.jpa.show-sql=true, and this is what I see:
The Payment entity shows create table payment (id bigint not null,
The Invoice entity shows create table invoice (id bigint not null
auto_increment,
In fact, I tried copying the Payment class to Payment2, and Payment2 did not have the issue:
The Payment entity shows create table payment (id bigint not null,
The Payment2 entity shows create table invoice (id bigint not null
auto_increment,
Given this, I believe I should be looking for an overriding configuration that's not in the Payment class, but that somehow removes auto_increment from just that one class.
Does anyone know what could be causing this?
These are the versions of the libraries involved:
Spring Boot version 2.1.8
Hibernate 5.3.11
H2 Database 1.4.200
I finally figured it out. This assumption was correct:
I should be looking for an overriding configuration that's not in the Payment class, but that somehow removes auto_increment from just that one class.
Turned out that a colleague had added a "light" read-only version of the Payment entity called PaymentLight. It read from the same payment table but without any of the joins. The id field in the PaymentLight class did not have the #GeneratedValue annotation.
I haven't checked this, but I assume Hibernate merged the two entities (the light class was a subset anyway) and used the #GeneratedValue configuration from one of them (likely alphabetical order).

Spring Session table-name property does not change the table name

I have to be able to rename the default Spring Session table and found the following configuration options in the spring session documentation.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-##platform##.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions.
Here is what I am trying to set in application.properties:
spring.session.store-type=jdbc
spring.session.jdbc.table-name: APP_SPR_SESSION
spring.session.jdbc.schema: src/main/resources/appSpringSession.sql
Here are the contents of appSpringSession.sql which is a modified/renamed version of schema-oracle.sql
CREATE TABLE app_app.APP_SPR_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME NUMBER(19,0) NOT NULL,
LAST_ACCESS_TIME NUMBER(19,0) NOT NULL,
MAX_INACTIVE_INTERVAL NUMBER(10,0) NOT NULL,
EXPIRY_TIME NUMBER(19,0) NOT NULL,
PRINCIPAL_NAME VARCHAR2(100 CHAR),
CONSTRAINT APP_SPR_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);
CREATE UNIQUE INDEX APP_SPR_SESSION_IX1 ON APP_SPR_SESSION (SESSION_ID);
CREATE INDEX APP_SPR_SESSION_IX2 ON APP_SPR_SESSION (EXPIRY_TIME);
CREATE INDEX APP_SPR_SESSION_IX3 ON APP_SPR_SESSION (PRINCIPAL_NAME);
CREATE TABLE app_app.APP_SPR_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR2(200 CHAR) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT APP_SPR_SESSION_ATTRIBUTES_PK PRIMARY KEY
(SESSION_PRIMARY_ID,
ATTRIBUTE_NAME),
CONSTRAINT APP_SPR_SESSION_ATTRIBUTES_FK FOREIGN KEY
(SESSION_PRIMARY_ID)
REFERENCES APP_SPR_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
I have manually added the tables using the above ddl into the Oracle database and every time the app starts it's still looking for SPRING_SESSION table.
Seems like specifying these options has no effect. What am I reading wrong here from the docs?
Also I am using Spring Session 2.0.2 Release and Spring Boot 2.0.1.RELEASE
The reason spring.session.* do not work for you is because your are using #EnableJdbcHttpSession. That means that you are configuring Spring Session explicitly, so Spring Boot backs off with its auto-configuration.
You should remove #EnableJdbcHttpSession and ensure Spring Boot is auto-configuring Spring Session. Additionally, you could also leave spring.session.store-type out, since Spring Boot should be able to deduce which session repository implementation are you using as long as you have only only SessionRepository implementation on the classpath (i.e. you depend only on spring-session-jdbc and no other Spring Session modules).
You can also use #EnableJdbcHttpSession(tableName = "APP_SPR_SESSION")

#UniqueConstraint requires alter table in MariaDB if table already existed before with no constraints?

I apologize if I repeat the question, but I did not find a similar one.
I have added a unique constraint on an already existent table. We use MariaDB.
I have used the annotation:
#Table(uniqueConstraints={#UniqueConstraint(name="autonomy_name_energyType", columnNames={"autonomy","name","energyType"})})
The unit tests pass, but in the DB I am still allowed to create duplicates.
Do I need an ALTER table too? By checking the table I can see there are no constraints added to it.
Thanks
As explained in these SO posts :
Unique constraint not created in JPA
#Column(unique=true) does not seem to work
An explicit alter table query is needed for ur constaints to take effect on the db level.
As an extra info, it would have worked if the table was being re-created via JPA. see :
Add a unique constraint over muliple reference columns

Resources