英文:
Spring Data CRUD Repository: save method does not work
问题
这与Spring Boot无关。
我的英文可能需要提高。
使用以下Spring Data配置,我尝试执行DML请求。
确切地说,是`CrudRepository#save`方法。
然而,当执行Spring的CrudRepository#save方法时,我得到以下结果:
1. `hibernate.show_sql`功能仅记录选择查询。
2. 没有执行“insert”或“update”语句,因此未在`hibernate.show_sql`日志中显示。
3. 数据库没有任何变化。
====================================================
不确定,但这似乎是一个事务问题。
似乎在那一点上没有事务,
因此在事务之外,CRUD Repository无法执行DML请求,
包括`CrudRepository#save`。
也许配置有问题?
请查看并随时要求任何其他信息。
**更新:**
下面的这种不良实践的解决方法帮助我执行“Update”语句。
```java
//(自动装配,共享的实体管理器)
entityManager.joinTransaction();
repository.save(user);
然而,这仍然是一种不良的实践方法。在这种情况下,Spring的目标被忽视了。
无论如何,我需要使用声明性的基于代码的事务管理。
问题仍然存在:
我的配置有什么问题?@Transactional注解仍然不起作用
用户领域实体:
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User
{
@Id
@Column(name = "id_pk", length = 11)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int idPk;
@Column(name = "user_id", length = 25, nullable = false, unique = true)
private String userId;
@Column(name = "email_addr", length = 120)
private String email;
}
特定于领域的Spring Data CRUD Repository声明:
public interface UserRepository extends CrudRepository<User, Integer> {
//没有特定的内容
}
Spring (非Spring Boot) 基于代码的配置:
@EnableJpaRepositories(basePackages = "***",
transactionManagerRef = "jpaTransactionManager")
@EnableTransactionManagement
public class DataConfig
{
@Bean
public EntityManagerFactory entityManagerFactory()
{
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource());
factory.setPackagesToScan(DOMAIN_ENTITY_SCAN_PACKAGE);
factory.setJpaVendorAdapter(getVendorAdapter());
factory.afterPropertiesSet();
return factory.getObject();
}
private HibernateJpaVendorAdapter getVendorAdapter()
{
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(Boolean.TRUE);
return vendorAdapter;
}
@Bean
public JpaTransactionManager jpaTransactionManager()
{
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
txManager.afterPropertiesSet();
return txManager;
}
}
英文:
This is not about Spring Boot at all.
My English could be better.
Using below Config for Spring Data I'm trying to execute DML requests.
Exactly CrudRepository#save
method.
However executing Spring's CrudRepository#save method I'm getting next:
- ONLY Selects are logged by
hibernate.show_sql
feature. - No "insert" or "update" statements are being executed as to hibernate.show_sql logging.
- No changes at database at all.
====================================================
Not sure but it looks like a Transaction issue.
Seems that there is no transaction at that point,
so out of transaction CRUD Repos is not able to execute DML requests,
including CrudRepository#save
.
Maybe it is something wrong with configuration?
Have a look please and feel free to ask for any additional info.
UPDATE:
The next bad-practice workaround helped me to reach the "Update" statements execution.
//(autowired, shared entity manager)
entityManager.joinTransaction();
repository.save(user);
However it is still a bad practice approach. In this case Spring's purpose is lost.
Anyway it is required for me to use Declarative Code-based Transaction managment.
Question is still open:
What is wrong with my config? @Transactional annotation still doesn't work
User domain entity:
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User
{
@Id
@Column(name = "id_pk", length = 11)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int idPk;
@Column(name = "user_id", length = 25, nullable = false, unique = true)
private String userId;
@Column(name = "email_addr", length = 120)
private String email;
}
Domain-specific Spring Data CRUD Repository declaration:
public interface UserRepository extends CrudRepository<User, Integer> {
//nothing specific
}
Spring (Boot-less) Code-based configuration:
@EnableJpaRepositories(basePackages = "***",
transactionManagerRef = "jpaTransactionManager")
@EnableTransactionManagement
public class DataConfig
{
@Bean
public EntityManagerFactory entityManagerFactory()
{
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource());
factory.setPackagesToScan(DOMAIN_ENTITY_SCAN_PACKAGE);
factory.setJpaVendorAdapter(getVendorAdapter());
factory.afterPropertiesSet();
return factory.getObject();
}
private HibernateJpaVendorAdapter getVendorAdapter()
{
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(Boolean.TRUE);
return vendorAdapter;
}
@Bean
public JpaTransactionManager jpaTransactionManager()
{
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
txManager.afterPropertiesSet();
return txManager;
}
}
答案1
得分: 2
终于,我找到了适用于我的情况的解决方案。
由于我在使用Spring,但没有使用它的Boot部分,我不得不配置自定义的WebApplicationInitializer,以便让Spring管理应用程序入口点:
public class MainWebAppInitializer implements WebApplicationInitializer
{
@Override
public void onStartup(final ServletContext sc)
{
AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
root.register(WebAppConfiguration.class, DataConfig.class);
sc.addListener(new ContextLoaderListener(root));
...省略其他不相关的代码
}
}
因此,因为我使用AnnotationConfigWebApplicationContext#register注册了两个配置类(WebAppConfiguration.class、DataConfig.class),我认为使用*@Configuration*注解进行配置是多余的。
在这一点上,我错了。
要正确注册TransactionManager,您应该在您的Jpa配置类上注解*@Configuration*。
因此,我已经设法使用*@Configuration*注解来注释我的配置类,这解决了我的问题。
现在,Spring的CRUD存储库能够运行DML查询以与数据库交互(借助于#save方法)。
更确切地说:现在存储库能够打开自己的事务并在这些事务的范围内运行所需的查询。
英文:
Finally, I've found a solution for my case.
Since I'm using Spring without its Boot part
I had to configure custom WebApplicationInitializer to let Spring manage application entry point:
public class MainWebAppInitializer implements WebApplicationInitializer
{
@Override
public void onStartup(final ServletContext sc)
{
AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
root.register(WebAppConfiguration.class, DataConfig.class);
sc.addListener(new ContextLoaderListener(root));
...other not related code ommited
}
}
So because I've registered both Config Classes (WebAppConfiguration.class, DataConfig.class) using AnnotationConfigWebApplicationContext#register
I thought annotating configs with @Configuration would be Redundand.
And I was wrong at that point.
To register TransactionManager correctly you SHOULD annotate your Jpa Config class with @Configuration.
Thus I've managed to annotate my config classes with @Configuration and this solved my issue.
Now Spring CRUD Repositories are able to run DML queries to DB (with help of #save methods).
Precisely talking: now Repositories are able to open own transactions and run required queries in terms of these transactions.
答案2
得分: 0
@Configuration
@EnableJpaRepositories(basePackages = "package-name",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "jpaTransactionManager")
@EnableTransactionManagement
@PropertySource(value = { "classpath:application.yml" })
public class DataConfig {
@Autowired
private Environment environment;
@Value("${datasource.myapp.maxPoolSize:10}")
private int maxPoolSize;
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.myapp")
public DataSourceProperties dataSourceProperties(){
return new DataSourceProperties();
}
@Bean
public DataSource dataSource() {
DataSourceProperties dataSourceProperties = dataSourceProperties();
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder
.create(dataSourceProperties.getClassLoader())
.driverClassName(dataSourceProperties.getDriverClassName())
.url(dataSourceProperties.getUrl())
.username(dataSourceProperties.getUsername())
.password(dataSourceProperties.getPassword())
.type(HikariDataSource.class)
.build();
dataSource.setMaximumPoolSize(maxPoolSize);
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan(new String[] { "package-name" });
factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
factoryBean.setJpaProperties(jpaProperties());
return factoryBean;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
return hibernateJpaVendorAdapter;
}
private Properties jpaProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("datasource.myapp.hibernate.dialect"));
properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("datasource.myapp.hibernate.hbm2ddl.method"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("datasource.myapp.hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("datasource.myapp.hibernate.format_sql"));
if(StringUtils.isNotEmpty(environment.getRequiredProperty("datasource.myapp.defaultSchema"))){
properties.put("hibernate.default_schema", environment.getRequiredProperty("datasource.myapp.defaultSchema"));
}
return properties;
}
@Bean
@Autowired
public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory emf) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(emf);
return txManager;
}
}
application.yaml
server:
port: 8081
servlet:
context-path: /CRUDApp
spring:
profiles: local,default
datasource:
myapp:
url: jdbc:h2:~/test
username: SA
password:
driverClassName: org.h2.Driver
defaultSchema:
maxPoolSize: 10
hibernate:
hbm2ddl.method: create-drop
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.H2Dialect
英文:
I didnot get this, how you are initializing the datasource properties ? I could not see in your given code.
factory.setDataSource(dataSource());
You should be designing you Configuration class like below. Use both the :
entityManagerFactoryRef="entityManagerFactory",
transactionManagerRef="jpaTransactionManager"
Read the hibernate properties from a yaml or properties file.
And set your datasource
Configuration class : DataConfig
/**
* @author Som
*
*/
@Configuration
@EnableJpaRepositories(basePackages="package-name",
entityManagerFactoryRef="entityManagerFactory",
transactionManagerRef="jpaTransactionManager")
@EnableTransactionManagement
@PropertySource(value = { "classpath:application.yml" })
public class DataConfig {
@Autowired
private Environment environment;
@Value("${datasource.myapp.maxPoolSize:10}")
private int maxPoolSize;
/**
* Populate DataSourceProperties object directly from application.yml
* *
*/
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.myapp")
public DataSourceProperties dataSourceProperties(){
return new DataSourceProperties();
}
/**
* Configure HikariCP pooled DataSource.
*
*/
@Bean
public DataSource dataSource() {
DataSourceProperties dataSourceProperties = dataSourceProperties();
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder
.create(dataSourceProperties.getClassLoader())
.driverClassName(dataSourceProperties.getDriverClassName())
.url(dataSourceProperties.getUrl())
.username(dataSourceProperties.getUsername())
.password(dataSourceProperties.getPassword())
.type(HikariDataSource.class)
.build();
dataSource.setMaximumPoolSize(maxPoolSize);
return dataSource;
}
/**
* Entity Manager Factory setup.
*
*/
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan(new String[] { "package-name" });
factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
factoryBean.setJpaProperties(jpaProperties());
return factoryBean;
}
/**
* Provider specific adapter.
*
*/
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
return hibernateJpaVendorAdapter;
}
/**
* Hibernate properties.
*
*/
private Properties jpaProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("datasource.myapp.hibernate.dialect"));
properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("datasource.myapp.hibernate.hbm2ddl.method"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("datasource.myapp.hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("datasource.myapp.hibernate.format_sql"));
if(StringUtils.isNotEmpty(environment.getRequiredProperty("datasource.myapp.defaultSchema"))){
properties.put("hibernate.default_schema", environment.getRequiredProperty("datasource.myapp.defaultSchema"));
}
return properties;
}
@Bean
@Autowired
public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory emf) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(emf);
return txManager;
}
}
application.yaml
server:
port: 8081
servlet:
context-path: /CRUDApp
---
spring:
profiles: local,default
datasource:
myapp:
url: jdbc:h2:~/test
username: SA
password:
driverClassName: org.h2.Driver
defaultSchema:
maxPoolSize: 10
hibernate:
hbm2ddl.method: create-drop
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.H2Dialect
---
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论