【问题标题】:Spring Data CRUD Repository: save method does not workSpring Data CRUD Repository:保存方法不起作用
【发布时间】:2020-08-27 09:59:19
【问题描述】:

这根本与 Spring Boot 无关。

我的英语可能会更好。

使用下面的 Config for Spring Data 我正在尝试执行 DML 请求。

正是CrudRepository#save 方法。

但是我接下来要执行 Spring 的 CrudRepository#save 方法:

  1. hibernate.show_sql 功能仅记录选择。
  2. 没有针对 hibernate.show_sql 日志记录执行“插入”或“更新”语句。
  3. 数据库完全没有变化。

================================================ =====

不确定,但看起来像是事务问题。

当时好像没有交易,

因此事务 CRUD Repos 无法执行 DML 请求, 包括CrudRepository#save

也许是配置有问题? 请查看并随时询问任何其他信息。

更新: 下一个错误的解决方法帮助我实现了“更新”语句的执行。

//(autowired, shared entity manager)
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> {
  //nothing specific
}

Spring (无引导) 基于代码的配置:

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

【问题讨论】:

  • 把id改成非原始类型,这样可以取null值,否则去insert的时候总是PK 0
  • @JLazar0 感谢您的建议,但这一点与我上面描述的问题无关。

标签: java spring jpa


【解决方案1】:

最后,我找到了适合我的情况的解决方案。

因为我使用的是没有 Boot 部分的 Spring 我必须配置自定义 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));
        
        ...other not related code ommited
    }
}

因为我已经使用 AnnotationConfigWebApplicationContext#register 注册了两个配置类(WebAppConfiguration.classDataConfig.class) 我认为用 @Configuration 注释配置会是多余的。

那时我错了。

要正确注册 TransactionManager,您应该使用 @Configuration 注释您的 Jpa Config 类。

因此,我设法用 @Configuration 注释我的配置类,这解决了我的问题。

现在 Spring CRUD 存储库能够对 DB 运行 DML 查询(在 #save 方法的帮助下)。 准确地说:现在存储库能够打开自己的事务并根据这些事务运行所需的查询。

【讨论】:

  • 为我工作!我在测试环境中遇到了这个问题。我使用所有 bean 创建了配置,例如 DataSource、EntityManagerFactory、PlatformTransactionManager。然后我在“@ContextConfiguration”中添加了这个配置。但是保存操作不起作用。所以我用'@Configuration'标记配置,它就像一个魅力。非常感谢!
  • @Alex 很高兴它有帮助:)
【解决方案2】:

我没明白,你是如何初始化数据源属性的?我在你给定的代码中看不到。

factory.setDataSource(dataSource());

你应该像下面这样设计你的配置类。同时使用:

entityManagerFactoryRef="entityManagerFactory",
transactionManagerRef="jpaTransactionManager"

从 yaml 或属性文件中读取休眠属性。

并设置您的数据源

配置类: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
---

【讨论】:

  • 谢谢你,索姆。我隐藏了#dataSource 方法,因为它的内部工作与上述问题无关。这绝对与数据源无关,因为与 db 的所有通信在数据源配置方面都正常工作。正如我在上面的更新中提到的那样:使用 entityManager#joinTransaction 一切正常,尽管无论如何这是一个不好的做法。谢谢,将尝试明确指定 entityManagerFactoryRef。
  • @MykytaChelombitko:没问题。啊,我明白了。是的,这并不一定是关于数据源的,我明白这一点。正如我在代码中描述的那样,您可以在配置类中使用 entityManagerFactoryReftransactionManagerRef 尝试一次。
  • 很遗憾没有帮助。
  • 有趣的是事务注解似乎在起作用。为什么?因为如果我不在方法/类上使用事务,我将无法加入事务。所以它看起来像是事务上下文或类似的东西之间的差异。不确定。
  • @MykytaCheombitko :奇怪的这种情况,我在我的一个项目中多年来一直使用相同的配置。 @Transactional 工作是什么意思,您之前在服务类或服务方法中错过了它?最好在你的服务类中使用这个 anno。很高兴看到您的问题得到解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-31
  • 1970-01-01
  • 2018-07-26
  • 1970-01-01
相关资源
最近更新 更多