Insert to hibernate audit table on condition - java

I am using hibernate envers to audit my entity. I have entity with next fields:
public class Settings
#Id
#Column(length = 80)
private String key;
#NotNull
#Column(length = 1200)
private String value;
#Version
#Column(columnDefinition = "numeric")
private Integer version;
}
It contains key-value pairs. Some of fields in this table are updated automatically. The question is: is it possible to insert or not insert record into _AUDIT table depending on value of 'key' property?
Example:
There are records in my table:
|KEY |VALUE |VERSION
_________________________________
|laskCheckDate|12-01-2017|0
|numberOfsmth |3 |0
I want to insert record to _AUDIT table if numberOfsmth is updated/deleted, but NOT insert if laskCheckDate is updated.

What you would need to do is extend the EnversPostUpdateEventListenerImpl event listener class and add your logic to check for the necessary entity type and values and decide whether to call into the super-class to audit the update or not.
Unfortunately the above approach is a bit intrusive for the novice user and I would certainly not recommend doing this if you're not super familar with Hibernate ORM and Envers.
There are some thoughts on conditional auditing in HHH-11326 which is tentatively planned for Envers 6.0 where you can influence auditing based on hooks you tie into your entities through annotations.
Should you decide to move forward and extend the listeners in 5.x, just be mindful that you should always allow the INSERT of your entity to occur. This becomes extremely important if you're using the ValidityAuditStrategy as the UPDATE expects an INSERT revision type to exist in the table or else the strategy asserts.
If all you want to control are UPDATEs, then this should not be a problem for you regardless of which strategy you leverage.

Related

How to persist data through JPA without needing a primary key

I have data that flows through my application and normally it doesn't need to be bothered but to implement a new feature I need to store it temporarily (e.g. 1 hr). The data going in can be the exact same as something that is already in there so there is no need for a primary key. However, with JPA Entities need an Id but I don't need/want one. This is preventing me from getting it working.
This is through Spring using JPA. Since the data is moving in and out of the database frequently, the use of an auto generated ID is discouraged because it'll go through the IDs in a few years time. I have tried to make it embeddable to which it says I need to do a component scan to find where it is used but if I make it an entity then it gives me the error that it needs a primary key.
This is my entity that stores the data I need to persist.
#Entity
#Table(name = "quoteOrderHistory")
public class QuoteOrderHistory {
#Column(name = "storeNumber")
private int storeNumber;
#Column(name = "invoiceNumber")
private String invoiceNumber;
#Column(name = "quoteSaleDate")
private Date quoteSaleDate;
#Column(name="orderTotal")
private BigDecimal orderTotal;
#Column(name="orderHistoryDate")
private Timestamp orderHistoryDate;
// Constructors, Getters and Setters
}
This is my repository to access the data.
#Repository
public interface QuoteOrderHistoryRepository extends JpaRepository<QuoteOrderHistory, Long> {
#Query("DELETE FROM QuoteOrderHistory q WHERE q.orderHistoryDate > date")
void deleteAllExpired(Date date);
#Query("SELECT q FROM QuoteOrderHistory q WHERE q.storeNumber = ?1 AND q.invoiceNumber = ?2 ORDER BY q.orderHistoryDate DESC")
List<QuoteOrderHistory> findAllByStoreAndInvoiceDesc(int storeNumber, String invoiceNumber);
}
I can't figure out to get this to work. Again a primary key isn't needed since it's suppose to support duplicate entries. If there is another way around this without using JPA then I'm all for it but currently it seems to be the easiest to persist the data. If you need anymore information just let me know. I also might be missing something that can be done to avoid this all together but I'm not that familiar with JPA. So all help is appreciated.
You shouldn't run out of IDs for a column if you use the correct size. Stop trying to fight your framework and just add an auto-incrementing column.
https://hashrocket.com/blog/posts/running-out-of-ids
Let's say business is so good that we are inserting 10,000 records per
minute into our table. So, how long would it take to max out our
sequence? 1750380517 years
From How large can an id get in postgresql
Name Storage Size Description Range
smallint 2 bytes small-range integer -32768 to +32767
integer 4 bytes usual choice for integer -2147483648 to +2147483647
bigint 8 bytes large-range integer -9223372036854775808 to 9223372036854775807
serial 4 bytes autoincrementing integer 1 to 2147483647
bigserial 8 bytes large autoincrementing integer 1 to 9223372036854775807
If you're desperate to not use an id column for some reason I cannot possibly comprehend, it looks like you can do it in JPA by making every column part of the primary key description, but then your deletes and updates will delete/update any number of records. I HAVE NOT TRIED THIS. I WOULD NOT IMPLEMENT THIS ON A PRODUCTION SERVER.
https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#No_Primary_Key
Sometimes your object or table has no primary key. The best solution
in this case is normally to add a generated id to the object and
table. If you do not have this option, sometimes there is a column or
set of columns in the table that make up a unique value. You can use
this unique set of columns as your id in JPA. The JPA Id does not
always have to match the database table primary key constraint, nor is
a primary key or a unique constraint required.
If your table truly has no unique columns, then use all of the columns
as the id. Typically when this occurs the data is read-only, so even
if the table allows duplicate rows with the same values, the objects
will be the same anyway, so it does not matter that JPA thinks they
are the same object. The issue with allowing updates and deletes is
that there is no way to uniquely identify the object's row, so all of
the matching rows will be updated or deleted.
If your object does not have an id, but its table does, this is fine.
Make the object an Embeddable object, embeddable objects do not have
ids. You will need a Entity that contains this Embeddable to persist
and query it.
Jazzepi stated was correct but I was strictly requested not to use an auto generated number as the ID. Therefore, people linked this here depicting using a UUID. This is the best choice for this problem since the objects in the database are timed to be in there no more than a few hours. Since this is the case, a UUID will never overflow and the likelihood of a repeated UUID inside of the table any given time is almost zero since most won't stay there.
New Entity class:
#Entity
#Table(name = "quoteOrderHistory")
public class QuoteOrderHistory {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "uuid", unique = true)
private String uuid;
#Column(name = "storeNumber")
private int storeNumber;
#Column(name = "invoiceNumber")
private String invoiceNumber;
#Column(name = "quoteSaleDate")
private Date quoteSaleDate;
#Column(name="orderTotal")
private BigDecimal orderTotal;
#Column(name="orderHistoryDate")
private Timestamp orderHistoryDate;
// Constructor, getters, setters
}

Hibernate defining table in #Entity

I'm new in Hibernate. What is the difference between
#Id
#GeneratedValue
private Integer id;
and
#Id
#GeneratedValue
#Column(name="id", unique=true, nullable=false, etc)
private Integer id;
What is the reason of defining this if we already did it creating database? Does Hibernate warn us when we break these constraints (not in this example since it's auto generated value)? Does it create table when there isn't one?
The only reason is if the column is named differently, for example in a USER table, if the id column is called USER_ID, you might want to map that to a User object, on the field id.
The unique and nullable attributes are not used, as PK are unique and non-null. Read the javadoc of #Column to understand what else you can define there.
As you mentioned, if you create your DB from scripts (it's actually a bad practice to create your production DB from the JPA annotations), the only relevant attribute is the name... the other ones are not used for #Id columns or are used to autogenerate the DDL.
A slight word of caution, sometimes the attribute nullable influences on how updates are ordered. As a good practice, I always set it to false on mandatory associations (as otherwise Hibernate might decide clear the association and set the column to null... and then do another update with the new ID.).

JPA - How to use table default value for primary key generation?

I have a table with primary key generation of TO_NUMBER(TO_CHAR(SYSDATE#!,'YYDDD')||LPAD(TO_CHAR(SEQ_REFID.NEXTVAL),11,'0'))
This has been given as default value for the table. When I insert through JDBC, I could leave the column as NULL, so the pk would be generated/defaulted and i'll get the key using getGeneratedKeys() method.
I require similar behavior using JPA. I'm a beginner in JPA. Pl help.
Database used is Oracle 11g.
EDIT: The above mentioned value is not required to be table default. It can be applied from JPA layer if it is possible.
Other Entities depends on this Entity for the pk. PK must be passed over to all child tables.
#Entity
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
Can also be
GenerationType.AUTO
GenerationType.SEQUENCE
GenerationType.TABLE
This reference describes the various strategies
Add the following annotation to the id field:
#Column(insertable = false)
This way, JPA will ignore the field when inserting new values and the database automatically generates the desired key.
However, you shouldn't use such a primary key. It effectively contains 2 different kinds of data in one column which should better be split into two seperate columns.
Make a simple id column with an ascending integer (and absolutely meaning other than "this is entry nr. x"). Then add an additional column with the current timestamp. This timestamp can have a default value and be protected against updates.
This is how it's supposed to be and not only simplifies your queries, but also improves the performance. You can query the table for entries of a specific hour, week and so on, or generate detailed statistics.
Don't try to put multiple information into one column. There's no advantage.
Where did you get the idea that this default PK was a good idea?
If you want the creation time of the row, add a column to your table. Don't embed it in the PK like this.

JPA throwing "multiple assignments to same column" during save operation

I have a model class that references another model class and seem to be encountering an issue where the #OneToOne annotation fixes one problem but causes another. Removing it causes the inverse.
JPA throws "multiple assignments to same column" when trying to save changes to model. The generated SQL has duplicate columns and I'm not sure why.
Here's a preview of what the classes look like:
The parent class references look like this:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne
public Location location;
}
The child Location class has an id field and a few other text fields -- very simple:
public class Location {
public Integer id;
public String name;
}
When I attempt to perform a save operation, does anyone know why JPA is creating an insert statement for the Appliance table that contains two fields named "location_id"?
I need to annotate the reference to the child class with #OneToOne if I want to be able to retrieve data from the corresponding database table to display on screen. However, If I remove #OneToOne, the save works fine, but it obviously won't load the Location data into the child object when I query the db.
Thanks in advance!
It appears you did not define an #InheritanceType on the parent Class. Since you did not, the default is to combine the the parent and the child class into the same Table in the Single Table Strategy.
Since both entities are going into the same table, I think that #OneToOne is trying to write the id twice - regardless of which side it is on.
If you want the parent to be persisted in its own table, look at InheritanceType.JOINED.
Or consider re-factoring so that you are not persisting the parent separately as JOINED is not considered a safe option with some JPA providers.
See official Oracle Documentation below.
http://docs.oracle.com/javaee/7/tutorial/doc/persistence-intro002.htm#BNBQR
37.2.4.1 The Single Table per Class Hierarchy Strategy
With this strategy, which corresponds to the default InheritanceType.SINGLE_TABLE, all classes in the hierarchy are mapped to a single table in the database. This table has a discriminator column containing a value that identifies the subclass to which the instance represented by the row belongs.
In OpenJPA, according to the docs (http://openjpa.apache.org/builds/1.0.1/apache-openjpa-1.0.1/docs/manual/jpa_overview_mapping_field.html), section 8.4, the foreign key column in a one-to-one mapping:
Defaults to the relation field name, plus an underscore, plus the name
of the referenced primary key column.
And the JPA API seems to concur with this (http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html)
I believe this means that in a one-to-one mapping, the default column name for properties in a dependent class is parentClassFieldName_dependentClassFieldName (or location_id in your case). If that's the case, the location_id column you are defining in your Appliance class is conflicting with the location_id default column name which would be generated for your Location class.
You should be able to correct this by using the #Column(name="someColumnName") annotation and the #JoinColumn annotation on your #OneToOne relationship to force the column name to be something unique.
Ok gang, I figured it out.
Here's what the new code looks like, followed by a brief explanation...
Parent Class:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="location_id", referencedColumnName="id")
public Location location;
}
Child Class:
public class Location {
public Integer id;
public String name;
}
The first part of the puzzle was the explicit addition of "cascade = CascadeType.ALL" in the parent class. This resolved the initial "multiple assignments to same column" by allowing the child object to be persisted.
However, I encountered an issue during update operations which is due to some sort of conflict between EBean and JPA whereby it triggers a save() operation on nested child objects rather than a cascading update() operation. I got around this by issuing an explicit update on the child object and then setting it to null before the parent update operation occurred. It's sort of a hack, but it seems like all these persistence frameworks solve one set of problems but cause others -- I guess that's why I've been old school and always rolled my own persistence code until now.

Do you need to create index on the #id column of a Hibernate table

Do I need to add an index anoatation for the primary key of a hibernate table for decent performance, I assumed that marking a field with #id would mean an index was created
#Id
private String guid;
but I didnt notice anything being created in the ddl that was generated
But if I added an #index annotation
#Id
#org.hibernate.annotations.Index(name = "IDX_GUID")
private String guid;
then I do notice an index being created in the DDL.
So I'm thinking I need to do this for every table, but part of me is thinking is this really neccessary as surely hibernate would want indexes created for the primary key as a starting point ?
You do NOT have to create index explicitly. Instead of seeing DDL statements; I will recommend you to check the final schema created by hibernate. The index is created as part of create table statement.

Categories

Resources