SQL sequence: Hibernate and Liquibase - java

I am quite new to development with databases, so maybe this question is not entirely spot on, but I'd appreciate if someone can make it a bit clearer to me... I've read all about sequences, and how they are preferred over identities. I have a hypothetical question. If I were to use a sequence to generate my PK along with Hibernate (data insertion) and Liquibase (schema creation), what would be the right spot to define sequence?
For example: Sequence generation on class level.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#SequenceGenerator(name = "USER_SEQ", sequenceName = "USER_SEQ")
#GeneratedValue(strategy = SEQUENCE, generator = "USER_SEQ")
#Column(name = "ID")
private Long id;
// other fields
}
Or other example: Sequence generation on Schema level.
changelog.xml
<changeSet author="wesleyy">
<createSequence catalogName="cat"
cycle="true"
incrementBy="1"
maxValue="1000"
minValue="10"
ordered="true"
schemaName="public"
sequenceName="user_seq"
startValue="1"/>
</changeSet>
Is it required to define a sequence in both Liquibase and Hibernate? What exactly is the difference between the two?

By lines in class User
#SequenceGenerator(name = "USER_SEQ", sequenceName = "USER_SEQ")
#GeneratedValue(strategy = SEQUENCE, generator = "USER_SEQ")
you say to hibernate: for each object User inserted into database give new value for primary key from sequence named USER_SEQ.
By adding described changeSet to liquibase xml script you say to liquibase: next time create in database sequence named user_seq if this changeSet was not already applied to database.
In other words in liquibase script you create sequence and in code of class User you use it.

If what you wish for is the id primary key to be auto generated then this can be simply done as follows using a JPA implementation such as Hibernate or others:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
The Id primary key will be auto generated and managed for you.
Using liquibase, in your base changelog xml file, you can do the following as long as your database supports auto generated id's (many do this but check if unsure):
<changeSet author="name" id="auto increment example">
<createTable tableName="users">
<column autoIncrement="true" name="id" type="SERIAL">
<constraints primaryKey="true" primaryKeyName="users_pkey"/>
</column>
<column name="some_other_column" type="VARCHAR(255)"/>
<column name="another_column" type="VARCHAR(255)"/>
</createTable>
</changeSet>

Your question is a bit misleading because it is related to two different things:
generation of entity identifiers (behavior of your application) and
definition of the underlying database schema.
"If I were to use a sequence to generate my PK along with Hibernate (data insertion) and Liquibase (schema creation), what would be the right spot to define sequence?"
You could define a sequence in many different ways. It always ends with SQL statement, but sometimes you may prefer to use Liquibase, Flyway or Hibernate to DDL auto export (Hibernate hbm2ddl.auto possible values and what they do?).
My personal preference is to rely on Hibernate automatic schema export during initial development and later on use some of the mentioned version-based DB migration tools.

Related

Convert Entity object to changeSet automatically

I created many Entities Like this in my Java project:
#Data
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name="backup_item")
public class BackupItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public Path path;
}
Now I need to create the change logs for it:
<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.0.xsd">
<changeSet id="1" author="rrrr">
<sql>
CREATE TABLE backup_item(
id SERIAL PRIMARY KEY NOT NULL,
);
</sql>
<rollback>
DROP TABLE backupset;
</rollback>
</changeSet>
Is there any tool that will create it for me automatically? I have at lease 10 tables and it will take me a lot of time to create them manually one by one.
You can use liquibase-maven-plugin as shown in chapter 6. (titled: Generate the changeLog With a Maven Plugin) of this tutorial:
https://www.baeldung.com/liquibase-refactor-schema-of-java-app
Although the auto generated changelogs are not that reliable and I even saw posts where liquibase developers don't recommended. They don't support a lot of stuff like detecting renamed columns etc.
It is recommended to write the changelogs manually as you have more control of the script itself...

Hibernate 5 ID AUTO Generation Type for Oracle as Sequence and MySQL as Identity

We have a Java program using Hibernate 5 that can be installed using different DB Engines: Oracle, PostgreSQL or SQL Server. Now we are introducing MySql.
The entities have a Generated Value AUTO identifier.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
This worked fine until now. Oracle and PostgreSQL installations are using HIBERNATE_SEQUENCE sequence to generate the id values and SQL Server is using a table generated id (IDENTITY).
With MySQL we would like to use also the table generated id, but Hibernate 5 GenerationType.AUTO on MySQL tries the TABLE generator instead of the IDENTITY generator.
I found this solution by Vlad Mihalcea where using a native generator makes Hibernate to select IDENTITY for MySQL:
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
protected Long id;
That's nice for MySQL, but then the application is no longer working in Oracle (ORA-02289: sequence does not exist). I think Hibernate is then searching for a sequence called native in the Oracle DB.
Is there a solution to keep the ID generation the same for all the other DB engines (Oracle, PostgreSQL and SQL Server) and use the IDENTITY for MySQL?
If not the only solution I can see is to implement the TABLE ID generation in MySQL, but it seems not to have a good performance.
I almost had it in the question, but in case somebody falls in the same situation: calling the generator HIBERNATE_SEQUENCE will make it backwards compatible with Oracle and PostgreSQL (SQL Server will continue using IDENTITY).
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "HIBERNATE_SEQUENCE")
#GenericGenerator(name = "HIBERNATE_SEQUENCE", strategy = "native")
protected Long id;
Hey this question/answer got me on the road to the answer. I am using Hibernate core 5.4.30 with MSSQL SQLServer2012Dialect and Oracle Oracle12cDialect. I needed to use the #GenericGenerator annotation as well for it's native strategy. maybe I could have used this directly with #GeneratedValue? anyway...
I found Oracle did not come with a sequence named HIBERNATE_SEQUENCE, and even when I added one, hibernate was still giving me sequence not found errors, seemingly having to do with my schema... So you can make things more specific like this:
#Id
#GenericGenerator(name = "MY_SEQUENCE", strategy = "native",
parameters = {
#org.hibernate.annotations.Parameter(name = "schema", value="MY_SCHEMA"),
#org.hibernate.annotations.Parameter(name = "sequence_name", value="MY_SEQUENCE")
}
)
#GeneratedValue(generator = "MY_SEQUENCE")
private Long id;
Hypothetically the name of the #GenericGenerator only has to match the generator name in the #GeneratedValue, but I made it the same as my actual database sequence for simplicity.

Hibernate expects sequence instead of table for postgres

I have my id generation defined as follows:
#Id
#GeneratedValue(generator = "pooled")
#GenericGenerator(name = "pooled", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = {
#Parameter(name = "value_column_name", value = "sequence_next_hi_value"),
#Parameter(name = "prefer_entity_table_as_segment_value", value = "true"),
#Parameter(name = "optimizer", value = "pooled-lo"),
#Parameter(name = "increment_size", value = "100")})
private Long id;
I'm using liquibase for table generation and let hibernate validate the schema, so that forgetting to edit the changelog will be detected. The liquibase definition for the hibernate_sequences looks like:
<changeSet author="david (generated)" id="1477051164467-23">
<createTable tableName="hibernate_sequences">
<column name="sequence_name" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="sequence_next_hi_value" type="bigint"/>
</createTable>
</changeSet>
This works for my local SQLServer Express as well as the h2 database that is used in unit tests. When I deploy the application on our dev server with postgres hibernate expects a database sequence instead of using the sequence table: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing sequence [hibernate_sequence]
I thought that using the TableGenerator strategy will not require sequences on any database.
Using Spring Boot with hibernate 5.0.11.Final
At this point we do not know what databases will be used in production so I need a portable solution. (at least supporting Oracle, MsSQL, Postgres, MySql... so in fact almost everything)
Update:
I have installed a local postgres and added the sequence by hand to check if it used. It is indeed used (the counter is 3) but I have no clue by which entity.
I have also hibernate envers enabled for entity auditing. The revinfo table uses a separate sequence, but this one is created by liquibase.
Turned out, the hibernate envers default entity uses #GeneratedValue hence the need for the sequence
Thanks in advance. If you need more information feel free to ask.
Please review your entities and check if all of them are using org.hibernate.id.enhanced.TableGenerator. That's the only possible cause - if you take a look at https://github.com/hibernate/hibernate-orm/blob/5.0.11/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java#L662 you will realize it doesn't require any sequences.

JPA GenerationType.AUTO not considering column with auto increment

I have a table with a simple int id column with Identity auto increment in SQL Server.
The entity's Id is annotated with #Id and #GeneratedValue
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", length = 4, precision = 10, nullable = false)
private Integer id;
In SQL Server the column is properly set as Identity with Seed and Increment equals to 1.
When I try to persist an instance of this entity, Hibernate tries to query the hibernate_sequence table to obtain the ID value. Since I haven't created that table in my schema I'm getting an error:
could not read a hi value: com.microsoft.sqlserver.jdbc.SQLServerException: Invalid object name 'MySchema.hibernate_sequence'
If I change the generation type to IDENTITY everything works as expected
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", length = 4, precision = 10, nullable = false)
private Integer id;
I cannot change it this way, since my App will run both on MS SQL and ORACLE, and the latter does not support auto incremented columns.
As far as I know the AUTO type should use the auto increment behaviour if the underlying database has support to it, so I don't know why is not working.
UPDATE:
It took me some time but I was able to understand exactly what is going on.
I am working with legacy databases with the following behaviours:
MSSQL: id generation uses table IDENTITY
ORACLE: id generation uses a trigger. The trigger queries and updates a custom table where all the "next ids" are stored. This table is called SEQ.
Here is the outcome of using some id generation strategies:
AUTO: does not work in MSSQL, as explained above
IDENTITY: works in MSSQL but is not supported by Oracle
"native": works in MSSQL but fails in ORACLE. It fails because Hibernate activates its default sequence strategy, which uses hibernate_sequences.nextval. Since this is a legacy application the values from the SEQ table (mentioned above) and the hibernate_sequences are not synchronized (SEQ's value for that particular table is at 6120, and hibernate_sequences' is at 1, which is expected since it was not used until now).
So what I need to figure out is a way to configure that entity to:
Use MSSQL Identity feature
OR
When using Oracle, do not automatically set any value to the ID variable and leave everything up to the pre-existing trigger
This can cause me serious issues on Oracle when I need to insert entities that depend on the main entity (via foreign key), because Hibernate won't know which ID value was generated by the "external" trigger.
I had a similar problem and found this information (deeper explained in here).
Adding this property into my persistence.xml file fixed the issue:
<property name="hibernate.id.new_generator_mappings" value="false" />
Orcale 12c supports IDENTITY and SQL SERVER 2012 supports SEQUENCES. I believe a SEQUENCE is always a better choice than an IDENTITY. IDENTITY disables batching and SEQUENCES allow you to provide optimizers, such as the pooled-lo optimization strategy.
This is how the actual identifier generator is chosen for the configured GenerationType value:
switch ( generatorEnum ) {
case IDENTITY:
return "identity";
case AUTO:
return useNewGeneratorMappings
? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName()
: "native";
case TABLE:
return useNewGeneratorMappings
? org.hibernate.id.enhanced.TableGenerator.class.getName()
: MultipleHiLoPerTableGenerator.class.getName();
case SEQUENCE:
return useNewGeneratorMappings
? org.hibernate.id.enhanced.SequenceStyleGenerator.class.getName()
: "seqhilo";
}
If you use the new identifier generators:
properties.put("hibernate.id.new_generator_mappings", "true");
The AUTO will actually use a SequenceStyleGenerator and where the database doesn't support sequences, you end up using a TABLE generator instead (which is a portable solution but it's less efficient than IDENTITY or SEQUENCE).
If you use the legacy identifier generators, you then end up with the "native" generation strategy, meaning:
public Class getNativeIdentifierGeneratorClass() {
if ( supportsIdentityColumns() ) {
return IdentityGenerator.class;
}
else if ( supportsSequences() ) {
return SequenceGenerator.class;
}
else {
return TableHiLoGenerator.class;
}
}
If a new Oracle12gDialect is going to be added and it will support IDENTITY, then AUTO might switch to IDENTITY rather than SEQUENCE, possibly breaking your current expectations. Currently there is no such dialect available so on Oracle you have SEQUENCE and in MSSQL you have IDENTITY.
Conclusion:
Try it like this:
#Id
#GenericGenerator(name = "native_generator", strategy = "native")
#GeneratedValue(generator = "native_generator")
private Long id;
make the id a Long instead of Integer, and you can let the HBMDDL handle the primary key column type.
force Hibernate to use the "native" generator
If your legacy system uses a table for generating sequence values and there was no hilo optimization ever used you can use a table identifier generator:
#Id
#GeneratedValue(generator = "table", strategy=GenerationType.TABLE)
#TableGenerator(name = "table", allocationSize = 1
)
private Long id;
You can also use the JPA table generator, just make sure you configure the right optimizer. For more info check my Hibernate tutorial
because
#GeneratedValue(strategy = GenerationType.AUTO)
use SequenceStyleGenerator by default in earlier versions
you have to look at this https://hibernate.atlassian.net/browse/HHH-11014

hibernate id generator AUTO_INCREMENT at h2 and MySQL in a cluster

For testing I'm using the H2 database.
For production it's MySQL.
I understrand that both support AUTO_INCREMENT (mysql / h2), but it seems like Hibernate doesn't work this way.
identity is supported for MySQL. Fine.
What about H2? Should I write my own generator or...? (using the org.hibernate.id.IdentifierGenerator interface as the doc says).
I must have a nice clean & quick way to get an ID (of type long by the way) from the database itself because the application is in a cluster (i.e several servers INSERT into the database at once)... that's why increment is definitely not for me.
Thanks!
You should just annotate your id property which needs the generated value with #GeneratedValue. This will automatically select the appropriate generation strategy for the database you're using. See GenerationType.AUTO for more details.
Your property will look like this:
#Id
#GeneratedValue
private long id;
Use the native generator, for example
<id name="id" type="int">
<column name="id_column" />
<generator class="native" >
<param name="sequence">id_column_sequence</param>
</generator>
</id>
The generator with the class native uses the best generation strategy for the database. In the case of MySql this is auto_increment, in the case of Oracle this is a sequence (and for H2 it also should be a sequence, but I've never tried, because I don't use H2). The generator parameter sequence only is used if it is useful, i. e. for MySql databases the parameter is ignored, and for Oracle it is used.
In that way you can use the same mapping file for different database types (at least as long as the table and column names are the same).

Categories

Resources