简体   繁体   中英

Springboot, hibernate4 and hsql accessing schema incorrectly?

I'm trying to use springboot, hsql and hibernate together to persist and retrieve some fairly boring data. The issue I'm running into is that hibernate seems unable to reference my tables correctly, throwing the following exception:

ERROR [main] (SpringApplication.java:826) - Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'strangerEntityManagerFactory' defined in class path resource [com/healz/stranger/config/profiles/GenericSqlConfig.class]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Missing column: user_USER_ID in PUBLIC.STRANGER.PROTECTED_PROPERTIES
at
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578)
at    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
...

Initially I was using HSQL's default schema name, PUBLIC, and noticed that the exception getting thrown was that the application couldn't find PUBLIC.PUBLIC.PROTECTED_PROPERTIES. This looks highly suspicious -- why is there an "extra layer" of PUBLIC here? It definitely doesn't look right. The code that does the EntityManagerFactory setup looks like this:

@Log4j
@Configuration
@EnableAspectJAutoProxy
@ComponentScan (basePackages = {"com.healz.stranger.data"})
@EnableJpaRepositories (
  entityManagerFactoryRef="strangerEntityManagerFactory",
  transactionManagerRef="txManager",
  basePackages={"com.healz.stranger.data.model"}
)
@EntityScan (basePackages={
    "com.healz.stranger.data.model" 
    })
@Import ( {HsqlConfig.class, DevMySqlConfig.class, ProdMySqlConfig.class} )
public class GenericSqlConfig {

  @Configuration
  @EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  protected static class TransactionManagementConfigurer {
    // ignore annoying bean auto-proxy failure messages
  }

  @Bean
  public static PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() throws Exception {
    return new PersistenceAnnotationBeanPostProcessor();
  }

  @Bean
  public JpaDialect jpaDialect() {
    return new HibernateJpaDialect();
  }

  @Autowired
  @Qualifier("hibernateProperties") 
  private Properties hibernateProperties;

  @Autowired
  @Qualifier("dataSource") 
  private DataSource dataSource;


  @Bean (name="strangerEntityManagerFactory")
  public LocalContainerEntityManagerFactoryBean strangerEntityManagerFactory(
                final @Qualifier("hibernateProperties") Properties props,
                final JpaDialect jpaDialect) {
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setDataSource(dataSource);
    emf.setPackagesToScan("com.healz.stranger.data");

    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    emf.setJpaVendorAdapter(vendorAdapter);
    emf.setJpaProperties(hibernateProperties);
    emf.setJpaDialect(jpaDialect);

    emf.setPersistenceUnitName("strangerEntityManagerFactory");

    return emf;
  }

  @Bean (name="sessionFactory")
  public SessionFactory configureSessionFactory(LocalContainerEntityManagerFactoryBean emf) {
    SessionFactory sessionFactory = emf.getObject().unwrap(SessionFactory.class);
    return sessionFactory;
  }

  /**
   * Helper method to get properties from a path. 
   * @param path
   * @return
   */
  @SneakyThrows (IOException.class)
  public static Properties getHibernatePropertiesList(final String path) {
    Properties props = new Properties();
    Resource resource = new ClassPathResource(path); 
    InputStream is = resource.getInputStream();
    props.load( is );
    return props;
  }

  @Bean (name="txManager")
  @Autowired
  public PlatformTransactionManager getTransactionManager(LocalContainerEntityManagerFactoryBean lcemfb, JpaDialect jpaDialect) {
    EntityManagerFactory emf = null;
    emf = lcemfb.getObject();

    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(emf);
    jpaTransactionManager.setJpaDialect(jpaDialect);
    return jpaTransactionManager;
  }
}

The HSQL config looks like this:

@Configuration
@Profile ("hsql")
public class HsqlConfig {
  @Bean(name = "dataSource")
  public DataSource initDataSource() {
    EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
                                        .setType(EmbeddedDatabaseType.HSQL)
                                        .addScript("classpath:env/dbcache/hsql-schema.sql")
                                        .addScript("classpath:env/dbcache/hsql-data.sql");
    builder.setName("stranger");
    builder.setScriptEncoding("UTF-8");
    return builder.build();
  }

  @Bean(name = "hibernateProperties")
  public Properties getHibernateProperties() {
    Properties props = new Properties();
    props.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
    props.put("hibernate.hbm2ddl.auto", "validate"); // using auto and ignoring the hsql scripts "works", but isn't correct
    props.put("hibernate.default_schema", "stranger");
    props.put("hibernate.current_session_context_class", "org.hibernate.context.internal.ThreadLocalSessionContext");
    return props;
  }

}

The other noticably odd thing about this is that hibernate seems to be looking for a column with the name user_USER_ID instead of USER_ID, and I'm not sure why that's happening either. I doubt this has all been caused by a mapping error since similar code seems to work with a differently configured EntityMappingFactory but I don't want to preclude the possibility. The code for this looks as follows:

@Entity (name="properties")
@Table (name="PROTECTED_PROPERTIES")
public class DbProtectedProperties extends AbstractModel<DbProtectedPropertiesId> implements Serializable {

  private static final long serialVersionUID = 1L;

  public void setId(DbProtectedPropertiesId id) {
    super.id = id;
  }

  @EmbeddedId
  public DbProtectedPropertiesId getId() {
    if (super.id == null) {
      super.id = new DbProtectedPropertiesId();
    }
    return super.id;
  }

  @Column (name="PROPERTY_VALUE", length=4096, nullable=false)
  public String getPropertyValue() {
    return propertyValue;
  }


  @Setter
  private String propertyValue;

}

And the ID class:

@EqualsAndHashCode ( of={ "user", "propertyName" } )
@ToString
public class DbProtectedPropertiesId implements Serializable {

  private static final long serialVersionUID = 1L;

  @Setter
  private DbUsers user;

  @Setter
  private String propertyName;

  @ManyToOne (optional=false, fetch=FetchType.EAGER)
  @PrimaryKeyJoinColumn (name="USER_ID")
  public DbUsers getUser() {
    return user;
  }

  @Column (name="PROPERTY_NAME", length=2048, nullable=false, insertable=false, updatable=false)
  public String getPropertyName() {
    return propertyName;
  }
}

The issue here seems to be that Spring Boot defines its own instance of LocalContainerEntityManagerFactoryBean and the second definition was causing strange conflicts. Additionally, there's no reason to apply the JPA dialect in to the TransactionManager since the TransactionManager will pick up the settings from the EntityManagerFactory , which Spring Boot will configure anyway.

I assume here that you have a StrangerApplication in the package com.healz.stranger if you don't you really should or move it there as it will save you a lot of configuration.

You are using Spring Boot but your configuration tries very hard not to.

First the application

@SpringBootApplication
public class StrangerApplication {

    public static void main(String... args) throws Exception {
        SpringApplication.run(StrangerApplication.class, args);
    }

    @Bean (name="sessionFactory")
    public SessionFactory configureSessionFactory(EntityManagerFactoryBean emf) {
        SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);
        return sessionFactory;
    }

}

Now create an application.properties which contains the default properties and general properties. For the hsql profile add an application-hsql.properties which contains at least the following (deducted from your configuration classes).

spring.jpa.properties.hibernate.default_schema=stranger
spring.jpa.hibernate.ddl-auto=validate # maybe this needs to be in application.properties (?)

Then rename your hsql-data.sql and hsql-schema.sql to data-gsql.sql and schema-hsql.sql and place it in src/main/resources spring boot will detect those for the specific profile (explained here in the reference guide). Make sure that you create the schema and the tables in that new schema in your schema.sql .

Everything else will be automagically configured (Spring Data JPA, AspectJ proxying, detection of entities). You can basically remove all the config classes and create the addition application-{profile}.properties for the 2 remaining MySQL configuration options.

The general advice would be to work with the framework instead of trying to work around it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM