user11814208
user11814208

Reputation:

Spring Data CRUD Repository: save method does not work

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:

  1. ONLY Selects are logged by hibernate.show_sql feature.
  2. No "insert" or "update" statements are being executed as to hibernate.show_sql logging.
  3. 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;
    }
}

Upvotes: 6

Views: 12484

Answers (2)

user11814208
user11814208

Reputation:

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.

Upvotes: 2

Som
Som

Reputation: 1628

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

Upvotes: 0

Related Questions