#java #spring #jpa
#java #spring #jpa
Вопрос:
Речь идет совсем не о Spring Boot.
Мой английский мог бы быть лучше.
Используя приведенную ниже конфигурацию для Spring Data, я пытаюсь выполнить запросы DML.
Точно CrudRepository#save
метод.
Однако, выполняя метод Spring CrudRepository #save, я получаю следующий:
- С помощью
hibernate.show_sql
функции регистрируются только выборки. - Инструкции «вставить» или «обновить» не выполняются для ведения журнала hibernate.show_sql.
- Никаких изменений в базе данных вообще.
====================================================
Не уверен, но это похоже на проблему с транзакцией.
Кажется, что в этот момент транзакции нет,
таким образом, репозитории 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 в вашем классе обслуживания. Приятно видеть, что ваша проблема была решена.