I have created a project with spring boot. I have hikariConfig to create the data source for connection pooling with the autocommmit property set as false. Doing batch insert with jdbcTemplate running inside method annotated with #Transaction for DataSourceTransactionManager. I am unable to see the data getting inserted in Db after the program execution. If I make the autocommit true in hikariconfig it works fine.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Component
#EnableTransactionManagement
public class DataSourceConfig {
#Bean (name = "dateSourceForSqlServer")
public DataSource dataSourceForSqlServer () {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setConnectionTimeout(10000L);
hikariConfig.setIdleTimeout(10000L);
hikariConfig.setMinimumIdle(1);
hikariConfig.setMaximumPoolSize(1);
hikariConfig.setMaxLifetime(600000L);
hikariConfig.setConnectionTestQuery("select 1");
hikariConfig.setValidationTimeout(4000L);
hikariConfig.setJdbcUrl("jdbc:sqlserver://localhost:1433;database=invt_mgmt");
hikariConfig.setUsername("sa");
hikariConfig.setPassword("sql_server_pass_123");
hikariConfig.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
hikariConfig.setAutoCommit(false);
return new HikariDataSource(hikariConfig);
}
#Bean (name = "jdbcTemplateForSqlServer")
public JdbcTemplate jdbcTemplateForSqlServer () {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSourceForSqlServer());
return jdbcTemplate;
}
#Primary
#Bean(name = "invtMgmtTxMangerForSqlServer")
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSourceForSqlServer());
return manager;
}
}
#Component
public class startBean {
#Autowired
private Business Business;
#PostConstruct
public void startApp() throws SQLException {
Business.insertContainerHierarchy();
Business.insertContainerHierarchy();
}
}
#Component
class public Business {
#Autowired
#Qualifier("jdbcTemplateForSqlServer")
private JdbcTemplate jdbcTemplateForSqlServer;
String insertIntStudent = "INSERT INTO student (id, name) Values(?, ?)";
#Transactional(value = "invtMgmtTxMangerForSqlServer")
public void insertContainerHierarchy () throws SQLException {
System.out.println(TransactionSynchronizationManager.isActualTransactionActive());
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
Date start = new Date();
for (int i = 0; i < 500; i++) {
System.out.println(i);
jdbcTemplateForSqlServer.batchUpdate(insertIntStudent, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, i);
ps.setString(2, String.valueOf(i));
}
#Override
public int getBatchSize() {
return 1000;
}
});
}
System.out.println(new Date().getTime() - start.getTime());
}
}
I have used TransactionSynchronizationManager.isActualTransactionActive() which is returing true when the method is executed.
Q1. Why the data is not getting inserted, Transaction is supposed to autocommit once the method is executed?
Q2. In case spring transaction is getting used will database connection autocommit value make any difference?
Q3. How currently I am able to insert with autocommit set to true?
You are trying to invoke a transaction-wrapped proxy from within the #PostConstruct method. For that bean, all the initialization may be complete but not necessarily for the rest of the context. Not all proxies may be set at that point.
I would suggest implementing the ApplicationListener<ContextRefreshedEvent> interface in order to trigger any data creation inside that class. This will ensure it will be called after the entire context has been set-up:
#Component
public class OnContextInitialized implements
ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Business Business;
#Override public void onApplicationEvent(ContextRefreshedEvent event) {
Business.insertContainerHierarchy();
Business.insertContainerHierarchy();
}
}
Related
I am trying to create minimalistic Schema Based - Multitenant - Spring Boot but I am getting following error. Application can be downloaded from GitHub https://github.com/ivoronline/spring_boot_db_multitenant
Error
Unable to instantiate specified multi-tenant connection provider [com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider]
application.properties
#spring.jpa.properties.hibernate.multiTenancy = SCHEMA
spring.jpa.properties.hibernate.multiTenancy = SCHEMA
spring.jpa.properties.hibernate.multi_tenant_connection_provider = com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider
spring.jpa.properties.hibernate.tenant_identifier_resolver = com.ivoronline.spring_boot_db_multitenant.config.TenantIdentifierResolver
# JPA / HIBERNATE
spring.jpa.hibernate.ddl-auto = create
DataSourceConfig.java
#Configuration
public class DataSourceConfig {
#Bean
public DataSource dataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:postgresql://localhost:5432/multitenant");
dataSourceBuilder.username("postgres");
dataSourceBuilder.password("letmein");
dataSourceBuilder.driverClassName("org.postgresql.Driver");
return dataSourceBuilder.build();
}
}
TenantIdentifierResolver.java
#Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
static final String DEFAULT_TENANT = "public";
#Override
public String resolveCurrentTenantIdentifier() {
return "public";
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
TenantConnectionProvider.java
#Component
public class TenantConnectionProvider implements MultiTenantConnectionProvider {
private DataSource datasource;
public TenantConnectionProvider(DataSource dataSource) {
this.datasource = dataSource;
}
#Override
public Connection getAnyConnection() throws SQLException {
return datasource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
//connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", tenantIdentifier));
connection.createStatement().execute("SET Schema 'public'");
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
//connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", TenantIdentifierResolver.DEFAULT_TENANT));
connection.createStatement().execute("SET Schema 'public'");
releaseAnyConnection(connection);
}
#Override
public boolean supportsAggressiveRelease() {
return false;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
Hibernate expects that property hibernate.multi_tenant_connection_provider defines either already initialized object or class name which Hibernate can instantiate. In your case TenantConnectionProvider class has no default constructor, thus Hibernate fails (moreover, you expect that TenantConnectionProvider is a spring component). There are typically two options to configure such Hibernate SPIs:
Hibernate way - interact with spring context using ManagedBeanRegistry
public class TenantConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private ServiceRegistryImplementor serviceRegistry;
// default constructor
public TenantConnectionProvider() {
super();
}
#Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
protected DataSource getDataSource() {
return serviceRegistry.getService(ManagedBeanRegistry.class)
.getBean(DataSource.class)
.getBeanInstance();
}
...
}
spring-boot way - configure JPA properties in Java code:
#Bean
public HibernatePropertiesCustomizer tenantConnectionProviderCustomizer(TenantConnectionProvider tenantConnectionProvider) {
return hibernateProperties -> {
hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, tenantConnectionProvider);
};
}
I am new to JDBC template and am trying to use prepared statement for inserting data into database using auto commit mode off for achieving high performance but at the end i'm not able to commit the transaction. Please suggest some correct approach or reference that might solve my problem.
Thanks in advance...
SpringjdbcApplication.java
#SpringBootApplication
public class SpringjdbcApplication
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(SpringjdbcApplication.class, args);
SampleService service = context.getBean(SampleService.class);
List<Batch> batchList = new ArrayList<>();
batchList.add(new Batch("A","B"));
batchList.add(new Batch("B","B"));
batchList.add(new Batch("C","B"));
batchList.add(new Batch("D","B"));
batchList.add(new Batch("E","B"));
System.err.println("The number of rows inserted = "+service.singleInsert(batchList));
System.err.println("The count of batch class is = "+service.getCount());
}
}
SampleConfiguration.java
#Configuration
public class SampleConfiguration
{
#Bean
public DataSource mysqlDataSource()
{
HikariConfig config= new HikariConfig();
config.setDriverClassName("ClassName");
config.setJdbcUrl("URL");
config.setUsername("User");
config.setPassword("Password");
config.setMinimumIdle(600);
config.setMaximumPoolSize(30);
config.setConnectionTimeout(251);
config.setMaxLifetime(250);
config.setAutoCommit(false);
return new HikariDataSource(config);
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource)
{
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
Batch.java
#Entity
public class Batch implements Serializable
{
private static final long serialVersionUID = -5687736664713755991L;
#Id
#Column(name="field1")
private String field1;
#Column(name="field2")
private String field2;
....... getter, setter and constructor
}
SampleService.java
#Service
public interface SampleService
{
public int singleInsert(List<Batch> batchList);
}
SampleServiceImpl.java
#Service
public class SampleServiceImpl implements SampleService
{
#Autowired
JdbcTemplate jdbcTemplate;
#Override
public int singleInsert(List<Batch> batchList)
{
for(Batch i:batchList)
{
jdbcTemplate.update("insert into batch values(?,?)",i.getField1(),i.getField2());
}
try
{
DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).commit();
}
catch(Exception e)
{
e.printStackTrace();
}
return 1;
}
}
After going through #KevinBowersox's Infinite Skills course on Spring Data for Java Developers, the only part that didn't seem to work as advertised were the Async methods. Mind you, at the beginning of the course he covered xml and Java configuration, but he proceeded to use the xml configuration throughout the rest of the course, whereas, I kept using Java configuration for each of the exercises and was able to get all the other parts to work. One minor difference is I am using IntelliJ IDEA rather than STS, as he uses throughout the course.
If anyone familiar with Spring Data Async Queries or the segment of his course (https://www.safaribooksonline.com/library/view/spring-data-for/9781771375924/video241705.html) has some insight into what might be missing, please let me know.
Here are the relevant bits:
/* Application.java */
#EnableAsync
public class Application {
public static void main(String[] args) throws ParseException {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DataConfiguration.class)) {
BookRepository repository = context.getBean(BookRepository.class);
// TODO: Make method Async
for (long x = 0; x < 4; x++) {
repository.findByIds(x);
}
}
}
}
/* BaseRepository.java */
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
#Override
#Async("executor")
List<T> findByIds(ID... ids);
}
/* ExtendedRepositoryImpl.java */
public class ExtendedRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
private JpaEntityInformation<T, ?> entityInformation;
private final EntityManager entityManager;
public ExtendedRepositoryImpl(
JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.entityManager = entityManager;
}
#Override
public List<T> findByIds(ID... ids) {
Query query = this.entityManager.createQuery("select e from " + this.entityInformation.getEntityName()
+ " e where e." + this.entityInformation.getIdAttribute().getName() + " in :ids");
query.setParameter("ids", Arrays.asList(ids));
long wait = new Random().nextInt(10000-1) +1;
System.out.println(wait);
try {
Thread.sleep(wait);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Executing query for ID: " + Arrays.toString(ids));
return (List<T>) query.getResultList();
}
}
/* DataConfiguration.java (aka AppConfig.java) */
#EnableJpaRepositories(
basePackages = {"com.infiniteskills.springdata.async"},
repositoryBaseClass = com.infiniteskills.springdata.async.data.repository.ExtendedRepositoryImpl.class,
repositoryImplementationPostfix = "CustomImpl")
#EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
#EnableAsync
#EnableTransactionManagement
#ComponentScan("com.infiniteskills.springdata.async")
#Configuration
public class DataConfiguration implements AsyncConfigurer {
#Bean
public CustomAuditorAware customAuditorAware() {
return new CustomAuditorAware();
}
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build();
}
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
// Generate tables in database
vendorAdapter.setGenerateDdl(true);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", "create-drop");
//jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
//jpaProperties.put("hibernate.connection.driver_class", "org.h2.Driver");
// After DDL has been run, run init script to populate table with data.
jpaProperties.put("hibernate.hbm2ddl.import_files", "init.sql");
// Entity Manager Factory Bean
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan("com.infiniteskills.springdata.async");
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
entityManagerFactoryBean.setJpaProperties(jpaProperties);
entityManagerFactoryBean.afterPropertiesSet();
return entityManagerFactoryBean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
#Override
#Bean(name = "executor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("executor-");
executor.initialize();
return executor;
}
#Override
#Bean
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
The method has to have return type void or Future in order to be called asynchronous.
You can read up on it in the documentation here: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support-async
#EnableAsync is used on any of your #Configuration classes.
From the docs
To be used together with #Configuration classes as follows, enabling annotation-driven async processing for an entire Spring application context:
So annotate your Application class with #Configuration.
Hope this helps.
Since I kept using Java configuration in the exercise, I initially wasn't sure if I was using all the correct annotations equivalent to the demonstrated xml configuration. And IntelliJ IDEA can render differing code suggestions than STS...
Anyway, the Spring Data Async Queries segment of the course (https://www.safaribooksonline.com/library/view/spring-data-for/9781771375924/video241705.html) made me question my sanity.
Basically, it turns out my Application.java class:
public class Application {
public static void main(String[] args) throws ParseException {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DataConfiguration.class)) {
BookRepository repository = context.getBean(BookRepository.class);
// TODO: Make method Async
for (long x = 0; x < 4; x++) {
repository.findByIds(x);
}
}
}
}
should have done without the try-with-resources syntax and instead looked like this:
public class Application {
public static void main(String[] args) throws ParseException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DataConfiguration.class)
BookRepository repository = context.getBean(BookRepository.class;
for (long x = 0; x < 4; x++) {
repository.findByIds(x);
}
}
}
I have two spring services. Service1.foo() which is not transactional calls Service2.bar() which is transactional. So I am wondering if my transaction will only be limited to service2.bar(). I want to make sure that Service1.foo() is not run in transaction. Is my code correct?
class Service1
{
public void foo() {
//some non transaction code
service2.bar();
//some non transaction code
}
}
#Transactional
class Service2
{
public void bar() {
//TODO;
}
}
Yes. If you have setup transaction support correctly and you are using the right import (org.springframework.transaction.annotation.Transactional) for #Transactional, then only the methods in Service2 run inside a transaction, and the methods in Service1 do not.
If the setup is not correct, nothing runs inside a transaction.
Yes your code will work for the scenario you mentioned. See my code example below, after executing it, only 1 record got inserted into TEST_TABLE with CD=INSERT3. I put the wrong column in insert from Dao2impl, to fail the transaction.
Below is Service1 class, from where I am calling insertMainServiceCall method that has a call to insertData method which need to be executed in transaction:
#Service
public class Service1 {
#Autowired
private Service2 service2;
#Autowired
private DataSource datasource;
private JdbcTemplate jdbcTemplate;
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
this.service2 = service2;
}
public DataSource getDatasource() {
return datasource;
}
public void setDatasource(DataSource datasource) {
this.datasource = datasource;
}
public void insertMainServiceCall(){
String sqlCmd="INSERT INTO TEST_TABLE " +
"(CD, NAME, DEPT) VALUES (?, ?, ?)";
jdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.update(sqlCmd, new Object[] {"INSERT3",
"INSERT3","INSERT3"
});
service2.insertData();
}
}
Below is my Service2 class :
#Transactional
#Service
public class Service2 {
#Autowired
private Dao1 dao1;
#Autowired
private Dao2 dao2;
public Dao1 getDao1() {
return dao1;
}
public void setDao1(Dao1 dao1) {
this.dao1 = dao1;
}
public Dao2 getDao2() {
return dao2;
}
public void setDao2(Dao2 dao2) {
this.dao2 = dao2;
}
public void insertData(){
dao1.insert1();
dao2.insert2();
}
}
Below is my Dao1Impl class code:
#Repository
public class Dao1Impl implements Dao1{
#Autowired
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void insert1(){
String sqlCmd="INSERT INTO TEST_TABLE " +
"(CD, NAME, DEPT) VALUES (?, ?, ?)";
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(sqlCmd, new Object[] {"INSERT1",
"INSERT1","INSERT1"
});
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Below is my Dao2Impl class code:
#Repository
public class Dao2Impl implements Dao2 {
#Autowired
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void insert2(){
String sqlCmd="INSERT INTO TEST_TABLE " +
"(CD, NAME, DEPT1) VALUES (?, ?, ?)";
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(sqlCmd, new Object[] {"INSERT2",
"INSERT2","INSERT2"
});
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
In spring-config.xml, you need to setup transactional support as below: -
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
I am very new to webapp development in general and Spring framework in particular. I am following this Spring JDBC Transactions tutorial But instead of accessing the db service from only one class, I wish to access it from multiple classes.
In the tutorial the service is defined like this
public class BookingService {
#Autowired
JdbcTemplate jdbcTemplate;
#Transactional
public void book(String... persons) {
for (String person : persons) {
System.out.println("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
public List<String> findAllBookings() {
return jdbcTemplate.query("select FIRST_NAME from BOOKINGS", new RowMapper<String>()
#Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString("FIRST_NAME");
}
});
}
}
These are the Beans
#Configuration
#EnableTransactionManagement
#EnableAutoConfiguration
public class Application {
#Bean
BookingService bookingService() {
return new BookingService();
}
#Bean
DataSource dataSource() {
return new SimpleDriverDataSource() {{
setDriverClass(org.h2.Driver.class);
setUsername("sa");
setUrl("jdbc:h2:mem");
setPassword("");
}};
}
#Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
System.out.println("Creating tables");
jdbcTemplate.execute("drop table BOOKINGS if exists");
jdbcTemplate.execute("create table BOOKINGS(" +
"ID serial, FIRST_NAME varchar(5) NOT NULL)");
return jdbcTemplate;
}
They instantiated the service in only the Application class and did all the transactions there
ApplicationContext ctx = SpringApplication.run(Application.class, args);
BookingService bookingService = ctx.getBean(BookingService.class);
//bookingService.doStuff()
In my test project, I copied the same Bean definitions but I performed the transactions in multiple classes.
public class foo {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
BookingService bookingService = ctx.getBean(BookingService.class);
bookingService.book(...);
// some other stuff
}
public class bar {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
BookingService bookingService = ctx.getBean(BookingService.class);
bookingService.findAllBookings(...);
// some other stuff
}
It seems that when I perform all transactions in one class only (for example, book and find in foo class) it performs as expected. But when I try to separate them into multiple classes it doesn't behave as expected. If I perform the book in foo, I cannot find in bar. What concept am I missing? Am I instantiating multiple instances of the datasource and jdbctemplate because I instantiated the service multiple times. But I thought Spring handles the injection? And since there is only one physical database then there would only be one instance of datasource and jdbctemplate. What concept did I misunderstood? Please help and point me to the right direction. Thanks.
You need to inject your dependencies, for example:
public class Foo {
#Autowired
private BookingService bookingService;
public doStuff() {
bookingService.book(...);
// some other stuff
}
}