How to create indexes on multiple columns - mysql

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.

Related

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

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));

#UniqueConstraint for nullable field

I have a Product entity and it's name and category combination should be unique for every product.
In my case name is required, but category may not be present.
Product is defined like this:
#Entity
#Table(
uniqueConstraints=
#UniqueConstraint(columnNames={"name", "category")
)
public class Product {
// ...
#NotNull
#Size(min = 1, max = 64)
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private ProductCategory category;
// ...
}
... but the constraint works only when category is not NULL.
In other words I cannot persist (and this is ok) entities with:
name="example", category=1
name="example", category=1
... at the same time I can persist (and this is not what I want) entities wihh:
name="example", category=null
name="example", category=null
So, my question is - how do I implement unique constraint for combination of fields, one of which can be nullable (with NULL treated as any other value)?
PS: I use Hibernate as JPA provider and MySQL database.
The #UniqueConstraints annotation should do the work. Check the documentation with an example. Also if the table is autogenerated, you might consider dropping the table, depending on how your autogeneration of the schema is set in your persistence.xml file.
UPDATE
Of course, you must specify both columns (not fields as you did):
#UniqueConstraint(columnNames={"name", "category_id")//or check the name of the column in the db for category
UPDATE 2
Because the problem is actually in mysql, you could insert other two persistent fields to your entity:
#Column(columnName="category", insertable=false, updatable=false)//map to the same column used for your category
private Long categoryId;//I suppose it is a Long.
#Column(columnName="categoryIdConstraint")//
private long categoryIdConstraint;//this is not nullable. If categoryId is null, then simply put 0 or -1
//and now synchronize the categoryIdConstraint field with entity listeners
//and add the #UniqueConstraint over "name" and "categoryIdConstraint" (which now cannot be null.)
Managed to avoid solving this this issue relatively easy (by appying null object pattern).
For anyone who cannot avoid this issue easily and is wondering how to solve this with as less pain as possible - have a look on implementing mysql trigger that will do unique constraint validation taking nullable columns into account. Here is a nice starting point.

adding tables dynamicly in spring via jdbctemplate

In springJdbctemplate crud application I want to give user an option to create a new custom table with custom fields.The problem is if I create query to create table ,how can I give the user the option to customise it ,as domain class are fixed.Some one suggest to dump data in xml.
thanks
You need to have user table metadata definition in this way:
Domain classes (pseudo-code):
class UserTableMetadata {
String tableName;
// List of personalisable columns
List<UserTableColumnMetadata> columns;
// List of personalisable indexes
List<UserTableIndexMetadata> indexes;
}
class UserTableColumnMetadata {
// Owner table
UserTableMetadata table;
String columnName;
int jdbcType;
int lenght;
int precision;
boolean nullable;
}
class UserTableIndexMetadata {
// Owner table
UserTableMetadata table;
String indexName;
boolean unique;
boolean primary;
// Index columns
List<UserTableColumnMetadata> columns;
}
As you can see domain classes are fixed, but you can build up a simple table definition with Name, Columns and Indexes.
After that can be easy - processing metadata - build the create table SQL code in dynamic way (or alter table if user change column definition and table already exists).
Metadata can be stored in database as well as on XML, it's only a choice based on your preference and requirements.
Enjoy!

Naming a multi-column constraint using JPA

The name attribute of #UniqueConstraint seems to have no effect.
#Entity
#Table(name = "TAG", uniqueConstraints = #UniqueConstraint(columnNames = {
"TAG_NAME", "USERS_ID" }, name="UQ_TAG_USER"))
public class Tag extends BaseEntity {
}
I'm usning SQL Server 2008, JPA 2.0 with Hibernate 3.6.
On the DB side an index, UQ__TAG__6EF57B66 is created instead of UQ_TAG_USER.
What am I missing? is there no way to enforce a given name from java side? and one must resort to editing schema files? we are a small shop without a DBA and I try to make do as much as I can by the help of hibernate schema facilities.
I assume you are using hibernate because you have it in the tags for this question. It's a bug/missing feature in hibernate:
https://hibernate.onjira.com/browse/HB-1245
It will simply ignore the unique constraint name when the dialect supports creating the constraint in the same statement as create table.
I've checked SqlServer and Oracle dialects and they both support this way of creating the constraint, that will cause the bug you are experiencing.
There are two ways to workaround this bug:
1. The quick way:
Just extend the dialect and return false for supportsUniqueConstraintInCreateAlterTable() method:
public static class SQLServerDialectImproved extends SQLServerDialect {
#Override
public boolean supportsUniqueConstraintInCreateAlterTable() {
return false;
}
}
And set this class as your dialect in hibernate.dialect property of the persistence unit configuration (persistence.xml).
2. The right way:
Fix the hibernate code and recompile:
The bug is at org.hibernate.mapping.UniqueKey class, the method sqlConstraintString() will return unique (TAG_NAME, USERS_ID) for all dialects, even if they support constraint UQ_TAG_USER unique (TAG_NAME, USERS_ID).
But that is probably a larger change (need to support all kinds of dialects, etc.)
Under the hood:
If you use the original dialect, it will cause the following sql statement to be executed to create the table (added id column):
create table TAG (
id bigint not null,
TAG_NAME varchar(255),
USERS_ID varchar(255),
primary key (id),
unique (TAG_NAME, USERS_ID)
)
And after you apply the fix as stated in first option the following sql statements will be executed:
create table TAG (
id numeric(19,0) not null,
TAG_NAME varchar(255),
USERS_ID varchar(255),
primary key (id)
)
create unique index UQ_TAG_USER on TAG (TAG_NAME, USERS_ID)
which include the creation of the unique constraint with the chosen name (UQ_TAG_USER) in a separate statement.

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!