I've been trying to use HibernateTransactionManager to manage transactions in my service layer, but it doesn't work.
Java class configuration for creating PlatformTransactionManager:
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:hibernateConfig.properties")
public class HibernateConfig {
#Value("${hibernate.dialect}")
private String dialect;
//Other hibernate properties
#Autowired
private DataSource dataSource;
private Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.put("hibernate.dialect", dialect);
//Other hibernate properties removed here for brevity
return hibernateProperties;
}
#Bean
#DependsOn("dataSource")
public SessionFactory sessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean =
new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setPackagesToScan("com.ldp.vigilantBean.domain");
sessionFactoryBean.setHibernateProperties(hibernateProperties());
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
#Bean
#DependsOn("sessionFactory")
public PlatformTransactionManager platformTransactionManager() throws IOException {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory());
txManager.afterPropertiesSet();
return txManager;
}
}
Later in this method call there are two calls to the persistence layer and a Runtime Exception thrown in the end. So I want these two calls to the repository to be rolled back.
#Override
#Transactional(rollbackFor = { RuntimeException.class })
public boolean removeCartItem(Long cartItemId) {
Cart cart = getCartOutOfContext();
Optional<CartItem> optCartItemToRemove =
cart.getCartItems()
.stream()
.filter(cartItem -> cartItem.getCartItemId().equals(cartItemId))
.findAny();
if (optCartItemToRemove.isPresent()) {
CartItem cartItemToRemove = optCartItemToRemove.get();
//There is a bug with PersistentSet in Hibernate that makes
//using .contains() and .remove() methods of Set interface unpredictable.
//This is a workaround: reset the whole set.
cart.setCartItems(
cart.getCartItems().stream()
.filter(cartItem -> !cartItem.equals(cartItemToRemove))
.collect(Collectors.toSet())
);
Optional<Product> optProduct =
productRetrievalRepository.getProductById(cartItemToRemove.getProduct().getProductId());
if (!optProduct.isPresent())
throw new IllegalArgumentException("Specified product not found");
Product productToRemove = optProduct.get();
productToRemove.setUnitsInOrder(productToRemove.getUnitsInOrder() - cartItemToRemove.getQuantity());
//First call
productAlterRepository.updateProduct(productToRemove);
//Second call
cartRepository.updateCart(cart);
if (true) throw new RuntimeException("An exception to check transactions");
return true;
} else
return false;
}
Repository for managing products:
#Repository
class ProductAlterRepositoryImpl implements ProductAlterRepository {
private SessionFactory sessionFactory;
public ProductAlterRepositoryImpl(
#Autowired
SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
#Override
public Optional<Product> updateProduct(Product product) {
try (Session session = sessionFactory.openSession()) {
session.getTransaction().begin();
session.merge(product);
session.getTransaction().commit();
}
return Optional.of(product);
}
}
I don't understand why the changes made prior to the RuntimException thrown in my service method are not rolled back: I use the same session factory to initialize Platform Transaction Manager and to make changes via Session in my repository.
Also, I've got this line in my logger
*** LOG4J *** HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
If I'm right, when using only one resource (which is a Hibernate repository in my case), you don't need a Global Transaction Provider like Atomikos.
I think there suppose to be 3 transaction: one outer (the service call) and 2 inner (to repositories). The idea is that if one of the inner transactions fails it should cause the outer transaction rollback meaning all two calls to repositories would be rolled back.
Inside updateProduct(Product) you have opened programmatic transaction again in addition to declarative at service level. So it will ignore Spring container managed transaction manager and will use its own in isolation.
Can you please remove that and retry.
Related
I was setting up a basic CRUD web app with JPA in plain Spring (no Spring Boot or Spring Data JPA) for educational purposes and faced a strange problem: Spring doesn't translate exceptions for my repository. According to the Spring documentation (here and here), it is sufficient to mark the repository with the #Repository annotation and Spring will automatically enable exception translation for this repository.
However, when I did so and triggered a UNIQUE constraint violation, I still was getting a JPA PersistenceException (with a Hibernate ConstraintViolationException inside) instead of the Spring DataIntegrityViolationException.
I used pure Java Spring configuration and it took me quite some time to realize that I should compare it with the XML configuration in the documentation. Compared to the pure Java configuration, the XML configuration adds a PersistenceExceptionTranslationPostProcessor into the context. When I added it manually with #Bean, it worked, but now I have a question.
Have I misconfigured something? The Spring documentation doesn't require registering that post-processor manually for pure Java configuration. Maybe there is another way to register it, say an #EnableXXX annotation?
Here is the summary of my configuration.
#Configuration
#ComponentScan("com.example.secured_crm")
public class SpringConfiguration {
// the problem is solved if I uncomment this
//#Bean
//public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
// return new PersistenceExceptionTranslationPostProcessor();
//}
}
#Configuration
#PropertySource("classpath:db.properties")
#EnableTransactionManagement
public class DataSourceConfiguration {
#Value("${jdbc.driver}")
private String driverClass;
#Value("${jdbc.url}")
private String url;
// ...
#Value("${hibernate.debug}")
private String hibernateDebug;
#Bean
public DataSource dataSource() {
var dataSource = new ComboPooledDataSource();
// ...
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
var emFactory = new LocalContainerEntityManagerFactoryBean();
emFactory.setDataSource(dataSource());
emFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emFactory.setPackagesToScan("com.example.secured_crm.entities");
var properties = new Properties();
properties.setProperty("hibernate.dialect", hibernateDialect);
properties.setProperty("hibernate.show_sql", hibernateDebug);
properties.setProperty("hibernate.format_sql", "true");
emFactory.setJpaProperties(properties);
return emFactory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
var txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
public interface UserRepository {
User findByName(String username);
List<User> findAll();
void save(User user);
boolean deleteById(int id);
User findById(int id);
}
#Repository
public class UserJpaRepository implements UserRepository {
#PersistenceContext
EntityManager em;
#Override
public void save(User user) {
if (user.getId() == null) {
em.persist(user);
} else {
em.merge(user);
}
}
// and so on...
}
By the way, when I tried to add the post-processor in DataSourceConfiguration, it disabled #PropertySource effect. So far my impression of Spring is that it's one big hack...
It requires to manually register PersistenceExceptionTranslationPostProcessor in order for the exception translation to take effect.
The documentation you mentioned simply does not updated yet to show a fully working java configuration. It should mention to register this post processor. ( So feel free to provide a PR to update the docs.).
If you check from its javadoc , it already mentioned PersistenceExceptionTranslationPostProcessor is necessary to be registered :
As a consequence, all that is usually needed to enable automatic
exception translation is marking all affected beans (such as
Repositories or DAOs) with the #Repository annotation, along with
defining this post-processor as a bean in the application context.
P.S. If you are using spring-boot , and if it detects PersistenceExceptionTranslationPostProcessor is in the class-path , it will automatically register it by default such that you do not need to register manually.
I am using JPA (Hibernate) in a Spring project and asking myself if my class 'BooksHandler' (which is a DAO) is thread safe ?
Code for my App Config:
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(getDataSource());
emf.setPackagesToScan("jpa.models");
JpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
emf.setJpaVendorAdapter(adapter);
emf.setJpaProperties(getProperties());
return emf;
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dtSrc = new DriverManagerDataSource();
dtSrc.setDriverClassName("com.mysql.jdbc.Driver");
dtSrc.setUrl("jdbc:mysql://localhost:3306/jpa_example");
dtSrc.setUsername("dbuser1");
dtSrc.setPassword("dbuser1");
return dtSrc;
}
private Properties getProperties() {
Properties p = new Properties();
p.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
p.setProperty("hibernate.hbm2ddl.auto", "create");
p.setProperty("hibernate.show_sql", "true");
return p;
}
//auto transaction management
#Bean
public PlatformTransactionManager getTransactionManager(EntityManagerFactory emf) {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(emf);
return manager;
}
//auto exception management
#Bean
public PersistenceExceptionTranslationPostProcessor getPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
DAO class:
#Component
#Transactional
public class BooksHandler {
#PersistenceContext
private EntityManager em;
public Book createBook(String title, String isbn, int year) {
Book b = new Book();
b.setIsbn(isbn);b.setTitle(title);b.setYear(year);
em.persist(b);
System.out.println("book created: "+b.getId());
return b;
}
public Book getBook(int id) {
return em.find(Book.class, id);
}
//other CRUD methods
}
The method of BooksHandler will be used by multiple threads and I know that EntityManager itself is NOT thread-safe. But an online course I am attending does it this way. Maybe there is some behind the scene magic by spring to make it thread safe ?
Yes, it is thread-safe. Spring injects a proxy that delegates to the EM associated to the current transaction/thread. See the documentation, which says:
The injected JPA EntityManager behaves like an EntityManager fetched from an application server’s JNDI environment, as defined by the JPA specification. It delegates all calls to the current transactional EntityManager, if any. Otherwise, it falls back to a newly created EntityManager per operation, in effect making its usage thread-safe.
(emphasis mine).
If by thread safe, you mean that you won't have any errors of Dirty Read, Non Repeatable Read or Phantom Read. No, that depends wholly on your transaction isolation level, which is configure in your Hibernate properties or in each #Transactional annotation.
Otherwise, it doesn't really matter much if two different sessions or thread (if you are using #Async), can access the same class methods at the same time, since in the end, it will purely depend on your isolation level. This blog gives a good idea about when to use which isolation level.
I am working on a Spring-MVC application in which I have 2 data-sources defined for different types of tasks. Currently, during migration from XML to Java, I stumbled upon a requirement to add a new HibernateTransactionManager object for #Transactional to work. For the 2 different data-sources, I have 2 separate SessionFactory instances. But when I try to create yet another instance of HibernateTransactionManager with secondary data-source, I get a non-unique exception.
Do I require a secondary HibernateTransactionManager instance for the config I am posting or will 1 suffice? If required, how can I create one? Thank you.
Error log :
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: primary_tx,extended_tx
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:368)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:271)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springfram
WebConfig.java :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.ourapp.spring"})
#EnableTransactionManagement
#EnableCaching
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
#Bean(name="extended_tx")
public HibernateTransactionManager txName() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(getExtendedSessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("sessionFactory_origin")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig().primaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
#Bean
#Qualifier("sessionFactory_extended")
public LocalSessionFactoryBean getExtendedSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig_Extended().secondaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
}
Typical Service Layer method :
#Service
#Transactional("primary_tx")
public class ChatRoomMembersServiceImpl implements ChatRoomMembersService{
private final ChatRoomMembersDAO chatRoomMembersDAO;
#Autowired
public ChatRoomMembersServiceImpl(ChatRoomMembersDAO chatRoomMembersDAO){
this.chatRoomMembersDAO = chatRoomMembersDAO;
}
}
Typical DAO layer method :
#Repository
#Transactional("primary_tx")
public class ChatRoomMembersDAOImpl implements ChatRoomMembersDAO{
#Autowired
#Qualifier(value = "sessionFactory_origin")
private SessionFactory sessionFactory;
#Autowired
#Qualifier(value = "sessionFactory_extended")
private SessionFactory sessionFactory_extended;
}
Now, whenever required, I am referring to the extended SessionFactory instance. Right now, I have annotated DAO layer methods requiring extended sessionFactory with secondary_tx, but it's not working. Thank you. :-)
The answer to your question
Do I require to define separate TransactionManager for 2 data sources?
is YES
Java8 onwards you can have same annotation(provided that the annotation is marked #Repeatable) multiple times on a method. Other way to accomplish this is to gave a custom annotation which takes care of one of the datasources.
The exception you are getting is because there are two qualifying beans for transation management. You need a Qualifier for this.
Instead of:
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Use
#Bean #Qualifier("primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Now, Let say You want to make the transactions in my method m1 as atomic. The following is what you need
#Transactional("primary_tx")
public void m1(){
}
I'm trying to convert a Spring 4 project to use HibernateTransactionManager to manage the transactions, but transactional services throw this exception:
org.hibernate.HibernateException: createSQLQuery is not valid without
active transaction
I've searched on google, and 80 different people have 80 different ways of doing it. I've tried them all, and either they don't work or they require using EntityManager instead of Hibernate Sessions. Since I'm converting existing code that uses Hibernate sessions, things would be far easier if I could get them to work. How do I get rid of this exception?
Spring version: 4.2.5.RELEASE
Hibernate version: 5.1.0.Final
Hikari version: 2.7.6
JDK version: 1.8.0_161
DataSource config:
#Configuration
public class DataSourceConfig {
#Bean
public DataSource dataSource() {
// return a Hikari connection pool as the data source
try {
HikariDataSource bean = new HikariDataSource();
// set connection pool and JDBC properties
return bean;
} catch (Exception e) {
throw new RuntimeException("could create data source", e);
}
}
}
Session factory config:
#Configuration
public class SessionFactoryConfig {
#Autowired
private DataSource dataSource;
#Bean
public LocalSessionFactoryBean sessionFactory() {
// create a session factory
try {
LocalSessionFactoryBean bean = new LocalSessionFactoryBean();
// set Hibernate properties
bean.setDataSource(dataSource);
bean.setPackagesToScan(packagesToScan);
bean.setHibernateProperties(properties);
bean.afterPropertiesSet(); // tried without this line as well
return bean;
} catch (Exception e) {
throw new RuntimeException("could not create session factory", e);
}
}
}
Transaction config:
#Configuration
#EnableTransactionManagement
#ComponentScan("com.mydomain.*")
public class TransactionConfig {
#Autowired
private SessionFactory sessionFactory;
#Bean
public PlatformTransactionManager transactionManager() {
HibernateTransactionManager bean = new HibernateTransactionManager(sessionFactory);
// bean.setHibernateManagedSession(true); // similar exception when set
bean.afterPropertiesSet(); // tried without this line as well
return bean;
}
}
Service:
#Service
#Transactional // tried using only the #Transactional on the method
public class TestTransactionService {
#Autowired
private SessionFactory sessionFactory;
#Transactional // tried using only the #Transactional on the class
public String get(String key) {
Session session = sessionFactory.getCurrentSession();
// the below line throws the exception!
SQLQuery q = session.createSQLQuery("SELECT value FROM test_transaction WHERE key = :key");
q.setParameter("key", key);
q.addScalar("value", StringType.INSTANCE);
String out = (String)q.uniqueResult();
return out;
}
}
Any ideas? I hope so because I'm out.
Buried in our settings was this innocent-looking one, in there since the initial git checkin of our project, so I cannot find out who put it in (they're lucky):
hibernate.current_session_context_class = thread
Since no one knew what this setting was for, no one dared touch it since then. This breaks Spring managed transactions, and had no business being in there.
Let's this be a lesson, boys and girls: if you're trying to get something to work, DON'T just fiddle with settings when you don't know what they are. It will come back to bite you in the ass--sometimes years later. And then I will come find you and get you.
Greetings from Ecuador:
Currently I have created a project in which I must perform operations on 3 different databases. For this purpose I decided to use Hibernate ORM 5.2.7, Spring Framework 4.3.6 and other libraries for implementation of connection pools among others. For the implementation of the configuration of the context of Spring support me in annotations which I show below:
#Configuration
#ComponentScan("fttg.*")
#EnableTransactionManagement
#EnableScheduling
#PropertySources({
#PropertySource("classpath:application.properties"),
#PropertySource("classpath:schedule.properties")
})
public class ApplicationConfig {
#Autowired
private Environment environment;
#Bean(destroyMethod = "close")
public BasicDataSource dataSourceBitacora() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("postgres.jdbc.driver"));
dataSource.setUrl(environment.getRequiredProperty("bitacora.jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("bitacora.jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("bitacora.jdbc.password"));
dataSource.setPoolPreparedStatements(true);
dataSource.setInitialSize(4);
dataSource.setMaxTotal(4);
dataSource.setMaxIdle(2);
dataSource.setMinIdle(1);
dataSource.setDefaultAutoCommit(Boolean.FALSE);
return dataSource;
}
#Bean(destroyMethod = "close")
public BasicDataSource dataSourceFacturacion() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("postgres.jdbc.driver"));
dataSource.setUrl(environment.getRequiredProperty("facturacion.jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("facturacion.jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("facturacion.jdbc.password"));
dataSource.setPoolPreparedStatements(true);
dataSource.setInitialSize(1);
dataSource.setMaxTotal(4);
dataSource.setDefaultAutoCommit(Boolean.FALSE);
return dataSource;
}
#Bean(destroyMethod = "close")
public BasicDataSource dataSourceSietab() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("postgres.jdbc.driver"));
dataSource.setUrl(environment.getRequiredProperty("sietab.jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("sietab.jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("sietab.jdbc.password"));
dataSource.setPoolPreparedStatements(true);
dataSource.setInitialSize(1);
dataSource.setMaxTotal(2);
dataSource.setDefaultAutoCommit(Boolean.FALSE);
return dataSource;
}
#Bean
public LocalSessionFactoryBean sessionFactoryBitacora() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSourceBitacora());
sessionFactory.setPackagesToScan(environment.getRequiredProperty("bitacora.sessionFactory.packagesToScan"));
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("postgres.hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
sessionFactory.setHibernateProperties(properties);
return sessionFactory;
}
#Bean
public LocalSessionFactoryBean sessionFactoryFacturacion() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSourceFacturacion());
sessionFactory.setPackagesToScan(environment.getRequiredProperty("facturacion.sessionFactory.packagesToScan"));
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("postgres.hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
sessionFactory.setHibernateProperties(properties);
return sessionFactory;
}
#Bean
public LocalSessionFactoryBean sessionFactorySietab() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSourceSietab());
sessionFactory.setPackagesToScan(environment.getRequiredProperty("sietab.sessionFactory.packagesToScan"));
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("postgres.hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
sessionFactory.setHibernateProperties(properties);
return sessionFactory;
}
#Bean
public HibernateTransactionManager transactionManagerBitacora() {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactoryBitacora().getObject());
return txManager;
}
#Bean
public HibernateTransactionManager transactionManagerFacturacion() {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactoryFacturacion().getObject());
return txManager;
}
#Bean
public HibernateTransactionManager transactionManagerSietab() {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactorySietab().getObject());
return txManager;
}
}
The DAOS configuration is the same for all objects in the database:
#Repository
public class BitacoraFacturasDetalleDao extends GenericDaoImpl<BitacoraFacturasDetalle, Integer>{
private final static Logger LOGGER = Logger.getLogger(BitacoraFacturasDetalleDao.class);
#Qualifier("sessionFactoryBitacora")
#Autowired
private SessionFactory sessionFactory;
public BitacoraFacturasDetalleDao() {
super(BitacoraFacturasDetalle.class);
}
public BitacoraFacturasDetalle findByEstablecimientoAndPuntoEmisionAndSecuencial(String establecimiento, String puntoEmision, String secuencial) {
LOGGER.info("evento findByEstablecimientoAndPuntoEmisionAndSecuencial");
BitacoraFacturasDetalle ret = (BitacoraFacturasDetalle) getCurrentSession().createNamedQuery("BitacoraFacturasDetalle.findByEstablecimientoAndPuntoEmisionAndSecuencial").setParameter("establecimiento", establecimiento).setParameter("puntoEmision", puntoEmision).setParameter("secuencial", secuencial).uniqueResult();
return ret;
}
#Override
protected Session getCurrentSession() {
return this.sessionFactory.getCurrentSession();
}
}
Transactional objects are implemented as follows:
#Service("facturasService")
#Transactional(value="transactionManagerFacturacion", readOnly = false)
public class FacturasServiceImpl implements FacturasService, Serializable {
private static final long serialVersionUID = 1L;
private final static Logger LOGGER = Logger.getLogger(FacturasServiceImpl.class);
#Autowired
private FacturasCabeceraDao facturasCabeceraDao;
#Override
public boolean save(FacturasCabecera factura) {
LOGGER.info("evento save");
return facturasCabeceraDao.save(factura);
}
}
#Service("bitacoraFacturasDetalleService")
#Transactional(readOnly = false, value = "transactionManagerBitacora")
public class BitacoraFacturasDetalleServiceImpl implements BitacoraFacturasDetalleService, Serializable {
private static final long serialVersionUID = 1L;
private final static Logger LOGGER = Logger.getLogger(BitacoraFacturasDetalleServiceImpl.class);
#Autowired
private BitacoraFacturasDetalleDao bitacoraFacturasDetalleDao;
#Override
public boolean save(BitacoraFacturasDetalle b) {
LOGGER.info("evento save");
return bitacoraFacturasDetalleDao.save(b);
}
#Override
public boolean edit(BitacoraFacturasDetalle b) {
LOGGER.info("evento edit");
return bitacoraFacturasDetalleDao.edit(b);
}
#Override
#Transactional(readOnly = true, value = "transactionManagerBitacora")
public BitacoraFacturasDetalle findByEstablecimientoAndPuntoEmisionAndSecuencial(String establecimiento, String puntoEmision, String secuencial) {
LOGGER.info("evento findByEstablecimientoAndPuntoEmisionAndSecuencial");
return bitacoraFacturasDetalleDao.findByEstablecimientoAndPuntoEmisionAndSecuencial(establecimiento, puntoEmision, secuencial);
}
}
And in a service that implements Quartz I invoke the 3 different types of services through which:
I retrieve the information from a database, I generate a few xmls, I insert records for bitacora in the second database and if this action is correct I update a state of the records retrieved from the first base, then I make a digital signature on the generated xmls And if this action is executed correctly I make a change of state in the records of the second database and insert two tables type master and detail of the third database
Then the code with which I make the invocation:
#Service
public class ScheduleService implements Serializable {
#Autowired
private LocalidadService localidadService;
#Autowired
private CooperativaService cooperativaService;
#Autowired
private BoletoTasaService boletoTasaService;
#Autowired
private BitacoraFacturasDetalleService bitacoraFacturasDetalleService;
#Autowired
private InformacionTributariaService informacionTributariaService;
#Autowired
private ClientesService clientesService;
#Autowired
private FacturasService facturasService;
#Scheduled(cron = "${schedule.cronExpresion}")
public void start() {
if(XMLUtil.generarXML(factura, XML_GENERADO)) {
LOGGER.info("XML para la factura " + SECUENCIAL_DOCUMENTO + " generado correctamente");
//code that fills a javaBean
//Execution of service that inserts in the database # 2
if(bitacoraFacturasDetalleService.save(bitacoraFacturaDetalle)) {
LOGGER.info("Factura " + SECUENCIAL_DOCUMENTO + " registrada en bitacora correctamente");
// object retrieved from database # 1 to be changed status not to be taken into account in future
tasa.setStatusFacturacionElectronica("P");
if(boletoTasaService.update(tasa)) {
//Other post-upgrade operations
}
}
}
}
The case is that this code works until a certain amount of registers (approximately 700 or 800 of the database 1), after the next action of insertion or update of the different bases the code goes to "sleep" for after much Time to run again
For the tests carried out prior to the transition to production, make copies of the 3 databases which for the purposes of this scenario did not have concurrent connections of the systems and / or interfaces that interact with them.
I do not know if the cause of the "problem" is: the programming code used, the strategy of defining the transactional objects (I have read and been advised to use JTA but from what I have read this mechanism uses only a transactional thread [a service that Controls the operations on the databases]) or if this inconvenience is presented by the concurences of other applications to the tables of the different databases
Please help if there is anything wrong with the spring configuration, the definition of transactional services or if you definitely need to use JTA for this purpose.
It is possible to indicate that previously I have used this scheme where I have one or several databases from which I extract information and only a database on which I make insertions, therefore I do not have problems; On the other hand, on the three databases is written given certain circumstances
As for the problem described, it's hard to tell exactly what might be wrong. Nevertheless, I can give you some tips:
You're not using any connection pooling at all. Try replacing the BasicDataSource with a HikariCP connection pool.
Instead of having a long-running transaction. Try to split it in chunks. Use Spring Integration to build a pipeline instead. Each database transaction should process only a small subset of data at a time. This way, the VACUUM has a better chance to run than if you're using a very long-running transaction.