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

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;

Related

Hibernate search does not remove old value from lucene index when the object is deleted via an #NoRepositoryBean Jpa method

I have a NoRepositoryBean Jpa interface that has one custom jpa method called deleteAllByIdIn(...) which is inherited by some concrete JpaRepositories. For some reason this custom delete method is ignored by Hibernate Search. Whenever an entity is deleted through this custom method its value is not removed from the lucene index after the delete is done. I will explain the problem some more further down this post; but first here's the code
#NoRepositoryBean
public interface NameTranslationDao<T extends NameTranslation> extends JpaRepository<T, Long> {
#Modifying
#Transactional
#Query(value = "DELETE FROM #{#entityName} c WHERE c.id IN :translationsToDelete")
public void deleteAllByIdIn(#Param("translationsToDelete") Set<Long> translationsToDelete);
}
Heres a JpaRepository subclass that extends this interface:
#Repository
#Transactional(readOnly = true)
public interface LifeStageCommonNameTranslationDao extends CommonNameTranslationDao<LifeStageCommonNameTranslation> {
}
Theres another #NoRepositoryBean interface in-between the concrete JpaRepository and the NameTranslationDao NoRepositoryBean. That one is called CommonNameTranslationDao but it doesn't override the custom method in any way, so it is unlikely the cause of the problem, nevertheless heres the code of that repository:
#NoRepositoryBean
public interface CommonNameTranslationDao<T extends NameTranslation> extends NameTranslationDao<T> {
#Deprecated
#Transactional(readOnly = true)
#Query("SELECT new DTOs.AutoCompleteSuggestion(u.parent.id, u.autoCompleteSuggestion) FROM #{#entityName} u WHERE u.autoCompleteSuggestion LIKE :searchString% AND deleted = false AND (u.language.id = :preferredLanguage OR u.language.id = :defaultLanguage)")
List<AutoCompleteSuggestion> findAllBySearchStringAndDeletedIsFalse(#Param("searchString") String searchString, #Param("preferredLanguage") Long preferredLanguage, #Param("defaultLanguage") Long defaultLanguage);
#Transactional(readOnly = true)
#Query(nativeQuery = true, value = "SELECT s.translatedName FROM #{#entityName} s WHERE s.language_id = :preferredLanguage AND s.parent_id = :parentId LIMIT 1")
public String findTranslatedNameByParentAndLanguage(#Param("preferredLanguage") Long languageId, #Param("parentId") Long parentId);
#Modifying
#Transactional
#Query(nativeQuery = true, value = "DELETE FROM #{#entityName} WHERE id = :id")
void hardDeleteById(#Param("id") Long id);
#Modifying
#Transactional
#Query(nativeQuery = true, value = "UPDATE #{#entityName} c SET c.deleted = TRUE WHERE c.id = :id")
void softDeleteById(#Param("id") Long id);
}
Also, heres the code of the LifeStageCommonNameTranslation entity class:
#Entity
#Indexed
#Table(
uniqueConstraints = {
#UniqueConstraint(name = "UC_life_cycle_type_language_id_translatedName", columnNames = {"translatedName", "parent_id", "language_id"})
},
indexes = {
#Index(name = "IDX_lifestage", columnList = "parent_id"),
#Index(name = "IDX_translator", columnList = "user_id"),
#Index(name = "IDX_species_language", columnList = "language_id, parent_id, deleted"),
#Index(name = "IDX_autoCompleteSuggestion_language", columnList = "autoCompleteSuggestion, language_id, deleted")})
public class LifeStageCommonNameTranslation extends NameTranslation<LifeStage> implements AuthorizationSubject {
#Id #DocumentId
#GenericGenerator(
name = "sequenceGeneratorLifeStageCommonNameTranslation",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "sequence_name", value = "_lifestagecommonnametranslation_hibernate_sequence"),
#org.hibernate.annotations.Parameter(name = "optimizer", value = "pooled"),
#org.hibernate.annotations.Parameter(name = "initial_value", value = "1"),
#org.hibernate.annotations.Parameter(name = "increment_size", value = "25"),
#org.hibernate.annotations.Parameter(name = "prefer_sequence_per_entity", value = "true")
}
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "sequenceGeneratorLifeStageCommonNameTranslation"
)
#Field(analyze = Analyze.NO, store = Store.YES, name = "parentId")
private Long id;
#IndexedEmbedded(includeEmbeddedObjectId = true)
#ManyToOne(fetch = FetchType.LAZY)
private LifeStage parent;
#Field(index = NO, store = Store.YES)
private String autoCompleteSuggestion;
//Getters and setters ommitted
The problem is the following: Whenever i use the inherited deleteAllByIdIn() method on LifeStageCommonNameTranslationDao then Hibernate Search will not remove the autoCompleteSuggestion field value from the lucene index after the entity has been deleted. If however i use the standard deleteById() JpaRepository method to delete the entity then the field value is removed from the lucene index.
Both the custom and the standard delete method were called within a #Transactional annotated method and i also called the flush() jpaRepository method right afterwards. I did this because I've read that this can sometimes help to update the lucene index. But in the case of deleteAllByIdIn() calling flush() afterwards did not help at all.
I already ruled out the possiblity that the problem was caused by the spEL expression in the SQL query. I tested this by replacing #{#entityName} with a concrete entity name like LifeStageCommonTranslation and then calling the deleteAllByIdIn() delete method. But the problem still persisted. The lucene index still did not remove the autoSuggestionText field value after the delete.
I can easily solve this problem by simply using the standard jpa method deleteById() but i want to know why the custom made jpa method deleteAllByIdIn() does not cause Hibernate search to update the lucene index.
Hibernate Search detects entity change events happening in your Hibernate ORM Session/EntityManager. This excludes insert/update/delete statements that you wrote yourself in JPQL or native SQL queries.
The limitation is documented here: https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#limitations-changes-in-session
The workaround is documented there too:
One workaround is to reindex explicitly after you run JPQL/SQL queries, either using the MassIndexer or manually.
EDIT: And of course your workaround might be valid as well, if deleteById loads the entity in the session before deleting it (I'm not that familiar with the internals of Spring Data JPA):
I can easily solve this problem by simply using the standard jpa method deleteById() but i want to know why the custom made jpa method deleteAllByIdIn() does not cause Hibernate search to update the lucene index.

Spring Boot Hibernate not picking up use-new-id-generator-mappings property

I'm upgrading my project to Spring Boot 2.1.18 that uses Hibernate 5.3.18.
Previously, my entity looked like thus and would use the SequenceHiLoGenerator:
#Entity
#Table(name = "group_link")
#SequenceGenerator(name = "group_link_seq", sequenceName = "group_link_seq")
public class GroupLinkEntity extends BaseObject {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "group_link_seq")
#Column(name = "group_link_id", unique = true, nullable = false)
private Long id
}
Now, by default in Hibernate 5, it uses the SequenceStyleGenerator which causes constraint violations because my increment size is 1 and the default allocationSize is 50.
The suggested thing to do to maintain compatibility is to set this property:
spring.jpa.properties.hibernate.use-new-id-generator-mappings: false
I do so but it does not seem to take, because the SequenceStyleGenerator is still used. From my understanding, this should cause it to use the SequenceHiLoGenerator. Is this incorrect?
However, if I modify the entity to look like the below it works as expected, replicating the previous functionality I had.
#Entity
#Table(name = "group_link")
#GenericGenerator(
name = "group_link_seq",
strategy = "org.hibernate.id.SequenceHiLoGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "group_link_seq"),
}
)
public class GroupLinkEntity extends BaseObject {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "group_link_seq")
#Column(name = "group_link_id", unique = true, nullable = false)
private Long id;
}
So, it would seem the property is not being taken somehow and I'm looking to figure out why that is. I see it show up in my JpaProperties bean. If I change other properties, like my dialect, I can see that they are taking effect.
Could anyone point me to the code that actually reads that property and makes a decision on what generator to use or point out some obvious error I'm making here?
As it's stated in the documentation:
You need to ensure that names defined under spring.jpa.properties.* exactly match those expected by your JPA provider. Spring Boot will not attempt any kind of relaxed binding for these entries.
For example, if you want to configure Hibernate’s batch size you must use spring.jpa.properties.hibernate.jdbc.batch_size. If you use other forms, such as batchSize or batch-size, Hibernate will not apply the setting.
So, for your case you should use:
spring.jpa.properties.hibernate.id.new_generator_mappings: false
See also this part of hibernate documentation.

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.

How to Seed Data to H2 DB in Spring Boot App with JPA

A little bit of background:
I work on a Spring boot app in a large corporation and we have no control over the Database Schema, table names, and column names. Because of this, our table and column names have no obvious meaning to them.
We are currently seeding data with the schema.sql and data.sql files in the resources directory. This hasn't been working for our team, because of the effort to seed the data with these obscure table and column makes. We often end up looking through our QA server for an account, then writing our code against a QA database.
My question:
How do I keep the schema.sql and data.sql files, but enable our team to seed data to the H2 database by using a seeder made available by JPA or Spring Data JPA.
I found a few examples of using JPA to seed data, but they don't mention where the files should be stored or how the files get called by the Spring Boot app on start up.
Our application only pulls data, and never inserts, so will I have to override the save function from the JpaRepository in order to accomplish this? Or can I just create an entity, and call the JpaRepository's save function?
Here is an obfuscated example of our account entity and repository:
AccountEntity.java
#Data
#Entity
#Table(name = "Table_Name")
#SecondaryTables({
#SecondaryTable(name = "Table_Name2", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name3", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name4", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") })
})
public class AccountEntity {
#Column(name = "ACCT_ID")
#Id
private Integer accountIdNumber;
#Column(name = "SOME_OTHER_COLUMN1")
private String someOtherColum1;
#Column(name = "SOME_OTHER_COLUMN2", table = "Table_Name3")
private String someOtherColum2;
#Column(name = "SOME_OTHER_COLUMN3", table = "Table_Name4")
private Integer someOtherColum3;
...
}
AccountRepository.java
#Repository
public interface AccountRepository extends JpaRepository<AccountEntity, Integer> {
public AccountEntity findByAccountIdNumber(Integer accountNumber);
public List<AccountEntity> findAllByProp1Prop2AndProp3(
String prop1, String prop2, String prop3);
}

Persist hibernate entity with pk generated by trigger

I have Oracle database.
In my table i have trigger, which fire up when some row is inserted. That works fine, when i use it from SQLDeveloper. But in Java I want to insert some row using Hibernate 3.3. Te body of my trigger is not really important, let's say it defines id of the row upon current time.
My POJO entity looks like:
#Entity
#Table( name = "user" )
public class User implements Serializable
{
private static final long serialVersionUID = 1;
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY )
#Column( nullable = true, length = 14, name = "user_id" )
private Long userId;
#Column( nullable = true, length = 80, name = "name" )
private String name;
//getters and setters
}
Here is my code for persisting new user:
SessionFactory sessionFactory = sessionFactoryManager.getCurrentSession();
Session session = sessionFactory.openSession();
User user = new User();
user.setName("TEST");
session.persist(user);
session.flush();
As you can see I don't set the userId field because it should be set by my trigger before inserting a row, but it doesn't work. I found, that trigger will be called if I add #GeneratedValue annotation, but it seems it doesn't work on Oracle database: I have an error:
Dialect does not support identity key generation
Trying to resolve this error I only found articles about using sequence, but I don't want to use sequence, I have to use trigger.

Resources