1

We have a scenario where in the catalog/schema combination is different for the entity classes inside certain package from the default one used by all others. I am trying to set Catalog and Schema on @Table annotation using PersistenceUnitPostProcessors callback at runtime using javaassist as below.

The issue: The added member values on javaassist annotation are NOT getting reflected on to the actual class associated with it. Please help me in finding the wrong lines of code; OR if there are other ways to achieve this, more than happy to know.

Note: I do not want to create a separate EntityManagerFactory for each catalog/schema combination - it is not really required in our case as the datasource is same.

related content in spring context :

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
   <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven />     

<bean name="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" /> 
 <property name="persistenceUnitName" value="mainUnit" /> 
   <property name="packagesToScan" value="com.mycompany.lob.domain" />       
   <property name="jpaVendorAdapter">
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
   </property>
   <property name="persistenceUnitPostProcessors">
     <list>
        <bean class="com.mycompany.lob.jpa.CustomPersistenceUnitPostProcessor"/>
     </list>
  </property>
   <property name="jpaProperties">
     <props>
          <prop key="hibernate.dialect">org.hibernate.dialect.SqlmxDialect</prop>
          <prop key="hibernate.show_sql">true</prop>
          <prop key="hibernate.format_sql">true</prop>
          <prop key="hibernate.jdbc.batch_size">100</prop>
          <prop key="hibernate.order_inserts">true</prop>
          <prop key="hibernate.cache.use_second_level_cache">true</prop>
          <prop key="hibernate.connection.autocommit">true</prop>
          <prop key="hibernate.default_schema">DEFAULT_SCHEMA</prop>
          <prop key="hibernate.default_catalog">DEFAULT_CATALOG</prop>
     </props>
   </property>
</bean> 

PersistenceUnitPostProcessors callback :

public class CustomPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {
@Value("${user.schema}")
private String userSchema;
@Value("${user.catalog}")
private String userCatalog;
private static final Logger LOGGER = LoggerFactory.getLogger(CustomPersistenceUnitPostProcessor.class);
@SuppressWarnings("unchecked")
@Override
public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
    LOGGER.info("MutablePersistenceUnitInfo : {} ",pui);
    List<String> jpadomains = pui.getManagedClassNames();
    for (Iterator<?> iterator = jpadomains.iterator(); iterator.hasNext();) {
        String clazzName = (String) iterator.next();
        if(clazzName.startsWith("com.mycompany.lob.domain.user")){
            try {
            //modify annotation attributes using JavaAssist
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get(clazzName);
            ClassFile classFile = ctClass.getClassFile();
            ConstPool constPool = classFile.getConstPool();
             AnnotationsAttribute annotationsAttribute  = (AnnotationsAttribute)classFile.getAttribute(AnnotationsAttribute.visibleTag);
             if(annotationsAttribute!=null){
             //Get hold of @Table annotation
                 Annotation tableAnnotation = annotationsAttribute.getAnnotation("javax.persistence.Table");
                 if(tableAnnotation!=null){
                     tableAnnotation.addMemberValue("catalog", new StringMemberValue(userCatalog, constPool));
                     tableAnnotation.addMemberValue("schema", new StringMemberValue(userSchema, constPool));
                     annotationsAttribute.addAnnotation(tableAnnotation);
                     LOGGER.debug("Schema-Table : {} - {} ",  ((StringMemberValue)tableAnnotation.getMemberValue("schema")).getValue(), 
                                                        ((StringMemberValue)tableAnnotation.getMemberValue("name")).getValue() );
                     //write the file back
                     ctClass.writeFile();
                 }

             }
            } catch (Exception e) {
                LOGGER.error("Schema/Catalog could not be altered for {} ",clazzName);
            }
        }
    }

 }

}   
aRvi
  • 107
  • 1
  • 2
  • 13
  • That will never work, the classes are already loaded and scanned by hibernate. Chaning the table value afterwards isn't going to work as the mapping metadata is already generated. Include and [orm.xml](http://docs.jboss.org/hibernate/stable/annotations/reference/en/html/xml-overriding.html) and externalize it. Set the `MappingResources` property on the `LocalContainerEntityManagerFactoryBean` to load the external file for additional mapping/overrides. – M. Deinum Jun 25 '14 at 08:23
  • @M. Deinum - If I use `MappingResources` for those entities with a different catalog/schema combination, spring property placeholders can not be used to inject environment specific values. **Another observation:** The annotation attribute changes started showing effect on the original class when I replaced `ctClass.writeFile()` with `ctClass.toClass()`, at least from jUnit - not sure how it will behave when deployed on the app server. – aRvi Jun 25 '14 at 11:13

1 Answers1

1

Simple answer: 19. Multitenancy

Complex catalog mapping: interface PhysicalNamingStrategy in Hibernate v5 is helpful.

public interface PhysicalNamingStrategy {
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment);
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment);
    ....
}

Check the Example 2. Example PhysicalNamingStrategy implementation in Hibernate 5 User Guide and how to config it

Stony Wang
  • 53
  • 1
  • 6