0

I am struggling to get my PhysicalNamingStrategy Implementation in a Spring Boot + Hibernate Project to work as expected. The problem is that both, @Value and @Autowired annotated variables are always null / not properly injected with values from my application.properties. I need this in order to be able to externalize parameters like schema and table names and i can't use the fixed variant like this:

@Entity
@Table(name = "table_name", schema = "primary_schema")

Well actually I do, but the PhysicalNamingStrategy comes in and "translates" the static names to the parameterized names. At least that is what should happen.

I am 3/4 sure, that Spring is aware of my Class (Package and Class is scanned / found via ComponentScan) because when I create duplicates I get an expected Exception during bootup telling me that Spring found 2 identical beans. So I have no clue why the variables are not injected properly in this Class. Other classes (in the same and in other packages!) work fine with @Value and @Autowired annotations.

My project structure looks like this (reduced to the relevant parts):

project/src/main/java/
----------------------org.packagename
-------------------------configuration
-----------------------------BeanConfig.java
-----------------------------DataSourceConfiguration.java
-----------------------------PhysicalNamingStrategyImpl.java
-------------------------constant
-------------------------controller
-------------------------exception
-------------------------model
-------------------------repository
-------------------------scheduler
-------------------------service
-------------------------AppMainClass.java
project/src/main/resources
----------------------application.properties
----------------------application-devel.properties
----------------------application-prod.properties
----------------------schema-h2.sql
----------------------log4j2.xml

I am using:

- spring-boot-2.3.2 (multiple dependencies like spring-boot-autoconfigure, etc.)
- spring-5.2.8 (multiple dependencies like spring-beans, etc.
- hibernate-5.4.18

I configured Spring to use that Naming Strategy Implementation in the application.properties using this parameter:

spring.jpa.properties.hibernate.physical_naming_strategy=org.packagename.configuration.PhysicalNamingStrategyImpl

The Class is definitly used because i see the expected Log-Outputs as well as reach my breakpoints in the methods of the Class. So up until here everything is working as it should. But the @Value annotated variables are always null while in other Classes the same values have the expected values from the properties file.

Here is the (test) class in Question (added those manual variable initialization after the //FIXME Comments in order to make it work at all, this will be removed as soon as the injection is working):

package org.packagename.configuration;

import java.io.Serializable;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * //stripped
 */
@Component
public class PhysicalNamingStrategyImpl implements PhysicalNamingStrategy, Serializable {
    //public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl implements Serializable {

    /** */
    private static final long serialVersionUID = 2389476641866177676L;

    //  public static final PhysicalNamingStrategyImpl INSTANCE = new PhysicalNamingStrategyImpl();

    private Logger logger = LogManager.getLogger(PhysicalNamingStrategyImpl.class);

    @Value("${app.primary.schema.name}")
    private String primarySchemaName;

    @Value("${app.primary.table.name}")
    private String primaryTableName;

    @Value("${app.secondary.schema.name}")
    private String secondarySchemaName;

    @Value("${app.secondary.table.name}")
    private String secondaryTableName;


    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {

        logger.debug("toPhysicalSchemaName: {} with primarySchemaName: {}, secondarySchemaName: {}", name, primarySchemaName, secondarySchemaName);

        if (name == null) {
            return null;
        }

        // FIXME @Value always null.... 
        primarySchemaName = "ACTUAL_SCHEMA_NAME";
        secondarySchemaName = "ACTUAL_SCHEMA_NAME2";

        switch (name.getText()) {
            case "primary_schema":
                return new Identifier(primarySchemaName, name.isQuoted());
            case "secondary_schema":
                return new Identifier(secondarySchemaName, name.isQuoted());
            default:
                return name;
        }
    }


    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {

        logger.debug("toPhysicalTableName: {} with primaryTableName : {}, secondaryTableName : {}", name, primaryTableName , secondaryTableName );

        if (name == null) {
            return null;
        }

        // FIXME @Value always null....
        primaryTableName = "ACTUAL_TABLE_NAME";
        secondaryTableName = "ACTUAL_TABLE_NAME2";

        switch (name.getText()) {
            case "table_name":
                return new Identifier(syncLogTableTableName, name.isQuoted());
            case "table_name2":
                return new Identifier(assetExportDataTableName, name.isQuoted());
            default:
                return name;
        }
    }


    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return name;
    }


    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
        return name;
    }


    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
        return name;
    }

}

I am not instantiating this Class manually (no new PhysicalNamingStrategyImpl()) in the Code of my project so it should be handled completly by Spring itself. But I am not sure how, where and when does Spring create this Class during boot.

Ulathar
  • 309
  • 3
  • 13
  • Naming strategies are not spring beans. Therefore, you can't inject values into them. – Philippe Sep 30 '20 at 16:16
  • Already tried the approach described here: https://stackoverflow.com/questions/49148172/table-name-configured-with-external-properties-file But can't get that to work either (can't figure out the correct value to set for the Property `spring.jpa.properties.hibernate.physical_naming_strategy` in order to make it use this strategy instead). – Ulathar Oct 01 '20 at 12:12
  • Hi, How did you solve this issue. I use multiple databases and have to create LocalContainerEntityManagerFactoryBean beans manually. I set jpa properties on it works but it creates its own instance of custom physical naming strategy using reflecting and Value or Autowired annotations are not used on instantiated on this custom physical naming strategy. Any solution? – Sammy Pawar Jan 27 '23 at 09:31

1 Answers1

1

Naming strategy is not instantiated as a spring component. You can use a helper component to inject the properties and get the instance in a static way:

@Component
public class SomeComponent {

    private static SomeComponent selfInstance;

    // Your @Value properties here

    @PostConstruct
    public void init() {
        selfInstance = this;
    }

    public static SomeComponent getInstance() {
        return selfInstance;
    }
}

Then in your naming strategy: SomeComponent.getInstance().getXX

Santiago Medina
  • 529
  • 2
  • 12
  • But since the configuration property spring.jpa.properties.hibernate.physical_naming_strategy controls what class should be used, how do you make use of your component? – Philippe Oct 01 '20 at 02:51
  • That does not work because SomeComponent.getInstance().getXX results in NullPointer Exceptions whenever i call it within my naming strategy. (SomeComponent.getInstance() returns always null). Looks like the same reason to me that @Value can not be injected. Just like as if nothing has been created by Spring at that point in time where it creates the naming strategy instance. – Ulathar Oct 01 '20 at 11:55