Using multiple #GenericGenerator? - java

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.

Related

How to setup Liquibase to use custom SequenceStyleGenerator

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!

Spring JPA exception while inserting Json value (using hibernate-types-52)

I'm using hibernate-types-52 by Vlad Mihalcea together with Spring JPA to insert a POJO as a Json value into my Postgresql database.
My entity is defined this way:
#Entity
#Table(name = "hoshin_kanri")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
public class HKEntity {
#Id
#Column(name = "id_ai", columnDefinition = "bigint")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id_ai;
#Column(name = "id_hk", columnDefinition = "bigint")
private Integer id_hk;
#Type(type = "jsonb")
#Column(name = "hk_data", columnDefinition = "jsonb")
private HKData hk_data;
public HKEntity(Integer id_hk, HKData hk_data) {
this.id_hk = id_hk;
this.hk_data = hk_data;
}
And this is the POJO:
public class HKData {
private String name;
private Year targetYear;
private String description;
public HKData(String name, Year targetYear, String description) {
this.name = name;
this.targetYear = targetYear;
this.description = description;
}
I've defined a Repository interface to query the objects into the database:
public interface HKRepository extends CrudRepository<HKEntity, Integer> {
#Query(value = "INSERT INTO 'hk_data' VALUES :Entity", nativeQuery = true)
void test_json(#Param("Entity") HKEntity e);
}
and a test Service just to see if it's working properly:
#Service
public class HKService {
#Autowired
HKRepository hk_repository;
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.test_json(e);
return "Value created";
}
}
However, i keep getting the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ehk.rest.entity.HKEntity
I've tried many fixes suggested for this error, but i cannot understand the nature of the error itself. What is wrong with this approach? Beside a tip for fixing this, i would like to understand why this error is originated.
The error means that there's an instance of the HKEntity entity which is referenced from somewhere in the current Hibernate session, and you've neither explicitly persisted this instance, nor instructed Hibernate to persist it cascadly. It's hard to say what exactly is going on, but there are some issues with your code that might have confused either Spring Data JPA framework, or the Hibernate itself.
First, the Spring's CrudRepository interface already has a save() method, so you could use it instead of your test_json() method.
I also see no reason in inserting a Hibernate entity with a native query, and I don't even think this is a valid query. Your test_json() method tries to natively insert an HKEntity entity into the hk_data table, but the HKEntity entity should be saved into the hoshin_kanri table, according to your mapping.
So I would change your service code as follows:
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.save(e);
return "Value created";
}

Creating custom user types for jsonb columns in hibernate postgresql

I am trying to migrate my spring mvc project database from mongodb to postgresql.
Spring version 4.3.8.RELEASE
Hibernate version 5.2.10.Final
Postgresql db version is 9.6
I am trying to implement create, read, update operations for the following entity.
BeneficiaryData.java
#Document(collection = "beneficiary_data")
public class BeneficiaryData extends BaseEntity {
#Id
private String id;
private Integer beneficiaryId;
private Map<Integer, Object> customData;
private Map<Integer, List<Integer>> customMultipleData;
private Date lastUpdatedOn;
public BeneficiaryData() {
}
}
In mongodb this entity was stored as a json document and the conversion from json to object for the fields customData, customMultipleData, rcfData were handled by spring data.
For storing this in postgresql I thought of using the following table structure making use of postgresqls jsonb type.
CREATE TABLE ebbin.beneficiary_data
(
id integer NOT NULL DEFAULT nextval('bms_v21.beneficiary_data_id_seq'::regclass),
beneficiary_id integer NOT NULL,
custom_data jsonb,
custom_multiple_data jsonb,
last_updated_on timestamp(3) without time zone,
CONSTRAINT beneficiary_data2_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
For handling the two jsonb types I implemented two cutom hibernate UserTypes
JSONBCustomDataUserType.java
public class JSONBCustomDataUserType extends CollectionUserType implements ParameterizedType {
private static final String JSONB_TYPE = "jsonbCustomData";
public static final String CLASS = "CLASS";
#Override
public Class<Object> returnedClass() {
return Object.class;
}
#Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT };
}
// ... implementations for nullSafeGet, nullSafeSet ...
}
and JSONBCustomMultipleDataUserType.java
public class JSONBCustomMultipleDataUserType extends CollectionUserType implements ParameterizedType {
private static final String JSONB_TYPE = "jsonbCustomData";
public static final String CLASS = "CLASS";
#Override
public Class<Object> returnedClass() {
return Object.class;
}
#Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT };
}
// ... implementations for nullSafeGet, nullSafeSet ...
}
My entity for postgresql is now
BeneficiaryData.java
#TypeDefs({
#TypeDef(name = "jsonbCustomData", typeClass = JSONBCustomDataUserType.class,
parameters = {#Parameter(name = JSONBCustomDataUserType.CLASS, value = "java.util.Map")}),
#TypeDef(name = "jsonbCustomMultipleData", typeClass = JSONBCustomMultipleDataUserType.class,
parameters = {#Parameter(name = JSONBCustomMultipleDataUserType.CLASS, value = "java.util.Map")})
})
#Entity
#Table(name = "beneficiary_data")
public class BeneficiaryData extends BaseEntity {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "beneficiary_id", nullable = false)
private Integer beneficiaryId;
#Type(type = "jsonbCustomData")
#Column(name = "custom_data", nullable = true)
private Map<Integer, Object> customData;
#Type(type = "jsonbCustomMultipleData")
#Column(name = "custom_multiple_data", nullable = true)
private Map<Integer, List<Integer>> customMultipleData;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_updated_on", length = 19)
private Date lastUpdatedOn;
public BeneficiaryData() {
}
// ...
}
Also created a custom postgresql dialect so that the new user types can be registered.
CustomPostgreSqlDialect.java
public class CustomPostgreSqlDialect extends PostgreSQL95Dialect {
public CustomPostgreSqlDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "jsonbCustomData");
this.registerColumnType(Types.JAVA_OBJECT, "jsonbCustomMultipleData");
}
}
However when I try to save an entity using session.save() I get the following error.
org.springframework.dao.InvalidDataAccessResourceUsageException: could not insert: [com.tcs.bms.persistence.entity.BeneficiaryData];
.
.
.
Caused by: org.postgresql.util.PSQLException: Unknown type jsonbCustomData.
I am guessing this has something to do with me registering column type JAVA_OBJECT to both jsonbCustomData and jsonbCustomMultipleData
I am saying this because when I tried with just the jsonbCustomData user type (after commenting out the customMultipleData field in the entity and removing from the table), I was able to successfully insert, update and fetch data.
Any idea on what I am doing wrong ?
Might be a little late to the party, but without knowing the exact solution to the problem, I'd have two suggestions:
Based on your PSQLException of Unknown type, your dialect might need to look like this:
public class CustomPostgreSqlDialect extends PostgreSQL95Dialect {
public CustomPostgreSqlDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
}
}
...as the according javadoc states, that the second attribute needs to be 'The database type name' & PSQL won't know anything about a type named "jsonbCustom.."
Don't even write a custom dialect! I don't know the content of your extended CollectionUserType, but I got a working project with a pretty similar setup (Spring 4.3.14 / Hibernate 5.2.12 / PSQL 9.5) and all I had to do was creating custom UserTypes & registering/annotate those types to the class & their according fields. If you prefer to I'm happy to share the code.

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

Hibernate: Call a SequenceGenerator manually?

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
}

Categories