【问题标题】:Springboot, hibernate4 and hsql accessing schema incorrectly?Spring Boot、hibernate 4 和 hsql 访问模式不正确?
【发布时间】:2016-04-20 14:04:47
【问题描述】:

我正在尝试同时使用 springboot、hsql 和 hibernate 来持久化和检索一些相当无聊的数据。我遇到的问题是休眠似乎无法正确引用我的表,引发以下异常:

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)
...

最初我使用 HSQL 的默认模式名称 PUBLIC,并注意到抛出的异常是应用程序找不到 PUBLIC.PUBLIC.PROTECTED_PROPERTIES。这看起来非常可疑——为什么这里有一个“额外层”的 PUBLIC?它看起来肯定不正确。进行 EntityManagerFactory 设置的代码如下所示:

@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;
  }
}

HSQL 配置如下所示:

@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;
  }

}

另一个值得注意的奇怪的事情是,hibernate 似乎正在寻找一个名为 user_USER_ID 而不是 USER_ID 的列,我也不知道为什么会这样。我怀疑这一切都是由映射错误引起的,因为类似的代码似乎适用于不同配置的 EntityMappingFactory,但我不想排除这种可能性。代码如下:

@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;

}

还有ID类:

@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;
  }
}

【问题讨论】:

  • 您是否有理由不使用弹簧靴?您正在努力解决它...更糟糕的是您的配置正在破坏事务管理之类的东西。
  • 你能扩展一下这个评论吗?我不清楚你的意思是什么。 Spring boot 做了很多自动配置,我不太清楚自动执行了什么。我已经发现其中一些对象是自动创建和配置的。是否还设置和配置了 TransactionManager?
  • 基本上你所拥有的一切都是自动配置的。唯一没有的是SessionFactory,但如果你可以使用EntityManager,你为什么需要它。我将删除所有内容,创建一个全局 application.properties 和特定的 application-&lt;profile&gt;.properties 并从那里开始。

标签: java hibernate spring-boot hsqldb


【解决方案1】:

我在这里假设您在包com.healz.stranger 中有一个StrangerApplication,如果您不应该这样做,或者将它移到那里,因为它会为您节省大量配置。

您正在使用 Spring Boot,但您的配置非常努力地不这样做。

先申请

@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;
    }

}

现在创建一个application.properties,其中包含默认属性和常规属性。对于hsql 配置文件,添加一个application-hsql.properties,其中至少包含以下内容(从您的配置类中扣除)。

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

然后将您的hsql-data.sqlhsql-schema.sql 重命名为data-gsql.sqlschema-hsql.sql 并将其放入src/main/resources 中,spring boot 将检测特定配置文件的那些(在参考指南中解释了here)。确保在 schema.sql 中的新架构中创建架构和表。

其他一切都将自动配置(Spring Data JPA、AspectJ 代理、实体检测)。您基本上可以删除所有配置类并为剩余的 2 个 MySQL 配置选项创建添加 application-{profile}.properties

一般建议是使用框架,而不是尝试绕过它。

【讨论】:

  • 谢谢!这是一个很好的解释。大多数额外的 bean 都是双重定义的,这导致了各种奇怪的行为。也就是说,我正在寻找的数据库配置需要足够多的自定义行为,以至于实际上不可能获得“正常”RDBMS 与 H2 或 HSQL 等嵌入式 RDBMS 的不对称行为。我主要以您推荐的方式解决了这个问题:有 2 个活动配置文件(一个用于环境,一个用于数据库端)。 DB 端主要通过属性文件配置,但也有一些脚手架代码。
  • 你需要做什么样的脚手架?一般来说,在仍然使用自动配置的情况下可以很容易地解决这个问题。
【解决方案2】:

这里的问题似乎是 Spring Boot 定义了自己的 LocalContainerEntityManagerFactoryBean 实例,而第二个定义导致了奇怪的冲突。此外,没有理由将 JPA 方言应用于 TransactionManager,因为 TransactionManager 将从 EntityManagerFactory 中获取设置,Spring Boot 无论如何都会配置这些设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-08
    • 2017-08-14
    • 2019-09-13
    • 2016-10-04
    • 2020-04-17
    • 2015-07-20
    • 1970-01-01
    • 2019-03-03
    相关资源
    最近更新 更多