Hibernate Envers - select "all" changes for entity ID - java

I have problem with Hibernate Envers.
I have classes like:
#Entity
#Table(name = "REVINFO")
#RevisionEntity(MyRevisionEntityListener.class)
public class RevEntity {
#Id
#RevisionNumber
#Column(name = "REV", nullable = false, updatable = false)
private Integer id;
#RevisionTimestamp
#Column(name = "REVTSTMP", nullable = false, updatable = false)
private Date timestamp;
#Column(name = "MODIFIED_BY", length = 100)
private String modifiedBy;
#Column(name = "COMMENT", length = 100)
private String comment;
public class MyRevisionEntityListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
RevEntity a = (RevEntity) revisionEntity;
a.setComment("Some value");
}
}
How can i select every change for entity ID and their "REVINFO" object?
I've got something like this:
List resultList = AuditReaderFactory.get(entityManager)
.createQuery()
.forRevisionsOfEntityWithChanges(ClientType.class, true)
.add(AuditEntity.id().eq(entityId))
.getResultList();
And it's almost work good. I received every "change" but REVINFO looks strange. All fields are null - and there are 1 more object $$_hibernate_interceptor which actually hold "information" but i cannot acces it via code (or i dont know how). See example at the image.
So my question is:
1 - How can i get REVINFO values ?
2 - Do i realy have to use entityManager, or can it be achived with different approach ?
Edit 2:
Correct me if i am wrong, but does forRevisionsOfEntityWithChanges works as Lazy Initialization? I mean, if i try to receive for example modifiedBy field i actually get my data. Debugger log make me confused.

The call to forRevisionsOfEntityWithChanges returns an object array that contains:
Entity instance
Revision Entity
Revision Type
Property names that were changed.
How can i get REVINFO values ? 2 - Do i realy have to use entityManager, or can it be achived with different approach ?
So in your code, to get the revision info attributes, you would do the following. Note that in this code, the type of the revision-info object will depend on your configuration or if you're using a custom revision-info entity class in your deployment. Just be sure to cast it to the proper type.
for (Object entry : resultList) {
final Object[] row = (Object[]) entry;
final TheRevisionEntityClassType revisionInfo = row[1];
// now you can get the revision entity attributes from revisionInfo using getters
}
Correct me if i am wrong, but does forRevisionsOfEntityWithChanges works as Lazy Initialization? I mean, if i try to receive for example modifiedBy field i actually get my data. Debugger log make me confused.
Depending on the query, yes Hibernate may use proxies and its important to understand that in this case, the visual representation you get in the debugger may or may not be accurate depending if the object's internal state gets initialized by the debugger window or not.

Related

Hibernate Tools (hbm2java): Column Default Value is Missing [duplicate]

This question already has answers here:
How to set a default entity property value with Hibernate
(18 answers)
Closed 26 days ago.
I am using hbm2java, which is part of Hibernate Tools, to reverse engineer a database into JPA entity classes.
I run the tool via ./mvnw clean generate-sources and the entity classes are generated and saved to target/generated-sources.
In the UserAccount database table, the Created column is defined like this. Note the default value:
Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
When hbm2java reverse engineers that column, the default value is not included:
...
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
public void setCreated(Timestamp created) {
this.created = created;
}
...
As a result, a DataIntegrityViolationException is thrown when trying to save a UserAccount entity to the database:
org.springframework.dao.DataIntegrityViolationException: not-null
property references a null or transient value :
com.example.UserAccount.created
Really hoping there is a way around this as I have quite a few database columns with default values, the most complex being:
DEFAULT 'User' + CAST(NEXT VALUE FOR SeqUserAccountUsername as VARCHAR(19))
...that just generates a string such as User13.
I'm still learning Spring Boot and Hibernate and could use some advice on the best approach to solving this problem.
My current research:
The same question was asked back in 2007 in the Hibernate Forums but a solution was not provided.
This documentation talks about using the "default-value" attribute to set the "Default initialization value for a field". Is that the correct approach?
I believe that the following mapping should work in all recent-ish versions of Hibernate:
#Generated(INSERT)
#ColumnDefault("CURRENT_TIMESTAMP")
#Column(nullable = false)
public Timestamp getCreated() {
return this.created;
}
If that doesn't work, let me know.
(It's certainly true that the reverse engineering tool doesn't know anything about default values.)
At first, you need to find someone who will able to clarify how that database works. The problem is SQL column definition like TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL means following:
you never select null
you may not insert/set null
you may insert/set not null
you may either omit column in DML statement or specify DEFAULT, in that case DB generates value according to default expression (CURRENT_TIMESTAMP in your case)
The simplest option to get exactly the same functionality/capabilities in Hibernate, is to use JPA Callbacks, smth. like:
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, /* updatable = false, */ length = 29)
public Timestamp getCreated() {
return this.created;
}
#PrePersist
protected void onPrePersist() {
if (this.created == null) {
this.created = Timestamp.from(Instant.now());
}
}
that allows you to specify arbitrary created timestamp, and only if it is null Hibernate will use current time - that is exactly what DB allows you to do.
Other options do something similar, but not the same, however some of them may suit you.
#CreationTimestamp:
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
it ignores value of this.created and inserts current time, calculated on Java side, into DB.
#Generated(GenerationTime.INSERT)
#Generated(GenerationTime.INSERT)
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED", nullable = false, length = 29)
public Timestamp getCreated() {
return this.created;
}
it ignores value of this.created and delegates generation of value to DB, basically it omits corresponding column in INSERT statement (well... "generate" is not correct definition here).
if you choose to use either #CreationTimestamp or #Generated(GenerationTime.INSERT) remove corresponding setter void setCreated(Timestamp created) in order to avoid any confusion about mutability of created field.

How to decouple client from entity variable names when Spring JPA sorting is used

I am using Spring JPA Pageable to manage the pagination and sorting for an endpoint. The issue is the client should use the same variable name for sorting as the entity name for JPA to be able to accept the sort request. However, I am trying to decouple the sort in the request from the entity so if the entity variable is renamed there is no impact on the request params.
#Entity
class Example {
#Id
#GeneratedValue
#Column(nullable = false)
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
// constructor, getters etc.
}
// controller
#GetMapping("/examples")
public ResponseEntity<ExampleResponseModel> getAllExamples(
#RequestParam(required = false, defaultValue = "1") int pageNum,
#RequestParam(required = false, defaultValue = "10") int pageSize,
#RequestParam(required = false, defaultValue = "firstName, lastName") String[] sortFields,
#RequestParam(required = false, defaultValue = "asc") String sortOrder {
Page<Example> pages = repository.findAll(PageRequest
.of(pageNum - 1, pageSize, Sort.by(Direction.fromString(sortOrder), sortFields)));
return new ResponseEntity<>(pages, HttpStatus.OK);
}
One way could be implementing a mapping in the middle to translate the request model sort field names to Entity field names. However, this will be very verbose and does not solve the sorting sensitivity to the field name in the Entity class. My primary objective is to decouple the client from entity entirely when it comes to sorting and also make sure that if another developer renames firstName variable of Example class to firstname the sorting won't break (or at least he gets a compile-time error to notify him to fix it).
If you use paging and sorting I would guess that you have a grid or something similar at the frontend. If so, you can create your own annotation which would keep a meta-name of the property that wouldn't be changed whatever happened.
After that you should create a new GET method in the controller which would have requested all the metas and its real names read through the reflection. Entering the grid tab or whatever, front should, firstly, check its storge looking for metas of the Example entity. If succeed, just take this data and use in the request, if not, request the new endpoint to get metas with mapped real names and store it and only then make a request with sorting and paging. If front- and back and are independent then add actualization of metas while backend startup.

Hibernate Envers wrong modified flag on list

I use Envers to audit my data and sometimes the value of the _MOD is incorrect. It stays at 0 instead of 1 when I am adding an element in my list. But it happens only in a specific case.
My entity:
#Entity
#Table(name = "PERSONNE")
#Audited(withModifiedFlag = true)
public class PersonEntity {
#Id
#Column(name = "ID_PERSONNE")
private Long id;
#Column(name = "NAME", length = 100)
private String name;
#Audited( withModifiedFlag = true, modifiedColumnName = "SERVICES_MOD")
private Set<PersonneServiceEntity> services = new HashSet<>(); // Entity with attributs, gettters, setters and envers annotations...
#Audited( withModifiedFlag = true, modifiedColumnName = "OPT_INS_MOD")
private Set<OptinEntity> optIns = new HashSet<>();// Entity with attributs, gettters, setters and envers annotations...
// more fields
// + getters, setteurs, equals, tostring
my service:
// personFromDB is retrieve via an Id
private void update(PersonEntity personFromRequest, PersonEntity personFromDB) {
personFromDB.setName(personFromRequest.getName());
updateServices(personFromRequest, personFromDB); // add new support to the list
updateOptins(personFromRequest, personFromDB); // add new services to the list
personDao.saveAndFlush(personFromDB);
}
This is were the magic happens: When I am updating name, services and optIns. Values in my database are all correct, my entity is correctly persisted, except one envers's column: OPT_INS_MOD ( OPT_INS_MOD == 0).
But if I am not updating the name ( line commented ) then everything is correctly persisted including all _MOD values ( OPT_INS_MOD == 1 and SERVICES_MOD ).
And finally if I am switching updateSupport(personFromRequest, personFromDB) and updateServices(personFromRequest, personFromDB), in this case OPT_INS_MOD is correct but not SERVICES_MOD.
My guess is that there is a problem when Envers is getting all modified fields. Because it does not make any sense to me.
Any ideas? I am using Envers version 4.3.11.Final
I'm not sure this will help you because it doesn't sound like the same problem but I've noticed a weirdness with modified flags and collections.
I get my entities back from the front end converted from JSON back to POJOs. In order to keep from having a transient object error from Hibernate, I need to reset the value in the #Id field (which was never sent to the FE). This works fine for 1-1 entities.
On collections, I found that if I create a new instance of the collection class and fill it with refreshed entities from the old collection and then assign that new collection to the old attribute, the modified flag is set to true.
However, if I fill a new collection with refreshed entities, clear() the old collection, then add all the items in the new collection, modified flag will be false unless there were actual changes to the collection.

Get back UniqueID after persist JPA

I have a uniqueID of date + sequence in Oracle. Example: 20130307-000021
My entity object uniqueID looks like this:
#Id
// #GeneratedValue
// #Basic(optional = false)
// #NotNull
// #Size(min = 1, max = 15)
#Column(name = "UNIQUE_ID")
private String uniqueId;
Anytime I use #GeneratedValue or it's variants I get a "sequence not found" or some other error. I'm trying to get back the unique ID that is generated by the Oracle database after I persist an object.
this.dbConn.persist(orderDetail);
this.dbConn.flush();
//this.dbConn.refresh(orderDetail);
//OrderDetails od = new OrderDetails();
//od =(OrderDetails)this.dbConn.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(orderDetail);
The above are some various things I've tried. When I use refresh I get an error saying "entity" not found (I assume it is looking up the entity based on the pre-persisted uniqueID of null. Is it possible to get back that auto-generated date+sequence number? I'm able to persist and retrieve objects just fine by looking at the inserted object and copying the uniqueID directly but obviously that is useless. Any help much appreciated and if you would like to see error messages etc just let me know. I've searched around and I either see 'solutions' that appear to be what I am trying or one person said he used a "brute-force work-around" but did not describe what that meant.
For anyone that may happen upon this. I kept the database trigger and just used:
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 15)
#Column(name = "ORDER_ID")
private String orderId;
I didn't need to use #GeneratedValue or any of its variants.

How to create foreign key in Hibernate on Integer column

I have an entity in Java and I would like Hibernate to create a foreign key from an Integer field (since I don't have an object reference):
#Entity
public class Invoice {
...
#Column(nullable = true)
private Integer generatedBy;
...
I think I'd like to do something like this with an attribute:
#ForeignKey(name="FK_Invoice_GeneratedBy", references="UserTable.UserId")
#Column(nullable = true)
private Integer generatedBy;
What is the best way to achieve this? I would preferably not have to maintain these relationships in a separate file (if possible).
There doesn't seem to be a solution to this, thus accepting this as an answer.
There is a way to do it, but it is not very nice...
You can have your integer attribute, AND an object attribute mapped this way:
#Column(ame = "GENERATED_BY", nullable = true)
private Integer generatedBy;
#ForeignKey(name="FK_Invoice_GeneratedBy")
#JoinColumn(name = "GENERATED_BY", nullable = false, updatable = false, insertable = false)
private User generatedByUser;
You may keep no external access to your generatedByUser field, it will only show hibernate that there is a relationship. You can set the Integer field at will, when you load this object from DB later you'll have your user reference.
Again, not very pretty, but can be useful sometimes.

Categories