I run a Spring Boot Application wiht #EnableTransactionManagement and want to use #Transactional(readOnly = true) for some database queries.
But I receive a confusing error message.
I'm using Spring, Spring Boot and and Spring Data JPA.
MySpringBootApplication.java
#SpringBootApplication
#EnableTransactionManagement
#ComponentScan("com.deutscheboerse.regrephub")
#EntityScan(basePackages = "com.deutscheboerse.regrephub")
#EnableJpaRepositories(basePackages = "com.deutscheboerse.regrephub")
#Slf4j
public class MySpringBootApplication
{
... Some #Autowired variables ...
public static void main(String[] args)
{
SpringApplication.run(MySpringBootApplication.class, args);
}
...
}
MySpringBootApplicationConfiguration.java
#Configuration
#EnableEncryptableProperties
#EnableTransactionManagement
#EnableAsync
#Slf4j
public class MySpringBootApplicationConfiguration
{
... Some #Autowired variables ...
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource()
{
return DataSourceBuilder
.create(this.dataSourceProperties.getClassLoader())
.url(this.dataSourceProperties.getUrl())
.username(this.dataSourceProperties.getUsername())
.password(this.dataSourceProperties.getPassword())
.build();
}
...
}
MyBeanDao.java
#Repository
public interface MyBeanDao extends JpaRepository<MyBeanData, Long>
{
#QueryHints(value = #QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE))
#Query(value = "SELECT * FROM MY_TABLE", nativeQuery = true)
#Transactional(readOnly = true)
Stream<MyBeanData> streamAll();
}
MyBeanService.java
#Service
#Slf4j
public class MyBeanService extends AbstractService
{
#Autowired
public MyBeanService(...)
{
...
}
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public void handleRequest(Object request, Message msg)
{
try (Stream<MyBeanData> data = myBeanDao.streamAll())
{
...
}
catch (Exception e)
{
...
}
}
}
When I run my SpringBootApplication I will receive the following log messages / errors:
[TransactionInterceptor:474] Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.streamAll]
[TransactionInterceptor:517] Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.streamAll] after exception: org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses #Transactional or any other way of declaring a (read-only) transaction.
[RuleBasedTransactionAttribute:131] Applying rules to determine whether transaction should rollback on org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses #Transactional or any other way of declaring a (read-only) transaction.
[RuleBasedTransactionAttribute:148] Winning rollback rule is: null
[RuleBasedTransactionAttribute:153] No relevant rollback rule found: applying default rules
First JPA opens a transaction and close it immediately with an exception, that I want to execute a streaming query method without a surrounding transaction.
Does someone had this before?!
I fixed it!
It was a problem with the spring context.
When I initialize MyBeanService, I stored the bean into an HashMap with an corresponding request-object.
dispatcher.put(MyBeanRequest.class, this);
...
((MyAbstractService) dispatcher.get(MyBeanRequest.class).handleRequest(...);
it works, when I search for the bean in the spring context:
dispatcher.put(MyBeanRequest.class, this.getClass());
...
((MyAbstractService) appContext.getBean(dispatcher.get(requestObject.getClass()))).handleRequest(...);
Related
In our application we are using both MySQL server and Redis databases. we use Redis as a database and not just a cache. we use both of them in a service method and I want to make the method #Transactional to let the spring manages my transactions. Hence if in the middle of an transactional method a RuntimeException is thrown all works on both Redis and MySQL are rolled back. I have followed the spring docs and configured my #SpringBootApplication class as following:
#SpringBootApplication
#EnableTransactionManagement
public class TransactionsApplication {
#Autowired
DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(TransactionsApplication.class, args);
}
#Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);
return template;
}
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}
#Bean
public PlatformTransactionManager transactionManager() throws SQLException, IOException {
return new DataSourceTransactionManager(dataSource);
}
}
and this is my service method:
#Service
#RequiredArgsConstructor
#Slf4j
public class FooService {
private final StringRedisTemplate redisTemplate;
private final FooRepository fooRepository;
#Transactional
public void bar() {
Foo foo = Foo.builder()
.id(1001)
.name("fooName")
.email("foo#mail.com")
.build();
fooRepository.save(foo);
ValueOperations<String, String> values = redisTemplate.opsForValue();
values.set("foo-mail", foo.getEmail());
}
However after the test method of TestService is called there is no user in MySQL db and I think it's because there is no active transaction for it.Is there any solution for this problem? Should I use spring ChainedTransactionManager class and then how? or I can only manage Redis transactions manually through MULTI?
After playing around with FooService class I found that using #Transactional in a service method that is intended to work with Redis and specially read a value from Redis (which I think most service methods are supposed to read some value from DB) is somehow useless since any read operation would result a null value and that is because Redis queues all operations of a transaction and executes them at the end. Summing up, I think using MULTI and EXEC operations is more preferable since it gives more control to use data in Redis.
After all, any suggestions to use Redis transactions is appreciated.
I wanted to know how the Spring #Transactional will work for the following Coding scenarios. I am using Spring 4 + Hiberante 5 with Oracle 19C database for this example.
Example 1:
#Service
public class UserService {
#Transactional(readOnly = true)
public void invoice() {
createPdf();
// send invoice as email, etc.
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
Example 2:
#Service
public class UserService {
#Autowired
private InvoiceService invoiceService;
#Transactional(readOnly = true)
public void invoice() {
invoiceService.createPdf();
// send invoice as email, etc.
}
}
#Service
public class InvoiceService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
Thanks
Example 1: As you are calling the createPDF method from inside your Service, the #Transactional(REQUIRES_NEW) annotation will effectively be ignored. There will be no new transaction opened.
Example 2: As your are calling another service, which is wrapped in a transactional proxy, you will get a new transaction, as the annotation is respected.
You also might want to read up on this article: Spring Transaction Management: #Transactional In-Depth
I am trying to update a node on Neo4J, but what ends up happening is that it creates a duplicate Node. I read that the update has to be in a single transaction and I added #Transactional, but still same result. Here is what I have. I tried the approach of reading and deleting the old node, and saving the new one and it appears to be working. But, I think that is not the right approach. Why the #Transactional annotation not working. Thank you.
#EnableNeo4JRepositories(com.example.graph.repo)
#EnableTransactionManagement
#org.springframework.contect.annotation.Configuration
public class Neo4JConfig {
#Bean
public Configuration configuration() {
Configuration cfg = new Configuration();
cfg.driverConfiguration()
.setDriverClassName("org.neo4j.ogm.drivers.http.driver.HttpDriver")
.setURI("http://neo4j:neo4j#localhost:7474");
return cfg;
}
#Bean
public SessionFactory sessionFactory() {
return new SessionFactory(configuration(), "com.example");
}
#Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4JTransactionManager(sessionFactory());
}
}
#Service
public class UserService{
#Autowired
UserRepository userRepository;
#Transactional
public void updateUser(User user) {
User existingUser = userRepository.getExistingUser(user.getUserName());
if(existingUser != null ) {
user.setSomeValue(existingUser.getSomeValue());
userRepository.save(user);
}
}
}
Spring AOP uses JDK Proxy mechanism by default. It means that you must invoke #Transactional method via interface method.
So you should split your service into interface UserService and implementation (say UserServiceImpl), autowire the interface into the code where you currently autowire the impementation, and then invoke transactional method via interface.
P.S. Another approach is to force Spring to use CGLIB as long as this mechanism is not limited to interfaces. More details for both mechanisms https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.html
I have a Spring Boot application that happens to use Camunda for BPMN. Everything works fine. I have the Hikairi DBPool and the datasource properties in my application.properties file. Every thing runs fine, and workflows work etc...
I now want to access my DB via JdbcTemplate, using the same DataSource, as all the tables are on the same DB. I add this class:
#Component
public class MyDao extends JdbcDaoSupport {
public MyRow getMyRowById(int id) {
String sql = "select * from MyTable where id = ?";
try {
MyRow myRow = (MyRow)getJdbcTemplate().queryForObject(sql, new Object[] { id }, new MyRowMapper());
return myRow;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
}
And I get the error:
Caused by: java.lang.IllegalArgumentException: 'dataSource' or 'jdbcTemplate' is required
How is that possible when I know it's there. I see in the logs that Hikari is using it and adding itself as the DataSource for pooling. If I simply remove the #Component and it at least deploys, but as you would think, it throws a null pointer at the getJdbcTemplate() call.
Is there an annotation I am missing to get this to autowire correctly and expose the DataSource to my JdbcTemplate?
First, you should annotate your MyDao with the #Repository annotation and not with just the #Component one. For this reason, please take a moment to read What's the difference between #Component, #Repository & #Service annotations in Spring?.
Second, looking at your exception, seems you are missing the injection of the jdbcTemplate/datasource in the MyDao. For this point, if you are working with the datasource itself and not with the JdbcTemplate, you can inject the datasource as following:
#Autowired
public void setDs(DataSource dataSource) {
setDataSource(dataSource);
}
However, if you are using the JdbcTemplate, you can add a setter injection inside you MyDao as following:
#Autowired
public void setJt(JdbcTemplate jdbcTemplate) {
setJdbcTemplate(jdbcTemplate);
}
I'm using Spring 3.1.2 with Hibernate 4.
I have a DAO implementation class MyDaoImpl annotated with #Repository so that exception translation is enabled. I have a service class MyService annotated with #Transactional as follows:
public class MyService implements IMyService {
private MyDao myDao;
#Autowired
public void setMyDao(MyDao dao) {
this.myDao = dao;
}
#Override
#Transactional
public void createA(String name)
{
A newA = new A(name);
this.myDao.saveA(newA);
}
}
I've wrote a unit tests class MyServiceTest as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:beans.xml" })
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class MyServiceTest implements IMyServiceTest {
private IMyService myService;
private SessionFactory sessionFactory;
#Autowired
public void setMyService(IMyService myService)
{
this.myService = myService;
}
#Autowired
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
#Test
#Override
public void testCreateA()
{
//Assume that there is already a row of table A with the name "A1"
//so I expect to get a Spring DataAccessException (or subtypes)
//when the session is flushed
this.myService.createA("A1");
this.sessionFactory.getCurrentSession().flush();
//asserts
}
}
When I run the test, I get a Hibernate specific exception ConstraintViolationException. I've found on the forum that this is because the translation system takes place outside the transaction, so in this case after testCreateA() returns. I don't know if this is the real cause, but if it is, it means that I can't test that the translation works for my DAOs. One solution would be to remove the #Transactional annotations from my unit tests, but I would no benefit from the rollback feature.
What are your recommendations?
EDIT: I've added the SessionFactory declared in my context to the test class, so that I can access the current session for flushing.
Some additional explanations: In this case, I get the exception when the session is flushed (which is inside the transaction). I flush the session in order to avoid false positives as it is explained in the docs. Also, since the default propagation is REQUIRED, the testCreateA() transaction is also used for the call to createA(), so the changes are not flushed (generally) until testCreateA() returns.
Have you added PersistenceExceptionTranslationPostProcessor bean defination? Like
<!--
Post-processor to perform exception translation on #Repository classes
(from native exceptions such as JPA PersistenceExceptions to
Spring's DataAccessException hierarchy).
-->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
From Spring doc.
Bean post-processor that automatically applies persistence exception
translation to any bean that carries the #Repository annotation, adding
a corresponding PersistenceExceptionTranslationAdvisor to the exposed
proxy (either an existing AOP proxy or a newly generated proxy that
implements all of the target's interfaces).
Translates native resource exceptions to Spring's DataAccessException
hierarchy. Autodetects beans that implement the
PersistenceExceptionTranslator interface, which are subsequently asked
to translate candidate exceptions