Can I have #Transactional multiple times in same method call - spring

I'm new to Transactional Management, and I have a requirement that I might have to update the same column in DB with in the same call..
Here is what I have :
#Override
public void updateData(Keys keys) {
update1(keys);
update2(keys);
}
#Transactional
private void update1(Keys kesy) {
if(StringUtils.isNotBlank(keys.getValue1())) {
repo.updateKey1(keys.getValue1());
}
}
#Transactional
private void update2(Keys keys) {
if(StringUtils.isNotBlank(keys.getValue2())) {
repo.updateKey2(keys.getValue2());
}
}
I wrote it like this because I might get the same result for both methods, and I want to commit the data every time and get the lastest data
Any help is much appriciated.

I'm not sure if i understood your question right, but if you want to have both update calls in one transaction, it would be enough to annotate the updateData method with #Transactional:
#Override
#Transactional
public void updateData(Keys keys) {
update1(keys);
update2(keys);
}

Related

Correct way to finish transaction and than do some staff

In my project I need to do some updates in database and finish transaction. Then I need to call external application that should have access to result of current transaction (via other endpoints). currently I have just #Transactional annotation above of endpoint method.
Which is common way to deal with such situations?
Use ApplicationEventPublisher to publish an event at the end of the #Transactional method. Implement a #TransactionalEventListener method to handle this event which by default will only get called after the transaction commits successfully which means you don't need to worry about it will execute accidentally if the transaction fails.
Code wise , it looks like :
#Service
public class MyServce {
#Autowired
private ApplicationEventPublisher appEventPublisher;
#Transactional
public void doSomething(){
Result result = doMyStuff();
appEventPublisher.publishEvent(new StuffFinishedEvent(result));
}
}
public class StuffFinishedEvent{
private Result result;
public StuffFinishedEvent(Result result){
this.result = result;
}
}
And the #TransactionalEventListener :
#Component
public class FinishStuffHandler {
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(StuffFinishedEvent event) {
//access the result here.....
event.getResult();
}
}

Why does Spring Data MongoDB not expose events for update…(…) methods?

It appears that the update for mongoOperations do not trigger the events in AbstractMongoEventListener.
This post indicates that was at least the case in Nov 2014
Is there currently any way to listen to update events like below? This seems to be quite a big omission if it is the case.
MongoTemplate.updateMulti()
Thanks!
This is no oversight. Events are designed around the lifecycle of a domain object or a document at least, which means they usually contain an instance of the domain object you're interested in.
Updates on the other hand are completely handled in the database. So there are no documents or even domain objects handled in MongoTemplate. Consider this basically the same way JPA #EntityListeners are only triggered for entities that are loaded into the persistence context in the first place, but not triggered when a query is executed as the execution of the query is happening in the database.
I know it's too late to answer this Question, I have the same situation with MongoTemplate.findAndModify method and the reason I needed events is for Auditing purpose. here is what i did.
1.EventPublisher (which is ofc MongoTemplate's methods)
public class CustomMongoTemplate extends MongoTemplate {
private ApplicationEventPublisher applicationEventPublisher;
#Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher
applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
//Default Constructor here
#Override
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
T result = super.findAndModify(query, update, entityClass);
//Publishing Custom Event on findAndModify
if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
this.applicationEventPublisher.publishEvent(new AfterFindAndModify
(this,((Parent)result).getId(),
result.getClass().toString())
);
return result;
} }
2.Application Event
public class AfterFindAndModify extends ApplicationEvent {
private DocumentAuditLog documentAuditLog;
public AfterFindAndModify(Object source, String documentId,
String documentObject) {
super(source);
this.documentAuditLog = new DocumentAuditLog(documentId,
documentObject,new Date(),"UPDATE");
}
public DocumentAuditLog getDocumentAuditLog() {
return documentAuditLog;
}
}
3.Application Listener
public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
#Autowired
MongoOperations mongoOperations;
#Override
public void onApplicationEvent(AfterFindAndModify event) {
mongoOperations.save(event.getDocumentAuditLog());
}
}
and then
#Configuration
#EnableMongoRepositories(basePackages = "my.pkg")
#ComponentScan(basePackages = {"my.pkg"})
public class MongoConfig extends AbstractMongoConfiguration {
//.....
#Bean
public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
return new FindandUpdateMongoEventListner();
}
}
You can listen to database changes, even the changes completely outside your program (MongoDB 4.2 and newer).
(code is in kotlin language. same for java)
#Autowired private lateinit var op: MongoTemplate
#PostConstruct
fun listenOnExternalChanges() {
Thread {
op.getCollection("Item").watch().onEach {
if(it.updateDescription.updatedFields.containsKey("name")) {
println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
}
}
}.start()
}
This code only works when replication is enabled. You can enable it even when you have a single node:
Add the following replica set details to mongodb.conf (/etc/mongodb.conf or /usr/local/etc/mongod.conf or C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg) file
replication:
replSetName: "local"
Restart mongo service, Then open mongo console and run this command:
rs.initiate()

advising all delete and save methods in spring-data-jpa

I have a requirement whereby I need to advise all delete and save methods and send the deleted/saved record somewhere else.
I am using JpaRepository which has
6 x delete
3 x save
Basically I need to advise all these methods. The trouble is that each of these has different method signatures and return types, sometimes accepting a Long, Object or List. I am considering using aspects to achieve this but it seems that it would be nasty as I currently have 4 objects I need to audit which comes to 4 x 9 = 36 different pointcuts. There are more of these to come so this would soon come into the hundreds.
Is there a better way?
I got it working as #sheltem suggested. I used EntityListeners. In my case I needed access to a spring bean and was able to it this way:
#Component
public class PublishEntityListener {
private static PublishingService publishingService;
#Autowired(
required = true)
public void setPublishingService(PublishingService publishingService) {
this.publishingService = publishingService;
}
#PostConstruct
public void init() {
//Allow the static dependency to be setup post construct as #EntityListeners are no spring managed
}
#PostPersist
public void prePersist(DomainObject<?> entity) {
publishingService.publish(getTopicName(entity), HttpMethod.POST, entity);
}
#PostUpdate
public void preUpdate(DomainObject<?> entity) {
publishingService.publish(getTopicName(entity), HttpMethod.PUT, entity);
}
#PostRemove
public void onDelete(DomainObject<?> entity) {
publishingService.publish(getTopicName(entity), HttpMethod.DELETE, entity);
}
}

Practical Usage of HttpSessionBindingListener And HttpSessionAttributeListener

I am reading through head first JSP and servlets. Going through different type of listeners, I came across HttpSessionBindingListener and HttpSessionAttributeListener.
I was thinking about the difference between the two - I want to see the practical usages in real world examples of those two listeners. I tested HttpSessionBindingListener by implementing valueBound() and valueUnBound() - why would an object need to know whether it has been added or not?
I am pretty confused about the practical usages. Please help in clarifying this.
The HttpSessionBindingListener is to be implemented on the class whose instances may be stored in the session, such as the logged-in user.
E.g.
public class ActiveUser implements HttpSessionBindingListener {
#Override
public void valueBound(HttpSessionBindingEvent event) {
logins.add(this);
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
logins.remove(this);
}
}
When an instance of this ActiveUser get set as a session attribute by HttpSession#setAttribute(), then the valueBound() will be invoked. When it get removed by either HttpSession#removeAttribute(), or an invalidate of the session, or get replaced by another HttpSession#setAttribute(), then the valueUnbound() will be invoked.
Here are some real world use cases:
Getting SessionScoped bean from HttpSessionListener?
How to call a method before the session object is destroyed?
Call an action when closing a JSP
implementing HttpSessionListener
How to access HTTP sessions in Java
The HttpSessionAttributeListener is to be implemented as an application wide #WebListener which get invoked when any attribute is added, removed or replaced in the HttpSession. Continuing with the above ActiveUser example, this is particularly useful if you can't modify the ActiveUser class to implement HttpSessionBindingListener (because it's 3rd party or so), or when you want to make use of a "marker interface" on an arbitrary amount of classes so that you can do the listening job in a single central place.
#WebListener
public class ActiveUserListener implements HttpSessionAttributeListener {
#Override
public void attributeAdded(HttpSessionBindingEvent event) {
if (event.getValue() instanceof ActiveUser) {
logins.add(event.getValue());
}
}
#Override
public void attributeRemoved(HttpSessionBindingEvent event) {
if (event.getValue() instanceof ActiveUser) {
logins.remove(event.getValue());
}
}
#Override
public void attributeReplaced(HttpSessionBindingEvent event) {
if (event.getValue() instanceof ActiveUser) {
logins.add(event.getValue());
}
}
}
Here's a real world use case:
Getting notification when bounded/unbounded to a HTTP session

Test jdbcTemplate.batchUpdate with rollback by default

I'm trying to test my dao, that uses jdbcTemplate.batchUpdate method under the hood.
My tests are run against real datasource and all methods are performed in transactions marked as rollback-only (any changes are rolled back after the test). Test transactions are managed by PlatformTransactionManager.
The issue here, is that jdbcTemplate.batchUpdate seems to be executed in separate transaction started by DataSourceTransactionManager and thus, I can't see changes made by jdbcTemplate.
My test:
#Transactional
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
#RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractDbUnitTest
...
#Test
public void removeSpecific() {
myDao.removeSpecific(myDao.findAllAliasedItems());
Assert.assertEquals(0, myDao.findAllAliasedItems().size());
}
Dao
#Override
public void removeSpecific(final List<? extends Item> items) {
jdbcTemplate.batchUpdate("delete from ITEM where item_type = ? and item_id = ?", new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, items.get(i).getType().name());
ps.setString(2, items.get(i).getId(););
}
#Override
public int getBatchSize() {
return items.size();
}
});
}
Is there any way to test batchUpdate method item without actually altering the data?
Thanks in advance.
I'm sorry for misleading you all. The issue was not caused by nested transaction.
This question is a result of my total misunderstanding of the hierarchy of transactions-related classes in Java and how batch statements in JDBC implemented.
Test was failing because subsequent calls to
myDao.findAllAliasedItems()
were cached. Spring JDBC template worked just fine and as one might expect.

Resources