I have an situation where I need to save a hibernate object, but I am not sure whether an ID would be assigned by calling app (using special logic with in a range - unique) or not.
If the ID is not assigned, I need hibernate to generate an ID higher than the possible ID range which the app would input (i know the range). Else should go with what using app inputs.
I am working on MySQL - checking to see if I can custom generator like below
public class MyDOIdGenerator extends IdentityGenerator{
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
if ((((MyDO) obj).getId()) == null) {
Serializable id = super.generate(session, obj) ;
return id;
} else {
return ((MyDO) obj).getId();
}
}
}
But my problem is, I dont know how the super.generate would behave in a clustered environment. Would it maintain the ID synchronization across servers? How do I specify the number to start from? (Because I need to exclude the app using id range when having hibernate generate it)
Please help
Thanks
In the DB, set your sequence to start with a specific number (beginning of the range you mentioned) and then use something like this for your ID annotation, it will ensure hibernate will use the next ID in the sequence and you won't have to worry about clustered/non-clustered environment issue:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
I finally implemented this using a table in database as below
package com.yahoo.mb.do;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.IdentityGenerator;
import com.yahoo.sm.ads.common.log.Logger;
public class MyDOGenerator extends IdentityGenerator{
private static final Class clazz = MethodHandles.lookup().lookupClass();
private static final Logger logger = Logger.getLogger(clazz);
private static String selectQuery = "SELECT nextval FROM identity_value WHERE name='MY_DO' FOR UPDATE";
private static String updateQuery = "UPDATE identity_value SET nextval=?, last_updated=? WHERE name='MY_DO'";
#Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
if ((((MyDO) obj).getId()) == null) {
Connection connection = session.connection();
try {
PreparedStatement selectStatement = connection.prepareStatement(selectQuery);
PreparedStatement updateStatement = connection.prepareStatement(updateQuery);
logger.info(clazz, "generate", "Generating nextval");
ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
Long id = rs.getLong("nextval");
logger.info(clazz, "generate", "Generated nextval: " + id);
updateStatement.setLong(1, id+1);
updateStatement.setDate(2, new java.sql.Date(new java.util.Date().getTime()));
logger.info(clazz, "generate", "Updating nextval: " + id);
updateStatement.executeUpdate();
return id;
}
} catch (SQLException e) {
logger.error(clazz, "generate", "Error generating ID" + e);
throw new HibernateException("Unable to generate MyDO id value");
}
return null;
} else {
return ((MyDO) obj).getId();
}
}
}
Related
I get an javax.persistence.EntityNotFoundException from the following code:
#Transactional
public ManagementEmailConfig save(ManagementEmailConfig managementEmailConfig)
{
logger.info("Save Management Email Config");
try
{
managementEmailConfig = entityManager.merge(managementEmailConfig);
entityManager.flush();
} catch (Exception e)
{
//ERROR: com.xxx.app.dao.kpi.ManagementEmailConfigDAO -
Not able to save Management Email Config
//javax.persistence.EntityNotFoundException: Unable to find com.xxx.app.model.configuration.AlertCommunicationAddress with id 1260
logger.error("Not able to save Management Email Config", e);
return null;
}
return managementEmailConfig;
}
where the model looks like this (shortened version):
import java.io.Serializable;
import javax.persistence.*;
import java.util.List;
/**
* The persistent class for the MANAGEMENT_EMAIL_CONFIG database table.
*
*/
#Entity
#Table(name="MANAGEMENT_EMAIL_CONFIG")
#NamedQuery(name="ManagementEmailConfig.findAll", query="SELECT m FROM ManagementEmailConfig m")
public class ManagementEmailConfig implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="MANAGEMENT_EMAIL_CONFIG_ID")
private long managementEmailConfigId;
//bi-directional many-to-one association to AlertCommunicationAddress
#OneToMany(mappedBy="managementEmailConfig")
private List<AlertCommunicationAddress> alertCommunicationAddresses;
public ManagementEmailConfig() {
}
public long getManagementEmailConfigId() {
return this.managementEmailConfigId;
}
public void setManagementEmailConfigId(long managementEmailConfigId) {
this.managementEmailConfigId = managementEmailConfigId;
}
public List<AlertCommunicationAddress> getAlertCommunicationAddresses() {
return this.alertCommunicationAddresses;
}
public void setAlertCommunicationAddresses(List<AlertCommunicationAddress> alertCommunicationAddresses) {
this.alertCommunicationAddresses = alertCommunicationAddresses;
}
public AlertCommunicationAddress addAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().add(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(this);
return alertCommunicationAddress;
}
public AlertCommunicationAddress removeAlertCommunicationAddress(AlertCommunicationAddress alertCommunicationAddress) {
getAlertCommunicationAddresses().remove(alertCommunicationAddress);
alertCommunicationAddress.setManagementEmailConfig(null);
return alertCommunicationAddress;
}
}
The use case is that the user provides a new alertCommunicationAddress to an existing ManagementEmailConfig and I want create the alertCommunicationAddress then update the ManagementEmailConfig.
If you are using Spring you've made life really difficult for yourself by not using Spring features
I suggest you do the following:
Using Spring Data JPA, write a repository interface to allow
you to easily persist your entity:
public interface ManagementEmailConfigRepository extends JpaRepository { }
use it to persist your entity (save is insert if it's not there,
update if it is)
#Inject
private ManagementEmailConfigRepository managementEmailConfigRepository;
....
managementEmailConfigRepository.save(managementEmailConfig);
This gets rid of the following from your code:
needing to write a save method at all
needing to do a flush
no need for try catch type code
no need for that named query on your entity
(you get that for free on your repository)
I'll leave it up to you to decide where you want the #Transactional annotation; it really shouldn't be on your DAO layer but higher up, e.g. your service layer.
How to generate order number in this format ORD000001 in hibernate and spring
Please help me to generate above number.i tried various ways to generate this sequence number but no solution worked well.
Try this.
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.HibernateException;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.UUID;
public class RandomIdentifierGenerator implements IdentifierGenerator {
private final static String label = "ORD";
private final static SecureRandom sr = new SecureRandom();
public Serializable generate(SessionImplementor sessionImplementor, Object o) throws HibernateException {
long val = sr.nextLong();
return label + Long.toString(Math.abs(val), Character.MAX_RADIX);
}
}
I think what you really need is parse in some more functional way the orders generated, then i wont go for the approach of generate a randomID i would use the real ID of your order, which will be sequential and format it to give the result as you wish. In code would be something like this:
Class Order
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Transient
public String getOrderIdBusiness(){
DecimalFormat myFormatter = new DecimalFormat("ORD000000");
return myFormatter.format(id);
}
I have one problem with my configuration of play framework, when I insert a new Notification I have received the error. I don´t know why this error occured. Because I extends Model class, Play must generated a new ID for each row.
If you can say to me what is the problem, or other better way to do this, Maybe if I extends the GenericModel and I do the code that is commented like this:
// #Id
// #GeneratedValue(strategy = GenerationType.AUTO)
// public long id;
but if I do this, How I must do the insert a new row?
Thanks a lot!!!!
Error found:
PersistenceException occured : org.hibernate.HibernateException: The database returned no natively generated identity value
This is /app/controllers/WSConfiguracion.java :
if (cliente.valorBateria < filasConfiguracionClientes.get(i).limiteBateria) {
if (!estadoNotificacion.hayNotiBateria) {
// code below generated the error
Notificacion notificacion = new Notificacion(
filasConfiguracionClientes.get(i).imeiadmin,imei, "bateria baja"
).save();
estadoNotificacion.hayNotiBateria = true;
//notificacion.save();
estadoNotificacion.save();
renderText("NOTIFICA BATERIA");
}
} else {
...
}
This is my model.
package models;
import javax.persistence.Entity;
import play.db.jpa.Model;
#Entity
public class Notificacion extends Model {
//#Id
//#GeneratedValue(strategy = GenerationType.AUTO)
//public long id;
//#Id
public long imeiadmin;
//#Id
public long imeiclient;
public String detalleNotificacion;
public Notificacion(long imeiadmin, long imeiclient,String detalleNotificacion) {
this.imeiadmin = imeiadmin;
this.imeiclient = imeiclient;
this.detalleNotificacion = detalleNotificacion;
}
}
I think the error:
PersistenceException occured : org.hibernate.HibernateException: The database returned no natively generated identity value
is occurred because there is no sequence in your database. If you extends Model class for your model and you are on Development mode, Play!Framework automatically generated sequence on your database named hibernate_sequence. The sequence is used to generated ID for your model. You may check your database to ensure that sequence is present.
If the hibernate_sequence is present, you can insert data like you do before :
Notificacion notificacion = new Notificacion(
filasConfiguracionClientes.get(i).imeiadmin,imei, "bateria baja"
).save();
then, the error above should be resolved.
Note:
I am referring this answer if you used PostgreSQL database. If you use other database such as MySQL, you should define AUTO_INCREMENT on ID column as the sequence definition.
Update - I have tried this for H2 DB setting
Using H2 database as configure in application.conf :
# Development mode
application.mode=dev
# Set simple file written database (H2 file stored)
db=fs
# JPA DDL update for development purpose only
jpa.ddl=update
The controller :
public static void test15878866() {
// force to insert dummy data
Notificacion notificacion = new Notificacion(
1L, 2L, "This is new notificacion"
).save();
renderText(notificacion.detalleNotificacion);
}
The model :
#Entity
public class Notificacion extends Model {
public long imeiadmin;
public long imeiclient;
public String detalleNotificacion;
public Notificacion(long imeiadmin, long imeiclient,String detalleNotificacion) {
this.imeiadmin = imeiadmin;
this.imeiclient = imeiclient;
this.detalleNotificacion = detalleNotificacion;
}
}
I found the mistake!! I have only had to add super(); inside the constructor.
Thanks for all, iwawiwi.
The model :
#Entity
public class Notificacion extends Model {
public long imeiadmin;
public long imeiclient;
public String detalleNotificacion;
public Notificacion(long imeiadmin, long imeiclient,String detalleNotificacion) {
super();
this.imeiadmin = imeiadmin;
this.imeiclient = imeiclient;
this.detalleNotificacion = detalleNotificacion;
}
}
I'm using a single table inheritance as explained here in a Glassfish 3.1 environment. So far, everything works as expected. But after adding some code into the derived entity to parse an xml String, JPA does not load the attributes of the base entity anymore.
I've set up an example netbeans project to reproduce this problem, you can download it from github here.
Here is the base entity defining the discriminator field and a testField:
#Entity
#DiscriminatorColumn(name="discriminator")
#XmlRootElement
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String testField;
private String discriminator;
public String getDiscriminator() {
return discriminator;
}
public void setDiscriminator(String discriminator) {
this.discriminator = discriminator;
}
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof BaseEntity)) {
return false;
}
BaseEntity other = (BaseEntity) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.ce.entityloadingbug.entites.NewEntity[ id=" + id + " ]";
}
}
And here is the code of the derived entity, with a private methode containing the code that causes jpa to not load the fields anymore...
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
/**
*
* #author stefan
*/
#Entity
#DiscriminatorValue("derived")
public class DerivedEntity extends BaseEntity {
/**
* The problem code
*/
private void theProblemCode() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
try {
db = dbf.newDocumentBuilder();
// This is the line that makes Problems!!
Document doc = db.parse("");
}catch(Exception e) {
System.out.println(e);
}
}
}
As soon as I remove the line "Document doc = db.parse("")" everything works again. As you can see the problem exists although the code that causes the problem is never called.
My current solouting is to move that code into a innerclass. But I don't like it because I do not know what the Problem is.
Please feel free to download my example project from github and try it.
Here is my Setup that I used to reproduce that bug:
Glassfish 3.1.1
NetBeans IDE 7.0.1
Database: Derby 10.6.2.1
JPA: EclipseLink 2.3
In fact, all libraries I used were shiped with the NetBeans IDE 7.0.1 setup for Linux.
.
Thank you for any answers or suggestions.
That is very odd. Check you log for errors, or any logging. Enable logging on finest in EclipseLink in your persistence.xml, so odd is occurring.
Ensure both classes are listed in your persistence.xml.
Also try disabling weaving ("eclipselink.weaving"="false"), does that affect the issue?
Also include the SQL that is logged, does it select the data?
I work on an application that has been converted from pure JDBC to Spring template with row mapper. The issue that I have is that the column in database doesn't match the property names which prevent me from using BeanPropertyRowMapper easily.
I saw some posts about using aliases in queries. This would work but it makes it impossible to do a SELECT *
Isn't there any annotation that can be used with BeanPropertyRowMapper as #Column from JPA?
I saw Some posts about using aliases in queries
This is actually an approach suggested in JavaDocs:
To facilitate mapping between columns and fields that don't have matching names, try using column aliases in the SQL statement like "select fname as first_name from customer".
From: BeanPropertyRowMapper.
impossible to do a SELECT *
Please do not use SELECT *. This makes you vulnerable to any database schema change, including completely backward compatible ones like adding or rearranging columns.
Isn't there any annotation that can be used with BeanPropertyRowMapper as #Column from JPA?
Yes, it is called jpa, hibernate and maybe ibatis. Seriously, either use aliases or implement your own RowMapper, Spring is not a full-featured orm.
You can override the BeanPropertyRowMapper.underscoreName, and get the name of the Column annotation to mapping the field with #Column(name = "EXAMPLE_KEY") in the PropertyDescriptor(getter/setter binding).
#Slf4j
public class ColumnRowMapper<T> extends BeanPropertyRowMapper<T> {
private ColumnRowMapper(final Class<T> mappedClass)
{
super(mappedClass);
}
#Override
protected String underscoreName(final String name)
{
final Column annotation;
final String columnName;
Field declaredField = null;
try
{
declaredField = getMappedClass().getDeclaredField(name);
}
catch (NoSuchFieldException | SecurityException e)
{
log.warn("Ups, field «{}» not found in «{}».", name, getMappedClass());
}
if (declaredField == null || (annotation = declaredField.getAnnotation(Column.class)) == null
|| StringUtils.isEmpty(columnName = annotation.name()))
{
return super.underscoreName(name);
}
return StringUtils.lowerCase(columnName);
}
/**
* New instance.
*
* #param <T> the generic type
* #param mappedClass the mapped class
* #return the bean property row mapper
*/
public static <T> BeanPropertyRowMapper<T> newInstance(final Class<T> mappedClass)
{
return new ColumnRowMapper<>(mappedClass);
}
}
A version of above mapper but with early initiation of mapping index, since reflection is way too slow:
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.Column;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
#Slf4j
public class ColumnRowMapper<T> extends BeanPropertyRowMapper<T> {
private Map<String, String> columnIndex;
private ColumnRowMapper(final Class<T> mappedClass)
{
super(mappedClass);
}
#Override
protected void initialize(Class<T> mappedClass) {
columnIndex = new ConcurrentHashMap<>();
for (Field f: mappedClass.getDeclaredFields()) {
String fieldName = f.getName();
Column annotation = f.getAnnotation(Column.class);
if (annotation == null) {
continue;
}
String columnName = annotation.name();
if (StringUtils.isEmpty(columnName)) {
continue;
}
columnIndex.put(fieldName, StringUtils.lowerCase(columnName));
}
super.initialize(mappedClass);
}
#Override
protected #NonNull String underscoreName(final #NonNull String name)
{
if (columnIndex.containsKey(name)) {
return columnIndex.get(name);
}
return super.underscoreName(name);
}
/**
* New instance.
*
* #param <T> the generic type
* #param mappedClass the mapped class
* #return the bean property row mapper
*/
public static <T> BeanPropertyRowMapper<T> newInstance(final Class<T> mappedClass)
{
return new ColumnRowMapper<>(mappedClass);
}
}