Spring Boot、hibernate 4 和 hsql 访问模式不正确?
Springboot, hibernate4 and hsql accessing schema incorrectly?
我正在尝试结合使用 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 的 "extra layer"?它看起来肯定不对。执行 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;
}
}
关于此的另一个明显奇怪的事情是休眠似乎正在寻找名称为 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 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;
}
}
这里的问题似乎是 Spring Boot 定义了它自己的 LocalContainerEntityManagerFactoryBean
实例,而第二个定义导致了奇怪的冲突。此外,没有理由将 JPA 方言应用于 TransactionManager
,因为 TransactionManager
将从 EntityManagerFactory
获取设置,Spring Boot 无论如何都会配置。
我在这里假设您在包 com.healz.stranger
中有一个 StrangerApplication
,如果您没有,您真的应该或将它移到那里,因为它将为您节省大量配置。
您正在使用 Spring 引导,但您的配置非常努力地不这样做。
首先申请
@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.sql
和 hsql-schema.sql
重命名为 data-gsql.sql
和 schema-hsql.sql
并将其放置在 src/main/resources
spring 引导将检测那些用于具体配置文件(在参考指南中解释 here)。确保在 schema.sql
的新模式中创建模式和表。
其他一切都将自动配置(Spring数据 JPA、AspectJ 代理、实体检测)。您基本上可以删除所有配置 类 并为剩余的 2 个 MySQL 配置选项添加 application-{profile}.properties
。
一般建议是使用框架而不是尝试绕过它。
我正在尝试结合使用 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 的 "extra layer"?它看起来肯定不对。执行 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;
}
}
关于此的另一个明显奇怪的事情是休眠似乎正在寻找名称为 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 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;
}
}
这里的问题似乎是 Spring Boot 定义了它自己的 LocalContainerEntityManagerFactoryBean
实例,而第二个定义导致了奇怪的冲突。此外,没有理由将 JPA 方言应用于 TransactionManager
,因为 TransactionManager
将从 EntityManagerFactory
获取设置,Spring Boot 无论如何都会配置。
我在这里假设您在包 com.healz.stranger
中有一个 StrangerApplication
,如果您没有,您真的应该或将它移到那里,因为它将为您节省大量配置。
您正在使用 Spring 引导,但您的配置非常努力地不这样做。
首先申请
@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.sql
和 hsql-schema.sql
重命名为 data-gsql.sql
和 schema-hsql.sql
并将其放置在 src/main/resources
spring 引导将检测那些用于具体配置文件(在参考指南中解释 here)。确保在 schema.sql
的新模式中创建模式和表。
其他一切都将自动配置(Spring数据 JPA、AspectJ 代理、实体检测)。您基本上可以删除所有配置 类 并为剩余的 2 个 MySQL 配置选项添加 application-{profile}.properties
。
一般建议是使用框架而不是尝试绕过它。