Auditing with Hibernate Envers: How to Query When ID is not 'id' - mysql

With Hibernate Envers, you create a corresponding auditing table with a suffix of "_AUD" for each of your JPA entities and then you can query using AuditReader.
This AuditReader assumes that the ID of the Entity is id and that it has a getId() getter. In my case, all off my entities have differently named identifiers like userId and accountId, etc... all with varying dataTypes.
How can I create a pattern that reduces boilerplate code to retrieve auditing history data by id without knowing the fieldName of the id?

That is what AuditEntity.id() is for :)
AuditQuery query = getAuditReader()
.forRevisionsOfEntity( MyEntity.class. true, false )
.add( AuditEntity.id().eq( myEntityClassId ) );
You shouldn't need to know what property maps to your entity's identifier property because Envers will handle all the necessary equality / inequality checks between types and property mappings behind the scenes.

what about ?
getAuditReader().createQuery().forRevisionsOfEntity(MyEntity.class, false, false).add( AuditEntity.property("accountId").eq(12));

Related

Cakephp 3.x Filter rows on array in multilevel joins

First important structure of my database:
teachers:
id, int
username, varchar
certificates:
id, int
teacher_id, int
vality_date, date
languages:
id, int
certificate_id, int
language_id, int
Teachers hasMany Certificates hasMany Languages
A teacher can have multiple certificates with multiple languages. Multiple languages can get splitted on multiple certificates.
I'm trying to find a cakephp-way to get all teachers, who have a valid certification for defined languages, probably in multiple certifications, but it's hard to build a query in cakephp. I tried so much, but I always get teachers who have all or only one of the requested languages.
How would you solve this problem?
You would do this through joins (or with what the QueryBuilder calls matching().
If you would use:
$teachers = $this->Teachers->find()
->innerJoinWith('Certificates.Languages');
...you would effectively get a table of teachers with for each teacher the certificates and for each certificate the language. There would probably be duplicates as well.
You can now filter on the joined data (and keep out duplicates):
$lang_list = ['NL', 'DE'];
$teachers = $this->Teachers->find()
->where(['Languages.lang IN' => $lang_list])
->innerJoinWith('Certificates.Languages')
->group('Teachers.id');
I am not sure if this would work directly, but it is definitely something like this.
The SQL IN keyword can be used to limit to values of an array. Alternatively you could construct multiple AND statements (for whatever reason).
Also note that I personally prefer a ...JoinWith over matching. (For no real reason.)
For completeness, with matching() it would look like:
$lang_list = ['NL', 'DE'];
$teachers = $this->Teachers->find()
->matching('Certificates.Languages', function ($q) use ($lang_list) {
return $q->where(['lang IN' => $lang_list]);
});
CakepHP - Using innerJoinWith
CakePHP - Filtering by associated data

In clause for a list of pair of conditions

There is a table from where I need to fetch paginated records by applying and condition in a list of paired values, Below is the explanation
Lets say I have a class Billoflading and there are various fields in it
The two important fields in the table are
tenant
billtype
I have a list of pairs which contain values as
[
{`tenant1`, `billtype1`},
{`tenant2`, `billtype2`},
{`tenant3`, `billtype3`},
....
]
I need a JPA query where the fetch will be like
findByTenantAndBilltypeOrTenantAndBillTypeOr.....
in simple sql query it will be like
Select * from `Billoflading` where
`tenant` = 'tenant1' and billtype = 'billtype1'
OR `tenant` = 'tenant2' and billtype = 'billtype2'
OR `tenant` = 'tenant3' and billtype = 'billtype3'
OR ......... so on..
I tried writing a JPA query as follows
Page<Billoflading> findByTenantInAndBillTypeIn(List<String> tenants, List<String> billTypes, Page page);
but this had crossover records as well
i.e it gave records for tenant1 and billtype2, benant2 and billtype 3 so on... which are not needed in the result set
can anyone please solve this and help me finding a simple solution like
Page<Billoflading> findByTenantAndBillTypeIn(Map<String, String> tenantsAndBilltyes, Page page);
I am also ready for the native queries in JPA all I need is there should be no crossovers as this is a very sensitive data
The other workaround I had was fetching the records and applying java 8 filters and that works but the no. of records in a page gets reduced
Section 4.6.9 of the JPA specification makes it clear that this is not supported by JPQL, at least not in the form of an in-clause:
4.6.9 In Expressions
The syntax for the use of the comparison operator [NOT] IN in a conditional expression is as follows:
in_expression ::=
{state_valued_path_expression | type_discriminator} [NOT] IN
{ ( in_item {, in_item}* ) | (subquery) | collection_valued_input_parameter }
in_item ::= literal | single_valued_input_parameter
The state_valued_path_expression must have a string, numeric, date, time, timestamp, or enum value.
The literal and/or input parameter values must be like the same abstract schema type of the state_valued_path_expression in type. (See Section 4.12).
The results of the subquery must be like the same abstract schema type of the state_valued_path_expression in type.
It just doesn't operate on tuples.
Your best bet is probably to create a Specification to construct the combination of AND and OR you require. See this blog article how to create Specifications

Entity Framework persisted computed column woes

I recently updated a computed column to be persisted due to poor performance when querying it.
However now updates to child records throw this error
System.Data.SqlClient.SqlException
Internal Query Processor Error: The query processor could not produce a query plan. For more information, contact Customer Support Services.
Having an inner exception of
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types.
This happens with or without an index on the column.
FYI I have a configuration for the column thus:
Property(c => c.DisplayName).HasColumnName("DISPLAY_NM").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
Any workaround?
edit
The child element statement is pretty irrelevant - but here it is. Imagine entities Order, Company (an Order must have a Company). I create an Order with an un-changed Company and its the Company entity with the computed column that is causing the problem - it works fine without the persistence on the computed column (as expected there's 1 insert statement on Order and 0 update statements on Company)
I think this is the solution but unsure how to do it in EF
I solved similar problem by adding ID of related entity as property (essentialy exposed FK value in table), and when creating record, if I only assign id of related object to exposed FK property, everything works, related object is not needed.
You can see sample of entity having both related object and its ID mapped using EF fluid mapping here:
public ThingMap()
{
this.Property(t => t.Name)
.IsRequired()
.IsMaxLength()
.IsUnicode();
//// Table mappings
this.ToTable("Things", "go");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.TypeId).HasColumnName("Type_Id");
//// References
this.HasRequired(t => t.Type)
.WithMany(t => t.Things)
.HasForeignKey(t => t.TypeId);
}

Tell Hibernate's hbm2ddl to add MySQL enum columns for #Enumerated annotated fields

I'm creating a DB table using hbm2ddl with Java code similar to the following:
#Entity
public class Filter {
public enum Type {
TypeA, TypeB;
}
#Enumerated(EnumType.STRING)
private Type type;
}
It works fine, but for "type" a VARCHAR column is created, i.e. the DDL code looks like this:
CREATE TABLE IF NOT EXISTS `filter` (`type` varchar(255) DEFAULT NULL)
But what I want to have is this:
CREATE TABLE IF NOT EXISTS `filter` (`type` enum('TypeA','TypeB') NOT NULL)
Is this possible to declare in Hibernate, preferred with annotations?
Or is there a way to extend SchemaUpdate and overwrite the method that renders the alter script part for enumerated field the way I like it?
Background: The same database is used in a PHP part of the project and I want to prevent that invalid values are inserted.
Although it seems there is no way to handle MySQL enums 100% automatically, as pointed out by Lucas on his answer, there is actually a simple way to contour it. You may use columnDefinition attribute on #Column annotation, which seems to be specifically designed to generate custom DDL code.
See the documentation excerpt describing the attribute:
(Optional) The SQL fragment that is used when generating the DDL for the column.
Defaults to the generated SQL to create a column of the inferred type.
The NOT NULL restriction is quite standard, and is supported by another attribute nullable.
Thus, your property definition would look like this:
#Enumerated(EnumType.STRING)
#Column(columnDefinition = "enum ('TypeA', 'TypeB')", nullable = false)
private Type type;
I believe that's going to be complicated, since the java.sql.Types, which define the sql types treated by java, does not have enum type (since it's not a standardized type according to SQL-92).
If that was the case you could create a hibernate custom UserType extending the EnumType and setting the sqlType accordingly, but since java.sql.Types doesn't handle it I don't see how to use native sql enum.
best regards!

How to create indexes on multiple columns

We have the following entity relationships where a User belongs to a particular Organization. My queries either look like "select * from User where org=:org" or "select * from User where org=:org and type=:type"
I have separate indexes on the User class. The first query will be fine, because of the Index on the foreign key element. Does the second query mandate a multi columnindex on org and type columns. If so how should I annotate to create one such index.
#Entity
class User {
...
#ManyToOne
#ForeignKey
#Index
Organization org;
#Index
Type type;
...
}
This is doable using the Hibernate specific #Table annotation. From the documentation:
2.4.1 Entity
...
#Table(appliesTo="tableName", indexes = { #Index( name="index1", columnNames={"column1", "column2"} ) } ) creates the defined indexes on the columns of table tableName. This can be applied on the primary table or any secondary table. The #Tables annotation allows your to apply indexes on different tables. This annotation is expected where #javax.persistence.Table or #javax.persistence.SecondaryTable(s) occurs.
Reference
Hibernate Annotations Reference Guide
2.4. Hibernate Annotation Extensions
As you can read in JSR-000338 Java Persistence 2.1 Proposed Final Draft Specification:
11.1.23 Index Annotation
The Index annotation is used in schema generation. Note that it is not necessary to specify an index for a primary key, as the primary key index will be created automatically, however, the Index annotation may be used to specify the ordering of the columns in the index for the primary key.
#Target({}) #Retention(RUNTIME)
public #interface Index {
String name() default "";
String columnList();
boolean unique() default false;
}
The syntax of the columnList element is a column_list, as follows:
column::= index_column [,index_column]*
index_column::= column_name [ASC | DESC]
The persistence provider must observe the specified ordering of the
columns.
If ASC or DESC is not specified, ASC (ascending order) is
assumed.
Usage example:
#Table(indexes = {
#Index(columnList = "org,type"),
#Index(columnList = "another_column")})
Yes, it is possible using JPA 2.1 as seen in the specification here:
http://download.oracle.com/otndocs/jcp/persistence-2_1-pfd-spec/index.html
on page 445 it states that
The Index annotation is used in schema generation
columnList (Required) The names of the columns to be included in the index.
An example of usage can be seen here:
http://java-persistence-performance.blogspot.co.uk/2013/03/but-what-if-im-not-querying-by-id.html
It seems that the syntax is the same or very similar to Hibernate.