Hibernate: Call a SequenceGenerator manually? - java

I have written my own IdGenerator:
public class AkteIdGenerator implements IdentifierGenerator {
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
// if custom id is set -> use this id
if (object instanceof SomeBean) {
SomeBean someBean = (SomeBean) object;
Long customId = someBean.getCustomId();
if (customId != 0) {
return customId;
}
}
// otherwise --> call the SequenceGenerator manually
SequenceStyleGenerator sequenceGenerator ...
}
}
Does anyone know how I could call the sequenceGenerator from my generator class what I normally can define per annotations:
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "MY_SEQUENCE")
#SequenceGenerator(
allocationSize = 1,
name = "MY_SEQUENCE",
sequenceName = "MY_SEQUENCE_NAME")
I would be very thankful for any solutions!!!!
Thanks a lot, Norbert

You can eassly call the SequenceGenerator from your Generator class. By writing this code.
THe Custom generator class should be
public class StudentNoGenerator implements IdentifierGenerator {
public Serializable generate(SessionImplementor session, Object object)throws HibernateException {
SequenceGenerator generator=new SequenceGenerator();
Properties properties=new Properties();
properties.put("sequence","Stud_NoSequence");
generator.configure(Hibernate.STRING, properties, session.getFactory().getDialect());
return generator.generate(session, session);
}
}
In the above code Stud_NoSequence is the Sequence name, which shoulb be created. in Data base by wring create sequence Stud_NoSequence;
Hibernate.String is the type which will be return by the SequenceGenerator class.
and the domain class will be
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
#org.hibernate.annotations.GenericGenerator(
name = "Custom-generator",
strategy = "com.ssis.id.StudentNoGenerator"
)
public class Student {
#Id #GeneratedValue(generator = "Custom-generator")
String rno;
#Column
String name;
public String getRno() {
return rno;
}
public void setRno(String rno) {
this.rno = rno;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

#Id
#GenericGenerator(name = "seq_id", strategy = "de.generator.AkteIdGenerator")
#GeneratedValue(generator = "seq_id")
#Column(name = "ID")
private Integer Id;
http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/

Not sure if this helps, but I kept coming across this post while searching for my answer, which I didn't find posted anywhere, but found a solution myself. So I thought this might be the best place to share.
If you are using hibernate as the JPA provider, you can manually call an ID generator assigned to a given entity class. First inject the JpaContext:
#Autowired
org.springframework.data.jpa.repository.JpaContext jpaContext;
Then obtain the internal org.hibernate.id.IdentifierGenerator with this:
org.hibernate.engine.spi.SessionImplementor session = jpaContext.getEntityManagerByManagedType(MyEntity.class).unwrap(org.hibernate.engine.spi.SessionImplementor.class);
org.hibernate.id.IdentifierGenerator generator = session.getEntityPersister(null, new MyEntity()).getIdentifierGenerator();
Now you can obtain an ID from the generator programatically:
Serializable id = generator.generate(session, new MyEntity());

Your post was helpful to update the name of the sequence.
Because I use a sequence per month, and the configuration does not update each identifier generation.
Here is my code:
#Override
public Serializable generate(SessionImplementor sessionImplementator,
Object object) throws HibernateException {
Calendar now = Calendar.getInstance();
// If month sequence is wrong, then reconfigure.
if (now.get(Calendar.MONTH) != SEQUENCE_DATE.get(Calendar.MONTH)) {
super.configure(new LongType(), new Properties(),
sessionImplementator.getFactory().getDialect());
}
Long id = (Long) super.generate(sessionImplementator, object);
String sId = String.format("%1$ty%1$tm%2$06d", SEQUENCE_DATE, id);
return Long.parseLong(sId);// 1301000001
}

Related

Is There A Way to manage a Custom generated ID with IdentifierGenerator from Hibernate to serve more than one Entity with different prefix?

I have a custom ID generator that generates an UUID string with a prefix for my entities ID, but since I'm using different prefix for each entity I'm having to create one ID generation class for each Entity, is there a way to only use one class for this?
My ID generation class is this:
import java.io.Serializable;
import java.util.UUID;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
public class ProductIdGenerator implements IdentifierGenerator{
public static final String generatorName = "produtcIdGenerator";
#Override
public Serializable generate(SharedSessionContractImplementor arg0, Object arg1) throws
HibernateException {
String prefix = "PROD";
String uuid = UUID.randomUUID().toString().substring(0, 8);
return prefix + uuid;
}
}
My Entities looks like this:
#Entity
public class Product {
#Id
#GeneratedValue(generator = ProductIdGenerator.generatorName)
#GenericGenerator(name = ProductIdGenerator.generatorName, strategy = "net.ddns.mrq.util.ProductIdGenerator")
#Column(name = "product_id")
private String id;
private String name;
.
.
.
I have 8 entities and I had to create 8 classes like this for each of one them with different prefix.
Is there a way to make this more dynamic and less "time consuming"?
Is there a way to only change the prefix for each class without creating multiple id generation classes?
I can think of a couple of ways to solve this (which is basically the need for a custom IdentifierGenerator to be parameterized).
One idea involves each entity implementing an interface that can return the appropriate ID prefix for that entity type. Since the target entity is passed to the generator's generate() method, the generator could cast it to that interface and ask it for the prefix to use.
Another solution takes advantage of the fact that IdentifierGenerators can implement the org.hibernate.id.Configurable interface to have configuration "injected" into them, and the #GenericGenerator annotation supports setting those as #Parameters in the annotation. That way, each usage of #GenericGenerator can dictate what prefix it wants the custom generator to use. It would look something like this (note, this is untested code):
public class ProductIdGenerator implements IdentifierGenerator, org.hibernate.id.Configurable {
public static final String GENERATOR_NAME = "produtcIdGenerator";
public static final String PREFIX_PARAM = "prefix";
private String prefix = "";
#Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
this.prefix = params.getProperty(PREFIX_PARAM, "");
}
#Override
public Serializable generate(SharedSessionContractImplementor session, Object entityObject) throws HibernateException {
String uuid = UUID.randomUUID().toString().substring(0, 8);
return prefix + uuid;
}
}
References to it would look like this:
#Id
#GeneratedValue(generator = ProductIdGenerator.GENERATOR_NAME)
#GenericGenerator(
name = ProductIdGenerator.GENERATOR_NAME,
strategy = "net.ddns.mrq.util.ProductIdGenerator",
parameters = {#Parameter(name = ProductIdGenerator.PREFIX_PARAM, value = "foo")})
private String id;
Personally, I find the second idea a little cleaner, but there's nothing wrong with the first that I can see. It's a matter of style.

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.

Why user defined #javax.persistence.Converter isn't recognized by hibernate when mapping the entity field to database column

I have the following Entity containing a field of Enum type:
#Entity
#Table(name = "INPUT_DATA")
public class InputDataEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "INPUT_DATA_SEQ", allocationSize = 1, sequenceName = "INPUT_DATA_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INPUT_DATA_SEQ")
private Long id;
#Column(name = "FIELD1", nullable = false)
private String field1;
#Column(name = "FIELD2", nullable = false)
#Convert(converter = Type.Converter.class)
private Type field2;
// getters and setters
}
The Enum type looks like:
public enum Type {
ENUM_ITEM_1("item1"),
// more items
ENUM_ITEM_N("itemN");
private String code;
private Type(String code) {
this.code = code;
}
public static Type fromString(String name) {
switch (name) {
case "item1":
return ENUM_ITEM_1;
// more cases
case "itemN":
return ENUM_ITEM_N;
default:
throw new IllegalArgumentException("Wrong value for Type");
}
}
#Override
public String toString() {
return code;
}
#javax.persistence.Converter
public static class Converter implements AttributeConverter<Type, String> {
#Override
public String convertToDatabaseColumn(Type attribute) {
return attribute.toString();
}
#Override
public Type convertToEntityAttribute(String s) {
return Type.fromString(s);
}
}
}
The problem is that hibernate doesn't recognize my Converter when I want to fetch data from the database.
I've also tried:
#Embedded and #Embeddable but with no luck.
#Enumerated(EnumType.STRING) but again with no luck.
My question is:
how to make hibernate to recognize my converter when converting the appropriate field?
Many thanks in advance.
I eventually ended up by implementing a StringValuedEnum interface and its relevant reflector and type class by implementing EnhancedUserType, ParameterizedType as it was described here.
This helped me to properly store into and retrieve from DB data corresponding to user defined enum types, although the questions with converters remains still open. If someday a proper answer will be given, that will be very appreciated.

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());

ID created by Table Generator is not persisted to object

I created the following entity class where the primary key is calculated by a Table Generator.
#SuppressWarnings("serial")
#Entity
public class Article implements Serializable {
#Id
#TableGenerator(name = "ARTICLE_TABLE_GEN", table = "sequences", pkColumnName = "seq_name", valueColumnName = "seq_count", pkColumnValue = "ART_SEQ")
#GeneratedValue(strategy = GenerationType.TABLE, generator = "ARTICLE_TABLE_GEN")
private long id;
In the debug log, I read that the generation worked.
[DEBUG] Generated identifier: 200, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
The entites are managed by a JpaRepository.
#Repository
public interface IArticleRepository extends JpaRepository<Article, Long> {
List<Article> findByShortTextLike(String shortText);
}
In Java code, I access this repository via a Service.
#Service
public class ArticleService implements IArticleService {
#Autowired
private IArticleRepository articleRepository;
#Override
#Transactional(readOnly = true)
public Article getArticleByID(long id) {
return this.articleRepository.findOne(id);
}
#Override
#Transactional
public Article createArticle(String shortText, String longText,
String packageUnit, double weight, String group, char abcClass) {
if (getArticleByShortText(shortText).size() == 0) {
Article article = new Article();
article.setShortText(shortText);
article.setDescription(longText);
article.setPackageUnit(packageUnit);
article.setWeight(weight);
article.setMaterialGroup(group);
article.setClassABC(abcClass);
this.articleRepository.saveAndFlush(article);
return article;
} else
return null;
}
#Override
#Transactional(readOnly = true)
public List<Article> getArticleByShortText(String short_text) {
return this.articleRepository.findByShortTextLike(short_text);
}
}
After the service-method "createArticle" called the repository to save and flush the new instance to the database, this is perfectly done. However, the generated ID is not written to the object the method returns.
I remember that this was done when I used the AUTO_INCREMENT specification on the database column. Why does this not happen in the new case?
I have no experience with Spring-data-JPA, but the javadoc of saveAndFlush() shows that it returns an entity. So it probably uses EntityManager.merge() internally. Try changing your code to
article = this.articleRepository.saveAndFlush(article);
return article;

Categories