Spring Boot application doesn't create schemas for JPA #Table annotation - java

I want to have tables located in different database schemas. But unfortunately, I can't achieve this with Spring Boot. Here steps to reproduce it.
Create a new Spring Boot project on http://start.spring.io version 2.0.5 (with derby and PostgreSQL dependencies)
Create simple entity
#Entity
#Table(name = "my_table")
public class MyTable {
#Id Integer id;
}
Add only next property to the application.properties with value 'update' or 'create' (if you try 'create-drop' then you get another error described here: https://github.com/spring-projects/spring-boot/issues/7706#issuecomment-268798059). Now Derby datasource will be used by default.
spring.jpa.hibernate.ddl-auto=create
Run a generated test or main class. Be sure all works fine.
Modify the entity, add attribute schema to the #Table annotation. Now the entity looks like:
#Entity
#Table(name = "my_table", schema = "my_schema")
public class MyTable {
#Id Integer id;
}
Run a test (or main class). This time I get an error while Spring Boot initialization process "java.sql.SQLSyntaxErrorException: Schema 'MY_SCHEMA' does not exist":
Full log listing is available here: https://gist.github.com/asaushkin/8d767c92b2e7025dd359f7be43eefdd6
Check on PostgreSQL. This error reproduces on a PostgreSQL instance too. Without the 'schema' attribute Spring Boot app runs perfect, but as soon as this attribute appears on the #Table annotation the exceptions are thrown.
Full log is here: https://gist.github.com/asaushkin/dd0d677964556bf943c4f013d4785372
My question is: why are schemas not created by Spring Boot?
These options can't resolve this issue too:
spring.jpa.properties.javax.persistence.schema-generation.create-database-schemas=true
spring.jpa.properties.hibernate.hbm2dll.create_namespaces=true
Links
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#configurations-hbmddl
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html#howto-configure-jpa-properties
Update (11 March 2019):
I've just check the current behavior of the issue. I wonder, but currently with Derby driver all works fine and the table is created with the specified schema. But in PostgreSQL an error continues exists.
Generated SQL (for PostgreSQL) is:
create table my_schema.my_table (id int4 not null, primary key (id))

Check that are you specifying the database dialect in the application.properties file or not for more check this thread.
Unable to get spring boot to automatically create database schema

I had the same problem with PostgreSQL and JPA (ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: relation "schema.table" does not exist) and I figured out this solution.
In your entities classes, add escape characters \", between database element´s name. For instance:
Use this form:
#Table(name = "\"USUARIO\"", schema="\"INVENTARIODB\"")
Rather than a typical way
#Table(name = "USUARIO", schema="INVENTARIODB")
The same applies for columns names
#Column(name = "\"ID\"", nullable = false, updatable = false)
private Long id;
Rather than
#Column(name = "ID", nullable = false, updatable = false)
private Long id;
UPDATE:
I discovered the reason that was causing the problem. I used Valentina Studio to create my DB, if I use capital letters (MYTABLE), instead lower-case letters (mytable) to create my tables, I had to use double quotes inside SQL statements. This is because PostgreSQL is case sensitive. If you can´t change your database then use my last solution. Also is a good idea to enable spring.jpa.show-sql=true property, so you can see hibernate´s queries and know what´s going on.

Rename spring.jpa.properties.javax.persistence.schema-generation.create-database-schemas to spring.jpa.properties.javax.persistence.create-database-schemas. In other words, remove '.schema-generation'.
I just had the same problem not with PostgreSQL but H2 - schemas weren't being created. But, as I've discovered, the problem is not with H2 (or, likely, PostgreSQL) but, rather, Hibernate (it deviates from the standard, regarding that nomenclature). That likely means that this solution will work for you too.

Related

spring / hibernate: Generate UUID automatically for Id column

I am trying to persist a simple class using Spring, with hibernate/JPA and a PostgreSQL database.
The ID column of the table is a UUID, which I want to generate in code, not in the database.
This should be straightforward since hibernate and postgres have good support for UUIDs.
Each time I create a new instance and write it with save(), I get the following error:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2) ...
This error indicates that it's expecting the ID column to be auto-populated (with some default value) when a row is inserted.
The class looks like this:
#lombok.Data
#lombok.AllArgsConstructor
#org.springframework.data.relational.core.mapping.Table("doodahs")
public class Doodah {
#org.springframework.data.annotation.Id
#javax.persistence.GeneratedValue(generator = "UUID")
#org.hibernate.annotations.GenericGenerator(name="UUID", strategy = "uuid2")
#javax.persistence.Column(nullable = false, unique = true)
private UUID id;
//... other fields
Things I have tried:
Annotate the field with #javax.persistence.Id (in addition to existing spring Id)
Annotate the field with #org.hibernate.annotations.Type(type = "pg-uuid")
Create the UUID myself - results in Spring complaining that it can't find the row with that id.
Specify strategy = "org.hibernate.id.UUIDGenerator"
Annotate class with #Entity
Replace spring #Id annotation with #javax.persistence.Id
I've seen useful answers here, here and here but none have worked so far.
NB the persistence is being handled by a class which looks like this:
#org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
The DDL for the table is like this:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
Update
Thanks to Sve Kamenska, with whose help I finally got it working eventually. I ditched the JPA approach - and note that we are using R2DBC, not JDBC, so the answer didn't work straight away. Several sources (here, here, here, here, here and here) indicate that there is no auto Id generation for R2DBC. So you have to add a callback Bean to set your Id manually.
I updated the class as follows:
#Table("doodahs")
public class Doodah {
#org.springframework.data.annotation.Id
private UUID id;
I also added a Bean as follows:
#Bean
BeforeConvertCallback<Doodah> beforeConvertCallback() {
return (d, row, table) -> {
if (d.getId() == null){
d.id = UUID.randomUUID();
}
return Mono.just(d);
};
}
When a new object (with id = null, and isNew = true) is passed to the save() method, the callback method is invoked, and it sets the id.
Initially I tried using BeforeSaveCallback but it was being called too late in the process, resulting in the following exception:
JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"....
Update
There are, at least, 2 types of Spring Data: JPA and JDBC.
The issue happens because you are mixing the 2 of them.
So, in order to fix, there are 2 solutions.
Solution 1 - Use Spring Data JDBC only.
Pom.xml dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
Generate ID.
Spring Data JDBC assumes that ID is generated on database level (like we already figured that out from log). If you try to save an entity with pre-defined id, Spring will assume that it is existing entity and will try to find it in the database and update. That is why you got this error in your attempt #3.
In order to generate UUID, you can:
Leave it to DB (it looks like Postgre allows to do it)
or Fill it in BeforeSaveCallback (more details here https://spring.io/blog/2021/09/09/spring-data-jdbc-how-to-use-custom-id-generation)
#Bean BeforeSaveCallback<Doodah> beforeSaveCallback() {
return (doodah, mutableAggregateChange) -> {
if (doodah.id == null) {
doodah.id = UUID.randomUUID();
}
return doodah;
};
}
Solution 2 - Use Spring Data JPA only
Pom.xml dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Generate ID.
Here you can, actually, use the approach with the UUID auto-generation, like you wanted to do initially
Use javax.persistence #Entity annotation instead of springdata #Table on the class-level
and Use #javax.persistence.Id and #javax.persistence.GeneratedValue with all defaults on id-field.
#javax.persistence.Id
#javax.persistence.GeneratedValue
private UUID id;
Other notes:
Specification of generator and strategy is not required, since it will generate based on the type of the id field (UUID in this case).
Specification of Column(nullable = false, unique = true) is not required either, since putting #Id annotation already assumes these constraints.
Initial answer before update
The main question: how do you save the entity? As id-generation is handled by JPA provider, Hibernate in this case. It is done during save method of em or repository. In order to create entities and ids Hibernate is looking for javax.persistence annotations, while you have Spring-specific, so I am wandering how do you save them.
And another question here: the error you provided INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2 shows that there is no id field in the insert-query at all. Did you just simplified the error-message and removed ID from it? Or this is original error and your code does not even "see" field ID? In that case the issue in not related to the id-generation, but rather is related to the question why your code does not see this field.

With DataJpaTest Repository Tests using MySQL, Hibernate doesn't set User to auto-increment in H2 DB

I'm trying to test my Spring Boot repositories using DataJpaTest. I'm using MySQL, so all of my models are using IDENTITY for id generation:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
But as you can see from the screenshot below, when I run my tests, Hibernate sets "generated by default as identity" for the ids of all of my models except for User and Property:
This makes it so that I cannot create users (or properties) in my tests, since GenerationType.IDENTITY sets always sets the id to null before sending it to the db, which results in the following error:
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
Does anyone know why this is happening? And, of course, more important than the why is what can I do to fix it? :)
Update
To simplify the problem a bit and provide different errors for different things I've tried...
My DataJpaTests are using H2, which I guess is automagically generating the db structure from the code. My real DB is MYSQL, which is generated in a separate project.
Scenario 1:
When I set GenerationType to IDENTITY, the live DB works, but the test DB gives me could not execute statement; SQL [n/a]; constraint [null].
Scenario 2:
When I set GenerationType to AUTO, the test DB works, but the live DB gives me Table 'tablename.hibernate_sequence' doesn't exist.
Scenario 3:
Trying to make MYSQL work with GenerationType AUTO. I have found nothing in quite a lot of searching on how to tell Hibernate to default MYSQL to identity instead of table. I've found rather a lot saying to never use table and that overriding this is impossible without changing the Hibernate source code. This is a bunch of BS that makes me wonder what the Hibernate developers are smoking.
Scenario 4:
Trying to make H2 work with GenerationType IDENTITY. I've had some luck here with the following:
Putting spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MYSQL in a test.properties file.
Annotating my Test class with #TestPropertySource(locations= "classpath:test.properties") and #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
This gets rid of the null constraint error, but instead I'm now getting The database returned no natively generated identity value. Presumably, this is because, while the actual SQL code had auto_increment for the id, whatever magic is being used to generate the table for H2 isn't getting it there.
Addendum: The thing that is really frustrating about all of this is that it works totally fine for most of the tables. Out of the more than 20 tables in my DB, all but two of them auto generate and increment their ids just fine using IDENTITY. It makes no sense at all that some of them would work and some of them wouldn't.
The problem was another model with #Table(name = "user") as an annotation. That model also had the Id column defined, but without a generated value. With the conflict, Hibernate chose that one to define the id.
MySql works better with
#GeneratedValue(strategy = GenerationType.AUTO)
its also better because it produces auto-increment primary key in most RDBMS

Access sequence from another schema in JPA

I have the "master_seq" defined in 2 schemes. I have a table in schema_2 say table_2. Schema_1 has been provided insert grant on table_2 and its master_seq. The problem is, I am trying to insert a record in table_2 from schema_1 (From the spring boot app I am running) and it is using schema_1's master_seq whereas I want it should use schema_2's master_seq.
In short, I want to use master_seq of the schema where the table is.
Below is the code sample:
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="MASTER_SEQ")
#SequenceGenerator(name="MASTER_SEQ",sequenceName="MASTER_SEQ",allocationSize=1)
public Long getId() {
return id;
}
Do I need to use another schema name in code somehow? But schema name keeps on changing as per the different environment.
Thanks in advance
You can try accessing the sequence by appending the schema name to sequence name:
#SequenceGenerator(name="MASTER_SEQ",sequenceName="schema_2.MASTER_SEQ",allocationSize=1)

Migrating Spring application from Postgres to Oracle: defining table's schema

FINAL EDIT
The reason why I didn't see any logs is that I was using an old version of log4j. Hibernate hbm2ddl uses slf4j for logging and so all the logs were ignored by log4j 1. Now I upgraded to log4j2 that has a slf4j bridge and I see all the errors in the log.
Godd new is that from here I can pick the conversion errors one by one and fix them, bad news (for me) is that they are a lot!
Thanks to everyone!
FINAL EDIT END
I've a very complex java7+Spring3.2.0+hibernate3 web application that works with a Postgresql database. A client now imposed as requirement to use Oracle as a database for a project. I'm having trouble figuring out the Schema use in Oracle.
The Postgresql database is divided in about 10 different schemas, plus separated schemas for the audit records. I define tables and schemas in the annotation. I.e.:
#Entity
#Table(name = "language", schema = "live")
#Audited
#AuditTable(value="language_aud", schema="live_aud")
public class Language implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
etc...
This works quite well in Postgres. To create the database I create manually the schemas first:
CREATE SCHEMA live;
And then start the application with hibernate.hbm2ddl.auto=create. This works like a charm and the database is correctly built.
Now moving to Oracle I have problems with the schema definition. I tried the CREATE SCHEMA statement with no succes but then found out that in Oracle schema=user (very strange to me but there must be a reason). So I created all the users to match the Schema structure. I.e.:
CREATE USER live IDENTIFIED BY 'password';
** EDIT **
But this still doesn't work, when I run the application the tables are not created by hibernate (I fixed the previous exception adding try/catch to postProcess methods, they failed as the database was not created)
I can get the connection correctly with Oracle, I use the parameter:
jdbc.driver=oracle.jdbc.driver
jdbc.url=jdbc:oracle:thin:#//localhost:1521/XE
jdbc.username=system
jdbc.password='password'
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
What I am doing wrong? do I have to create the schemas in a different way? or is the system user not to have the right privileges?
The application is shared by many clients so a requirement I have is to try to avoid to change completely the structure removing the schemas. In a perfect world the application should be database indipendant so work with any database.
Thank you for any help you can give and please ask if you need more info.
Mattia
In oracle id is not auto generated. You have to manage it manually or use a sequence.
Do not use
#GeneratedValue(strategy = GenerationType.AUTO)
Try this
#Id
#SequenceGenerator(name="language_generator", sequenceName="language_sequence")
#GeneratedValue(generator="language_generator")
#Column(name = "id")
private Integer id;
Make sure you do this for all your models
** EDIT **
Try this when creating the Oracle user:
SQL> CREATE USER live IDENTIFIED BY 'password'
2 QUOTA UNLIMITED ON SYSTEM
3 QUOTA UNLIMITED ON SYSAUX;
SQL> GRANT CREATE SESSION TO live;
SQL> GRANT CREATE TABLE TO live;
SQL> GRANT SELECT_CATALOG_ROLE TO live;
SQL> GRANT EXECUTE_CATALOG_ROLE TO live;
SQL> EXECUTE DBMS_STREAMS_AUTH.GRANT_ADMIN_PRIVILEGE(grantee => 'live');
SQL> GRANT UNLIMITED TABLESPACE TO live;
SQL> GRANT RESOURCE TO live;
Lock it down when you move to production. As in take away some of the privileges like ability to drop tables etc. PS do not call your model user. Just as a precaution.

Unit testing Hibernate with multiple database catalogs

I have an issue testing a Hibernate application which queries multiple catalogs/schemas.
The production database is Sybase and in addition to entities mapped to the default catalog/schema there are two entities mapped as below. There are therefore three catalogs in total.
#Table(catalog = "corp_ref_db", schema = "dbo", name = "WORKFORCE_V2")
public class EmployeeRecord implements Serializable {
}
#Table(catalog = "reference", schema = "dbo", name="cntry")
public class Country implements Serializable {
}
This all works in the application without any issues. However when unit testing my usual strategy is to use HSQL with hibernate's ddl flag set to auto and have dbunit populate the tables.
This all works fine when the tables are all in the same schema.
However, since adding these additional tables, testing is broken as the DDL will not run as HSQL only supports one catalog.
create table corp_ref_db.dbo.WORKFORCE_V2
user lacks privilege or object not found: CORP_REF_DB
If there were only two catalogs then I think it would maybe be possible to get round this by changing the default catalog and schema in the HSQL database to that one explicitly defined:
Is there any other in-memory database for which this might work or is there any strategy for getting the tests to run in HSQL.
I had thought of providing an orm.xml file which specified the default catalog and schema (overiding any annotations and having all the defined tables created in the default catalog/schema) however these overrides do not seem to be observed when the DDL is executed i.e. I get the same error as above.
Essentially, then I would like to run my existing tests and either somehow have the tables created as they are defined in the mappings or somehow override the catalog/schema definitions at the entity level.
I cannot think of any way to achieve either outcome. Any ideas?
I believe H2 supports catalogs. I haven't used them in it myself, but there's a CATALOGS table in the Information Schema.
I managed to achieve something like this in H2 via IGNORE_CATALOGS property and version 1.4.200
However, the url example from their docs did not seem to work for me, so I added a statement in my schema.xml:
SET IGNORE_CATALOGS = true;

Categories