I've been searching a way to make envers not recording any entity that I merged when there were no modification since last record.
I turns out that this should be Envers' normal behavior (no audit if there are no modifications).
Entities only have the #Audited annotations, but they keep being audited even when there is no change since last audit.
This is the configuration of my persitence.xml:
<property name="org.hibernate.envers.revision_field_name" value="revision" />
<property name="org.hibernate.envers.revision_type_field_name" value="revision_type" />
<property name="org.hibernate.envers.revision_on_collection_change" value="false"/>
<property name="org.hibernate.envers.store_data_at_delete" value="true"/>
I have found this Hibernate Envers: Auditing an object, calling merge on it, gives an audit record EVERY time even with no change? but there is no answer.
Some of my equals()/hascode() methods are only testing for IDs (the primary keys), but I didn't find out any topic on how this could be related.
I'v also seen taht there is a new parameter to see which field changed, but I don't think that's related to my problem too.
I'm using Postgresql, if that matters.
Any ideas for this behavior ? The only solution I have for the moment is to get the entity through the entityManager and compare them (I'll use some reflection based API if it comes to this).
The problem wasn't from the application, but from the code itself. Our entites has a field "lastUpdateDate", which was set at the current date on every merge(). The comparaison is done by envers after the merge, so this field has change since last revision.
For those who are curious, changes between versions are evaluated in org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map() (at least on evers 4.3.5.Final) which returns true if there are any changes between oldState and newState. It uses a specific mapper depending on the property compared.
EDIT: I'll put here how I solved the problem, but Dagmar's solution can also be used. Mine might be a little bit trickier and dirtier however.
I used Envers's EnversPostUpdateEventListenerImpl as describerd in The official documentation and various SO answers: I created mine and forced Envers to use it.
#Override
public void onPostUpdate(PostUpdateEvent event) {
//Maybe you should try catch that !
if ( event.getOldState() != null ) {
final EntityPersister entityPersister = event.getPersister();
final String[] propertiesNames = entityPersister.getPropertyNames();
for ( int i = 0; i < propertiesNames.length; ++i ) {
String propertyName = propertiesNames[i];
if(checkProperty(propertyName){
event.getOldState()[i] = event.getState()[i];
}
}
// Normal Envers processing
super.onPostUpdate(event);
}
My checkProperty(String propertyName) just checked if it was an update date property (propertyName.endsWith("lastUpdateDate") because that's how they are in our app). The trick is, I set the old state to the new state so if that's the only modified field in my entity, it won't audit it (persist it with envers). But if there are other fields which where modified, Envers will audit the entity with those modified fields and with the right lastUpdateDate.
I also had a problem where oldState was time with hh:mm:ss not set (only zero's) and the new state was the same day with hours set. So I used a similar trick:
Date oldDtEffet = (Date) event.getOldState()[i];
Date newDtEffet = (Date) event.getState()[i];
if(oldDtEffet != null && newDtEffet != null &&
DateUtils.isDateEqualsWithoutTime(oldDtEffet,newDtEffet)){
event.getOldState()[i] = event.getState()[i];
}
(Note: you must reimplement ALL event listeners, even though they will juste inherit Envers classes, there's no turnaround. Be sure that the org.hibernate.integrator.spi.Integrator is in your application)
The good news is that Hibernate Envers works as expected - versions (entries into the AUD tables) are not created unless an auditable property is modified.
However, in our application we had implemented a MergeEventListener which was updating tracking fields (lastUpdated, lastUpdatedBy) on every entity save. This caused Envers to make a new version even when there were no changes to the entity.
The solution was quite simple in the end (for us) - using an example of how to use Interceptors and Events from Hibernate: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html
We replaced our class implementing PersistEventListener and MergeEventListener with a class that extends EmptyInterceptor and overrides the onFlushDirty and onSave methods.
public class EntitySaveInterceptor extends EmptyInterceptor {
#Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
setModificationTrackerProperties(entity);
return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}
#Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
setModificationTrackerProperties(entity);
return super.onSave(entity, id, state, propertyNames, types);
}
private void setModificationTrackerProperties(Object object) {
if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal != null && principal instanceof MyApplicationUserDetails) {
User user = ((MyApplicationUserDetails) principal).getUser();
if (object instanceof ModificationTracker && user != null) {
ModificationTracker entity = (ModificationTracker) object;
Date currentDateTime = new Date();
if (entity.getCreatedDate() == null) {
entity.setCreatedDate(currentDateTime);
}
if (entity.getCreatedBy() == null) {
entity.setCreatedBy(user);
}
entity.setLastUpdated(currentDateTime);
entity.setLastUpdatedBy(user);
}
}
}
}
}
Hooking up the EntitySaveInterceptor to the Hibernate JPA persistence unit
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="myapplication" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.ejb.interceptor" value="org.myapplication.interceptor.EntitySaveInterceptor" />
<property name="hibernate.hbm2ddl.auto" value="none"/>
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
</persistence>
And for completeness, here is the ModificationTracker interface:
public interface ModificationTracker {
public Date getLastUpdated();
public Date getCreatedDate();
public User getCreatedBy();
public User getLastUpdatedBy();
public void setLastUpdated(Date lastUpdated);
public void setCreatedDate(Date createdDate);
public void setCreatedBy(User createdBy);
public void setLastUpdatedBy(User lastUpdatedBy);
}
It should also be possible to solve this problem by using an implementation of PreUpdateEventListener to set the ModificationTracker values because that listener is also only fired when the object is dirty.
I had similar situation.
I found out that the reason for duplicate rows in audit tables was usage of LocalDateTime field in the audited entity.
LocalDateTime field is persisted to DATETIME field in MySQL database. The problem was that DATETIME field has precision of 1 second, while LocalDateTime has much higher precision, so when Envers compares the data from the database to the object it sees the difference, even the LocalDateTime field hasn't been changed.
I solved this by truncating LocalDateTime field to seconds.
Related
Previously, when I was adding a entity to database with Hibernate I used to check that it hadn't already been added. But in an effort to improve performance I forgot this check and just tried to add without checking, as I was using saveOrUpdate() it was my understanding that if Hibernate found it was already added it would just update with and changes made by my save.
But instead it fails with
18/08/2018 21.58.34:BST:Errors:addError:SEVERE: Adding Error:Database Error:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.jthink.songlayer.MusicBrainzReleaseWrapper#95f6f584-407f-4b26-9572-bb8c6e9c580a]
java.lang.Exception
at com.jthink.songkong.analyse.general.Errors.addError(Errors.java:28)
at com.jthink.songkong.exception.ExceptionHandling.handleHibernateException(ExceptionHandling.java:209)
at com.jthink.songkong.db.ReleaseCache.addToDatabase(ReleaseCache.java:394)
at com.jthink.songkong.db.ReleaseCache.add(ReleaseCache.java:65)
#Entity
public class MusicBrainzReleaseWrapper
{
#Id
private String guid;
#Version
private int version;
#org.hibernate.annotations.Index(name = "IDX__MUSICBRAINZ_RELEASE_WRAPPER_NAME")
#Column(length = 1000)
private String name;
#Lob
#Column(length = 512000)
private String xmldata;
public String getGuid()
{
return guid;
}
public void setGuid(String guid)
{
this.guid = guid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getXmldata()
{
return xmldata;
}
public void setXmldata(String xmldata)
{
this.xmldata = xmldata;
}
}
private static boolean addToDatabase(Release release)
{
Session session = null;
try
{
session = HibernateUtil.beginTransaction();
//Marshall to String
StringWriter sw = new StringWriter();
Marshaller m = jc.createMarshaller();
m.marshal(release, sw);
sw.flush();
MusicBrainzReleaseWrapper wrapper = new MusicBrainzReleaseWrapper();
wrapper.setGuid(release.getId());
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
session.getTransaction().commit();
MainWindow.logger.info("Added to db release:" + release.getId() + ":" + release.getTitle());
return true;
}
catch (ConstraintViolationException ce)
{
MainWindow.logger.warning("Release already exists in db:"+release.getId()+":"+release.getTitle());
return true;
}
catch(GenericJDBCException jde)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +jde.getMessage());
ExceptionHandling.handleDatabaseException(jde);
}
catch(HibernateException he)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +he.getMessage());
ExceptionHandling.handleHibernateException(he);
}
catch(Exception e)
{
MainWindow.logger.log(Level.WARNING,"Failed AddReleaseToDatabase:"+release.getId()+ ':' +e.getMessage(),e);
throw new RuntimeException(e);
}
finally
{
HibernateUtil.closeSession(session);
}
return false;
}
Used to check first before call to addToDatabase
if(ReleaseCache.get(release.getId())==null)
{
addToDatabase(release)
}
Hiberante object has 3 states for an Entity. They are:
- Transient Or New
- Detached (Objects are fetched from DB and hibernate session is closed)
- Persistent (Object are fetched from DB and hibernate session is open)
In saveOrUpdate method, it either save the transient object or update the detached/ persistent object.
In your code, you are trying to create Transient/New object and setting the old id in it. That's the reason you are getting above error. The correct way to fetch the object first using id and then update it.
The problem you are hitting is directly related to the Optimistic locking you have enabled through the #Version annotation on the MusicBrainzReleaseWrapper. saveOrUpdate really can either add or update an entity but this is only if the entity version is the same as the one of the detached object you are trying to add or merge.
In your particular example your detached object has a version previous to the last version in the database therefore the operation can not be executed on a stale data.
UPDATE:
MusicBrainzReleaseWrapper wrapper = session.get(release.getId()):
//the wrapper is managed object
if (wrapper == null) {
//initilize wrapper with the values from release
.......
session.save(wrapper)
}
else {
// do not set ID here. ID is aready present!!!
// never manuay set the version field here
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
//In case you don't need update logic at all
// remove the #Version field from the entity
// and do othing in the else clause , or throw exception
// or log error or anything you see fit
}
No. saveOrUpdate method is used either to persist or merge an entity with the current session. It doesn't do what you expect. Either save or update entity is application's specific logic. Hibernate doesn't do any application's specific logic.
Session.merge() can directly save a previously unknown instance, but note it won't necessarily avoid the extra select against the database.
#Pavan is right about the entity being transient or detached in Hibernate (or JPA) terminology. Both of these states mean that Hibernate has not yet got a reference to this instance of the entity in its session (in the StatefulPersistenceContext), but detached clearly means it is known to the database.
merge() instructs Hibernate to stop and check for a detached instance. The first check is for the #Id value in the session, but if it's not already there, it must hit the database.
saveOrUpdate() instructs Hibernate that the caller knows it is safe to only check the StatefulPersistenceContext for the #Id. If it's not there, the entity is assumed to be transient (i.e. new), and Hibernate will proceed to the insert operation.
saveOrUpdate() is good for instances (with or without an #Id value) that are known to the session already.
In your case clearly Hibernate is unaware of the detached instance, so you would need to use merge(). But that also means Hibernate has to check the database for the instance it hasn't seen before - if the entity has an #Id value.
To come back to the original intent in your question, update without select is harder ...
For an update, Hibernate likes to know the prior state of the entity. This makes sense if it's using dynamic updates (so not updating all columns), but otherwise you would think it could go straight for the update. The only option I know of for this is a direct update query (via HQL or JPQL), but this is hardly convenient if you have an entity instance. Maybe someone else knows how to do this.
this is more than a simple question and my English is not as good as I want... I'll try my best.
I use java 8, with Mybatis 3.4.6 over Postgres 9.6 and I need to do a custom dynamic query.
In my mapper.java class I've created a method to use with myBatis SQL Builder class
#SelectProvider(type = PreIngestManager.class, method = "selectPreIngestsSQLBuilder")
#Results({ #Result(property = "id", column = "id"), #Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", column = "id_categoria_documentale"), #Result(property = "idCliente", column = "id_cliente"),
#Result(property = "outputSipPath", column = "output_sip_path"), #Result(property = "esito", column = "esito"),
#Result(property = "stato", column = "stato"), #Result(property = "pathRdp", column = "path_rdp"),
#Result(property = "dataInizio", column = "data_inizio"), #Result(property = "dataFine", column = "data_fine") })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto,
#Param("dataInizioInferiore") Date dataInizioInferiore, #Param("dataInizioSuperiore") Date dataInizioSuperiore,
#Param("statiPreIngest") String statiPreIngest);
I have specified the #SelectProvider annotation, class and method to point at, which, in the example is PreIngestManager.class and selectPreIngestsSQLBuilder method.
This is the method
public String selectPreIngestsSQLBuilder(Map<String, Object> params) {
return new SQL() {
{
SELECT("*");
FROM("pre_ingest");
WHERE("id_categoria_documentale = #{idCatDoc}");
if (params.get("nomePacchetto") != null)
WHERE("input_path like '%' || #{nomePacchetto}");
if (params.get("dataInizioInferiore") != null) {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio between #{dataInizioInferiore} and #{dataInizioSuperiore}");
} else {
WHERE("data_inizio >= #{dataInizioInferiore}");
}
} else {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio <= #{dataInizioSuperiore}");
}
}
if (params.get("statiPreIngest") != null)
WHERE("stato in (#{statiPreIngest})");
ORDER_BY("id ASC");
}
}.toString();
}
and these are my questions:
have I to specify #Results annotation and every #Result , or can I use a java model class ? I have tried with #ResultMap(value = { "mycompany.model.PreIngest" }) but it did not work.
Most of all, as stated on documentation, with SQL builder you can access method parameters having them as final objects
// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName,
final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ? Am I mixing solutions ?
As far as I have researched, for now I see 3 methods to do a dynamic query, or a custom where condition.
To use MyBatisGenerator library and combine where condition as search criteria to use with SelectByExample method. (I use this when the query is simple)
To Write an SQL query directly, modifying XML mapper files using if, choose, statements and others as descripted here
To use SQL Builder class with #SelectProvider annotation.
Do you know when prefer the 2° method over the 3° one ? Why in the 3° method documentation I can't find how to use it ? There is written how to create custom queries but not how to launch them.
Thank a lot for your time and your suggestions.
I don't know whether you already found the answer, I just want to share my experience. Btw please forgive my english if it wasn't good.
Note: I use MyBatis 3.4.6 and Spring Framework.
have I to specify #Results annotation and every #Result , or can I use a java model class ?
Actually you can do either one.
if you want to use #Results and #ResultMap, you just need to specify #Results annotation just once in one mapper file. The trick is you need to specify id for the Results to be used in other functions.
Using truncated version of your classes, eg:
#Results(id="myResult", value= {
#Result(property = "id", column = "id"),
#Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", ... })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Then in another function you can use #ResultMap with the value refer to id from #Results mentioned before.
#ResultMap("myResult")
List<PreIngest> selectPreIngestsBySomethingElse(....);
..., or can I use a java model class ?
You can use java model class as result without using #Results and #ResultMap, but you have to make sure your java model class has the same properties/fields as the result of your query. Database tables usually have fields with snake_case. Since java is using camelCase, you have to add settings to your mybatis-config.xml file.
This is what I usually add to the mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- changes from the defaults -->
<setting name="lazyLoadingEnabled" value="false" />
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
</configuration>
The important one is mapUnderscoreToCamelCase, set this to true than you can use your java model class without the hassle of #Results and #ResultMap. You can find all the explanation of the settings in MyBatis 3 Configuration.
This is the example using your classes,
The class:
public class PreIngest {
private Long idCategoriaDocumentale;
private Long idCliente;
........ other fields
........ setter, getter, etc
}
The mapper file:
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Now onwards to SqlBuilder.
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ?
I can't answer about those final in your method since I never made SqlBuilder class with final parameters.
For SqlBuilder you must use #SelectProvider, #InsertProvider, #UpdateProvider or #DeleteProvider and it depends on the query you use.
In my experience with SQLBuilder, #Param is necessary if you need more than one parameters and use Map params to access it from the SqlBuilder class. If you don't want to use #Param in the mapper file, then you need to make sure there is only one parameter in the said mapper function. You can use java model class as the parameter though if you just specify one parameter.
If using your class for example, you can have one class
public class PersonFilter {
private Long id;
private String firstName;
private String lastName;
...... setter, getter, etc
}
the mapper function
#SelectProvider(type=PersonSqlBuilder.class, method="selectPersonLike")
List<Person> selectPersonLike(PersonFilter filter);
the SqlBuilder class
public class PersonSqlBuilder {
public String selectPersonLike(PersonFilter filter) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (filter.getId() != null) {
WHERE("P.ID like #{id}");
}
if (filter.getFirstName() != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (filter.getLastName() != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
}
That's it. Hopefully my experience can help.
I don't know how to do this with the sql builder but I do have an idea how to do this with an xml mapper file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="path.to.class.PreIngestMapper">
<resultMap id="preIngestManager" type="path.to.class.PreIngestManager">
<id property="id" column="id" />
<result property="id" column="id" />
<result property="inputPath" column="input_path" />
<result property="idCategoriaDocumentale" column="id_categoria_documentale" />
...
</resultMap>
<select id="selectPreIngests" parameterType="Map" resultMap="preIngestManager">
SELECT *
FROM pre_ingest
WHERE id_categoria_documentale = #{idCatDoc}
<if test = "nomePacchetto != null">
and input_path like '%' || #{nomePacchetto}
</if>
...
;
</select>
</mapper>
Situation:
I have a persistable class with variable of java.util.Date type:
import java.util.Date;
#Entity
#Table(name = "prd_period")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Period extends ManagedEntity implements Interval {
#Column(name = "startdate_", nullable = false)
private Date startDate;
}
Corresponding table in DB:
CREATE TABLE 'prd_period' (
'id_' bigint(20) NOT NULL AUTO_INCREMENT,
...
'startdate_' datetime NOT NULL
)
Then I save my Period object to DB:
Period p = new Period();
Date d = new Date();
p.setStartDate();
myDao.save(p);
After then if I'm trying to extract my object from DB, it is returned with variable startDate of Timestamp type - and all the places where I'm trying to use equals(...) are returning false.
Question: are there any means to force Hibernate to return dates as object of java.util.Date type instead of Timestamp without explicit modification of every such variable (e.g it must be able just work, without explicit modification of existed variables of java.util.Date type)?
NOTE:
I found number of explicit solutions, where annotations are used or setter is modified - but I have many classes with Date-variables - so I need some centralized solution and all that described below is not good enough:
Using annotation #Type: - java.sql.Date will be returned
#Column
#Type(type="date")
private Date startDate;
Using annotation #Temporal(TemporalType.DATE) - java.sql.Date will be returned
#Temporal(TemporalType.DATE)
#Column(name=”CREATION_DATE”)
private Date startDate;
By modifying setter (deep copy) - java.util.Date will be returned
public void setStartDate(Date startDate) {
if (startDate != null) {
this.startDate = new Date(startDate.getTime());
} else {
this.startDate = null;
}
}
By creation of my own type: - java.util.Date will be returned
Details are given here:
http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
So, I spent some time with this issue and found a solution. It is not pretty one, but at least a start point - maybe someone will supplement this with some useful comments.
Some info about mapping that I found in process:
Class that contains basic mapping of Hibernate types to property types is org.hibernate.type.TypeFactory. All this mappings are stored in unmodifiable map
private static final Map BASIC_TYPES;
...
basics.put( java.util.Date.class.getName(), Hibernate.TIMESTAMP );
...
BASIC_TYPES = Collections.unmodifiableMap( basics );
As you can see with java.util.Date type assosited with Hibernate type org.hibernate.type.TimestampType
Next interesting moment - creation of Hibernate org.hibernate.cfg.Configuration - object that contains all info about mapped classes. This classes and their properties can be extracted like this:
Iterator clsMappings = cfg.getClassMappings();
while(clsMappings.hasNext()){
PersistentClass mapping = (PersistentClass) clsMappings.next();
handleProperties(mapping.getPropertyIterator(), map);
}
Vast majority of properties are the objects of org.hibernate.mapping.SimpleValue types. Our point of interest is the method SimpleValue.getType() - in this method is defined what type will be used to convert properties values back-and-forth while working with DB
Type result = TypeFactory.heuristicType(typeName, typeParameters);
At this point I understand that I am unable to modify BASIC_TYPES - so the only way - to replace SimpleValue object to the properties of java.util.Date types to my custom Object that will be able to know the exact type to convert.
The solution:
Create custom container entity manager factory by extending HibernatePersistence class and overriding its method createContainerEntityManagerFactory:
public class HibernatePersistenceExtensions extends HibernatePersistence {
#Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
if ("true".equals(map.get("hibernate.use.custom.entity.manager.factory"))) {
return CustomeEntityManagerFactoryFactory.createCustomEntityManagerFactory(info, map);
} else {
return super.createContainerEntityManagerFactory(info, map);
}
}
}
Create Hibernate configuration object, modify value ojects for java.util.Date properties and then create custom entity manager factory.
public class ReattachingEntityManagerFactoryFactory {
#SuppressWarnings("rawtypes")
public static EntityManagerFactory createContainerEntityManagerFactory(
PersistenceUnitInfo info, Map map) {
Ejb3Configuration cfg = new Ejb3Configuration();
Ejb3Configuration configured = cfg.configure( info, map );
handleClassMappings(cfg, map);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
#SuppressWarnings("rawtypes")
private static void handleClassMappings(Ejb3Configuration cfg, Map map) {
Iterator clsMappings = cfg.getClassMappings();
while(clsMappings.hasNext()){
PersistentClass mapping = (PersistentClass) clsMappings.next();
handleProperties(mapping.getPropertyIterator(), map);
}
}
private static void handleProperties(Iterator props, Map map) {
while(props.hasNext()){
Property prop = (Property) props.next();
Value value = prop.getValue();
if (value instanceof Component) {
Component c = (Component) value;
handleProperties(c.getPropertyIterator(), map);
} else {
handleReturnUtilDateInsteadOfTimestamp(prop, map);
}
}
private static void handleReturnUtilDateInsteadOfTimestamp(Property prop, Map map) {
if ("true".equals(map.get("hibernate.return.date.instead.of.timestamp"))) {
Value value = prop.getValue();
if (value instanceof SimpleValue) {
SimpleValue simpleValue = (SimpleValue) value;
String typeName = simpleValue.getTypeName();
if ("java.util.Date".equals(typeName)) {
UtilDateSimpleValue udsv = new UtilDateSimpleValue(simpleValue);
prop.setValue(udsv);
}
}
}
}
}
As you can see I just iterate over every property and substitute SimpleValue-object for UtilDateSimpleValue for properties of type java.util.Date. This is very simple class - it implements the same interface as SimpleValue object, e.g org.hibernate.mapping.KeyValue. In constructor original SimpleValue object is passed - so every call to UtilDateSimpleValue is redirected to the original object with one exception - method getType(...) return my custom Type.
public class UtilDateSimpleValue implements KeyValue{
private SimpleValue value;
public UtilDateSimpleValue(SimpleValue value) {
this.value = value;
}
public SimpleValue getValue() {
return value;
}
#Override
public int getColumnSpan() {
return value.getColumnSpan();
}
...
#Override
public Type getType() throws MappingException {
final String typeName = value.getTypeName();
if (typeName == null) {
throw new MappingException("No type name");
}
Type result = new UtilDateUserType();
return result;
}
...
}
And the last step is implementation of UtilDateUserType. I just extend original org.hibernate.type.TimestampType and override its method get() like this:
public class UtilDateUserType extends TimestampType{
#Override
public Object get(ResultSet rs, String name) throws SQLException {
Timestamp ts = rs.getTimestamp(name);
Date result = null;
if(ts != null){
result = new Date(ts.getTime());
}
return result;
}
}
That is all. A little bit tricky, but now every java.util.Date property is returned as java.util.Date without any additional modifications of existing code (annotations or modifying setters). As I find out in Hibernate 4 or above there is a much more easier way to substitute your own type (see details here: Hibernate TypeResolver). Any suggestions or criticism are welcome.
A simple alternative to using a custom UserType is to construct a new java.util.Date in the setter for the date property in your persisted bean, eg:
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Column;
#Entity
public class Purchase {
private Date date;
#Column
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
// force java.sql.Timestamp to be set as a java.util.Date
this.date = new Date(date.getTime());
}
}
Approaches 1 and 2 obviously don't work, because you get java.sql.Date objects, per JPA/Hibernate spec, and not java.util.Date. From approaches 3 and 4, I would rather choose the latter one, because it's more declarative, and will work with both field and getter annotations.
You have already laid out the solution 4 in your referenced blog post, as #tscho was kind to point out. Maybe defaultForType (see below) should give you the centralized solution you were looking for. Of course will will still need to differentiate between date (without time) and timestamp fields.
For future reference I will leave the summary of using your own Hibernate UserType here:
To make Hibernate give you java.util.Date instances, you can use the #Type and #TypeDef annotations to define a different mapping of your java.util.Date java types to and from the database.
See the examples in the core reference manual here.
Implement a UserType to do the actual plumbing (conversion to/from java.util.Date), named e.g. TimestampAsJavaUtilDateType
Add a #TypeDef annotation on one entity or in a package-info.java - both will be available globally for the session factory (see manual link above). You can use defaultForType to apply the type conversion on all mapped fields of type java.util.Date.
#TypeDef
name = "timestampAsJavaUtilDate",
defaultForType = java.util.Date.class, /* applied globally */
typeClass = TimestampAsJavaUtilDateType.class
)
Optionally, instead of defaultForType, you can annotate your fields/getters with #Type individually:
#Entity
public class MyEntity {
[...]
#Type(type="timestampAsJavaUtilDate")
private java.util.Date myDate;
[...]
}
P.S. To suggest a totally different approach: we usually just don't compare Date objects using equals() anyway. Instead we use a utility class with methods to compare e.g. only the calendar date of two Date instances (or another resolution such as seconds), regardless of the exact implementation type. That as worked well for us.
Here is solution for Hibernate 4.3.7.Final.
pacakge-info.java contains
#TypeDefs(
{
#TypeDef(
name = "javaUtilDateType",
defaultForType = java.util.Date.class,
typeClass = JavaUtilDateType.class
)
})
package some.pack;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
And JavaUtilDateType:
package some.other.or.same.pack;
import java.sql.Timestamp;
import java.util.Comparator;
import java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.VersionType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor;
import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor;
/**
* Note: Depends on hibernate implementation details hibernate-core-4.3.7.Final.
*
* #see
* <a href="http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch06.html#types-custom">Hibernate
* Documentation</a>
* #see TimestampType
*/
public class JavaUtilDateType
extends AbstractSingleColumnStandardBasicType<Date>
implements VersionType<Date>, LiteralType<Date> {
public static final TimestampType INSTANCE = new TimestampType();
public JavaUtilDateType() {
super(
TimestampTypeDescriptor.INSTANCE,
new JdbcTimestampTypeDescriptor() {
#Override
public Date fromString(String string) {
return new Date(super.fromString(string).getTime());
}
#Override
public <X> Date wrap(X value, WrapperOptions options) {
return new Date(super.wrap(value, options).getTime());
}
}
);
}
#Override
public String getName() {
return "timestamp";
}
#Override
public String[] getRegistrationKeys() {
return new String[]{getName(), Timestamp.class.getName(), java.util.Date.class.getName()};
}
#Override
public Date next(Date current, SessionImplementor session) {
return seed(session);
}
#Override
public Date seed(SessionImplementor session) {
return new Timestamp(System.currentTimeMillis());
}
#Override
public Comparator<Date> getComparator() {
return getJavaTypeDescriptor().getComparator();
}
#Override
public String objectToSQLString(Date value, Dialect dialect) throws Exception {
final Timestamp ts = Timestamp.class.isInstance(value)
? (Timestamp) value
: new Timestamp(value.getTime());
// TODO : use JDBC date literal escape syntax? -> {d 'date-string'} in yyyy-mm-dd hh:mm:ss[.f...] format
return StringType.INSTANCE.objectToSQLString(ts.toString(), dialect);
}
#Override
public Date fromStringValue(String xml) throws HibernateException {
return fromString(xml);
}
}
This solution mostly relies on TimestampType implementation with adding additional behaviour through anonymous class of type JdbcTimestampTypeDescriptor.
There are some classes in the Java platform libraries that do extend an instantiable
class and add a value component. For example, java.sql.Timestamp
extends java.util.Date and adds a nanoseconds field. The equals implementation
for Timestamp does violate symmetry and can cause erratic behavior if
Timestamp and Date objects are used in the same collection or are otherwise intermixed.
The Timestamp class has a disclaimer cautioning programmers against
mixing dates and timestamps. While you won’t get into trouble as long as you
keep them separate, there’s nothing to prevent you from mixing them, and the
resulting errors can be hard to debug. This behavior of the Timestamp class was a
mistake and should not be emulated.
check out this link
http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
Just add the this annotation #Temporal(TemporalType.DATE) for a java.util.Date field in your entity class.
More information available in this stackoverflow answer.
I ran into a problem with this as well as my JUnit assertEquals were failing comparing Dates to Hibernate emitted 'java.util.Date' types (which as described in the question are really Timestamps). It turns out that by changing the mapping to 'date' rather than 'java.util.Date' Hibernate generates java.util.Date members. I am using an XML mapping file with Hibernate version 4.1.12.
This version emits 'java.util.Timestamp':
<property name="date" column="DAY" type="java.util.Date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
This version emits 'java.util.Date':
<property name="date" column="DAY" type="date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
Note, however, if Hibernate is used to generate the DDL, then these will generate different SQL types (Date for 'date' and Timestamp for 'java.util.Date').
Use #Type annotation on Hibernate entity Filed to customize your mapping with DB object and java object
Visit: https://www.baeldung.com/hibernate-custom-types
I have got a onetomany relation in Hibernate:
#Entity
public class Project
#OneToMany(cascade = CascadeType.ALL)
#OrderColumn(name = "project_index")
List<Application> applications;
#Entity
public class Application
Now, I tried to update an application directly:
session.merge(application);
The result is that a new Application is created, instead of updating the entity. How can I reatach my entity properly and update my entity?
Thank you
Solution:
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Application)) {
return false;
}
Application app = (Application)obj;
return id == app.getId();
}
#Override
public int hashCode () {
return status.hashCode();
}
When you call session.merge(), it returns an object which is the value of that entity in the context of the current session. You can't simply reuse the existing object in a new session, because the object is outside of its session's life (and a new object to represent the same entity may already have been created in the new session). You need to honor hibernate's single-instance-per-entity-per-session semantics by using the new object in place of the old.
If you find that calling merge() results in a new entity being created (an insert statement is generated on flush), this might mean that the identifier of the object you're trying to merge doesn't match, or there is an issue with your equals() or hashCode() implementation.
I use EJB3 container managed persistence i.e an EntityManager is injected via #PersistenceContext annotation. The persistent context then may be propagated to nested EJBs. Transactions are also managed by the contaner (glassfish).
Usually I would drop persistence.xml into META-INF directory and the container would work out which provider to use and how to configure the EntityManagerFactory (based in hibernate specific properties).
My problem is that I need to hook into the EntityManagerFactory configuration process.
Particularly I need to change discriminator values in some PersistentClasses before the EntityManagerFactory gets configure'ed (frozen for any change).
This is how I do it with Spring, but need to do similar with pure EJB3 CMP (or may be with the help of Spring).
public class AnnotationSessionFactoryBean extends org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean {
/** Log4j logging instance. */
protected static Logger log = Logger.getLogger(AnnotationSessionFactoryBean.class);
//some data preloaded from the database using jdbc
private Map<String, DatabaseConfiguration> configs;
#Override
protected void postProcessAnnotationConfiguration(AnnotationConfiguration config) throws HibernateException {
//Load and process dynamic Mappings.
Iterator classMappingsIter = config.getClassMappings();
while(classMappingsIter.hasNext()) {
PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();
String discriminatorValue = persistentClass.getDiscriminatorValue();
if(discriminatorValue != null) {
log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
//here I replace discriminator values.
//The Discriminator values are coded in the annotations
//as names (words). These words need to be replaced with ids
//previously loaded from the database using jdbc.
//The names are constant in all environments, however the ids are
//are different.
discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
persistentClass.setDiscriminatorValue(discriminatorValue);
log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
}
}
super.postProcessAnnotationConfiguration(config);
}
/**
* #return the configs
*/
public Map<String, DatabaseConfiguration> getConfigs() {
return configs;
}
/**
* #param configs the configs to set
*/
public void setConfigs(Map<String, DatabaseConfiguration> configs) {
this.configs = configs;
}
}
Thanks in advance,
Anton
I think I have found the solution.
The class org.hibernate.ejb.HibernatePersistence can be overridden.
public class HibernatePersistenceCustom extends org.hibernate.ejb.HibernatePersistence {
/** Log4j logging instance. */
protected static Logger log = Logger.getLogger(HibernatePersistenceCustom.class);
#Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
Ejb3Configuration cfg = new Ejb3Configuration();
//here you can configure it
doCustomConfiguration(cfg);
Ejb3Configuration configured = cfg.configure(info, map);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
...
//other methods can also be overridden if required.
public void doCustomConfiguration(Ejb3Configuration config) {
//Load and process dynamic Mappings.
Iterator classMappingsIter = config.getClassMappings();
while(classMappingsIter.hasNext()) {
PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();
String discriminatorValue = persistentClass.getDiscriminatorValue();
if(discriminatorValue != null) {
log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
//here I replace discriminator values.
//The Discriminator values are coded in the annotations
//as names (words). These words need to be replaced with ids
//previously loaded from the database using jdbc.
//The names are constant in all environments, however the ids are
//are different.
discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
persistentClass.setDiscriminatorValue(discriminatorValue);
log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
}
}
}
}
then in persistence.xml instead of org.hibernate.ejb.HibernatePersistence put com.mydomain.persistence.HibernatePersistenceCustom
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="mypersistenceunit" transaction-type="JTA">
<provider>com.mydomain.persistence.HibernatePersistenceCustom</provider>
<jta-data-source>jdbc/mydatasource</jta-data-source>
<properties>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.use_sql_comments" value="false"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.SunONETransactionManagerLookup"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
</properties>
</persistence-unit>
</persistence>
Have not tested it yet, but I think it will work.
Thanks
You could override metadata annotations by providing an XML mapping file (see the Chapter 10 XML Descriptor in the JPA 1.0 specification).
Of course, this is not dynamic (unless you generate the XML mapping file using for example FreeMarker and feed values from the database).