How to set annotation's values according to properties? - java

I am using hibernate's ORM and hibernate-generator to generate the Entity in the annotation way. I need to switch database frequently (dev/release). So, I have to change the entity's annotation every time. I want to know if there is a way to configure it.
#Entity
#Table(name = "my", catalog = "dev_db")
public class MyEntity {
}
As you can see, I've to change the catalog every time. How to configure it according to a jdbc.properties?

You can use Interceptors to modify SQL generated by hibernate.
public String onPrepareStatement(String sql) {
String superSQL = super.onPrepareStatement(newSQLWithNamespace);
//replace all catalog occurencies with desired value in the superSQL
return superSQL;
}
See e.g. Add a column to all MySQL Select Queries in a single shot
Your interceptor can read the catalog value from config and change the SQL.

Related

Hibernate SchemaFilterProvider get Java entity name

I would like Hibernate to disable certain classes from being validated on startup.
My particular use-case:
spring.jpa.hibernate.ddl-auto=validate
#Table (name = "SAME_TABLE")
public class Entity1 {
#Column
private Long value;
// rest of values
}
#Table (name = "SAME_TABLE")
public class SearchEntity2 {
#Column
private String value;
// rest of values
}
As you can see I have two classes mapped to the same table called SAME_TABLE. This is because I want to do wildcard searches on numeric field value
JPA Validation fails on Oracle (h2 succeeds suprisingly) because it detects that the String is not NUMERIC(10).
This question here by #b0gusb provides an excellent way of filtering out via table name:
How to disable schema validation in Hibernate for certain entities?
Unfortunately my table name is identical. Is there any way of getting to the Java class name from SchemaFilteror perhaps another way of doing this?
Thanks
X

Dynamic schema in Hibernate #Table Annotation

Imagine you have four MySQL database schemas across two environments:
foo (the prod db),
bar (the in-progress restructuring of the foo db),
foo_beta (the test db),
and bar_beta (the test db for new structures).
Further, imagine you have a Spring Boot app with Hibernate annotations on the entities, like so:
#Table(name="customer", schema="bar")
public class Customer { ... }
#Table(name="customer", schema="foo")
public class LegacyCustomer { ... }
When developing locally it's no problem. You mimic the production database table names in your local environment. But then you try to demo functionality before it goes live and want to upload it to the server. You start another instance of the app on another port and realize this copy needs to point to "foo_beta" and "bar_beta", not "foo" and "bar"! What to do!
Were you using only one schema in your app, you could've left off the schema all-together and specified hibernate.default_schema, but... you're using two. So that's out.
Spring EL--e.g. #Table(name="customer", schema="${myApp.schemaName}") isn't an option--(with even some snooty "no-one needs this" comments), so if dynamically defining schemas is absurd, what does one do? Other than, you know, not getting into this ridiculous scenario in the first place.
I have fixed such kind of problem by adding support for my own schema annotation to Hibernate. It is not very hard to implement by extending LocalSessionFactoryBean (or AnnotationSessionFactoryBean for Hibernate 3). The annotation looks like this
#Target(TYPE)
#Retention(RUNTIME)
public #interface Schema {
String alias() default "";
String group() default "";
}
Example of using
#Entity
#Table
#Schema(alias = "em", group = "ref")
public class SomePersistent {
}
And a schema name for every combination of alias and group is specified in a spring configuration.
you can try with interceptors
public class CustomInterceptor extends EmptyInterceptor {
#Override
public String onPrepareStatement(String sql) {
String prepedStatement = super.onPrepareStatement(sql);
prepedStatement = prepedStatement.replaceAll("schema", "Schema1");
return prepedStatement;
}
}
add this interceptor in session object as
Session session = sessionFactory.withOptions().interceptor(new MyInterceptor()).openSession();
so what happens is when ever onPrepareStatement is executed this block of code will be called and schema name will be changed from schema to schema1.
You can override the settings you declare in the annotations using a orm.xml file. Configure maven or whatever you use to generate your deployable build artifacts to create that override file for the test environment.

How to set the schema of #OneToMany autogenerated tables?

I want to put all of the following autogenerated tables into a specific schema.
#Entity
#Table(name = "master_table", schema = "test")
public class MasterTable {
#OneToMany
private List<VideoEntity> videos;
#Entity
#Table(name = "video_entity", schema = "test")
public static class VideoEntity {
}
}
Result: there are the two entity tables in test schema, but also one in the public schema called master_table_videos for the list mapping.
Question: how can I tell hibernate to also put the list-mapping table in the same schema than the others?
I think you should use the #JoinTable annotation, at least that allows to set the schema name in standard JPA. Check the JavaDoc for Java EE 7 or Java EE 6.
So it would be something like #JoinTable(name = "master_to_videos", schema = "test" ), and you could also specify the name of the join column if required.
Hibernate will create the table in whichever persistence.xml the entity is defined in. So if MasterTable and VideoEntity are both in persistence.xml, it will create both tables in the configured data schema.
I agree with Hein Blöd i tested the #joinTable annotation after any other annotation like #ManyToOne #OneToMany ... as for your example it becomes like this
#OneToMany
#JoinTable(schema = "testSchema" )
private List<VideoEntity> videos;
testSchema is interpreted by Hibernate as test-schema
i know this is for an old question i'm writing this so that any one
right now can find the correct answer i search the internet and this is the first question i found.

EclipseLink: How to turn off Multi-Tenancy even If I annotate Entity with #MultiTenant

I am using EclipseLink for Single Table Multi-Tenancy.
I have annotated entities with #Multi-Tenant and everything is working fine.
Is it possible to turn off Multi-Tenancy without any change?.
I know there is a org.eclipse.persistence.annotations.Multitenant.includeCriteria() which I can set turn off to disable Multi-tenancy.
But my solution is packaged and delivered as a Package mode and there is no any way to set above attribute to false.
What I want, even I annotated Entity with #Multi-Tenant annotation and even there is a column TENANT_ID in database,
but if I does not set PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT than it should not throw below exception and any CRUD operation should be succeed.
javax.persistence.PersistenceException: Exception [EclipseLink-6174] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.QueryException
Exception Description: No value was provided for the session property [eclipselink.tenant-id]. This exception is possible when using additional criteria or tenant discriminator columns without specifying the associated contextual property. These properties must be set through Entity Manager, Entity Manager Factory or persistence unit properties. If using native EclipseLink, these properties should be set directly on the session.
So, is there any way or any customization/extension point(s) so that If I set
PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT
then It should include TENANT_ID as an criteria and if I not set than simply it should not include TENANT_ID as an criteria?
This seems to be an open issue for eclipselink. I have posted a similar question lately. Chris posted an helpful answer with a link to a feature request. A workaround described there is the usage of multiple persistence units for the same tables with different multi tenancy settings.
I am trying to use an alternative approach: Instead of #Multitenant I use #AdditionalCriteria. This annotations enables you to define your own request criteria added to any database operation (with few exceptions like native SQL).
Example:
#AdditionalCriteria(":ADMINACCESS = 1 or this.tenant=:TENANT")
On creation of your entity manager you set the parameters. You could either provide a tenant of switch off multi tenancy. The drawback here is that you are responsible for the tenant attribute.
There is another workaround to this problem.
You can use an Eclipselink MetadataSource to override at runtime the annotations in the entity class and to pretend that you use a dedicated database per tenant, for example:
MultitenantMetadata md = new MultitenantMetadata();
md.setIncludeCriteria(false);
md.setType(MultitenantType.TABLE_PER_TENANT.name());
TenantTableDiscriminatorMetadata td = new TenantTableDiscriminatorMetadata();
td.setType(TenantTableDiscriminatorType.SCHEMA.toString());
td.setContextProperty(DISCRIMINATOR_SCHEMA_PROPERTY);
md.setTenantTableDiscriminator(td);
entityAccessor.setMultitenant(md);
The above will make Eclipselink happy from a multi-tenancy point of view, but your tables may still have the discriminator column if your annotations specify that multi-tenancy is achieved using a discriminator column.
To remove the discriminator column you can use a SessionCustomizer that manipulates the EclipseLink representation of the entities before the EMF is created in order to:
Remove the discriminator column
Change indices and foreign keys to remove any reference to the discriminator column.
In this way the database tables and the DDLs will no more include the discriminator column.
Set<String> discriminatorColumnNames = getDiscriminatorColumns(descriptor);
for (DatabaseTable databaseTable : descriptor.getTables()) {
List<IndexDefinition> indexDefinitions = databaseTable.getIndexes();
for (Iterator<IndexDefinition> definitionIt = indexDefinitions.iterator(); definitionIt.hasNext();) {
IndexDefinition indexDefinition = definitionIt.next();
// remove qualifier from index to prevent a syntax error in DDL
indexDefinition.setQualifier("");
for (Iterator<String> fieldNameIt = indexDefinition.getFields().iterator(); fieldNameIt.hasNext();) {
if (discriminatorColumnNames.contains(fieldNameIt.next().toUpperCase())) {
fieldNameIt.remove();
}
}
if (indexDefinition.getFields().isEmpty()) {
definitionIt.remove();
}
}
Map<String, List<List<String>>> uniqueConstraints = databaseTable.getUniqueConstraints();
for (Entry<String, List<List<String>>> uc : uniqueConstraints.entrySet()) {
List<List<String>> strings = uc.getValue();
for (List<String> list : strings) {
String found = null;
for (String string : list) {
if (discriminatorColumnNames.contains(string.toUpperCase())) {
found = string;
break;
}
}
if (found != null) {
list.remove(found);
}
}
}
}

Get physical column value with entity property value using hibernate

I have a table T with columns defined as usual.
#Entity
#Table(name="T")
public class T {
#Column(name="test_id")
private Long testId;
}
Given entity property "testId", I want to get corresponding DB column name (i.e. "test_id"). How could it be achieved?
Edit 1:
I want to keep this column at separate location with actual DB column name (test_id) than testId. I fetched these values from DB using HQL which have key as entity name (i.e. testId) and I want actual column name in DB.
If I understood your requirement correctly, you want to use HQL while having a consistent name for both DB column and the entity field, like this:
SELECT t.test_id FROM Test t
instead of
SELECT t.testId FROM Test t
There is only one way to do that - renaming the field to test_id. HQL works on entities, not on DB tables, so you must use proper field names in the query.
Since test_id contradicts the usual Java coding conventions, I would advise against it.
EDIT: Getting the annotation attribute value with reflection would work along this outline:
Field field = MyEntity.class.getDeclaredField("testId");
Column a = field.getAnnotation(Column.class);
String columnName = a.name();
I would try to avoid this by any means, but if you're really sure you'll need it, use:
Configuration configuration = sessionFactory.getConfiguration();
PersistentClass persistentClass = configuration
.getClassMapping(T.class.getName());
String columnName = ((Column) persistentClass.getProperty("testId")
.getColumnIterator().next()).getName();
See also Get table column names in Hibernate

Categories