web-dev-qa-db-fra.com

ImprovedNamingStrategy ne fonctionne plus dans Hibernate 5

J'ai une configuration Spring-JPA simple où j'ai configuré le ImprovedNamingStrategy d'Hibernate. Cela signifie que si ma classe d'entité a une variable userName, Hibernate doit la convertir en user_name pour interroger la base de données. Mais cette conversion de noms a cessé de fonctionner après la mise à niveau vers Hibernate 5. J'obtiens l'erreur:

ERREUR: colonne inconnue 'user0_.userName' dans 'liste de champs'

Voici ma configuration Hibernate:

@Configuration
@EnableJpaRepositories("com.springJpa.repository")
@EnableTransactionManagement
public class DataConfig {

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/test");
        ds.setUsername("root");
        ds.setPassword("admin");
        return ds;
    }


    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){ 

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setShowSql(Boolean.TRUE);
        vendorAdapter.setDatabase(Database.MYSQL);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setDataSource(dataSource());
        factory.setPackagesToScan("com.springJpa.entity");


        Properties jpaProperties = new Properties();

        jpaProperties.put("hibernate.ejb.naming_strategy","org.hibernate.cfg.ImprovedNamingStrategy");
        jpaProperties.put("hibernate.dialect","org.hibernate.dialect.MySQL5InnoDBDialect");

        factory.setJpaProperties(jpaProperties);
        factory.afterPropertiesSet();
        return factory;
    }

    @Bean
    public SharedEntityManagerBean entityManager() {
        SharedEntityManagerBean entityManager = new SharedEntityManagerBean();
        entityManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return entityManager;
    }



    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return txManager;
    }

    @Bean
    public ImprovedNamingStrategy namingStrategy(){
        return new ImprovedNamingStrategy();
    }
}

Voici ma classe d'entité:

@Getter
@Setter
@Entity
@Table(name="user")
public class User{

    @Id
    @GeneratedValue
    private Long id;

    private String userName;
    private String email;
    private String password;
    private String role;

}

Je ne veux pas nommer explicitement mes champs de base de données dans les annotations @Column. Je veux ma configuration qui peut implicitement convertir un étui de chameau en souligné.

Veuillez guider.

36
Anup

Merci d'avoir publié votre propre solution. Cela m'aide beaucoup à définir la stratégie de nommage d'Hibernate 5!

La propriété hibernate.ejb.naming_strategy De la version antérieure à Hibernate 5.0 semble divisée en deux parties:

  • hibernate.physical_naming_strategy
  • hibernate.implicit_naming_strategy

Les valeurs de ces propriétés n'implémentent pas l'interface NamingStrategy comme l'a fait hibernate.ejb.naming_strategy. Il existe deux nouvelles interfaces à ces fins:

  • org.hibernate.boot.model.naming.PhysicalNamingStrategy
  • org.hibernate.boot.model.naming.ImplicitNamingStrategy

Hibernate 5 fournit une seule implémentation de PhysicalNamingStrategy (PhysicalNamingStrategyStandardImpl) qui suppose que les noms d'identificateurs physiques sont les mêmes que les noms logiques.

Il existe plusieurs implémentations de ImplicitNamingStrategy mais je n'en ai trouvé aucune équivalente à l'ancienne ImprovedNamingStrategy. (Voir: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl)

J'ai donc implémenté mon propre PhysicalNamingStrategy ce qui est très simple:

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl implements Serializable {

 public static final PhysicalNamingStrategyImpl INSTANCE = new PhysicalNamingStrategyImpl();

 @Override
 public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
     return new Identifier(addUnderscores(name.getText()), name.isQuoted());
 }

 @Override
 public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
     return new Identifier(addUnderscores(name.getText()), name.isQuoted());
 }


 protected static String addUnderscores(String name) {
     final StringBuilder buf = new StringBuilder( name.replace('.', '_') );
     for (int i=1; i<buf.length()-1; i++) {
        if (
             Character.isLowerCase( buf.charAt(i-1) ) &&
             Character.isUpperCase( buf.charAt(i) ) &&
             Character.isLowerCase( buf.charAt(i+1) )
         ) {
             buf.insert(i++, '_');
         }
     }
     return buf.toString().toLowerCase(Locale.ROOT);
 }
}

Notez que la méthode addUnderscores() provient de l'original org.hibernate.cfg.ImprovedNamingStrategy.

Ensuite, j'ai défini cette stratégie physique dans le fichier persistence.xml:

  <property name="hibernate.physical_naming_strategy" value="my.package.PhysicalNamingStrategyImpl" />

C'est un piège pour définir la stratégie de nommage d'Hibernate 5 comme paramètres de la version précédente.

59
Samuel Andrés

Merci et +1 à Samuel Andrés pour la réponse très utile, mais c'est probablement une bonne idée d'éviter la logique de casse de serpent écrite à la main. Voici la même solution, en utilisant Guava.

Il suppose que vos noms d'entité sont écrits dans le StandardJavaClassFormat et les noms de colonne dans le standardJavaFieldFormat

J'espère que cela permettra à certaines personnes de venir ici à l'avenir sur googler :-)

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import static com.google.common.base.CaseFormat.*;

public class SnakeCaseNamingStrategy extends PhysicalNamingStrategyStandardImpl {

  public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
    return new Identifier(
      UPPER_CAMEL.to(LOWER_UNDERSCORE, name.getText()),
      name.isQuoted()
    );
  }

  public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
    return new Identifier(
      LOWER_CAMEL.to(LOWER_UNDERSCORE, name.getText()),
      name.isQuoted()
    );
  }
}
4
davnicwil

merci pour ce post. Peu ennuyeux que la mise à niveau casse la stratégie de nom de table et de colonne. Au lieu de copier la logique depuis ImprovedNamingStrategy, vous pouvez également utiliser la délégation.

public class TableNamingStrategy extends PhysicalNamingStrategyStandardImpl {
    private static final String TABLE_PREFIX = "APP_";
    private static final long serialVersionUID = 1L;
    private static final ImprovedNamingStrategy STRATEGY_INSTANCE = new ImprovedNamingStrategy();

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return new Identifier(classToTableName(name.getText()), name.isQuoted());
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return new Identifier(STRATEGY_INSTANCE.classToTableName(name.getText()), name.isQuoted());
    }

    private String classToTableName(String className) {
        return STRATEGY_INSTANCE.classToTableName(TABLE_PREFIX + className);
    }
}
3
Michael Hegner

Chaque réponse publie une solution en implémentant PhysicalNamingStrategy, mais tout ce dont vous avez besoin (et devriez faire) est d'implémenter ImplicitNamingStrategy.

Lorsqu'une entité ne nomme pas explicitement la table de base de données à laquelle elle est mappée, nous devons déterminer implicitement ce nom de table. Ou lorsqu'un attribut particulier ne nomme pas explicitement la colonne de base de données à laquelle il est mappé, nous devons déterminer implicitement ce nom de colonne. Il existe des exemples du rôle du contrat org.hibernate.boot.model.naming.ImplicitNamingStrategy pour déterminer un nom logique lorsque le mappage n'a pas fourni de nom explicite.

Et le code peut être aussi simple que cela (en utilisant le addUnderscores d'origine comme dans les autres réponses):

public class ImplicitNamingStrategyImpl extends ImplicitNamingStrategyJpaCompliantImpl {

    @Override
    protected Identifier toIdentifier(String stringForm, MetadataBuildingContext buildingContext) {
        return super.toIdentifier(addUnderscores(stringForm), buildingContext);
    }

    protected static String addUnderscores(String name) {
        final StringBuilder buf = new StringBuilder(name.replace('.', '_'));
        for (int i = 1; i < buf.length() - 1; i++) {
            if (Character.isLowerCase(buf.charAt(i - 1))
                    && Character.isUpperCase(buf.charAt(i))
                    && Character.isLowerCase(buf.charAt(i + 1))) {
                buf.insert(i++, '_');
            }
        }
        return buf.toString().toLowerCase(Locale.ROOT);
    }
}
2
ThreeDots

j'espère que cela t'aides:

hibernate.implicit_naming_strategy = .... ImplicitNamingStrategy hibernate.physical_naming_strategy = .... PhysicalNamingStrategyImpl

et voici le code (juste réarrangé du code existant):

import Java.io.Serializable;
import Java.util.Locale;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl implements Serializable {

    public static final PhysicalNamingStrategyImpl INSTANCE = new PhysicalNamingStrategyImpl();

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return new Identifier(addUnderscores(name.getText()), name.isQuoted());
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return new Identifier(addUnderscores(name.getText()), name.isQuoted());
    }

    protected static String addUnderscores(String name) {
        final StringBuilder buf = new StringBuilder( name.replace('.', '_') );
        for (int i=1; i<buf.length()-1; i++) {
            if (
                Character.isLowerCase( buf.charAt(i-1) ) &&
                Character.isUpperCase( buf.charAt(i) ) &&
                Character.isLowerCase( buf.charAt(i+1) )
            ) {
                buf.insert(i++, '_');
            }
        }
        return buf.toString().toLowerCase(Locale.ROOT);
    }

}
1
Ignacio

Pas d'utilitaires Goyave et Apache

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl {

    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return context.getIdentifierHelper().toIdentifier(
                name.getText().replaceAll("((?!^)[^_])([A-Z])", "$1_$2").toLowerCase(),
                name.isQuoted()
        );
    }

    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return context.getIdentifierHelper().toIdentifier(
                name.getText().replaceAll("((?!^)[^_])([A-Z])", "$1_$2").toLowerCase(),
                name.isQuoted()
        );
    }
}
0
SoBeRich