Boundary of spring transaction - java

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

Related

Springboot #Transcational is not getting rolledback

I'm new to springboot and has been working on a project. I'm using Springboot with hibernate, JPA and hikariCP for mysql DB operations. Currently i have two datasources in which i could not rollback a transaction which i written inside a service. I tried everything i could find, but i couldn't find out what am i missing. Any help would be appreciated and pardon my mistakes and i'm very open to suggestions. Thank you.
#SpringBootApplication
public class AdminModuleApplication {
public static void main(String[] args)
{
SpringApplication.run(AdminModuleApplication.class, args);
}
}
#Configuration
#PropertySource({ "classpath:acc_payDB_properties.properties" })
#EnableJpaRepositories(
basePackages = "com.pinnacle.accpay.dao",
entityManagerFactoryRef = "accpayEntityManager",
transactionManagerRef = "accpayTransactionManager"
)
public class acc_payDBConfig
{
#Autowired
private Environment env;
#Bean
public LocalContainerEntityManagerFactoryBean accpayEntityManager()
{
String packageList[]= {"com.pinnacle.accpay.model","com.pinnacle.admin.model"};
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(accpayDataSource());
em.setPackagesToScan(packageList);
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.hbm2ddl.auto", "none");
Properties jpaProperties2 = new Properties();
jpaProperties2.setProperty("connection.provider_class","org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
em.setJpaProperties(jpaProperties2);
return em;
}
#Bean
public DataSource accpayDataSource()
{
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driver-class-name"));
dataSource.setJdbcUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
dataSource.setCatalog("acc_pay");
//HikariCP specific properties. Remove if you move to other connection pooling library.
dataSource.setConnectionTimeout(20000);
dataSource.setMaximumPoolSize(20);
dataSource.setMinimumIdle(10);
dataSource.setIdleTimeout(20000);
dataSource.setMaxLifetime(40000);
dataSource.setAutoCommit(false);
dataSource.addDataSourceProperty("cachePrepStmts", true);
dataSource.addDataSourceProperty("prepStmtCacheSize", 25000);
dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", 20048);
dataSource.addDataSourceProperty("useServerPrepStmts", true);
dataSource.addDataSourceProperty("initializationFailFast", true);
dataSource.setPoolName("ACCPAY DB_HIKARICP_CONNECTION_POOL");
dataSource.addDataSourceProperty("useLocalTransactionState", false);
return new HikariDataSource(dataSource);
}
#Bean
public PlatformTransactionManager accpayTransactionManager()
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(accpayEntityManager().getObject());
return transactionManager;
}
}
#Service
public class TranscationReceptionService {
#Autowired
PO_TXN_MasterBeanRepository po_TXN_MasterBeanRepository;
#Autowired
GRN_TXN_MasterBeanRepository grn_TXN_MasterBeanRepository;
#Autowired
Invoice_TXN_MasterBeanRepository invoice_TXN_MasterBeanRepository;
#Autowired
POEventLogRepository poEventLogRepository;
#Autowired
InvoiceEventLogRepository invoiceEventLogRepository;
#Autowired
GRNEventlogRepository grnEventlogRepository;
#Autowired
PO_GRN_INVBeanRepository po_GRN_INVBeanRepository;
#Autowired
LinkEventLogBeanRepository linkEventLogBeanRepository;
#Autowired
ScheduledJob scheudledJob;
#Autowired
acc_payDBConfig acc_paydbConfig;
#Value("${in_process_status}")
private String in_process_status;
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void updateMatchStatus(Long poId, Long invoiceId, String poStatus, String invoiceStatus)
{
try
{
po_TXN_MasterBeanRepository.setPOStatusAfterMatching(poStatus, poId, invoiceId);
POEventLogBean poEventLogBean = new POEventLogBean();
poEventLogBean.setPo_id(poId); poEventLogBean.setEvent_time(LocalDateTime.now().toString());
poEventLogBean.setStatus(poStatus);
poEventLogRepository.save(poEventLogBean);
po_GRN_INVBeanRepository.setInvoiceNumber(poId, invoiceId);
Long linkId = po_GRN_INVBeanRepository.getLinkIdfromPONumber(poId);
LinkEventLogBean linkEventLogBean=new LinkEventLogBean();
linkEventLogBean.setLink_id(linkId);
linkEventLogBean.setEvent_time(LocalDateTime.now().toString());
linkEventLogBean.setStatus("");
linkEventLogBeanRepository.save(linkEventLogBean);
invoice_TXN_MasterBeanRepository.setInvoiceStatusAfterMatching(poId, invoiceStatus, invoiceId);
InvoiceEventLogBean invoiceEventLogBean=new InvoiceEventLogBean();
invoiceEventLogBean.setInv_id(invoiceId); invoiceEventLogBean.setEvent_time(LocalDateTime.now().toString());
invoiceEventLogBean.setStatus(invoiceStatus);
invoiceEventLogRepository.save(invoiceEventLogBean);
}
catch (Exception e)
{
}
}
}
#Transactional
#Repository
public interface PO_TXN_MasterBeanRepository extends JpaRepository<PO_TXN_MasterBean, Long>
{
#Transactional
#Modifying(clearAutomatically = true)
#Query("UPDATE PO_TXN_MasterBean p SET p.status= :status, p.inv_id= :invId WHERE p.po_id = :pId")
public void setPOStatusAfterMatching(#Param("status") String status, #Param("pId") Long poId, #Param("invId") Long invoiceId);
}
#Transactional
#Repository
public interface POEventLogRepository extends JpaRepository<POEventLogBean, Integer>
{
}
#Transactional
#Repository
public interface PO_GRN_INVBeanRepository extends JpaRepository<PO_GRN_INVBean, Long>
{
#Transactional
#Modifying(clearAutomatically = true)
#Query("UPDATE PO_GRN_INVBean p SET p.inv_id= :invId WHERE p.po_id = :pId")
public void setInvoiceNumber(#Param("pId") Long poId, #Param("invId") Long invoiceId);
#Query("SELECT p.link_id FROM PO_GRN_INVBean p WHERE p.po_id = :pId")
public Long getLinkIdfromPONumber(#Param("pId") Long poId);
}
#Transactional
#Repository
public interface LinkEventLogBeanRepository extends JpaRepository<LinkEventLogBean, Long>
{
}
#Repository
#Transactional
public interface Invoice_TXN_MasterBeanRepository extends JpaRepository<Invoice_TXN_MasterBean, Long>
{
#Modifying(clearAutomatically = true)
#Transactional
#Query("UPDATE Invoice_TXN_MasterBean i SET i.po_id = :pid, i.status =:status WHERE i.inv_id = :inid")
public void setInvoiceStatusAfterMatching(#Param("pid") Long pid, #Param("status") String status, #Param("inid") Long inId);
}
#Repository
public interface InvoiceEventLogRepository extends JpaRepository<InvoiceEventLogBean, Integer>
{
}
Transaction Rollback is not working as expected because the exception is handled with a try-catch block within TranscationReceptionService.updateMatchStatus(). This prevents the spring framework to be aware of the exception raised and hence does not result in rollback.

DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).commit(); Not working i.e, unable to commit

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

Spring database transaction not committing data in Db

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

Spring Testing Context Autowired

Sorry for the silly question, but i can't find out why this code is not working with Spring.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class DBDumpTest {
#Autowired
private DataSource source;
#Configuration
static class ContextConfiguration {
#Bean
public DataSource dataSource() {
return Mockito.mock(DataSource.class);
}
}
#Test
public void testDump() throws Exception {
Assert.assertNotNull(source); //OK
Assert.assertNotNull(new DBDump().getDatasource()); //NULL NOT OK
}
}
With Class:
public class DBDump {
private static final Logger logger = LoggerFactory.getLogger(DBDump.class);
#Autowired
private DataSource datasource;
public DataSource getDatasource() {
return datasource;
}
}
So why does new DBDump().getDatasource() not return the Mocked Instance, while in the Test-Class the #Autowired Annotation seems to work?

Spring Framework JDBC use two data sources

This is data source conf. file which has tow data sources. I want to use both of them in DAO class but now only the first data source is working. How can i use both of them ? May be they must be setted in the constructor of dao class or reused with getJdbcTemplate()
public class DataSourceConfig {
// datasource
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://url");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
#Bean
public DataSource dataSourceSecond() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://url");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// set jdbc template
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
#Bean
public JdbcTemplate jdbcTemplateSecond() {
return new JdbcTemplate(dataSourceSecond());
}
// transaction manager
#Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(dataSourceTransactionManager());
}
}
dao
#Repository
public class UsersDao extends JdbcDaoSupport {
#Autowired
private MessageSourceAccessor msa;
#Autowired
public UsersDao(JdbcTemplate jdbcTemplate) {
setJdbcTemplate(jdbcTemplate);
}
public void deleteUser(int userId) {
String sql = msa.getMessage("sql");
Object[] args = new Object[] { userId };
getJdbcTemplate().update(sql, args);
}
}
You can do that with using #Qualifier annotation.
Assuming you want to inject both the jdbcTemplates in a particular class
#Repository
public class MyDao extends JdbcDaoSupport {
#Autowired
public MyDao(#Qualifier("jdbcTemplate") JdbcTemplate jdbcTemplate,
#Qualifier("jdbcTemplateSecond") JdbcTemplate jdbcTemplateSecond) {
//code
}
}

Categories

Resources