Репозиторий Spring Data CRUD: метод сохранения не работает

#java #spring #jpa

#java #spring #jpa

Вопрос:

Речь идет совсем не о Spring Boot.

Мой английский мог бы быть лучше.

Используя приведенную ниже конфигурацию для Spring Data, я пытаюсь выполнить запросы DML.

Точно CrudRepository#save метод.

Однако, выполняя метод Spring CrudRepository #save, я получаю следующий:

  1. С помощью hibernate.show_sql функции регистрируются только выборки.
  2. Инструкции «вставить» или «обновить» не выполняются для ведения журнала hibernate.show_sql.
  3. Никаких изменений в базе данных вообще.

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

Не уверен, но это похоже на проблему с транзакцией.

Кажется, что в этот момент транзакции нет,

таким образом, репозитории CRUD вне транзакции не могут выполнять запросы DML, в том числе CrudRepository#save .

Может быть, что-то не так с конфигурацией? Посмотрите, пожалуйста, и не стесняйтесь запрашивать любую дополнительную информацию.

ОБНОВЛЕНИЕ: Следующий обходной путь с плохой практикой помог мне достичь выполнения инструкций «Обновить».

 //(autowired, shared entity manager)
entityManager.joinTransaction();
repository.save(user);
  

Однако это все еще плохой практический подход. В этом случае теряется назначение Spring.
В любом случае мне требуется использовать декларативное управление транзакциями на основе кода.
Вопрос все еще открыт:
Что не так с моей конфигурацией? @Транзакционная аннотация по-прежнему не работает

Объект пользовательского домена:

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

Объявление хранилища CRUD данных Spring для конкретного домена:

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

Комментарии:

1. Измените идентификатор на непримитивный тип, чтобы он мог принимать нулевое значение, в противном случае он всегда будет равен PK 0 при переходе к insert

2. @JLazar0 спасибо за совет, однако этот момент не имеет ничего общего с проблемой, которую я описал выше.

Ответ №1:

Наконец, я нашел решение для своего случая.

Поскольку я использую 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
    }
}
  

Итак, поскольку я зарегистрировал оба класса конфигурации (WebAppConfiguration.class, DataConfig.class ) с помощью AnnotationConfigWebApplicationContext#register
Я думал, что аннотирование конфигураций с помощью @Configuration было бы избыточным.

И в этот момент я был неправ.

Чтобы правильно зарегистрировать TransactionManager, вы ДОЛЖНЫ аннотировать свой класс конфигурации Jpa с помощью @Configuration.

Таким образом, мне удалось аннотировать мои классы конфигурации с помощью @Configuration, и это решило мою проблему.

Теперь репозитории Spring CRUD могут запускать запросы DML к БД (с помощью методов #save). Точнее говоря: теперь репозитории могут открывать собственные транзакции и запускать необходимые запросы в терминах этих транзакций.

Комментарии:

1. работает для меня! Я столкнулся с этой проблемой в тестовой среде. Я создал конфигурацию со всеми компонентами, такими как DataSource, EntityManagerFactory, PlatformTransactionManager. Затем я добавил эту конфигурацию в ‘@ContextConfiguration’. Но операция сохранения не сработала. Поэтому я отмечаю config с помощью ‘@Configuration’, и это работает как шарм. Большое спасибо!

Ответ №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
---
  

Комментарии:

1. Спасибо, Som. Я скрыл метод #DataSource, поскольку его внутренняя работа не имеет ничего общего с проблемой выше. Это то, что определенно не связано с источником данных, поскольку вся связь с БД работает корректно с точки зрения конфигурации источника данных. И, как я упоминал в ОБНОВЛЕНИИ выше: с EntityManager#joinTransaction все работает нормально, хотя в любом случае это плохая практика. Спасибо, попытаюсь явно указать entityManagerFactoryRef.

2. @MykytaChelombitko: Нет проблем . Ааа, я вижу. Да, это определенно не связано с источником данных, я это понимаю. Вы можете попробовать один раз в своем классе конфигурации с обоими entityManagerFactoryRef и transactionManagerRef , как я описал в своем коде.

3. К сожалению, это не помогло.

4. Интересно то, что транзакционная аннотация, похоже, работает. Почему? Потому что я не могу присоединиться к транзакции, если я не использую транзакционный над методом / классом. Таким образом, это похоже на разницу между контекстом транзакций или что-то в этом роде. Не уверен, что.

5. @MykytaChelombitko : Странный случай, я годами использую эту же конфигурацию в одном из своих проектов. И что вы подразумеваете под @ Transactional working, вы пропустили это ранее в своем классе обслуживания или методах обслуживания? Лучше использовать этот anno в вашем классе обслуживания. Приятно видеть, что ваша проблема была решена.