I want to use custom id generator in hibernate. This is my model:
#Entity(name="Poli")
#Table(name="POLI")
public class Poli extends DefaultEntityImpl implements Serializable{
#Id
#GenericGenerator(
name = "string-sequence",
strategy = "id.rekam.medis.service.generator.IdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence_name",
value = "pol_seq"),
#org.hibernate.annotations.Parameter(
name = "sequence_prefix",
value = "POL-")
})
#GeneratedValue(
generator = "string-sequence",
strategy = GenerationType.SEQUENCE)
#Basic(optional = false)
#Column(name = "ID",nullable = false)
private String id;
#Column(name = "NAMA", length = 10)
private String nama;
//getter setter
}
And my IdGenerator Class is :
public class IdGenerator implements IdentifierGenerator, Configurable {
private static final Log logger = LogFactory.getLog(IdGenerator.class);
private String sequenceName;
private String sequencePrefix;
public static final String SEQUENCE_PREFIX = "sequence_prefix";
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
Connection con = session.connection();
Long nextValue = null;
try {
PreparedStatement p = con.prepareStatement(" SELECT POL_SEQ.NEXTVAL FROM DUAL ");
ResultSet rs = p.executeQuery();
while(rs.next()) {
nextValue = rs.getLong("nextVal");
}
} catch (SQLException e) {
e.printStackTrace();
}
if(logger.isDebugEnabled()) logger.debug("new id is generated:" + nextValue);
return "POL-" + nextValue;
}
#Override
public void configure(Type type, Properties params, Dialect dlct) throws MappingException {
sequencePrefix = ConfigurationHelper.getString(SEQUENCE_PREFIX, params,"SEQ_");
}
}
My Goal is, I want that my IdGenerator Class can be used for all Entities/Models. Just need to change the paramters in entity.
My Question: How to catch the parameters in the IdGenerator Class?
I want to get "pol_seq" and "POL-" in IdGenerator Class.
Hot Regard,
Tarmizi
That's what you've implemented the Configurable Interface for.
The configure() Method has these parameters in the Properties parameter. Look at its JavaDoc, it's basically a HashMap, so just do
params.getProperty("sequence_prefix");
And maybe you want to turn these names into constants, either public static final Strings, or better yet Enums.
I've got table with column of type "jsonb".
In entity I set type String for this column with attribute converter:
#Convert(converter = JSONBConverter.class)
#Column(name = STATE_COLUMN, nullable = false)
private String getState() {
return state;
}
And my converter looks like:
#Converter
public class JSONBConverter implements AttributeConverter<String, Object> {
#Override
public Object convertToDatabaseColumn(String attribute) {
PGobject result = new PGobject();
result.setType("json");
try {
result.setValue(attribute);
} catch (SQLException e) {
throw new IllegalArgumentException("Unable to set jsonb value");
}
return result;
}
#Override
public String convertToEntityAttribute(Object dbData) {
if (dbData instanceof PGobject) {
return ((PGobject) dbData).getValue();
}
return StringUtils.EMPTY;
}
}
I got dialect set to: org.hibernate.dialect.PostgreSQL95Dialect
I thought it was gonna work. But I get an error with:
org.postgresql.util.PSQLException: Nieznana warto�� Types:
1�936�628�443
As I debugged it gets targetSqlType in PgPreparedStatement class setObject method 1936628443 - what indicates on Object type which is taken from my AttributeConverter class which is assigned in SqlTypeDescriptorRegistry class.
I've got:
postgresql version 42.2.1
hibernate version 5.2.10.Final
AttributeConverter + json/jsonb do not play well together because you need to bind the JSON object at the PreparedStatement level.
You have to declare a Hibernate Type to get JSONB working.
See this article for a detailed tutorial of how you can do that.
I am using Apache Commons DBUtils according to QueryRunner#insert method in its documentation,insert return the generic type of ResultSetHandler. I have a BR_Author object at my project.
BR_Author.java
import org.springframework.stereotype.Repository;
#Repository
public class BR_Author {
private int id;
private String authorName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
}
I write a simple insert statement at my service class like
AuthorService#createAuthor
public BR_Author createAuthor(String authorName) throws ApiException {
String sql = "Insert into BR_AUTHOR(authorName) VALUES(?)";
ResultSetHandler<BR_Author> rs = new BeanHandler<BR_Author>(
BR_Author.class);
QueryRunner qr = new QueryRunner(dataSource);
Object[] params = { authorName };
try {
BR_Author author = qr.insert(sql, rs, params);
System.out.println("Br_Author:" + author);
return author;
} catch (SQLException e) {
throw new ApiException(ErrorCode.ERR20001, e.getMessage());
}
}
I am trying to return the added value at createAuthor method, but if i configure id field as auto increment object return as
Br_Author:Br_Author [id=0, authorName=null]
when i check the db i see that it adds the values successfully.
If i disable auto increment and set id from code, author object is null. So i want to learn that am i misunderstand QueryRunner#insert method or it has a bug. I already check below links.
QueryRunner_insert_add_javadoc.patch
Generated key handling for updates
BTW: Select queries working fine for BR_Author class so it means there shouldn't be any mapping issue.
I didn't find any official solution from DBUtils API so i just return generated ID and then query it after insert the record with this id at Service layer. This way method return Object at service layer, but i do one extra query.
I have a class named ClBranch.java like below:
#Entity
#Table(name = "PROVINCE")
public class PROVINCE implements Serializable {
#Id
#Column(name="PR_CODE", length = 50)
private String provinceCode
#Column(name="PR_NAME", length = 500)
private String provinceName
......
getter-setter.
}
This is my code:
public static String getClassAnnotationValue(Class classType, Class annotationType, String attributeName) {
String value = null;
Annotation annotation = classType.getAnnotation(annotationType);
if (annotation != null) {
try {
value = (String) annotation.annotationType().getMethod(attributeName).invoke(annotation);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return value;
}
String columnName = getClassAnnotationValue(PROVINCE .class, Column.class, "name");
By this way, I only get ColumnName as PROVINCE. I can not get ColumnName. How can I do it?
The #Column annotation is defined on the fields, not on the class. So you must query annotation values from the private fields:
String columnName = getAnnotationValue(PROVINCE.class.getDeclaredField("provinceCode"), Column.class, "name");
To be able to pass Field objects to your method, change the type of your classType parameter from Class to AnnotatedElement. Then you can pass classes, fields, parameters or methods:
public static String getAnnotationValue(AnnotatedElement element, Class annotationType, String attributeName) {
...
}
My problem is the same as described in [1] or [2]. I need to manually set a by default auto-generated value (why? importing old data). As described in [1] using Hibernate's entity = em.merge(entity) will do the trick.
Unfortunately for me it does not. I neither get an error nor any other warning. The entity is just not going to appear in the database. I'm using Spring and Hibernate EntityManager 3.5.3-Final.
Any ideas?
Another implementation, way simpler.
This one works with both annotation-based or xml-based configuration: it rely on hibernate meta-data to get the id value for the object. Replace SequenceGenerator by IdentityGenerator (or any other generator) depending on your configuration. (The creation of a decorator instead of subclassing, passing the decorated ID generator as a parameter to this generator, is left as an exercise to the reader).
public class UseExistingOrGenerateIdGenerator extends SequenceGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
Answer to the exercise (using a decorator pattern, as requested), not really tested:
public class UseExistingOrGenerateIdGenerator implements IdentifierGenerator, Configurable {
private IdentifierGenerator defaultGenerator;
#Override
public void configure(Type type, Properties params, Dialect d)
throws MappingException;
// For example: take a class name and create an instance
this.defaultGenerator = buildGeneratorFromParams(
params.getProperty("default"));
}
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : defaultGenerator.generate(session, object);
}
}
it works on my project with the following code:
#XmlAttribute
#Id
#Basic(optional = false)
#GeneratedValue(strategy=GenerationType.IDENTITY, generator="IdOrGenerated")
#GenericGenerator(name="IdOrGenerated",
strategy="....UseIdOrGenerate"
)
#Column(name = "ID", nullable = false)
private Integer id;
and
import org.hibernate.id.IdentityGenerator;
...
public class UseIdOrGenerate extends IdentityGenerator {
private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName());
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
if (obj == null) throw new HibernateException(new NullPointerException()) ;
if ((((EntityWithId) obj).getId()) == null) {
Serializable id = super.generate(session, obj) ;
return id;
} else {
return ((EntityWithId) obj).getId();
}
}
where you basically define your own ID generator (based on the Identity strategy), and if the ID is not set, you delegate the generation to the default generator.
The main drawback is that it bounds you to Hibernate as JPA provider ... but it works perfectly with my MySQL project
Updating Laurent Grégoire's answer for hibernate 5.2 because it seems to have changed a bit.
public class UseExistingIdOtherwiseGenerateUsingIdentity extends IdentityGenerator {
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
and use it like this: (replace the package name)
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "{package}.UseExistingIdOtherwiseGenerateUsingIdentity")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#Column(unique = true, nullable = false)
protected Integer id;
I`m giving a solution here that worked for me:
create your own identifiergenerator/sequencegenerator
public class FilterIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator{
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
// TODO Auto-generated method stub
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
modify your entity as:
#Id
#GeneratedValue(generator="myGenerator")
#GenericGenerator(name="myGenerator", strategy="package.FilterIdentifierGenerator")
#Column(unique=true, nullable=false)
private int id;
...
and while saving instead of using persist() use merge() or update()
If you are using hibernate's org.hibernate.id.UUIDGenerator to generate a String id I suggest you use:
public class UseIdOrGenerate extends UUIDGenerator {
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
According to the Selectively disable generation of a new ID thread on the Hibernate forums, merge() might not be the solution (at least not alone) and you might have to use a custom generator (that's the second link you posted).
I didn't test this myself so I can't confirm but I recommend reading the thread of the Hibernate's forums.
For anyone else looking to do this, above does work nicely. Just a recommendation to getting the identifier from the object rather than having inheritance for each Entity class (Just for the Id), you could do something like:
import org.hibernate.id.IdentityGenerator;
public class UseIdOrGenerate extends IdentityGenerator {
private static final Logger log = Logger.getLogger(UseIdOrGenerate.class
.getName());
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
if (object == null)
throw new HibernateException(new NullPointerException());
for (Field field : object.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class)
&& field.isAnnotationPresent(GeneratedValue.class)) {
boolean isAccessible = field.isAccessible();
try {
field.setAccessible(true);
Object obj = field.get(object);
field.setAccessible(isAccessible);
if (obj != null) {
if (Integer.class.isAssignableFrom(obj.getClass())) {
if (((Integer) obj) > 0) {
return (Serializable) obj;
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return super.generate(session, object);
}
}
You need a running transaction.
In case your transaction are manually-managed:
entityManager.getTransaction().begin();
(of course don't forget to commit)
If you are using declarative transactions, use the appropriate declaration (via annotations, most likely)
Also, set the hibernate logging level to debug (log4j.logger.org.hibernate=debug) in your log4j.properties in order to trace what is happening in more details.