How to setup Liquibase to use custom SequenceStyleGenerator - java

so I have custom IdGenerator like that:
#Service
public class IdGenerator extends SequenceStyleGenerator {
private final SequenceRepository sequenceRepository;
public IdGenerator(SequenceRepository sequenceRepository) {
this.sequenceRepository = sequenceRepository;
}
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
BaseObject type = ((BaseObject) object);
return StringUtils.getNumberWithMaxRadix(sequenceRepository.getNext(type.sequenceName()));
}
}
And on Entity Id field Hibernate annotations:
#Id
#Column(name = "id", nullable = false, length = 18)
#SequenceGenerator(name = "location_road_seq", sequenceName = "location_road_seq", allocationSize = 10)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "location_road_seq")
#GenericGenerator(
name = "location_road_seq",
strategy = "com.wildtigerrr.bestgamebot.bin.service.base.IdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = IdGenerator.VALUE_PREFIX_PARAMETER, value = "a0lr")
})
private String id;
When inserting objects from the code with Hibernate - works like a charm. But I need to insert initial data with Liquibase, and here I have issues inserting data from Liquibase changeset:
INSERT INTO location (id, system_name)
VALUES (nextval('location_seq'), 'TestLocation');
Returns simple values from sequence as Id.
Is there an option and how should I configure Liquibase to use my IdGenerator?
And if it's not possible, what's best practices/possible solutions for overcoming that issue? Any feedback would be appreciated!

Related

Hibernate Sequence Generator requests 2 values

I have found a strange case about Hibernate Sequence Generator. When I save the entity with repository Hibernate performs two queries.
select nextval ('some_sequence')
select nextval ('some_sequence')
Is is some Hibernate pre-caching behavior? Can it be tuned?
Here is the entity:
#Entity
#Getter
#Setter
#Table(name = "host_black_list")
public class RestrictedHost {
#Id
#GeneratedValue(
strategy = SEQUENCE,
generator = "restricted_host_generator"
)
#SequenceGenerator(
name = "restricted_host_generator",
sequenceName = "some_sequence"
)
#Column(name = "host_black_list_id")
private Long id;
#Column(name = "host_name")
#NotNull
private String name;
#Column(name = "msisdn_count")
#NotNull
private long msisdnCount;
}
And here is the test code:
final var id = transactionTemplate.execute(status -> {
RestrictedHost restrictedHost = new RestrictedHost();
restrictedHost.setName("some_name");
restrictedHost.setMsisdnCount(156);
final var host = restrictedHostRepository.saveAndFlush(restrictedHost);
return host.getId();
});
I use Testcontainers + PostgreSQL 9.6.8
Yes hibernate default way cache seq values.
Default cached values are 50. But I didn't think it run one seq query twice.
persistence.xml:
this settings said old style or new style seq usage
<property name="hibernate.id.new_generator_mappings" value="false"/>
If you use GenericGenerator, then it works without cache, so all persist fetch a seq value!
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "some_seq_generator")
#GenericGenerator(name = "some_seq_generator", strategy = "sequence", parameters = { #Parameter(name = "sequence", value = "some_seq") })

Hibernate mapping if allocationSize exists id is 1 if not id is -48. How?

I have an entity mapped and I want to verbosely point it to its sequence. So I have the following mapping:
#Entity
#Table(schema = DbConstants.SCHEMA_PUBLIC, name =
DbConstants.PUBLIC_TABLE_TEST)
public class TestEntity implements Serializable {
private static final long serialVersionUID = 6284010266557287786L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test")
#SequenceGenerator(schema = DbConstants.SCHEMA_PUBLIC, name = "test", sequenceName = "test_id_seq")
private Integer id;
}
The problem is that if I don't include allocationSize in the #SequenceGenerator annotation the entities seem to have an id of something along the lines of -49 + nextval('test_id_seq'::regclass). Adding a new entity also increments the test_id_seq. I've compared the sql that hibernate uses with or without the allocationSize and it's exactly the same.
1) How does this happen?
2) How do the database ids end up being so different with seemingly the same sql being used by hibernate?

Using multiple #GenericGenerator?

I experimented with Hibernate's #GenericGenerator in a Spring Boot project (Hibernate 5). Made a test entity like this:
#Entity
public class BatchTest implements Serializable {
private static final long serialVersionUID = 3012542467060581674L;
#Id
#GeneratedValue(generator = "batchTestIdGenerator")
#GenericGenerator(
name = "batchTestIdGenerator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "increment_size",value = "1000")
}
)
private long id;
#Lob
#Column(nullable = false)
private String someVal;
...
Using this generator I can insert entities really fast with JPA (I have a Spring Repository, served by a #Service) and it's cool, but how is the sequence actually stored? My database currently is an SQL Server 2016 instance, and in the schema, I can see that I have a dbo.hibernate_sequence:
If I create a second entity, with a different generator, I get an exception during startup:
#Id
#GeneratedValue(generator = "batchTestIdGenerator2", strategy = GenerationType.SEQUENCE)
#GenericGenerator(
name = "batchTestIdGenerator2",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "increment_size",value = "500")
}
)
private long id;
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Multiple references to database sequence [hibernate_sequence] were encountered attempting toset conflicting values for 'increment size'. Found [500] and [1000]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
Can I define different sequences at all? It seems like It tries to use the same dbo.hibernate_sequence for both sequences (id did not create a new dbo sequence in the database) and just disregards the name. If it is possible to define N number of sequence, how can I do it?
Edit: I tried to create the sequences manually (matching names), but I still get the same error.
Hibernate allow you to create your own Sequence generator, that allows you to define the format , table everything using Hibernate IdentifierGenerator.
Please try below approach.
Create a SequenceGenerator class by implementing IdentifierGenerator, Use org.hibernate.id.Configurable interface to make your Generator configurable -that will accept parameters from the Entity class-
Custom identifier generator looks like this:
public class StringSequenceIdentifier
implements IdentifierGenerator, Configurable {
public static final String SEQUENCE_PREFIX = "sequence_prefix";
private String sequencePrefix;
private String sequenceCallSyntax;
#Override
public void configure(
Type type, Properties params, ServiceRegistry serviceRegistry)
throws MappingException {
final JdbcEnvironment jdbcEnvironment =
serviceRegistry.getService(JdbcEnvironment.class);
final Dialect dialect = jdbcEnvironment.getDialect();
sequencePrefix = ConfigurationHelper.getString(
SEQUENCE_PREFIX,
params,
"SEQ_");
final String sequencePerEntitySuffix = ConfigurationHelper.getString(
SequenceStyleGenerator.CONFIG_SEQUENCE_PER_ENTITY_SUFFIX,
params,
SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX);
final String defaultSequenceName = ConfigurationHelper.getBoolean(
SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
params,
false)
? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
: SequenceStyleGenerator.DEF_SEQUENCE_NAME;
sequenceCallSyntax = dialect.getSequenceNextValString(
ConfigurationHelper.getString(
SequenceStyleGenerator.SEQUENCE_PARAM,
params,
defaultSequenceName));
}
#Override
public Serializable generate(SessionImplementor session, Object obj) {
if (obj instanceof Identifiable) {
Identifiable identifiable = (Identifiable) obj;
Serializable id = identifiable.getId();
if (id != null) {
return id;
}
}
long seqValue = ((Number) Session.class.cast(session)
.createSQLQuery(sequenceCallSyntax)
.uniqueResult()).longValue();
return sequencePrefix + String.format("%011d%s", 0 ,seqValue);
}
}
Entity Will be like
#Entity(name = "Post") #Table(name = "post")
public class Post implements Identifiable<String> {
#Id
#GenericGenerator(
name = "assigned-sequence",
strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.StringSequenceIdentifier",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence_name", value = "hibernate_sequence"),
#org.hibernate.annotations.Parameter(
name = "sequence_prefix", value = "CTC_"),
}
)
#GeneratedValue(
generator = "assigned-sequence",
strategy = GenerationType.SEQUENCE)
private String id;
#Version
private Integer version;
public Post() {
}
public Post(String id) {
this.id = id;
}
#Override
public String getId() {
return id;
}
}
Please refer the Below link for detailed explanation.
https://vladmihalcea.com/how-to-implement-a-custom-string-based-sequence-identifier-generator-with-hibernate/
According to section 11.1.48 SequenceGenerator Annotation of the JPA 2.1 specification:
The scope of the generator name is global to the persistence unit (across all generator types).
So you can't have duplicate generators.

ClassCastException: Integer cannot be cast to Long, while trying to iterate over entity IDs

I have following method in my service:
public Set<BoardCard> moveHoldedCardsToNewBoard(Board newBoard, Board prevBoard) {
Set<BoardCard> boardCards = new HashSet<>();
if (prevBoard != null) {
List<Long> holdedCardIds = getExcludedCardIds(prevBoard);
for (Long item: holdedCardIds) {
}
}
When I want to loop the holdedCardIds list, I received: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long in this place -> for (Long item: holdedCardIds) {
My getExcludedCardIds() looks like:
#Override
public List<Long> getExcludedCardIds(Board board) {
return boardCardRepository.getExcludedCardIds(board.getId());
}
Repository:
#Repository
public interface BoardCardRepository extends JpaRepository<BoardCard, Long>, QueryDslPredicateExecutor<BoardCard> {
#Query(value = "SELECT bc.card_id FROM boards_cards bc WHERE bc.board_id =:boardId AND bc.on_hold=true", nativeQuery = true)
List<Long> getExcludedCardIds(#Param("boardId") Long boardId);
}
Entites:
#Entity
#NamedEntityGraph(name = "graph.BoardCard", attributeNodes = {})
#Table(name = "boards_cards")
public class BoardCard implements Serializable {
private static final long serialVersionUID = -9019060375256960701L;
#EmbeddedId
private BoardCardId id = new BoardCardId();
}
#Embeddable
public class BoardCardId implements Serializable {
private static final long serialVersionUID = -3630484760647866357L;
#ManyToOne
private Board board;
#ManyToOne
private Card card;
}
#Entity
#Table(name = "boards")
public class Board extends BaseEntity implements Serializable {
#Id
#SequenceGenerator(name = "boards_id_seq", sequenceName = "boards_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "boards_id_seq")
private Long id;
}
#Entity
#Table(name = "cards")
public class Card extends BaseEntity implements Serializable {
#Id
#SequenceGenerator(name = "cards_id_seq", sequenceName = "cards_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "cards_id_seq")
private Long id;
}
In my POSTGRES schema.sql the BoardCard entity is defined, as follows:
CREATE TABLE IF NOT EXISTS boards_cards(
board_id INTEGER,
card_id INTEGER,
on_hold BOOLEAN DEFAULT FALSE,
CONSTRAINT pk_user_card PRIMARY KEY (board_id, card_id),
FOREIGN KEY(board_id) REFERENCES boards(id),
FOREIGN KEY(card_id) REFERENCES cards(id)
);
I have found here , that equivalent of LONG type in postgresql is bigint . But, If I try to use it, how it will affect on the performance side of my app?
So, tell how can I solve this issue?
(long) can be casted into (int), and (int) can be casted to (long)
However,
(Long) **cannot** be casted into (Integer)
and vice versa, as they are not primitives. Same goes for the bigint
This is your underlying problem, though I'm not sure where in your program it is causing the cast.
If you're using java-8, this problem can be solved just by transforming your list of Integer objects into one contained with the same values, in long that will then be autoboxed by java.
getExcludedCardIds(prevBoard).stream
.mapToLong(x -> x).collect(Collectors.toList);
I have found solution here. The solution is to use JPQL query instead of SQL query.
Refactored repository:
#Repository
public interface BoardCardRepository extends JpaRepository<BoardCard, Long>, QueryDslPredicateExecutor<BoardCard> {
#Query(value = "SELECT id.card.id FROM BoardCard WHERE id.board.id = :boardId AND onHold = true")
List<Long> getExcludedCardIds(#Param("boardId") Long boardId);
}

How to use generated sequence number before persisting?

My scenario is very simple. I have an entityID identity field in the #Entity class and the DB (Oracle, which I'm not sure that matters):
#Id
#SequenceGenerator(name="ENTITY_SEQ_GEN", sequenceName="SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ENTITY_SEQ_GEN")
#Column(name="ENTITY_ID")
private long entityID;
I have another field called, let's say, entityReadableID and that should be a String consisting of the stringified entityID concatenated with another String field from the entity. E.g. if entityID is 1234, entityReadableID may be something like 1234ABC.
My problem is that, as far as I know, the value of entityID is not known before the row is created in the DB but I need to concatenate the entityReadableID using its value. Is there a way to fetch the value of the sequence generated ID before the row is created in the DB so that I can use it to generate the other ID? I know I can make it an insert with that field being null and then make an update once I know what entityID is but that solution seems less than elegant.
The way I am hoping Hibernate/Oracle may be able to support this is if Hibernate can somehow "reserve/issue" the next generated value for the entity being processed before the actual persistence, let me know what it is so I can manipulate with it, then at the end persist it.
You can get the generated Id before persisting of that entity by not using sequence directly for that entity, I mean use a separated entity for that sequence, so your entity should be something like:
#Entity
#Table(name = "ENTITY"
)
public class EntityClass implements java.io.Serializable {
private Long entityId;
private String entityRelatedId;
public EntityClass() {
}
// you may have other constructors
#Id
#Column(name = "ENTITY_ID", nullable = false)
public Long getEntityId() {
return this.entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
#Column(name = "ENTITY_RELATED_ID", length = 50)
public String getEntityRelatedId() {
return this.entityRelatedId;
}
public void setEntityRelatedId(String entityRelatedId) {
this.entityRelatedId = entityRelatedId;
}
}
and the entity for the sequence is something like:
#Entity
#Table(name = "SEQ_GENERIC_TBL"
)
public class SeqGenericTbl implements java.io.Serializable {
private Long id;
public SeqGenericTbl() {
}
public SeqGenericTbl(Long id) {
this.id = id;
}
#Id
#SequenceGenerator(name = "ENTITY_SEQ_GEN",
sequenceName = "SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ENTITY_SEQ_GEN")
#Column(name = "ID")
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you can get the Id from the entity of the sequence (persist it, you can rollback that tran or delete it or empty the related table later):
SessionFactory sf = HBUtil.getSessionFactory();
Session s = sf.openSession();
SeqGenericTbl sg=new SeqGenericTbl();
s.save(sg);
EntityClass entity1 = new EntityClass();
entity1.setEntityId(sg.getId());
//NOW YOU HAVE THE ID WITHOUT PERSISTING THE ENTITY
System.out.println(entity1.getEntityId());

Categories